From 03123a6d8b8f172bbebf3251dd9ba341ea857a4c Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Wed, 7 Aug 2024 19:00:11 +0400 Subject: [PATCH 01/42] [TH2-5226] moved kotlin files to the src/kotlin dir --- .../com/exactpro/th2/infraoperator/configuration/ConfigLoader.kt | 0 .../exactpro/th2/infraoperator/configuration/OperatorConfig.kt | 0 .../th2/infraoperator/configuration/fields/ChartConfig.kt | 0 .../th2/infraoperator/configuration/fields/EnvironmentConfig.kt | 0 .../th2/infraoperator/configuration/fields/RabbitMQConfig.kt | 0 .../configuration/fields/RabbitMQManagementConfig.kt | 0 .../configuration/fields/RabbitMQNamespacePermissions.kt | 0 .../th2/infraoperator/configuration/fields/SchemaSecrets.kt | 0 .../com/exactpro/th2/infraoperator/model/LinkDescription.kt | 0 .../th2/infraoperator/model/box/dictionary/DictionaryEntity.kt | 0 .../th2/infraoperator/model/box/grpc/GrpcEndpointConfiguration.kt | 0 .../th2/infraoperator/model/box/grpc/GrpcEndpointMapping.kt | 0 .../infraoperator/model/box/grpc/GrpcExternalEndpointMapping.kt | 0 .../th2/infraoperator/model/box/grpc/GrpcRouterConfiguration.kt | 0 .../th2/infraoperator/model/box/grpc/GrpcServerConfiguration.kt | 0 .../th2/infraoperator/model/box/grpc/GrpcServiceConfiguration.kt | 0 .../exactpro/th2/infraoperator/model/box/grpc/RoutingStrategy.kt | 0 .../th2/infraoperator/model/box/grpc/factory/TargetBoxSpec.kt | 0 .../th2/infraoperator/model/box/mq/factory/GlobalNotification.kt | 0 .../model/box/mq/factory/MessageRouterConfigFactory.kt | 0 .../model/box/mq/factory/MessageRouterConfigFactoryBox.kt | 0 .../model/box/mq/factory/MessageRouterConfigFactoryEstore.kt | 0 .../model/box/mq/factory/MessageRouterConfigFactoryMstore.kt | 0 .../th2/infraoperator/spec/helmrelease/HelmReleaseSpec.kt | 0 .../exactpro/th2/infraoperator/spec/shared/pin/GrpcClientPin.kt | 0 .../com/exactpro/th2/infraoperator/spec/shared/pin/GrpcSection.kt | 0 .../exactpro/th2/infraoperator/spec/shared/pin/GrpcServerPin.kt | 0 .../com/exactpro/th2/infraoperator/spec/shared/pin/Link.kt | 0 .../exactpro/th2/infraoperator/spec/shared/pin/MqPublisherPin.kt | 0 .../com/exactpro/th2/infraoperator/spec/shared/pin/MqSection.kt | 0 .../exactpro/th2/infraoperator/spec/shared/pin/MqSubscriberPin.kt | 0 .../com/exactpro/th2/infraoperator/spec/shared/pin/PinSpec.kt | 0 .../spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt | 0 33 files changed, 0 insertions(+), 0 deletions(-) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/configuration/ConfigLoader.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/configuration/OperatorConfig.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/configuration/fields/ChartConfig.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/configuration/fields/EnvironmentConfig.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQConfig.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQManagementConfig.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQNamespacePermissions.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/configuration/fields/SchemaSecrets.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/model/LinkDescription.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/model/box/dictionary/DictionaryEntity.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/model/box/grpc/GrpcEndpointConfiguration.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/model/box/grpc/GrpcEndpointMapping.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/model/box/grpc/GrpcExternalEndpointMapping.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/model/box/grpc/GrpcRouterConfiguration.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/model/box/grpc/GrpcServerConfiguration.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/model/box/grpc/GrpcServiceConfiguration.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/model/box/grpc/RoutingStrategy.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/model/box/grpc/factory/TargetBoxSpec.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/model/box/mq/factory/GlobalNotification.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactory.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryBox.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryEstore.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryMstore.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/spec/helmrelease/HelmReleaseSpec.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/spec/shared/pin/GrpcClientPin.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/spec/shared/pin/GrpcSection.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/spec/shared/pin/GrpcServerPin.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/spec/shared/pin/Link.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/spec/shared/pin/MqPublisherPin.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/spec/shared/pin/MqSection.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/spec/shared/pin/MqSubscriberPin.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/spec/shared/pin/PinSpec.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt (100%) diff --git a/src/main/java/com/exactpro/th2/infraoperator/configuration/ConfigLoader.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/ConfigLoader.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/configuration/ConfigLoader.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/configuration/ConfigLoader.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/configuration/OperatorConfig.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/OperatorConfig.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/configuration/OperatorConfig.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/configuration/OperatorConfig.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/configuration/fields/ChartConfig.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/ChartConfig.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/configuration/fields/ChartConfig.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/ChartConfig.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/configuration/fields/EnvironmentConfig.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/EnvironmentConfig.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/configuration/fields/EnvironmentConfig.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/EnvironmentConfig.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQConfig.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQConfig.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQConfig.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQConfig.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQManagementConfig.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQManagementConfig.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQManagementConfig.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQManagementConfig.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQNamespacePermissions.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQNamespacePermissions.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQNamespacePermissions.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQNamespacePermissions.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/configuration/fields/SchemaSecrets.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/SchemaSecrets.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/configuration/fields/SchemaSecrets.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/SchemaSecrets.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/model/LinkDescription.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/LinkDescription.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/model/LinkDescription.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/model/LinkDescription.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/model/box/dictionary/DictionaryEntity.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/dictionary/DictionaryEntity.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/model/box/dictionary/DictionaryEntity.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/model/box/dictionary/DictionaryEntity.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/model/box/grpc/GrpcEndpointConfiguration.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/grpc/GrpcEndpointConfiguration.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/model/box/grpc/GrpcEndpointConfiguration.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/model/box/grpc/GrpcEndpointConfiguration.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/model/box/grpc/GrpcEndpointMapping.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/grpc/GrpcEndpointMapping.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/model/box/grpc/GrpcEndpointMapping.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/model/box/grpc/GrpcEndpointMapping.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/model/box/grpc/GrpcExternalEndpointMapping.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/grpc/GrpcExternalEndpointMapping.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/model/box/grpc/GrpcExternalEndpointMapping.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/model/box/grpc/GrpcExternalEndpointMapping.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/model/box/grpc/GrpcRouterConfiguration.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/grpc/GrpcRouterConfiguration.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/model/box/grpc/GrpcRouterConfiguration.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/model/box/grpc/GrpcRouterConfiguration.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/model/box/grpc/GrpcServerConfiguration.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/grpc/GrpcServerConfiguration.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/model/box/grpc/GrpcServerConfiguration.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/model/box/grpc/GrpcServerConfiguration.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/model/box/grpc/GrpcServiceConfiguration.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/grpc/GrpcServiceConfiguration.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/model/box/grpc/GrpcServiceConfiguration.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/model/box/grpc/GrpcServiceConfiguration.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/model/box/grpc/RoutingStrategy.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/grpc/RoutingStrategy.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/model/box/grpc/RoutingStrategy.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/model/box/grpc/RoutingStrategy.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/model/box/grpc/factory/TargetBoxSpec.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/grpc/factory/TargetBoxSpec.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/model/box/grpc/factory/TargetBoxSpec.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/model/box/grpc/factory/TargetBoxSpec.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/model/box/mq/factory/GlobalNotification.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/GlobalNotification.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/model/box/mq/factory/GlobalNotification.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/GlobalNotification.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactory.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactory.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactory.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactory.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryBox.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryBox.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryBox.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryBox.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryEstore.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryEstore.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryEstore.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryEstore.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryMstore.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryMstore.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryMstore.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryMstore.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/helmrelease/HelmReleaseSpec.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/helmrelease/HelmReleaseSpec.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/spec/helmrelease/HelmReleaseSpec.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/spec/helmrelease/HelmReleaseSpec.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/shared/pin/GrpcClientPin.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/shared/pin/GrpcClientPin.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/spec/shared/pin/GrpcClientPin.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/spec/shared/pin/GrpcClientPin.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/shared/pin/GrpcSection.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/shared/pin/GrpcSection.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/spec/shared/pin/GrpcSection.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/spec/shared/pin/GrpcSection.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/shared/pin/GrpcServerPin.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/shared/pin/GrpcServerPin.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/spec/shared/pin/GrpcServerPin.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/spec/shared/pin/GrpcServerPin.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/shared/pin/Link.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/shared/pin/Link.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/spec/shared/pin/Link.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/spec/shared/pin/Link.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/shared/pin/MqPublisherPin.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/shared/pin/MqPublisherPin.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/spec/shared/pin/MqPublisherPin.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/spec/shared/pin/MqPublisherPin.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/shared/pin/MqSection.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/shared/pin/MqSection.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/spec/shared/pin/MqSection.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/spec/shared/pin/MqSection.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/shared/pin/MqSubscriberPin.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/shared/pin/MqSubscriberPin.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/spec/shared/pin/MqSubscriberPin.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/spec/shared/pin/MqSubscriberPin.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/shared/pin/PinSpec.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/shared/pin/PinSpec.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/spec/shared/pin/PinSpec.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/spec/shared/pin/PinSpec.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt From a19d4b5b60e412765141d99f0a2fa31264743cc6 Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Thu, 8 Aug 2024 12:07:32 +0400 Subject: [PATCH 02/42] [TH2-5226] Added RabbitMQGcTask --- README.md | 8 +- .../th2/infraoperator/OperatorState.java | 6 +- .../th2/infraoperator/Th2CrdController.java | 7 +- .../operator/StoreHelmTh2Op.java | 14 +-- .../operator/impl/EstoreHelmTh2Op.java | 6 +- .../operator/impl/MstoreHelmTh2Op.java | 5 +- .../linkresolver/mq/DeclareQueueResolver.java | 23 ++-- .../linkresolver/mq/RabbitMQContext.java | 69 +++++++----- .../fields/RabbitMQManagementConfig.kt | 3 +- .../mq/factory/MessageRouterConfigFactory.kt | 7 +- .../factory/MessageRouterConfigFactoryBox.kt | 5 +- .../MessageRouterConfigFactoryEstore.kt | 9 +- .../MessageRouterConfigFactoryMstore.kt | 11 +- .../spec/strategy/linkresolver/Util.kt | 62 +++++++++++ .../linkresolver/mq/BindQueueLinkResolver.kt | 17 +-- .../strategy/redeploy/tasks/RabbitMQGcTask.kt | 104 ++++++++++++++++++ 16 files changed, 275 insertions(+), 81 deletions(-) create mode 100644 src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/Util.kt create mode 100644 src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/RabbitMQGcTask.kt diff --git a/README.md b/README.md index ab85dc8f..3716b2bc 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,12 @@ rabbitMQManagement: persistence: true # determines if the RabbitMQ resources are persistent or not + + gcIntervalSec: 900 + # Interval of garbage collection in RabbitMQ. Each GC task integration consist of: + # 1) collect redundant resources according current RabbitMQ and Kubernetes resources state. + # 2) delete only resources included into both current and previous integrations (previous intersect current). + # 3) save collected resources but not included in the previous integration (current minus previous) for the next iteration schemaPermissions: # this section describes what permissions schema RabbitMQ user will have on its own resources @@ -131,7 +137,5 @@ openshift: # this section indicates whether application is run in openshift environment or not enabled: true/false #if not indicated default values is false - - ``` diff --git a/src/main/java/com/exactpro/th2/infraoperator/OperatorState.java b/src/main/java/com/exactpro/th2/infraoperator/OperatorState.java index c0e2155b..ed96275d 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/OperatorState.java +++ b/src/main/java/com/exactpro/th2/infraoperator/OperatorState.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -104,6 +104,10 @@ public Collection getAllBoxResources() { return resources; } + public Collection getNamespaces() { + return Collections.unmodifiableSet(namespaceStates.keySet()); + } + private NamespaceState computeIfAbsent(String namespace) { return namespaceStates.computeIfAbsent(namespace, s -> new NamespaceState()); } diff --git a/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java b/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java index 4518890b..bf3eb31b 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java +++ b/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,7 +41,6 @@ public static void main(String[] args) { OperatorMetrics.resetCacheErrors(); try { RabbitMQContext.declareTopicExchange(); - RabbitMQContext.cleanUpRabbitBeforeStart(); watchManager.addTarget(MstoreHelmTh2Op::new); watchManager.addTarget(EstoreHelmTh2Op::new); @@ -51,7 +50,9 @@ public static void main(String[] args) { watchManager.startInformers(); - new ContinuousTaskWorker().add(new CheckResourceCacheTask(300)); + ContinuousTaskWorker continuousTaskWorker = new ContinuousTaskWorker(); + continuousTaskWorker.add(new CheckResourceCacheTask(300)); + continuousTaskWorker.add(RabbitMQContext.buildGarbageCollectTask()); } catch (Exception e) { logger.error("Exception in main thread", e); watchManager.stopInformers(); diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/StoreHelmTh2Op.java b/src/main/java/com/exactpro/th2/infraoperator/operator/StoreHelmTh2Op.java index c2c9e44d..02d03b38 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/StoreHelmTh2Op.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/StoreHelmTh2Op.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,15 +28,7 @@ public abstract class StoreHelmTh2Op extends HelmReleaseTh2Op { - private static final Logger logger = LoggerFactory.getLogger(StoreHelmTh2Op.class); - - public static final String EVENT_STORAGE_PIN_ALIAS = "estore-pin"; - - public static final String EVENT_STORAGE_BOX_ALIAS = "estore"; - - public static final String MESSAGE_STORAGE_PIN_ALIAS = "mstore-pin"; - - public static final String MESSAGE_STORAGE_BOX_ALIAS = "mstore"; + private static final Logger LOGGER = LoggerFactory.getLogger(StoreHelmTh2Op.class); public StoreHelmTh2Op(KubernetesClient client) { super(client); @@ -57,7 +49,7 @@ private void nameCheck(CR resource) throws IOException { var msg = String.format("%s<%s.%s> has an invalid name, must be '%s'", extractType(resource), msNamespace, msName, stName); - logger.warn(msg); + LOGGER.warn(msg); resource.getStatus().failed(msg); updateStatus(resource); return; diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/impl/EstoreHelmTh2Op.java b/src/main/java/com/exactpro/th2/infraoperator/operator/impl/EstoreHelmTh2Op.java index fa863d54..4031c015 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/impl/EstoreHelmTh2Op.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/impl/EstoreHelmTh2Op.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,10 @@ public class EstoreHelmTh2Op extends StoreHelmTh2Op { + public static final String EVENT_STORAGE_PIN_ALIAS = "estore-pin"; + + public static final String EVENT_STORAGE_BOX_ALIAS = "estore"; + private final EstoreClient estoreClient; public EstoreHelmTh2Op(KubernetesClient client) { diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/impl/MstoreHelmTh2Op.java b/src/main/java/com/exactpro/th2/infraoperator/operator/impl/MstoreHelmTh2Op.java index e0763f9f..eb1e4a1f 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/impl/MstoreHelmTh2Op.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/impl/MstoreHelmTh2Op.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,9 @@ import io.fabric8.kubernetes.client.informers.SharedInformerFactory; public class MstoreHelmTh2Op extends StoreHelmTh2Op { + public static final String MESSAGE_STORAGE_PIN_ALIAS = "mstore-pin"; + public static final String MESSAGE_STORAGE_BOX_ALIAS = "mstore"; private final MstoreClient mstoreClient; public MstoreHelmTh2Op(KubernetesClient client) { @@ -57,5 +59,4 @@ protected MessageRouterConfigFactory getMqConfigFactory() { protected String getStorageName() { return MESSAGE_STORAGE_BOX_ALIAS; } - } diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/DeclareQueueResolver.java b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/DeclareQueueResolver.java index 785c878e..797831d3 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/DeclareQueueResolver.java +++ b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/DeclareQueueResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ import com.exactpro.th2.infraoperator.util.HelmReleaseUtils; import com.rabbitmq.client.Channel; import com.rabbitmq.http.client.domain.QueueInfo; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,12 +37,12 @@ import java.util.List; import java.util.Set; -import static com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op.EVENT_STORAGE_BOX_ALIAS; -import static com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op.EVENT_STORAGE_PIN_ALIAS; -import static com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op.MESSAGE_STORAGE_BOX_ALIAS; -import static com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op.MESSAGE_STORAGE_PIN_ALIAS; +import static com.exactpro.th2.infraoperator.spec.strategy.linkresolver.Util.buildEstoreQueue; +import static com.exactpro.th2.infraoperator.spec.strategy.linkresolver.Util.buildMstoreQueue; import static com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext.getChannel; import static com.exactpro.th2.infraoperator.util.ExtractUtils.extractName; +import static java.util.Collections.emptyList; +import static java.util.Objects.requireNonNullElse; public class DeclareQueueResolver { @@ -112,9 +113,13 @@ private static Set getBoxPreviousQueues(String namespace, String boxName return HelmReleaseUtils.extractQueues(hr.getComponentValuesSection()); } - private static Set getBoxQueuesFromRabbit(String namespace, String boxName) { + /** + * Collect all queues related to the {@code namespace} {@code boxName} component in RabbitMQ + * @return mutable set of queues + */ + private static @NotNull Set getBoxQueuesFromRabbit(String namespace, String boxName) { - List queueInfoList = RabbitMQContext.getQueues(); + List queueInfoList = requireNonNullElse(RabbitMQContext.getQueues(), emptyList()); Set queueNames = new HashSet<>(); queueInfoList.forEach(q -> { @@ -141,8 +146,8 @@ private static void removeExtinctQueues( String resourceLabel, String namespace ) { - String estoreQueue = new QueueName(namespace, EVENT_STORAGE_BOX_ALIAS, EVENT_STORAGE_PIN_ALIAS).toString(); - String mstoreQueue = new QueueName(namespace, MESSAGE_STORAGE_BOX_ALIAS, MESSAGE_STORAGE_PIN_ALIAS).toString(); + String estoreQueue = buildEstoreQueue(namespace); + String mstoreQueue = buildMstoreQueue(namespace); if (!extinctQueueNames.isEmpty()) { logger.info("Trying to delete queues associated with \"{}\"", resourceLabel); diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java index 5d1b5814..2b07cec4 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java +++ b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,12 +23,13 @@ import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQNamespacePermissions; import com.exactpro.th2.infraoperator.model.kubernetes.configmaps.ConfigMaps; import com.exactpro.th2.infraoperator.spec.shared.PinSettings; -import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.queue.QueueName; import com.exactpro.th2.infraoperator.spec.strategy.redeploy.NonTerminalException; import com.exactpro.th2.infraoperator.spec.strategy.redeploy.RetryableTaskQueue; +import com.exactpro.th2.infraoperator.spec.strategy.redeploy.tasks.RabbitMQGcTask; import com.exactpro.th2.infraoperator.spec.strategy.redeploy.tasks.RecreateQueuesAndBindings; import com.exactpro.th2.infraoperator.spec.strategy.redeploy.tasks.RetryRabbitSetup; import com.exactpro.th2.infraoperator.spec.strategy.redeploy.tasks.RetryTopicExchangeTask; +import com.exactpro.th2.infraoperator.spec.strategy.redeploy.tasks.Task; import com.exactpro.th2.infraoperator.util.Strings; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; @@ -41,17 +42,26 @@ import com.rabbitmq.http.client.domain.ExchangeInfo; import com.rabbitmq.http.client.domain.QueueInfo; import com.rabbitmq.http.client.domain.UserPermissions; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.net.MalformedURLException; import java.net.URISyntaxException; -import java.util.*; - -import static com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static com.exactpro.th2.infraoperator.spec.strategy.linkresolver.Util.buildEstoreQueue; +import static com.exactpro.th2.infraoperator.spec.strategy.linkresolver.Util.buildMstoreQueue; import static com.exactpro.th2.infraoperator.spec.strategy.linkresolver.queue.QueueName.QUEUE_NAME_REGEXP; import static java.lang.String.format; +import static java.util.Objects.requireNonNullElse; public final class RabbitMQContext { @@ -151,7 +161,7 @@ private static void createStoreQueues(String namespace) throws Exception { var channel = getChannel(); RabbitMQManagementConfig rabbitMQManagementConfig = getManagementConfig(); var declareResult = channel.queueDeclare( - new QueueName(namespace, EVENT_STORAGE_BOX_ALIAS, EVENT_STORAGE_PIN_ALIAS).toString(), + buildEstoreQueue(namespace), rabbitMQManagementConfig.getPersistence(), false, false, @@ -159,7 +169,7 @@ private static void createStoreQueues(String namespace) throws Exception { ); logger.info("Queue \"{}\" was successfully declared", declareResult.getQueue()); declareResult = channel.queueDeclare( - new QueueName(namespace, MESSAGE_STORAGE_BOX_ALIAS, MESSAGE_STORAGE_PIN_ALIAS).toString(), + buildMstoreQueue(namespace), rabbitMQManagementConfig.getPersistence(), false, false, @@ -203,18 +213,14 @@ private static void removeSchemaExchange(String exchangeName) { private static void removeSchemaQueues(String namespace) { try { Channel channel = getChannel(); - - List queueInfoList = getQueues(); + List queueInfoList = getTh2Queues(); queueInfoList.forEach(q -> { String queueName = q.getName(); - var queue = QueueName.fromString(queueName); - if (queue != null && queue.getNamespace().equals(namespace)) { - try { - channel.queueDelete(queueName); - logger.info("Deleted queue: [{}]", queueName); - } catch (IOException e) { - logger.error("Exception deleting queue: [{}]", queueName, e); - } + try { + channel.queueDelete(queueName); + logger.info("Deleted queue: [{}]", queueName); + } catch (IOException e) { + logger.error("Exception deleting queue: [{}]", queueName, e); } }); } catch (Exception e) { @@ -222,22 +228,22 @@ private static void removeSchemaQueues(String namespace) { } } + public static Task buildGarbageCollectTask() { + return new RabbitMQGcTask(getManagementConfig().getGcIntervalSec()); + } + public static void cleanUpRabbitBeforeStart() { try { Channel channel = getChannel(); List namespacePrefixes = ConfigLoader.getConfig().getNamespacePrefixes(); - ; - - List queueInfoList = getQueues(); + List queueInfoList = getTh2Queues(); queueInfoList.forEach(q -> { String queueName = q.getName(); - if (queueName != null && queueName.matches(QUEUE_NAME_REGEXP)) { - try { - channel.queueDelete(queueName); - logger.info("Deleted queue: [{}]", queueName); - } catch (IOException e) { - logger.error("Exception deleting queue: [{}]", queueName, e); - } + try { + channel.queueDelete(queueName); + logger.info("Deleted queue: [{}]", queueName); + } catch (IOException e) { + logger.error("Exception deleting queue: [{}]", queueName, e); } }); @@ -287,8 +293,7 @@ public static Map generateQueueArguments(PinSettings pinSettings } } - public static List getQueues() { - + public static @Nullable List getQueues() { String vHostName = getManagementConfig().getVhostName(); try { @@ -301,6 +306,12 @@ public static List getQueues() { } } + public static @NotNull List getTh2Queues() { + return requireNonNullElse(getQueues(), Collections.emptyList()).stream() + .filter(queueInfo -> queueInfo.getName() != null && queueInfo.getName().matches(QUEUE_NAME_REGEXP)) + .collect(Collectors.toList()); + } + public static List getQueueBindings(String queue) { String vHostName = getManagementConfig().getVhostName(); try { diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQManagementConfig.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQManagementConfig.kt index 70b31cee..74795eaa 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQManagementConfig.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQManagementConfig.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,5 +25,6 @@ data class RabbitMQManagementConfig( val username: String = "", val password: String = "", val persistence: Boolean = false, + val gcIntervalSec: Int = 15 * 60, val schemaPermissions: RabbitMQNamespacePermissions = RabbitMQNamespacePermissions() ) diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactory.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactory.kt index 3440f23d..24429ced 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactory.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactory.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,9 +19,10 @@ package com.exactpro.th2.infraoperator.model.box.mq.factory import com.exactpro.th2.infraoperator.model.LinkDescription import com.exactpro.th2.infraoperator.model.box.mq.MessageRouterConfiguration import com.exactpro.th2.infraoperator.model.box.mq.QueueConfiguration -import com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op import com.exactpro.th2.infraoperator.spec.Th2CustomResource import com.exactpro.th2.infraoperator.spec.shared.PinAttribute +import com.exactpro.th2.infraoperator.spec.shared.pin.PinSpec +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.buildEstoreRoutingKeyName import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.queue.QueueName import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.queue.RoutingKeyName import com.exactpro.th2.infraoperator.util.ExtractUtils @@ -42,7 +43,7 @@ abstract class MessageRouterConfigFactory { protected fun generatePublishToEstorePin(namespace: String, boxName: String) = QueueConfiguration( LinkDescription( QueueName.EMPTY, - RoutingKeyName(namespace, boxName, StoreHelmTh2Op.EVENT_STORAGE_PIN_ALIAS), + buildEstoreRoutingKeyName(namespace, boxName), namespace ), setOf(PinAttribute.publish.name, PinAttribute.event.name), diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryBox.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryBox.kt index 97fe18c1..27f4fa4f 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryBox.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryBox.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,8 +19,9 @@ package com.exactpro.th2.infraoperator.model.box.mq.factory import com.exactpro.th2.infraoperator.configuration.ConfigLoader import com.exactpro.th2.infraoperator.model.box.mq.MessageRouterConfiguration import com.exactpro.th2.infraoperator.model.box.mq.QueueConfiguration -import com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op.EVENT_STORAGE_PIN_ALIAS +import com.exactpro.th2.infraoperator.operator.impl.EstoreHelmTh2Op.EVENT_STORAGE_PIN_ALIAS import com.exactpro.th2.infraoperator.spec.Th2CustomResource +import com.exactpro.th2.infraoperator.spec.shared.pin.PinSpec import com.exactpro.th2.infraoperator.util.ExtractUtils /** diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryEstore.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryEstore.kt index ed8e30e5..822b7e61 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryEstore.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryEstore.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,10 +20,11 @@ import com.exactpro.th2.infraoperator.configuration.ConfigLoader import com.exactpro.th2.infraoperator.model.LinkDescription import com.exactpro.th2.infraoperator.model.box.mq.MessageRouterConfiguration import com.exactpro.th2.infraoperator.model.box.mq.QueueConfiguration -import com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op.EVENT_STORAGE_PIN_ALIAS +import com.exactpro.th2.infraoperator.operator.impl.EstoreHelmTh2Op.EVENT_STORAGE_PIN_ALIAS import com.exactpro.th2.infraoperator.spec.Th2CustomResource import com.exactpro.th2.infraoperator.spec.shared.PinAttribute -import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.queue.QueueName +import com.exactpro.th2.infraoperator.spec.shared.pin.PinSpec +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.buildEstoreQueueName import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.queue.RoutingKeyName import com.exactpro.th2.infraoperator.util.ExtractUtils @@ -47,7 +48,7 @@ class MessageRouterConfigFactoryEstore : MessageRouterConfigFactory() { // add event storage pin config for each resource queues[EVENT_STORAGE_PIN_ALIAS] = QueueConfiguration( LinkDescription( - QueueName(namespace, boxName, EVENT_STORAGE_PIN_ALIAS), + buildEstoreQueueName(namespace, boxName), RoutingKeyName.EMPTY, namespace ), diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryMstore.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryMstore.kt index c0e3f162..89bd0cab 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryMstore.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryMstore.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,11 +20,12 @@ import com.exactpro.th2.infraoperator.configuration.ConfigLoader import com.exactpro.th2.infraoperator.model.LinkDescription import com.exactpro.th2.infraoperator.model.box.mq.MessageRouterConfiguration import com.exactpro.th2.infraoperator.model.box.mq.QueueConfiguration -import com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op.EVENT_STORAGE_PIN_ALIAS -import com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op.MESSAGE_STORAGE_PIN_ALIAS +import com.exactpro.th2.infraoperator.operator.impl.EstoreHelmTh2Op.EVENT_STORAGE_PIN_ALIAS +import com.exactpro.th2.infraoperator.operator.impl.MstoreHelmTh2Op.MESSAGE_STORAGE_PIN_ALIAS import com.exactpro.th2.infraoperator.spec.Th2CustomResource import com.exactpro.th2.infraoperator.spec.shared.PinAttribute -import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.queue.QueueName +import com.exactpro.th2.infraoperator.spec.shared.pin.PinSpec +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.buildMstoreQueueName import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.queue.RoutingKeyName import com.exactpro.th2.infraoperator.util.ExtractUtils @@ -49,7 +50,7 @@ class MessageRouterConfigFactoryMstore : MessageRouterConfigFactory() { queues[EVENT_STORAGE_PIN_ALIAS] = generatePublishToEstorePin(namespace, boxName) queues[MESSAGE_STORAGE_PIN_ALIAS] = QueueConfiguration( LinkDescription( - QueueName(namespace, boxName, MESSAGE_STORAGE_PIN_ALIAS), + buildMstoreQueueName(namespace, boxName), RoutingKeyName.EMPTY, namespace ), diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/Util.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/Util.kt new file mode 100644 index 00000000..c4612324 --- /dev/null +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/Util.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2024 Exactpro (Exactpro Systems Limited) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@file:JvmName("Util") + +package com.exactpro.th2.infraoperator.spec.strategy.linkresolver + +import com.exactpro.th2.infraoperator.operator.impl.EstoreHelmTh2Op.EVENT_STORAGE_BOX_ALIAS +import com.exactpro.th2.infraoperator.operator.impl.EstoreHelmTh2Op.EVENT_STORAGE_PIN_ALIAS +import com.exactpro.th2.infraoperator.operator.impl.MstoreHelmTh2Op.MESSAGE_STORAGE_BOX_ALIAS +import com.exactpro.th2.infraoperator.operator.impl.MstoreHelmTh2Op.MESSAGE_STORAGE_PIN_ALIAS +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.queue.QueueName +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.queue.RoutingKeyName + + +fun buildEstoreQueueName(namespace: String): QueueName = QueueName( + namespace, + EVENT_STORAGE_BOX_ALIAS, + EVENT_STORAGE_PIN_ALIAS +) + +fun buildEstoreQueue(namespace: String): String = buildEstoreQueueName(namespace).toString() + +fun buildEstoreQueueName(namespace: String, component: String): QueueName = QueueName( + namespace, + component, + EVENT_STORAGE_PIN_ALIAS +) + +fun buildEstoreQueue(namespace: String, component: String): String = + buildEstoreQueueName(namespace, component).toString() + +fun buildEstoreRoutingKeyName(namespace: String, component: String) = RoutingKeyName(namespace, component, EVENT_STORAGE_PIN_ALIAS) + +fun buildMstoreQueueName(namespace: String) = QueueName( + namespace, + MESSAGE_STORAGE_BOX_ALIAS, + MESSAGE_STORAGE_PIN_ALIAS +) + +fun buildMstoreQueue(namespace: String): String = buildMstoreQueueName(namespace).toString() + +fun buildMstoreQueueName(namespace: String, component: String) = QueueName( + namespace, + component, + MESSAGE_STORAGE_PIN_ALIAS +) + +fun buildMstoreQueue(namespace: String, component: String): String = + buildMstoreQueueName(namespace, component).toString() diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt index 3ec3e759..c50b3605 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,13 +17,14 @@ package com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq import com.exactpro.th2.infraoperator.model.LinkDescription -import com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op.EVENT_STORAGE_BOX_ALIAS -import com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op.EVENT_STORAGE_PIN_ALIAS -import com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op.MESSAGE_STORAGE_BOX_ALIAS -import com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op.MESSAGE_STORAGE_PIN_ALIAS +import com.exactpro.th2.infraoperator.operator.impl.EstoreHelmTh2Op.EVENT_STORAGE_BOX_ALIAS +import com.exactpro.th2.infraoperator.operator.impl.MstoreHelmTh2Op.MESSAGE_STORAGE_BOX_ALIAS import com.exactpro.th2.infraoperator.spec.Th2CustomResource import com.exactpro.th2.infraoperator.spec.shared.PinAttribute import com.exactpro.th2.infraoperator.spec.shared.pin.Link +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.buildEstoreQueueName +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.buildEstoreRoutingKeyName +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.buildMstoreQueueName import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.queue.QueueName import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.queue.RoutingKeyName import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.queue.RoutingKeyName.ROUTING_KEY_REGEXP @@ -65,14 +66,14 @@ object BindQueueLinkResolver { } // create event storage link for each resource val estoreLinkDescription = LinkDescription( - QueueName(namespace, EVENT_STORAGE_BOX_ALIAS, EVENT_STORAGE_PIN_ALIAS), - RoutingKeyName(namespace, resourceName, EVENT_STORAGE_PIN_ALIAS), + buildEstoreQueueName(namespace), + buildEstoreRoutingKeyName(namespace, resourceName), namespace ) bindQueues(estoreLinkDescription, commitHash) val currentLinks: MutableList = ArrayList() - val queueName = QueueName(namespace, MESSAGE_STORAGE_BOX_ALIAS, MESSAGE_STORAGE_PIN_ALIAS) + val queueName = buildMstoreQueueName(namespace) // create message store link for only resources that need it for ((pinName, attributes) in resource.spec.pins.mq.publishers) { if (checkStorePinAttributes(attributes, resourceLabel, pinName)) { diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/RabbitMQGcTask.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/RabbitMQGcTask.kt new file mode 100644 index 00000000..1afc64c8 --- /dev/null +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/RabbitMQGcTask.kt @@ -0,0 +1,104 @@ +/* + * Copyright 2024 Exactpro (Exactpro Systems Limited) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.exactpro.th2.infraoperator.spec.strategy.redeploy.tasks + +import com.exactpro.th2.infraoperator.OperatorState +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.buildEstoreQueue +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.buildMstoreQueue +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext +import com.exactpro.th2.infraoperator.util.ExtractUtils +import com.exactpro.th2.infraoperator.util.HelmReleaseUtils +import com.rabbitmq.http.client.domain.QueueInfo +import mu.KotlinLogging +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.withLock + +class RabbitMQGcTask( + private val retryDelay: Long +): Task { + private val lock = ReentrantLock() + private var deleteCandidates: Set = emptySet() + + override fun run(): Unit = lock.withLock { + deleteRedundantQueues() + } + + override fun getName(): String = "RabbitMQGC" + + override fun getRetryDelay(): Long = retryDelay + + private fun deleteRedundantQueues() { + try { + val potentialGarbage = hashSetOf() + RabbitMQContext.getTh2Queues().asSequence() + .map(QueueInfo::getName) + .forEach(potentialGarbage::add) + + if (potentialGarbage.isEmpty()) { + deleteCandidates = emptySet() + return + } + + + val operatorState = OperatorState.INSTANCE + operatorState.namespaces.forEach { namespace -> + potentialGarbage.remove(buildEstoreQueue(namespace)) + potentialGarbage.remove(buildMstoreQueue(namespace)) + } + + operatorState.getAllBoxResources().asSequence() + .flatMap { cr -> + val namespace = ExtractUtils.extractNamespace(cr) + val name = ExtractUtils.extractName(cr) + val hr = operatorState.getHelmReleaseFromCache(name, namespace) + sequence { + yieldAll(HelmReleaseUtils.extractQueues(hr.componentValuesSection)) + yield(buildEstoreQueue(namespace, name)) + } + }.forEach(potentialGarbage::remove) + + if (potentialGarbage.isEmpty()) { + deleteCandidates = emptySet() + return + } + + val channel = RabbitMQContext.getChannel() + val nextDeleteCandidates = potentialGarbage.minus(deleteCandidates).toMutableSet() + deleteCandidates.intersect(potentialGarbage).forEach { queue -> + try { + channel.queueDelete(queue) + K_LOGGER.info { "Deleted queue: [$queue]" } + } catch (e: Exception) { + nextDeleteCandidates.add(queue) + K_LOGGER.error(e) { "Exception deleting queue: [$queue]" } + } + } + + if (nextDeleteCandidates.isNotEmpty()) { + K_LOGGER.info { "Queues $nextDeleteCandidates are delete candidates for the next iteration" } + deleteCandidates = nextDeleteCandidates + } else { + deleteCandidates = emptySet() + } + } catch (e: Exception) { + K_LOGGER.error(e) { "$name task failure" } + } + } + + companion object { + private val K_LOGGER = KotlinLogging.logger { } + } +} \ No newline at end of file From 04e15b9424be866ce5751d3335af9b7ca90c9f18 Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Thu, 8 Aug 2024 13:32:05 +0400 Subject: [PATCH 03/42] [TH2-5226] Updated libraries --- Dockerfile | 16 +-- README.md | 14 ++ build.gradle | 133 ++++-------------- gradle.properties | 22 +-- gradle/libs.versions.toml | 24 ++++ gradle/wrapper/gradle-wrapper.properties | 18 +-- .../operator/impl/MstoreHelmTh2Op.java | 1 + .../linkresolver/mq/RabbitMQContext.java | 20 ++- .../fields/RabbitMQManagementConfig.kt | 2 +- .../spec/strategy/linkresolver/Util.kt | 10 +- .../strategy/redeploy/tasks/RabbitMQGcTask.kt | 13 +- .../configuration/ConfigurationTests.java | 4 +- 12 files changed, 109 insertions(+), 168 deletions(-) create mode 100644 gradle/libs.versions.toml diff --git a/Dockerfile b/Dockerfile index d7a0206c..c0f3c6d5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,9 @@ -FROM gradle:7.6-jdk11 AS build +FROM gradle:8.7-jdk11 AS build ARG app_version=0.0.0 COPY ./ . -RUN gradle build -Prelease_version=${app_version} +RUN gradle --no-daemon clean build dockerPrepare -Prelease_version=${release_version} -RUN mkdir /home/app -RUN cp ./build/libs/*.jar /home/app/application.jar - -FROM eclipse-temurin:11-alpine -COPY --from=build /home/app /home/app - -WORKDIR /home/app/ -ENTRYPOINT ["java", "-Dlog4j2.configurationFile=file:/var/th2/config/log4j2.properties", "-jar", "/home/app/application.jar"] \ No newline at end of file +FROM adoptopenjdk/openjdk11:alpine +WORKDIR /home +COPY --from=build /home/gradle/build/docker . +ENTRYPOINT ["/home/service/bin/service", "-Dlog4j2.configurationFile=file:/var/th2/config/log4j2.properties"] \ No newline at end of file diff --git a/README.md b/README.md index 3716b2bc..1f5b1c54 100644 --- a/README.md +++ b/README.md @@ -139,3 +139,17 @@ openshift: #if not indicated default values is false ``` +## Release notes + +### 4.7.0 ++ Periodically deletes redundant resources from RabbitMQ instead of remove on start ++ Migrated to th2 plugin `0.1.1` + ++ Updated: + + bom: `4.6.1` + + kubernetes-client: `6.13.1` + + force okhttp: `4.12.0` + + force logging-interceptor: `4.12.0` + + http-client: `5.2.0` + + java-uuid-generator: `5.1.0` + + kotlin-logging: `3.0.5` \ No newline at end of file diff --git a/build.gradle b/build.gradle index a4d1eafd..875d6cdd 100644 --- a/build.gradle +++ b/build.gradle @@ -1,141 +1,70 @@ -/* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - plugins { - id 'java' + id 'application' id 'checkstyle' - id "io.gitlab.arturbosch.detekt" version "${detekt_version}" - id "org.jetbrains.kotlin.jvm" version "${kotlin_version}" - id "org.owasp.dependencycheck" version "${owaspVersion}" + alias(libs.plugins.detekt) + alias(libs.plugins.kotlin) + alias(libs.plugins.th2.component) } group = 'com.exactpro.th2' version = release_version +kotlin { + jvmToolchain(11) +} + repositories { mavenCentral() } checkstyle { - toolVersion = "10.12.0" + toolVersion = "10.12.4" } detekt { buildUponDefaultConfig = true autoCorrect = true - config = files("$rootDir/config/detekt/detekt.yml") -} - -ext { - uuid_generator_version = '4.2.0' - okhttp_version = '4.10.0' - fabric8_version = '6.6.2' - rabbit_amqp_version = '5.16.0' - rabbit_http_version = '5.0.0' - kotlin_logging_version = '3.0.0' // 3.0.0 the las version supported 1.6.21 - jetbrains_annotations_version = '24.0.1' - mockito_version = '3.11.2' - jupiter_version = '5.9.2' - guava_version = '32.0.1-jre' - snakeyaml_version = '2.0' -} - -configurations.configureEach() { - resolutionStrategy { - force "com.google.guava:guava:$guava_version" - force "org.yaml:snakeyaml:$snakeyaml_version" - } + config.setFrom("$rootDir/config/detekt/detekt.yml") } dependencies { - api platform('com.exactpro.th2:bom:4.3.0') implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml" - implementation group: 'com.fasterxml.jackson.module', name: 'jackson-module-kotlin' - implementation "com.fasterxml.uuid:java-uuid-generator:${uuid_generator_version}" - - implementation "com.squareup.okhttp3:okhttp:${okhttp_version}" + implementation "com.fasterxml.jackson.module:jackson-module-kotlin" + implementation libs.java.uuid.generator implementation "org.apache.commons:commons-text" - implementation "io.fabric8:kubernetes-client:${fabric8_version}" - implementation "com.rabbitmq:amqp-client:${rabbit_amqp_version}" - implementation "com.rabbitmq:http-client:${rabbit_http_version}" + implementation libs.kubernetes.client + implementation(libs.okhttp) { + because "okhttp:3.12.12 has vulnerabilities" + } + implementation(libs.logging.interceptor) { + because "logging-interceptor:3.12.12 has vulnerabilities" + } - implementation "org.slf4j:slf4j-api" - implementation "org.apache.logging.log4j:log4j-slf4j2-impl" - implementation "org.apache.logging.log4j:log4j-core" - implementation group: 'io.github.microutils', name: 'kotlin-logging', version: kotlin_logging_version + implementation "com.rabbitmq:amqp-client" + implementation libs.http.client - implementation "org.jetbrains:annotations:${jetbrains_annotations_version}" + implementation 'org.apache.logging.log4j:log4j-slf4j2-impl' + implementation 'org.apache.logging.log4j:log4j-core' + implementation libs.kotlin.logging + implementation "org.jetbrains:annotations" implementation "io.prometheus:simpleclient" implementation "io.prometheus:simpleclient_httpserver" implementation "io.prometheus:simpleclient_hotspot" - testImplementation group: 'org.mockito', name: 'mockito-core', version: "${mockito_version}" - - testImplementation "org.junit.jupiter:junit-jupiter-api:${jupiter_version}" - testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${jupiter_version}" + testImplementation libs.mockito.core + testImplementation libs.junit.jupiter.api + testRuntimeOnly libs.junit.jupiter.engine detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:${detekt_version}") } -dependencyCheck { - formats=['SARIF', 'JSON', 'HTML'] - failBuildOnCVSS=5 - - analyzers { - assemblyEnabled = false - nugetconfEnabled = false - nodeEnabled = false - } -} - -jar { - duplicatesStrategy = DuplicatesStrategy.INCLUDE - manifest { - attributes( - 'Created-By': "${System.getProperty('java.version')} (${System.getProperty('java.vendor')})", - 'Specification-Title': '', - 'Specification-Vendor': 'Exactpro Systems LLC', - 'Main-Class': 'com.exactpro.th2.infraoperator.Th2CrdController', - 'Implementation-Title': project.archivesBaseName, - 'Implementation-Vendor': 'Exactpro Systems LLC', - 'Implementation-Vendor-Id': 'com.exactpro', - 'Implementation-Version': project.version - ) - } - - from { - configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } - } -} - test { useJUnitPlatform() } -compileKotlin { - kotlinOptions { - jvmTarget = "11" - } -} - -compileTestKotlin { - kotlinOptions { - jvmTarget = "11" - } -} +dependencyCheck { + skipConfigurations += "checkstyle" +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index c00b6c2a..72df96a7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,21 +1 @@ -# -# Copyright 2020-2021 Exactpro (Exactpro Systems Limited) -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -release_version = 4.6.3 - -kotlin_version = 1.6.21 -detekt_version = 1.22.0 -owaspVersion = 8.2.1 \ No newline at end of file +release_version = 4.7.0 \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 00000000..d32ed0c2 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,24 @@ +[versions] +kotlin = "1.8.22" +th2-plugin = "0.1.1" +jupiter = "5.10.3" +okhttp3 = "4.12.0" + +[libraries] +kotlin-logging = { group = "io.github.microutils", name = "kotlin-logging", version = "3.0.5" } + +okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp3" } +logging-interceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp3" } +kubernetes-client = { group = "io.fabric8", name = "kubernetes-client", version = "6.13.1" } +http-client = { group = "com.rabbitmq", name = "http-client", version = "5.2.0" } +java-uuid-generator = { group = "com.fasterxml.uuid", name = "java-uuid-generator", version = "5.1.0" } + +mockito-core = { group = "org.mockito", name = "mockito-core", version = "5.12.0" } +junit-jupiter-api = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "jupiter" } +junit-jupiter-engine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", version.ref = "jupiter" } + +[plugins] +kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } +detekt = { id = "io.gitlab.arturbosch.detekt", version = "1.23.6" } + +th2-component = { id = "com.exactpro.th2.gradle.component", version.ref = "th2-plugin" } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6ba21842..7956eee5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,22 +1,6 @@ -# -# Copyright 2020-2021 Exactpro (Exactpro Systems Limited) -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - #Mon May 04 17:09:53 MSK 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/impl/MstoreHelmTh2Op.java b/src/main/java/com/exactpro/th2/infraoperator/operator/impl/MstoreHelmTh2Op.java index eb1e4a1f..2db5c8ef 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/impl/MstoreHelmTh2Op.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/impl/MstoreHelmTh2Op.java @@ -31,6 +31,7 @@ public class MstoreHelmTh2Op extends StoreHelmTh2Op { public static final String MESSAGE_STORAGE_PIN_ALIAS = "mstore-pin"; public static final String MESSAGE_STORAGE_BOX_ALIAS = "mstore"; + private final MstoreClient mstoreClient; public MstoreHelmTh2Op(KubernetesClient client) { diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java index 2b07cec4..98eda001 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java +++ b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java @@ -395,13 +395,7 @@ static class ChannelContext { private Channel channel; ChannelContext() { - RabbitMQManagementConfig rabbitMQManagementConfig = getManagementConfig(); - ConnectionFactory connectionFactory = new ConnectionFactory(); - connectionFactory.setHost(rabbitMQManagementConfig.getHost()); - connectionFactory.setPort(rabbitMQManagementConfig.getApplicationPort()); - connectionFactory.setVirtualHost(rabbitMQManagementConfig.getVhostName()); - connectionFactory.setUsername(rabbitMQManagementConfig.getUsername()); - connectionFactory.setPassword(rabbitMQManagementConfig.getPassword()); + ConnectionFactory connectionFactory = createConnectionFactory(); try { this.connection = connectionFactory.newConnection(); this.connection.addShutdownListener(new RmqClientShutdownEventListener()); @@ -435,6 +429,18 @@ synchronized void close() { } } + @NotNull + private static ConnectionFactory createConnectionFactory() { + RabbitMQManagementConfig rabbitMQManagementConfig = getManagementConfig(); + ConnectionFactory connectionFactory = new ConnectionFactory(); + connectionFactory.setHost(rabbitMQManagementConfig.getHost()); + connectionFactory.setPort(rabbitMQManagementConfig.getApplicationPort()); + connectionFactory.setVirtualHost(rabbitMQManagementConfig.getVhostName()); + connectionFactory.setUsername(rabbitMQManagementConfig.getUsername()); + connectionFactory.setPassword(rabbitMQManagementConfig.getPassword()); + return connectionFactory; + } + private static class RmqClientShutdownEventListener implements ShutdownListener { @Override diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQManagementConfig.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQManagementConfig.kt index 74795eaa..840ff378 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQManagementConfig.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQManagementConfig.kt @@ -25,6 +25,6 @@ data class RabbitMQManagementConfig( val username: String = "", val password: String = "", val persistence: Boolean = false, - val gcIntervalSec: Int = 15 * 60, + val gcIntervalSec: Int = 900, val schemaPermissions: RabbitMQNamespacePermissions = RabbitMQNamespacePermissions() ) diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/Util.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/Util.kt index c4612324..72115c16 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/Util.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/Util.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024 Exactpro (Exactpro Systems Limited) + * Copyright 2024-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + @file:JvmName("Util") package com.exactpro.th2.infraoperator.spec.strategy.linkresolver @@ -24,7 +25,6 @@ import com.exactpro.th2.infraoperator.operator.impl.MstoreHelmTh2Op.MESSAGE_STOR import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.queue.QueueName import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.queue.RoutingKeyName - fun buildEstoreQueueName(namespace: String): QueueName = QueueName( namespace, EVENT_STORAGE_BOX_ALIAS, @@ -42,7 +42,11 @@ fun buildEstoreQueueName(namespace: String, component: String): QueueName = Queu fun buildEstoreQueue(namespace: String, component: String): String = buildEstoreQueueName(namespace, component).toString() -fun buildEstoreRoutingKeyName(namespace: String, component: String) = RoutingKeyName(namespace, component, EVENT_STORAGE_PIN_ALIAS) +fun buildEstoreRoutingKeyName(namespace: String, component: String) = RoutingKeyName( + namespace, + component, + EVENT_STORAGE_PIN_ALIAS +) fun buildMstoreQueueName(namespace: String) = QueueName( namespace, diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/RabbitMQGcTask.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/RabbitMQGcTask.kt index 1afc64c8..3202f274 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/RabbitMQGcTask.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/RabbitMQGcTask.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024 Exactpro (Exactpro Systems Limited) + * Copyright 2024-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.exactpro.th2.infraoperator.spec.strategy.redeploy.tasks import com.exactpro.th2.infraoperator.OperatorState @@ -23,12 +24,13 @@ import com.exactpro.th2.infraoperator.util.ExtractUtils import com.exactpro.th2.infraoperator.util.HelmReleaseUtils import com.rabbitmq.http.client.domain.QueueInfo import mu.KotlinLogging +import java.io.IOException import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock class RabbitMQGcTask( private val retryDelay: Long -): Task { +) : Task { private val lock = ReentrantLock() private var deleteCandidates: Set = emptySet() @@ -52,7 +54,6 @@ class RabbitMQGcTask( return } - val operatorState = OperatorState.INSTANCE operatorState.namespaces.forEach { namespace -> potentialGarbage.remove(buildEstoreQueue(namespace)) @@ -81,7 +82,7 @@ class RabbitMQGcTask( try { channel.queueDelete(queue) K_LOGGER.info { "Deleted queue: [$queue]" } - } catch (e: Exception) { + } catch (e: IOException) { nextDeleteCandidates.add(queue) K_LOGGER.error(e) { "Exception deleting queue: [$queue]" } } @@ -93,7 +94,7 @@ class RabbitMQGcTask( } else { deleteCandidates = emptySet() } - } catch (e: Exception) { + } catch (e: IOException) { K_LOGGER.error(e) { "$name task failure" } } } @@ -101,4 +102,4 @@ class RabbitMQGcTask( companion object { private val K_LOGGER = KotlinLogging.logger { } } -} \ No newline at end of file +} diff --git a/src/test/java/com/exactpro/th2/infraoperator/configuration/ConfigurationTests.java b/src/test/java/com/exactpro/th2/infraoperator/configuration/ConfigurationTests.java index b987e769..2372ff65 100644 --- a/src/test/java/com/exactpro/th2/infraoperator/configuration/ConfigurationTests.java +++ b/src/test/java/com/exactpro/th2/infraoperator/configuration/ConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -63,6 +63,7 @@ void testFullConfig() { "username", "password", true, + 900, new RabbitMQNamespacePermissions( "configure", "read", "write" ) @@ -98,6 +99,7 @@ void testRabbitMQManagementConfig() { "username", "password", true, + 900, new RabbitMQNamespacePermissions( "configure", "read", "write" ) From a565d2d844b252fb591bb60fff8b5f49afc4df9a Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Thu, 8 Aug 2024 14:54:37 +0400 Subject: [PATCH 04/42] [TH2-5226] Implemented RabbitMQGcTask.deleteRedundantExchanges * Refactored --- build.gradle | 2 +- gradle/libs.versions.toml | 6 +- .../th2/infraoperator/Th2CrdController.java | 2 +- .../linkresolver/mq/DeclareQueueResolver.java | 11 +- .../linkresolver/mq/RabbitMQContext.java | 93 +++++------ .../mq/factory/MessageRouterConfigFactory.kt | 4 +- .../MessageRouterConfigFactoryEstore.kt | 4 +- .../MessageRouterConfigFactoryMstore.kt | 4 +- .../spec/strategy/linkresolver/Util.kt | 22 +-- .../linkresolver/mq/BindQueueLinkResolver.kt | 12 +- .../strategy/redeploy/tasks/RabbitMQGcTask.kt | 145 ++++++++++++------ 11 files changed, 167 insertions(+), 138 deletions(-) diff --git a/build.gradle b/build.gradle index 875d6cdd..fc06c634 100644 --- a/build.gradle +++ b/build.gradle @@ -58,7 +58,7 @@ dependencies { testImplementation libs.junit.jupiter.api testRuntimeOnly libs.junit.jupiter.engine - detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:${detekt_version}") + detektPlugins libs.detekt.formatting } test { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d32ed0c2..79fd3716 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,10 +3,10 @@ kotlin = "1.8.22" th2-plugin = "0.1.1" jupiter = "5.10.3" okhttp3 = "4.12.0" +detekt = "1.23.6" [libraries] kotlin-logging = { group = "io.github.microutils", name = "kotlin-logging", version = "3.0.5" } - okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp3" } logging-interceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp3" } kubernetes-client = { group = "io.fabric8", name = "kubernetes-client", version = "6.13.1" } @@ -17,8 +17,10 @@ mockito-core = { group = "org.mockito", name = "mockito-core", version = "5.12.0 junit-jupiter-api = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "jupiter" } junit-jupiter-engine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", version.ref = "jupiter" } +detekt-formatting = { group = "io.gitlab.arturbosch.detekt", name = "detekt-formatting", version.ref = "detekt" } + [plugins] kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } -detekt = { id = "io.gitlab.arturbosch.detekt", version = "1.23.6" } +detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } th2-component = { id = "com.exactpro.th2.gradle.component", version.ref = "th2-plugin" } \ No newline at end of file diff --git a/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java b/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java index bf3eb31b..7d8109d5 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java +++ b/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java @@ -52,7 +52,7 @@ public static void main(String[] args) { ContinuousTaskWorker continuousTaskWorker = new ContinuousTaskWorker(); continuousTaskWorker.add(new CheckResourceCacheTask(300)); - continuousTaskWorker.add(RabbitMQContext.buildGarbageCollectTask()); + continuousTaskWorker.add(RabbitMQContext.createGarbageCollectTask()); } catch (Exception e) { logger.error("Exception in main thread", e); watchManager.stopInformers(); diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/DeclareQueueResolver.java b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/DeclareQueueResolver.java index 797831d3..77b4d27f 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/DeclareQueueResolver.java +++ b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/DeclareQueueResolver.java @@ -21,6 +21,7 @@ import com.exactpro.th2.infraoperator.spec.Th2CustomResource; import com.exactpro.th2.infraoperator.spec.helmrelease.HelmRelease; import com.exactpro.th2.infraoperator.spec.shared.pin.MqSubscriberPin; +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.Util; import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.queue.QueueName; import com.exactpro.th2.infraoperator.spec.strategy.redeploy.NonTerminalException; import com.exactpro.th2.infraoperator.util.CustomResourceUtils; @@ -37,12 +38,8 @@ import java.util.List; import java.util.Set; -import static com.exactpro.th2.infraoperator.spec.strategy.linkresolver.Util.buildEstoreQueue; -import static com.exactpro.th2.infraoperator.spec.strategy.linkresolver.Util.buildMstoreQueue; import static com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext.getChannel; import static com.exactpro.th2.infraoperator.util.ExtractUtils.extractName; -import static java.util.Collections.emptyList; -import static java.util.Objects.requireNonNullElse; public class DeclareQueueResolver { @@ -119,7 +116,7 @@ private static Set getBoxPreviousQueues(String namespace, String boxName */ private static @NotNull Set getBoxQueuesFromRabbit(String namespace, String boxName) { - List queueInfoList = requireNonNullElse(RabbitMQContext.getQueues(), emptyList()); + List queueInfoList = RabbitMQContext.getQueues(); Set queueNames = new HashSet<>(); queueInfoList.forEach(q -> { @@ -146,8 +143,8 @@ private static void removeExtinctQueues( String resourceLabel, String namespace ) { - String estoreQueue = buildEstoreQueue(namespace); - String mstoreQueue = buildMstoreQueue(namespace); + String estoreQueue = Util.createEstoreQueue(namespace); + String mstoreQueue = Util.createMstoreQueue(namespace); if (!extinctQueueNames.isEmpty()) { logger.info("Trying to delete queues associated with \"{}\"", resourceLabel); diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java index 98eda001..4cedf01d 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java +++ b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java @@ -23,6 +23,7 @@ import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQNamespacePermissions; import com.exactpro.th2.infraoperator.model.kubernetes.configmaps.ConfigMaps; import com.exactpro.th2.infraoperator.spec.shared.PinSettings; +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.queue.QueueName; import com.exactpro.th2.infraoperator.spec.strategy.redeploy.NonTerminalException; import com.exactpro.th2.infraoperator.spec.strategy.redeploy.RetryableTaskQueue; import com.exactpro.th2.infraoperator.spec.strategy.redeploy.tasks.RabbitMQGcTask; @@ -43,7 +44,6 @@ import com.rabbitmq.http.client.domain.QueueInfo; import com.rabbitmq.http.client.domain.UserPermissions; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -57,10 +57,11 @@ import java.util.Map; import java.util.stream.Collectors; -import static com.exactpro.th2.infraoperator.spec.strategy.linkresolver.Util.buildEstoreQueue; -import static com.exactpro.th2.infraoperator.spec.strategy.linkresolver.Util.buildMstoreQueue; +import static com.exactpro.th2.infraoperator.spec.strategy.linkresolver.Util.createEstoreQueue; +import static com.exactpro.th2.infraoperator.spec.strategy.linkresolver.Util.createMstoreQueue; import static com.exactpro.th2.infraoperator.spec.strategy.linkresolver.queue.QueueName.QUEUE_NAME_REGEXP; import static java.lang.String.format; +import static java.util.Collections.emptyList; import static java.util.Objects.requireNonNullElse; public final class RabbitMQContext { @@ -80,8 +81,12 @@ public final class RabbitMQContext { private RabbitMQContext() { } + public static String getTopicExchangeName() { + return getManagementConfig().getExchangeName(); + } + public static void declareTopicExchange() { - String exchangeName = getManagementConfig().getExchangeName(); + String exchangeName = getTopicExchangeName(); RabbitMQManagementConfig rabbitMQManagementConfig = getManagementConfig(); try { getChannel().exchangeDeclare(exchangeName, "topic", rabbitMQManagementConfig.getPersistence()); @@ -161,7 +166,7 @@ private static void createStoreQueues(String namespace) throws Exception { var channel = getChannel(); RabbitMQManagementConfig rabbitMQManagementConfig = getManagementConfig(); var declareResult = channel.queueDeclare( - buildEstoreQueue(namespace), + createEstoreQueue(namespace), rabbitMQManagementConfig.getPersistence(), false, false, @@ -169,7 +174,7 @@ private static void createStoreQueues(String namespace) throws Exception { ); logger.info("Queue \"{}\" was successfully declared", declareResult.getQueue()); declareResult = channel.queueDeclare( - buildMstoreQueue(namespace), + createMstoreQueue(namespace), rabbitMQManagementConfig.getPersistence(), false, false, @@ -213,14 +218,18 @@ private static void removeSchemaExchange(String exchangeName) { private static void removeSchemaQueues(String namespace) { try { Channel channel = getChannel(); - List queueInfoList = getTh2Queues(); + + List queueInfoList = getQueues(); queueInfoList.forEach(q -> { String queueName = q.getName(); - try { - channel.queueDelete(queueName); - logger.info("Deleted queue: [{}]", queueName); - } catch (IOException e) { - logger.error("Exception deleting queue: [{}]", queueName, e); + var queue = QueueName.fromString(queueName); + if (queue != null && queue.getNamespace().equals(namespace)) { + try { + channel.queueDelete(queueName); + logger.info("Deleted queue: [{}]", queueName); + } catch (IOException e) { + logger.error("Exception deleting queue: [{}]", queueName, e); + } } }); } catch (Exception e) { @@ -228,45 +237,10 @@ private static void removeSchemaQueues(String namespace) { } } - public static Task buildGarbageCollectTask() { + public static Task createGarbageCollectTask() { return new RabbitMQGcTask(getManagementConfig().getGcIntervalSec()); } - public static void cleanUpRabbitBeforeStart() { - try { - Channel channel = getChannel(); - List namespacePrefixes = ConfigLoader.getConfig().getNamespacePrefixes(); - List queueInfoList = getTh2Queues(); - queueInfoList.forEach(q -> { - String queueName = q.getName(); - try { - channel.queueDelete(queueName); - logger.info("Deleted queue: [{}]", queueName); - } catch (IOException e) { - logger.error("Exception deleting queue: [{}]", queueName, e); - } - }); - - List exchangeInfoList = getExchanges(); - exchangeInfoList.forEach(e -> { - String exchangeName = e.getName(); - for (String namespacePrefix : namespacePrefixes) { - if (exchangeName.startsWith(namespacePrefix)) { - try { - channel.exchangeDelete(exchangeName); - break; - } catch (IOException ex) { - logger.error("Exception deleting exchange: [{}]", exchangeName, ex); - break; - } - } - } - }); - } catch (Exception e) { - logger.error("Exception cleaning up rabbit", e); - } - } - public static Channel getChannel() { Channel channel = getChannelContext().channel; if (!channel.isOpen()) { @@ -293,12 +267,12 @@ public static Map generateQueueArguments(PinSettings pinSettings } } - public static @Nullable List getQueues() { + public static @NotNull List getQueues() { String vHostName = getManagementConfig().getVhostName(); try { Client rmqClient = getClient(); - return rmqClient.getQueues(vHostName); + return requireNonNullElse(rmqClient.getQueues(vHostName), emptyList()); } catch (Exception e) { String message = "Exception while fetching queues"; logger.error(message, e); @@ -307,7 +281,7 @@ public static Map generateQueueArguments(PinSettings pinSettings } public static @NotNull List getTh2Queues() { - return requireNonNullElse(getQueues(), Collections.emptyList()).stream() + return getQueues().stream() .filter(queueInfo -> queueInfo.getName() != null && queueInfo.getName().matches(QUEUE_NAME_REGEXP)) .collect(Collectors.toList()); } @@ -324,8 +298,7 @@ public static List getQueueBindings(String queue) { } } - public static List getExchanges() { - + public static @NotNull List getExchanges() { try { Client rmqClient = getClient(); return rmqClient.getExchanges(); @@ -336,6 +309,20 @@ public static List getExchanges() { } } + public static @NotNull List getTh2Exchanges() { + List namespacePrefixes = ConfigLoader.getConfig().getNamespacePrefixes(); + return getExchanges().stream() + .filter(exchangeInfo -> { + String name = exchangeInfo.getName(); + for (String namespacePrefix : namespacePrefixes) { + if (name.startsWith(namespacePrefix)) { + return true; + } + } + return false; + }).collect(Collectors.toList()); + } + public static QueueInfo getQueue(String queueName) { String vHostName = getManagementConfig().getVhostName(); diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactory.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactory.kt index 24429ced..3636b314 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactory.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactory.kt @@ -22,7 +22,7 @@ import com.exactpro.th2.infraoperator.model.box.mq.QueueConfiguration import com.exactpro.th2.infraoperator.spec.Th2CustomResource import com.exactpro.th2.infraoperator.spec.shared.PinAttribute import com.exactpro.th2.infraoperator.spec.shared.pin.PinSpec -import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.buildEstoreRoutingKeyName +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.createEstoreRoutingKeyName import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.queue.QueueName import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.queue.RoutingKeyName import com.exactpro.th2.infraoperator.util.ExtractUtils @@ -43,7 +43,7 @@ abstract class MessageRouterConfigFactory { protected fun generatePublishToEstorePin(namespace: String, boxName: String) = QueueConfiguration( LinkDescription( QueueName.EMPTY, - buildEstoreRoutingKeyName(namespace, boxName), + createEstoreRoutingKeyName(namespace, boxName), namespace ), setOf(PinAttribute.publish.name, PinAttribute.event.name), diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryEstore.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryEstore.kt index 822b7e61..1ea2ee4a 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryEstore.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryEstore.kt @@ -24,7 +24,7 @@ import com.exactpro.th2.infraoperator.operator.impl.EstoreHelmTh2Op.EVENT_STORAG import com.exactpro.th2.infraoperator.spec.Th2CustomResource import com.exactpro.th2.infraoperator.spec.shared.PinAttribute import com.exactpro.th2.infraoperator.spec.shared.pin.PinSpec -import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.buildEstoreQueueName +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.createEstoreQueueName import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.queue.RoutingKeyName import com.exactpro.th2.infraoperator.util.ExtractUtils @@ -48,7 +48,7 @@ class MessageRouterConfigFactoryEstore : MessageRouterConfigFactory() { // add event storage pin config for each resource queues[EVENT_STORAGE_PIN_ALIAS] = QueueConfiguration( LinkDescription( - buildEstoreQueueName(namespace, boxName), + createEstoreQueueName(namespace, boxName), RoutingKeyName.EMPTY, namespace ), diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryMstore.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryMstore.kt index 89bd0cab..7d82d51c 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryMstore.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryMstore.kt @@ -25,7 +25,7 @@ import com.exactpro.th2.infraoperator.operator.impl.MstoreHelmTh2Op.MESSAGE_STOR import com.exactpro.th2.infraoperator.spec.Th2CustomResource import com.exactpro.th2.infraoperator.spec.shared.PinAttribute import com.exactpro.th2.infraoperator.spec.shared.pin.PinSpec -import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.buildMstoreQueueName +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.createMstoreQueueName import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.queue.RoutingKeyName import com.exactpro.th2.infraoperator.util.ExtractUtils @@ -50,7 +50,7 @@ class MessageRouterConfigFactoryMstore : MessageRouterConfigFactory() { queues[EVENT_STORAGE_PIN_ALIAS] = generatePublishToEstorePin(namespace, boxName) queues[MESSAGE_STORAGE_PIN_ALIAS] = QueueConfiguration( LinkDescription( - buildMstoreQueueName(namespace, boxName), + createMstoreQueueName(namespace, boxName), RoutingKeyName.EMPTY, namespace ), diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/Util.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/Util.kt index 72115c16..b004174b 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/Util.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/Util.kt @@ -25,42 +25,42 @@ import com.exactpro.th2.infraoperator.operator.impl.MstoreHelmTh2Op.MESSAGE_STOR import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.queue.QueueName import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.queue.RoutingKeyName -fun buildEstoreQueueName(namespace: String): QueueName = QueueName( +fun createEstoreQueueName(namespace: String): QueueName = QueueName( namespace, EVENT_STORAGE_BOX_ALIAS, EVENT_STORAGE_PIN_ALIAS ) -fun buildEstoreQueue(namespace: String): String = buildEstoreQueueName(namespace).toString() +fun createEstoreQueue(namespace: String): String = createEstoreQueueName(namespace).toString() -fun buildEstoreQueueName(namespace: String, component: String): QueueName = QueueName( +fun createEstoreQueueName(namespace: String, component: String): QueueName = QueueName( namespace, component, EVENT_STORAGE_PIN_ALIAS ) -fun buildEstoreQueue(namespace: String, component: String): String = - buildEstoreQueueName(namespace, component).toString() +fun createEstoreQueue(namespace: String, component: String): String = + createEstoreQueueName(namespace, component).toString() -fun buildEstoreRoutingKeyName(namespace: String, component: String) = RoutingKeyName( +fun createEstoreRoutingKeyName(namespace: String, component: String) = RoutingKeyName( namespace, component, EVENT_STORAGE_PIN_ALIAS ) -fun buildMstoreQueueName(namespace: String) = QueueName( +fun createMstoreQueueName(namespace: String) = QueueName( namespace, MESSAGE_STORAGE_BOX_ALIAS, MESSAGE_STORAGE_PIN_ALIAS ) -fun buildMstoreQueue(namespace: String): String = buildMstoreQueueName(namespace).toString() +fun createMstoreQueue(namespace: String): String = createMstoreQueueName(namespace).toString() -fun buildMstoreQueueName(namespace: String, component: String) = QueueName( +fun createMstoreQueueName(namespace: String, component: String) = QueueName( namespace, component, MESSAGE_STORAGE_PIN_ALIAS ) -fun buildMstoreQueue(namespace: String, component: String): String = - buildMstoreQueueName(namespace, component).toString() +fun createMstoreQueue(namespace: String, component: String): String = + createMstoreQueueName(namespace, component).toString() diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt index c50b3605..930a94c8 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt @@ -22,9 +22,9 @@ import com.exactpro.th2.infraoperator.operator.impl.MstoreHelmTh2Op.MESSAGE_STOR import com.exactpro.th2.infraoperator.spec.Th2CustomResource import com.exactpro.th2.infraoperator.spec.shared.PinAttribute import com.exactpro.th2.infraoperator.spec.shared.pin.Link -import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.buildEstoreQueueName -import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.buildEstoreRoutingKeyName -import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.buildMstoreQueueName +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.createEstoreQueueName +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.createEstoreRoutingKeyName +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.createMstoreQueueName import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.queue.QueueName import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.queue.RoutingKeyName import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.queue.RoutingKeyName.ROUTING_KEY_REGEXP @@ -66,14 +66,14 @@ object BindQueueLinkResolver { } // create event storage link for each resource val estoreLinkDescription = LinkDescription( - buildEstoreQueueName(namespace), - buildEstoreRoutingKeyName(namespace, resourceName), + createEstoreQueueName(namespace), + createEstoreRoutingKeyName(namespace, resourceName), namespace ) bindQueues(estoreLinkDescription, commitHash) val currentLinks: MutableList = ArrayList() - val queueName = buildMstoreQueueName(namespace) + val queueName = createMstoreQueueName(namespace) // create message store link for only resources that need it for ((pinName, attributes) in resource.spec.pins.mq.publishers) { if (checkStorePinAttributes(attributes, resourceLabel, pinName)) { diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/RabbitMQGcTask.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/RabbitMQGcTask.kt index 3202f274..7e936e17 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/RabbitMQGcTask.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/RabbitMQGcTask.kt @@ -17,11 +17,12 @@ package com.exactpro.th2.infraoperator.spec.strategy.redeploy.tasks import com.exactpro.th2.infraoperator.OperatorState -import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.buildEstoreQueue -import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.buildMstoreQueue +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.createEstoreQueue +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.createMstoreQueue import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext import com.exactpro.th2.infraoperator.util.ExtractUtils import com.exactpro.th2.infraoperator.util.HelmReleaseUtils +import com.rabbitmq.http.client.domain.ExchangeInfo import com.rabbitmq.http.client.domain.QueueInfo import mu.KotlinLogging import java.io.IOException @@ -32,70 +33,112 @@ class RabbitMQGcTask( private val retryDelay: Long ) : Task { private val lock = ReentrantLock() - private var deleteCandidates: Set = emptySet() + private var queuesForDelete: Set = emptySet() + private var exchangesForDelete: Set = emptySet() override fun run(): Unit = lock.withLock { - deleteRedundantQueues() + try { + val operatorState = OperatorState.INSTANCE + + deleteRedundantQueues(operatorState) + deleteRedundantExchanges(operatorState) + } catch (e: Exception) { + K_LOGGER.error(e) { "$name task failure" } + } } override fun getName(): String = "RabbitMQGC" override fun getRetryDelay(): Long = retryDelay - private fun deleteRedundantQueues() { - try { - val potentialGarbage = hashSetOf() - RabbitMQContext.getTh2Queues().asSequence() - .map(QueueInfo::getName) - .forEach(potentialGarbage::add) - - if (potentialGarbage.isEmpty()) { - deleteCandidates = emptySet() - return - } + private fun deleteRedundantQueues(operatorState: OperatorState) { + val potentialGarbage = hashSetOf() + RabbitMQContext.getTh2Queues().asSequence() + .map(QueueInfo::getName) + .forEach(potentialGarbage::add) - val operatorState = OperatorState.INSTANCE - operatorState.namespaces.forEach { namespace -> - potentialGarbage.remove(buildEstoreQueue(namespace)) - potentialGarbage.remove(buildMstoreQueue(namespace)) - } + if (potentialGarbage.isEmpty()) { + queuesForDelete = emptySet() + return + } - operatorState.getAllBoxResources().asSequence() - .flatMap { cr -> - val namespace = ExtractUtils.extractNamespace(cr) - val name = ExtractUtils.extractName(cr) - val hr = operatorState.getHelmReleaseFromCache(name, namespace) - sequence { - yieldAll(HelmReleaseUtils.extractQueues(hr.componentValuesSection)) - yield(buildEstoreQueue(namespace, name)) - } - }.forEach(potentialGarbage::remove) - - if (potentialGarbage.isEmpty()) { - deleteCandidates = emptySet() - return - } + operatorState.namespaces.forEach { namespace -> + potentialGarbage.remove(createEstoreQueue(namespace)) + potentialGarbage.remove(createMstoreQueue(namespace)) + } - val channel = RabbitMQContext.getChannel() - val nextDeleteCandidates = potentialGarbage.minus(deleteCandidates).toMutableSet() - deleteCandidates.intersect(potentialGarbage).forEach { queue -> - try { - channel.queueDelete(queue) - K_LOGGER.info { "Deleted queue: [$queue]" } - } catch (e: IOException) { - nextDeleteCandidates.add(queue) - K_LOGGER.error(e) { "Exception deleting queue: [$queue]" } + operatorState.getAllBoxResources().asSequence() + .flatMap { cr -> + val namespace = ExtractUtils.extractNamespace(cr) + val name = ExtractUtils.extractName(cr) + val hr = operatorState.getHelmReleaseFromCache(name, namespace) + sequence { + yieldAll(HelmReleaseUtils.extractQueues(hr.componentValuesSection)) + yield(createEstoreQueue(namespace, name)) } + }.forEach(potentialGarbage::remove) + + if (potentialGarbage.isEmpty()) { + queuesForDelete = emptySet() + return + } + + val channel = RabbitMQContext.getChannel() + val nextDeleteCandidates = potentialGarbage.minus(queuesForDelete).toMutableSet() + queuesForDelete.intersect(potentialGarbage).forEach { queue -> + try { + channel.queueDelete(queue) + K_LOGGER.info { "Deleted queue: [$queue]" } + } catch (e: IOException) { + nextDeleteCandidates.add(queue) + K_LOGGER.error(e) { "Exception deleting queue: [$queue]" } } + } + + if (nextDeleteCandidates.isNotEmpty()) { + K_LOGGER.info { "Queues $nextDeleteCandidates are delete candidates for the next iteration" } + queuesForDelete = nextDeleteCandidates + } else { + queuesForDelete = emptySet() + } + } + + private fun deleteRedundantExchanges(operatorState: OperatorState) { + val potentialGarbage = hashSetOf() + RabbitMQContext.getTh2Exchanges().asSequence() + .map(ExchangeInfo::getName) + .forEach(potentialGarbage::add) + + potentialGarbage.remove(RabbitMQContext.getTopicExchangeName()) - if (nextDeleteCandidates.isNotEmpty()) { - K_LOGGER.info { "Queues $nextDeleteCandidates are delete candidates for the next iteration" } - deleteCandidates = nextDeleteCandidates - } else { - deleteCandidates = emptySet() + if (potentialGarbage.isEmpty()) { + exchangesForDelete = emptySet() + return + } + operatorState.namespaces.forEach(potentialGarbage::remove) + + if (potentialGarbage.isEmpty()) { + exchangesForDelete = emptySet() + return + } + + val channel = RabbitMQContext.getChannel() + val nextDeleteCandidates = potentialGarbage.minus(exchangesForDelete).toMutableSet() + exchangesForDelete.intersect(potentialGarbage).forEach { exchange -> + try { + channel.exchangeDelete(exchange) + K_LOGGER.info { "Deleted exchange: [$exchange]" } + } catch (e: IOException) { + nextDeleteCandidates.add(exchange) + K_LOGGER.error(e) { "Exception deleting exchange: [$exchange]" } } - } catch (e: IOException) { - K_LOGGER.error(e) { "$name task failure" } + } + + if (nextDeleteCandidates.isNotEmpty()) { + K_LOGGER.info { "Exchanges $nextDeleteCandidates are delete candidates for the next iteration" } + exchangesForDelete = nextDeleteCandidates + } else { + exchangesForDelete = emptySet() } } From 17879e0f1ca8ecaa9751b362088169914f061eee Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Thu, 8 Aug 2024 15:13:54 +0400 Subject: [PATCH 05/42] [TH2-5226] added configure logging method * Updated GitHub workflow --- .github/workflows/build-dev-release.yml | 15 ++++++++++ .github/workflows/build-release.yml | 15 ++++++++++ ...-publish-docker.yml => build-sanpshot.yml} | 10 +++---- .github/workflows/ci-unwelcome-words.yml | 28 +++++++++---------- .github/workflows/java-publish-docker.yml | 20 ------------- Dockerfile | 2 +- .../th2/infraoperator/Th2CrdController.java | 21 ++++++++++++-- 7 files changed, 67 insertions(+), 44 deletions(-) create mode 100644 .github/workflows/build-dev-release.yml create mode 100644 .github/workflows/build-release.yml rename .github/workflows/{dev-java-publish-docker.yml => build-sanpshot.yml} (58%) delete mode 100644 .github/workflows/java-publish-docker.yml diff --git a/.github/workflows/build-dev-release.yml b/.github/workflows/build-dev-release.yml new file mode 100644 index 00000000..b438d11f --- /dev/null +++ b/.github/workflows/build-dev-release.yml @@ -0,0 +1,15 @@ +name: Build and publish dev release Docker image to Github Container Registry ghcr.io + +on: workflow_dispatch + +jobs: + build: + uses: th2-net/.github/.github/workflows/compound-java.yml@main + with: + build-target: 'Docker' + devRelease: true + createTag: true + docker-username: ${{ github.actor }} + secrets: + docker-password: ${{ secrets.GITHUB_TOKEN }} + nvd-api-key: ${{ secrets.NVD_APIKEY }} \ No newline at end of file diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml new file mode 100644 index 00000000..dcf70be4 --- /dev/null +++ b/.github/workflows/build-release.yml @@ -0,0 +1,15 @@ +name: Build and publish release Docker image to Github Container Registry ghcr.io + +on: workflow_dispatch + +jobs: + build: + uses: th2-net/.github/.github/workflows/compound-java.yml@main + with: + build-target: 'Docker' + devRelease: false + createTag: true + docker-username: ${{ github.actor }} + secrets: + docker-password: ${{ secrets.GITHUB_TOKEN }} + nvd-api-key: ${{ secrets.NVD_APIKEY }} \ No newline at end of file diff --git a/.github/workflows/dev-java-publish-docker.yml b/.github/workflows/build-sanpshot.yml similarity index 58% rename from .github/workflows/dev-java-publish-docker.yml rename to .github/workflows/build-sanpshot.yml index 4af7d8ca..9366a44e 100644 --- a/.github/workflows/dev-java-publish-docker.yml +++ b/.github/workflows/build-sanpshot.yml @@ -1,12 +1,11 @@ -name: Dev build and publish Docker images to github registry +name: Build and publish Docker image to Github Container Registry ghcr.io on: push: branches-ignore: - master - version-* - - dev-version-* - - dependabot* + - dependabot** paths-ignore: - README.md @@ -15,8 +14,7 @@ jobs: uses: th2-net/.github/.github/workflows/compound-java-dev.yml@main with: build-target: 'Docker' - runsOn: ubuntu-latest - gradleVersion: '7' docker-username: ${{ github.actor }} secrets: - docker-password: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + docker-password: ${{ secrets.GITHUB_TOKEN }} + nvd-api-key: ${{ secrets.NVD_APIKEY }} \ No newline at end of file diff --git a/.github/workflows/ci-unwelcome-words.yml b/.github/workflows/ci-unwelcome-words.yml index cd7adcf3..319d8194 100644 --- a/.github/workflows/ci-unwelcome-words.yml +++ b/.github/workflows/ci-unwelcome-words.yml @@ -7,17 +7,17 @@ jobs: test: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 - with: - ref: ${{ github.sha }} - - name: Checkout tool - uses: actions/checkout@v2 - with: - repository: exactpro-th2/ci-github-action - ref: master - token: ${{ secrets.PAT_CI_ACTION }} - path: ci-github-action - - name: Run CI action - uses: ./ci-github-action - with: - ref: ${{ github.sha }} + - uses: actions/checkout@v4 + with: + ref: ${{ github.sha }} + - name: Checkout tool + uses: actions/checkout@v4 + with: + repository: exactpro-th2/ci-github-action + ref: master + token: ${{ secrets.PAT_CI_ACTION }} + path: ci-github-action + - name: Run CI action + uses: ./ci-github-action + with: + ref: ${{ github.sha }} diff --git a/.github/workflows/java-publish-docker.yml b/.github/workflows/java-publish-docker.yml deleted file mode 100644 index 755369f7..00000000 --- a/.github/workflows/java-publish-docker.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Build and publish Docker images to github registry - -on: - push: - branches: - - master - - version-* - paths: - - gradle.properties - -jobs: - build: - uses: th2-net/.github/.github/workflows/compound-java.yml@main - with: - build-target: 'Docker' - runsOn: ubuntu-latest - gradleVersion: '7' - docker-username: ${{ github.actor }} - secrets: - docker-password: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index c0f3c6d5..7288b1ae 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,4 +6,4 @@ RUN gradle --no-daemon clean build dockerPrepare -Prelease_version=${release_ver FROM adoptopenjdk/openjdk11:alpine WORKDIR /home COPY --from=build /home/gradle/build/docker . -ENTRYPOINT ["/home/service/bin/service", "-Dlog4j2.configurationFile=file:/var/th2/config/log4j2.properties"] \ No newline at end of file +ENTRYPOINT ["/home/service/bin/service"] \ No newline at end of file diff --git a/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java b/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java index 7d8109d5..f8e98eaa 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java +++ b/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java @@ -27,15 +27,21 @@ import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext; import com.exactpro.th2.infraoperator.spec.strategy.redeploy.ContinuousTaskWorker; import com.exactpro.th2.infraoperator.spec.strategy.redeploy.tasks.CheckResourceCacheTask; +import org.apache.logging.log4j.core.LoggerContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.nio.file.Files; +import java.nio.file.Path; + public class Th2CrdController { - private static final Logger logger = LoggerFactory.getLogger(Th2CrdController.class); + private static final Logger LOGGER = LoggerFactory.getLogger(Th2CrdController.class); - public static void main(String[] args) { + private static final Path LOG4J2_PROPERTIES_DEFAULT_PATH = Path.of("var", "th2", "config", "log4j2.properties"); + public static void main(String[] args) { + configureLogger(); var watchManager = DefaultWatchManager.getInstance(); PrometheusServer.start(); OperatorMetrics.resetCacheErrors(); @@ -54,10 +60,19 @@ public static void main(String[] args) { continuousTaskWorker.add(new CheckResourceCacheTask(300)); continuousTaskWorker.add(RabbitMQContext.createGarbageCollectTask()); } catch (Exception e) { - logger.error("Exception in main thread", e); + LOGGER.error("Exception in main thread", e); watchManager.stopInformers(); watchManager.close(); throw e; } } + + private static void configureLogger() { + if (Files.exists(LOG4J2_PROPERTIES_DEFAULT_PATH)) { + LoggerContext loggerContext = LoggerContext.getContext(false); + loggerContext.setConfigLocation(LOG4J2_PROPERTIES_DEFAULT_PATH.toUri()); + loggerContext.reconfigure(); + LOGGER.info("Logger configuration from {} file is applied", LOG4J2_PROPERTIES_DEFAULT_PATH); + } + } } From 1add5b30b92e6a04dc3bacc78b73033c56e110cb Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Thu, 8 Aug 2024 16:18:56 +0400 Subject: [PATCH 06/42] [TH2-5226] Refactored --- README.md | 25 +++++++++++++------ .../th2/infraoperator/OperatorState.java | 4 ++- .../linkresolver/mq/DeclareQueueResolver.java | 7 +++--- .../linkresolver/mq/RabbitMQContext.java | 4 +-- .../fields/RabbitMQManagementConfig.kt | 2 +- ...sk.kt => RabbitMQRubbishCollectionTask.kt} | 4 +-- 6 files changed, 29 insertions(+), 17 deletions(-) rename src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/{RabbitMQGcTask.kt => RabbitMQRubbishCollectionTask.kt} (97%) diff --git a/README.md b/README.md index 1f5b1c54..d292f4ec 100644 --- a/README.md +++ b/README.md @@ -64,22 +64,31 @@ chart: rabbitMQManagement: host: host - # RabbitMQ host used for managing vHosts and users - - port: 8080 - # RabbitMQ port + # host used for managing vHosts and users + + managementPort: 15672 + # management port for HTTP requests + + applicationPort: 5672 + # AMQP port + + vhostName: vHost + # AMQP vHost name + + exchangeName: exchange + # topic exchange name username: username - # RabbitMQ management username + # username for management and AMQP password: password - # password for management user + # password for management and AMQP persistence: true # determines if the RabbitMQ resources are persistent or not - gcIntervalSec: 900 - # Interval of garbage collection in RabbitMQ. Each GC task integration consist of: + rubbishCollectionInterval: 900 + # Interval in seconds of rubbish collection (RC) in RabbitMQ. Each RC task integration consist of: # 1) collect redundant resources according current RabbitMQ and Kubernetes resources state. # 2) delete only resources included into both current and previous integrations (previous intersect current). # 3) save collected resources but not included in the previous integration (current minus previous) for the next iteration diff --git a/src/main/java/com/exactpro/th2/infraoperator/OperatorState.java b/src/main/java/com/exactpro/th2/infraoperator/OperatorState.java index ed96275d..4c7de721 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/OperatorState.java +++ b/src/main/java/com/exactpro/th2/infraoperator/OperatorState.java @@ -22,6 +22,8 @@ import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import static java.util.Collections.unmodifiableSet; + public enum OperatorState { INSTANCE; @@ -105,7 +107,7 @@ public Collection getAllBoxResources() { } public Collection getNamespaces() { - return Collections.unmodifiableSet(namespaceStates.keySet()); + return unmodifiableSet(namespaceStates.keySet()); } private NamespaceState computeIfAbsent(String namespace) { diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/DeclareQueueResolver.java b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/DeclareQueueResolver.java index 77b4d27f..578326ec 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/DeclareQueueResolver.java +++ b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/DeclareQueueResolver.java @@ -21,7 +21,6 @@ import com.exactpro.th2.infraoperator.spec.Th2CustomResource; import com.exactpro.th2.infraoperator.spec.helmrelease.HelmRelease; import com.exactpro.th2.infraoperator.spec.shared.pin.MqSubscriberPin; -import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.Util; import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.queue.QueueName; import com.exactpro.th2.infraoperator.spec.strategy.redeploy.NonTerminalException; import com.exactpro.th2.infraoperator.util.CustomResourceUtils; @@ -38,6 +37,8 @@ import java.util.List; import java.util.Set; +import static com.exactpro.th2.infraoperator.spec.strategy.linkresolver.Util.createEstoreQueue; +import static com.exactpro.th2.infraoperator.spec.strategy.linkresolver.Util.createMstoreQueue; import static com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext.getChannel; import static com.exactpro.th2.infraoperator.util.ExtractUtils.extractName; @@ -143,8 +144,8 @@ private static void removeExtinctQueues( String resourceLabel, String namespace ) { - String estoreQueue = Util.createEstoreQueue(namespace); - String mstoreQueue = Util.createMstoreQueue(namespace); + String estoreQueue = createEstoreQueue(namespace); + String mstoreQueue = createMstoreQueue(namespace); if (!extinctQueueNames.isEmpty()) { logger.info("Trying to delete queues associated with \"{}\"", resourceLabel); diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java index 4cedf01d..b153b169 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java +++ b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java @@ -26,7 +26,7 @@ import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.queue.QueueName; import com.exactpro.th2.infraoperator.spec.strategy.redeploy.NonTerminalException; import com.exactpro.th2.infraoperator.spec.strategy.redeploy.RetryableTaskQueue; -import com.exactpro.th2.infraoperator.spec.strategy.redeploy.tasks.RabbitMQGcTask; +import com.exactpro.th2.infraoperator.spec.strategy.redeploy.tasks.RabbitMQRubbishCollectionTask; import com.exactpro.th2.infraoperator.spec.strategy.redeploy.tasks.RecreateQueuesAndBindings; import com.exactpro.th2.infraoperator.spec.strategy.redeploy.tasks.RetryRabbitSetup; import com.exactpro.th2.infraoperator.spec.strategy.redeploy.tasks.RetryTopicExchangeTask; @@ -238,7 +238,7 @@ private static void removeSchemaQueues(String namespace) { } public static Task createGarbageCollectTask() { - return new RabbitMQGcTask(getManagementConfig().getGcIntervalSec()); + return new RabbitMQRubbishCollectionTask(getManagementConfig().getRubbishCollectionInterval()); } public static Channel getChannel() { diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQManagementConfig.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQManagementConfig.kt index 840ff378..4600e13e 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQManagementConfig.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQManagementConfig.kt @@ -25,6 +25,6 @@ data class RabbitMQManagementConfig( val username: String = "", val password: String = "", val persistence: Boolean = false, - val gcIntervalSec: Int = 900, + val rubbishCollectionInterval: Int = 900, val schemaPermissions: RabbitMQNamespacePermissions = RabbitMQNamespacePermissions() ) diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/RabbitMQGcTask.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/RabbitMQRubbishCollectionTask.kt similarity index 97% rename from src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/RabbitMQGcTask.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/RabbitMQRubbishCollectionTask.kt index 7e936e17..671d8db4 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/RabbitMQGcTask.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/RabbitMQRubbishCollectionTask.kt @@ -29,7 +29,7 @@ import java.io.IOException import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock -class RabbitMQGcTask( +class RabbitMQRubbishCollectionTask( private val retryDelay: Long ) : Task { private val lock = ReentrantLock() @@ -47,7 +47,7 @@ class RabbitMQGcTask( } } - override fun getName(): String = "RabbitMQGC" + override fun getName(): String = RabbitMQRubbishCollectionTask::class.java.simpleName override fun getRetryDelay(): Long = retryDelay From 98b1655bd2367bf786c91a1ca2ab827082dd4f38 Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Fri, 9 Aug 2024 10:35:47 +0400 Subject: [PATCH 07/42] [TH2-5226] added wrapper closure into build script --- build.gradle | 5 + gradle/wrapper/gradle-wrapper.properties | 5 +- gradlew | 289 ++++++++++++++--------- gradlew.bat | 76 +++--- 4 files changed, 225 insertions(+), 150 deletions(-) diff --git a/build.gradle b/build.gradle index fc06c634..d7366d79 100644 --- a/build.gradle +++ b/build.gradle @@ -61,6 +61,11 @@ dependencies { detektPlugins libs.detekt.formatting } +wrapper { + version '8.7' + distributionType Wrapper.DistributionType.BIN +} + test { useJUnitPlatform() } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 7956eee5..b82aa23a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ -#Mon May 04 17:09:53 MSK 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip diff --git a/gradlew b/gradlew index 54125e4d..1aa94a42 100755 --- a/gradlew +++ b/gradlew @@ -1,13 +1,13 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2020-2021 Exactpro (Exactpro Systems Limited) +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, @@ -17,78 +17,111 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -97,92 +130,120 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=$((i+1)) + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" fi +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index f9553162..25da30db 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,4 +1,20 @@ -@if "%DEBUG%" == "" @echo off +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -9,25 +25,29 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -35,48 +55,36 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal From d34bbb3dd31399383615ee73216811af29e06436 Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Wed, 7 Aug 2024 19:00:11 +0400 Subject: [PATCH 08/42] [TH2-5226] moved kotlin files to the src/kotlin dir --- .../com/exactpro/th2/infraoperator/configuration/ConfigLoader.kt | 0 .../exactpro/th2/infraoperator/configuration/OperatorConfig.kt | 0 .../th2/infraoperator/configuration/fields/ChartConfig.kt | 0 .../th2/infraoperator/configuration/fields/EnvironmentConfig.kt | 0 .../th2/infraoperator/configuration/fields/RabbitMQConfig.kt | 0 .../configuration/fields/RabbitMQManagementConfig.kt | 0 .../configuration/fields/RabbitMQNamespacePermissions.kt | 0 .../th2/infraoperator/configuration/fields/SchemaSecrets.kt | 0 .../com/exactpro/th2/infraoperator/model/LinkDescription.kt | 0 .../th2/infraoperator/model/box/dictionary/DictionaryEntity.kt | 0 .../th2/infraoperator/model/box/grpc/GrpcEndpointConfiguration.kt | 0 .../th2/infraoperator/model/box/grpc/GrpcEndpointMapping.kt | 0 .../infraoperator/model/box/grpc/GrpcExternalEndpointMapping.kt | 0 .../th2/infraoperator/model/box/grpc/GrpcRouterConfiguration.kt | 0 .../th2/infraoperator/model/box/grpc/GrpcServerConfiguration.kt | 0 .../th2/infraoperator/model/box/grpc/GrpcServiceConfiguration.kt | 0 .../exactpro/th2/infraoperator/model/box/grpc/RoutingStrategy.kt | 0 .../th2/infraoperator/model/box/grpc/factory/TargetBoxSpec.kt | 0 .../th2/infraoperator/model/box/mq/factory/GlobalNotification.kt | 0 .../model/box/mq/factory/MessageRouterConfigFactory.kt | 0 .../model/box/mq/factory/MessageRouterConfigFactoryBox.kt | 0 .../model/box/mq/factory/MessageRouterConfigFactoryEstore.kt | 0 .../model/box/mq/factory/MessageRouterConfigFactoryMstore.kt | 0 .../th2/infraoperator/spec/helmrelease/HelmReleaseSpec.kt | 0 .../exactpro/th2/infraoperator/spec/shared/pin/GrpcClientPin.kt | 0 .../com/exactpro/th2/infraoperator/spec/shared/pin/GrpcSection.kt | 0 .../exactpro/th2/infraoperator/spec/shared/pin/GrpcServerPin.kt | 0 .../com/exactpro/th2/infraoperator/spec/shared/pin/Link.kt | 0 .../exactpro/th2/infraoperator/spec/shared/pin/MqPublisherPin.kt | 0 .../com/exactpro/th2/infraoperator/spec/shared/pin/MqSection.kt | 0 .../exactpro/th2/infraoperator/spec/shared/pin/MqSubscriberPin.kt | 0 .../com/exactpro/th2/infraoperator/spec/shared/pin/PinSpec.kt | 0 .../spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt | 0 33 files changed, 0 insertions(+), 0 deletions(-) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/configuration/ConfigLoader.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/configuration/OperatorConfig.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/configuration/fields/ChartConfig.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/configuration/fields/EnvironmentConfig.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQConfig.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQManagementConfig.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQNamespacePermissions.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/configuration/fields/SchemaSecrets.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/model/LinkDescription.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/model/box/dictionary/DictionaryEntity.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/model/box/grpc/GrpcEndpointConfiguration.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/model/box/grpc/GrpcEndpointMapping.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/model/box/grpc/GrpcExternalEndpointMapping.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/model/box/grpc/GrpcRouterConfiguration.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/model/box/grpc/GrpcServerConfiguration.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/model/box/grpc/GrpcServiceConfiguration.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/model/box/grpc/RoutingStrategy.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/model/box/grpc/factory/TargetBoxSpec.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/model/box/mq/factory/GlobalNotification.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactory.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryBox.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryEstore.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryMstore.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/spec/helmrelease/HelmReleaseSpec.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/spec/shared/pin/GrpcClientPin.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/spec/shared/pin/GrpcSection.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/spec/shared/pin/GrpcServerPin.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/spec/shared/pin/Link.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/spec/shared/pin/MqPublisherPin.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/spec/shared/pin/MqSection.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/spec/shared/pin/MqSubscriberPin.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/spec/shared/pin/PinSpec.kt (100%) rename src/main/{java => kotlin}/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt (100%) diff --git a/src/main/java/com/exactpro/th2/infraoperator/configuration/ConfigLoader.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/ConfigLoader.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/configuration/ConfigLoader.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/configuration/ConfigLoader.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/configuration/OperatorConfig.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/OperatorConfig.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/configuration/OperatorConfig.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/configuration/OperatorConfig.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/configuration/fields/ChartConfig.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/ChartConfig.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/configuration/fields/ChartConfig.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/ChartConfig.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/configuration/fields/EnvironmentConfig.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/EnvironmentConfig.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/configuration/fields/EnvironmentConfig.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/EnvironmentConfig.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQConfig.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQConfig.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQConfig.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQConfig.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQManagementConfig.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQManagementConfig.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQManagementConfig.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQManagementConfig.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQNamespacePermissions.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQNamespacePermissions.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQNamespacePermissions.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQNamespacePermissions.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/configuration/fields/SchemaSecrets.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/SchemaSecrets.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/configuration/fields/SchemaSecrets.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/SchemaSecrets.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/model/LinkDescription.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/LinkDescription.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/model/LinkDescription.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/model/LinkDescription.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/model/box/dictionary/DictionaryEntity.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/dictionary/DictionaryEntity.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/model/box/dictionary/DictionaryEntity.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/model/box/dictionary/DictionaryEntity.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/model/box/grpc/GrpcEndpointConfiguration.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/grpc/GrpcEndpointConfiguration.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/model/box/grpc/GrpcEndpointConfiguration.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/model/box/grpc/GrpcEndpointConfiguration.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/model/box/grpc/GrpcEndpointMapping.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/grpc/GrpcEndpointMapping.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/model/box/grpc/GrpcEndpointMapping.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/model/box/grpc/GrpcEndpointMapping.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/model/box/grpc/GrpcExternalEndpointMapping.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/grpc/GrpcExternalEndpointMapping.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/model/box/grpc/GrpcExternalEndpointMapping.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/model/box/grpc/GrpcExternalEndpointMapping.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/model/box/grpc/GrpcRouterConfiguration.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/grpc/GrpcRouterConfiguration.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/model/box/grpc/GrpcRouterConfiguration.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/model/box/grpc/GrpcRouterConfiguration.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/model/box/grpc/GrpcServerConfiguration.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/grpc/GrpcServerConfiguration.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/model/box/grpc/GrpcServerConfiguration.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/model/box/grpc/GrpcServerConfiguration.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/model/box/grpc/GrpcServiceConfiguration.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/grpc/GrpcServiceConfiguration.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/model/box/grpc/GrpcServiceConfiguration.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/model/box/grpc/GrpcServiceConfiguration.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/model/box/grpc/RoutingStrategy.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/grpc/RoutingStrategy.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/model/box/grpc/RoutingStrategy.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/model/box/grpc/RoutingStrategy.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/model/box/grpc/factory/TargetBoxSpec.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/grpc/factory/TargetBoxSpec.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/model/box/grpc/factory/TargetBoxSpec.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/model/box/grpc/factory/TargetBoxSpec.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/model/box/mq/factory/GlobalNotification.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/GlobalNotification.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/model/box/mq/factory/GlobalNotification.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/GlobalNotification.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactory.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactory.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactory.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactory.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryBox.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryBox.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryBox.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryBox.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryEstore.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryEstore.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryEstore.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryEstore.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryMstore.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryMstore.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryMstore.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryMstore.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/helmrelease/HelmReleaseSpec.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/helmrelease/HelmReleaseSpec.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/spec/helmrelease/HelmReleaseSpec.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/spec/helmrelease/HelmReleaseSpec.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/shared/pin/GrpcClientPin.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/shared/pin/GrpcClientPin.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/spec/shared/pin/GrpcClientPin.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/spec/shared/pin/GrpcClientPin.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/shared/pin/GrpcSection.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/shared/pin/GrpcSection.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/spec/shared/pin/GrpcSection.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/spec/shared/pin/GrpcSection.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/shared/pin/GrpcServerPin.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/shared/pin/GrpcServerPin.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/spec/shared/pin/GrpcServerPin.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/spec/shared/pin/GrpcServerPin.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/shared/pin/Link.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/shared/pin/Link.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/spec/shared/pin/Link.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/spec/shared/pin/Link.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/shared/pin/MqPublisherPin.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/shared/pin/MqPublisherPin.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/spec/shared/pin/MqPublisherPin.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/spec/shared/pin/MqPublisherPin.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/shared/pin/MqSection.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/shared/pin/MqSection.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/spec/shared/pin/MqSection.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/spec/shared/pin/MqSection.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/shared/pin/MqSubscriberPin.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/shared/pin/MqSubscriberPin.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/spec/shared/pin/MqSubscriberPin.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/spec/shared/pin/MqSubscriberPin.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/shared/pin/PinSpec.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/shared/pin/PinSpec.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/spec/shared/pin/PinSpec.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/spec/shared/pin/PinSpec.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt similarity index 100% rename from src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt rename to src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt From 93913495ae310876b6dcc32eea31fda35cdc8e1d Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Tue, 13 Aug 2024 15:56:26 +0400 Subject: [PATCH 09/42] [TH2-5226] Added `rabbitMQManagement.cleanUpOnStart` option --- README.md | 60 ++++++++++++------- gradle.properties | 18 +----- .../linkresolver/mq/RabbitMQContext.java | 29 ++++++--- .../fields/RabbitMQManagementConfig.kt | 3 +- .../configuration/ConfigurationTests.java | 4 +- 5 files changed, 63 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index ab85dc8f..c9ccba76 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,19 @@ # infra-operator -The infra-operator is a java implementation of Kubernetes -[custom resource controller](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#custom-controllers). -It is part of _th2 infrastructure_. Together with [infra-mgr](https://github.com/th2-net/th2-infra-mgr) and -[helm-operator](https://github.com/fluxcd/helm-operator) -it ensures the synchronization of custom resource files from GitHub and the actual resources -in Kubernetes. The infra-operator uses [fabric8](https://fabric8.io/guide/) library for communication with Kubernetes. - -[Custom resources](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) -allow us to extend Kubernetes API with custom components specifically designed for our needs. -However, since such custom components are not part of the default Kubernetes installation, it is infra-operator's -task to look over them. The infra-operator monitors 5 kind of custom resources, which are defined in +The infra-operator is a java implementation of Kubernetes +[custom resource controller](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#custom-controllers). +It is part of _th2 infrastructure_. Together with [infra-mgr](https://github.com/th2-net/th2-infra-mgr) and +[helm-operator](https://github.com/fluxcd/helm-operator) +it ensures the synchronization of custom resource files from GitHub and the actual resources +in Kubernetes. The infra-operator uses [fabric8](https://fabric8.io/guide/) library for communication with Kubernetes. + +[Custom resources](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) +allow us to extend Kubernetes API with custom components specifically designed for our needs. +However, since such custom components are not part of the default Kubernetes installation, it is infra-operator's +task to look over them. The infra-operator monitors 5 kind of custom resources, which are defined in [th2-infra](https://github.com/th2-net/th2-infra/blob/master/values/CRD) repository. -For more information about custom resources used in th2 and their configuration, +For more information about custom resources used in th2 and their configuration, please refer to [th2-documentation](https://github.com/th2-net/th2-documentation) The infra-operator is also responsible for queues and users permission management on [RabbitMQ](https://www.rabbitmq.com/documentation.html). @@ -23,9 +23,9 @@ The list below covers the main duties and objectives of this component. #### Main objectives * It monitors Kubernetes events related to the _Th2CustomResources_ and generates or modifies the corresponding Helm Releases. * Based on the config map `rabbit-mq-app-config` which is deployed by infra-mgr, it creates Vhost in RabbitMQ for every schema namespace. -* For each Vhost it creates a user in RabbitMQ and configures its permissions. -* Based on the pins described in CRs, and the pins described in _Th2Link_ resources it declares queues in RabbitMQ. -* It binds queues in RabbitMQ according to _Th2Link_ resources. +* For each Vhost it creates a user in RabbitMQ and configures its permissions. +* Based on the pins described in CRs, and the pins described in _Th2Link_ resources it declares queues in RabbitMQ. +* It binds queues in RabbitMQ according to _Th2Link_ resources. * Generate RabbitMQ configs for each resource that needs it. * Generate [gRPC](https://grpc.io/docs/) configs for each resource that needs it. @@ -64,19 +64,31 @@ chart: rabbitMQManagement: host: host - # RabbitMQ host used for managing vHosts and users - - port: 8080 - # RabbitMQ port + # host used for managing vHosts and users + + managementPort: 15672 + # management port for HTTP requests + + applicationPort: 5672 + # AMQP port + + vhostName: vHost + # AMQP vHost name + + exchangeName: exchange + # topic exchange name username: username - # RabbitMQ management username + # username for management and AMQP password: password - # password for management user + # password for management and AMQP persistence: true # determines if the RabbitMQ resources are persistent or not + + cleanUpOnStart: false + # if option is true, operator removes all queues and exchanges from RabbitMQ on start schemaPermissions: # this section describes what permissions schema RabbitMQ user will have on its own resources @@ -131,7 +143,9 @@ openshift: # this section indicates whether application is run in openshift environment or not enabled: true/false #if not indicated default values is false - - ``` +## Release notes + +### 4.6.4 ++ Added `rabbitMQManagement.cleanUpOnStart` option \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index c00b6c2a..6d703e7e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,20 +1,4 @@ -# -# Copyright 2020-2021 Exactpro (Exactpro Systems Limited) -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -release_version = 4.6.3 +release_version = 4.6.4 kotlin_version = 1.6.21 detekt_version = 1.22.0 diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java index 5d1b5814..0f45c658 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java +++ b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,6 +41,7 @@ import com.rabbitmq.http.client.domain.ExchangeInfo; import com.rabbitmq.http.client.domain.QueueInfo; import com.rabbitmq.http.client.domain.UserPermissions; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -224,9 +225,13 @@ private static void removeSchemaQueues(String namespace) { public static void cleanUpRabbitBeforeStart() { try { + if (!getManagementConfig().getCleanUpOnStart()) { + logger.info("Cleanup RabbitMQ before start is skipped by config"); + return; + } + Channel channel = getChannel(); List namespacePrefixes = ConfigLoader.getConfig().getNamespacePrefixes(); - ; List queueInfoList = getQueues(); queueInfoList.forEach(q -> { @@ -384,13 +389,7 @@ static class ChannelContext { private Channel channel; ChannelContext() { - RabbitMQManagementConfig rabbitMQManagementConfig = getManagementConfig(); - ConnectionFactory connectionFactory = new ConnectionFactory(); - connectionFactory.setHost(rabbitMQManagementConfig.getHost()); - connectionFactory.setPort(rabbitMQManagementConfig.getApplicationPort()); - connectionFactory.setVirtualHost(rabbitMQManagementConfig.getVhostName()); - connectionFactory.setUsername(rabbitMQManagementConfig.getUsername()); - connectionFactory.setPassword(rabbitMQManagementConfig.getPassword()); + ConnectionFactory connectionFactory = createConnectionFactory(); try { this.connection = connectionFactory.newConnection(); this.connection.addShutdownListener(new RmqClientShutdownEventListener()); @@ -422,6 +421,18 @@ synchronized void close() { connection = null; channelContext = null; } + + @NotNull + private static ConnectionFactory createConnectionFactory() { + RabbitMQManagementConfig rabbitMQManagementConfig = getManagementConfig(); + ConnectionFactory connectionFactory = new ConnectionFactory(); + connectionFactory.setHost(rabbitMQManagementConfig.getHost()); + connectionFactory.setPort(rabbitMQManagementConfig.getApplicationPort()); + connectionFactory.setVirtualHost(rabbitMQManagementConfig.getVhostName()); + connectionFactory.setUsername(rabbitMQManagementConfig.getUsername()); + connectionFactory.setPassword(rabbitMQManagementConfig.getPassword()); + return connectionFactory; + } } private static class RmqClientShutdownEventListener implements ShutdownListener { diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQManagementConfig.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQManagementConfig.kt index 70b31cee..059cd646 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQManagementConfig.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQManagementConfig.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,5 +25,6 @@ data class RabbitMQManagementConfig( val username: String = "", val password: String = "", val persistence: Boolean = false, + val cleanUpOnStart: Boolean = false, val schemaPermissions: RabbitMQNamespacePermissions = RabbitMQNamespacePermissions() ) diff --git a/src/test/java/com/exactpro/th2/infraoperator/configuration/ConfigurationTests.java b/src/test/java/com/exactpro/th2/infraoperator/configuration/ConfigurationTests.java index b987e769..92776a39 100644 --- a/src/test/java/com/exactpro/th2/infraoperator/configuration/ConfigurationTests.java +++ b/src/test/java/com/exactpro/th2/infraoperator/configuration/ConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -63,6 +63,7 @@ void testFullConfig() { "username", "password", true, + false, new RabbitMQNamespacePermissions( "configure", "read", "write" ) @@ -98,6 +99,7 @@ void testRabbitMQManagementConfig() { "username", "password", true, + false, new RabbitMQNamespacePermissions( "configure", "read", "write" ) From bd6c67bfc8aa5b6513761429551047eb874466af Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Tue, 13 Aug 2024 16:11:51 +0400 Subject: [PATCH 10/42] [TH2-5226] Corrected GitHub workflow --- .github/workflows/dev-java-publish-docker.yml | 5 ++++- .github/workflows/java-publish-docker.yml | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dev-java-publish-docker.yml b/.github/workflows/dev-java-publish-docker.yml index 4af7d8ca..7f3c716f 100644 --- a/.github/workflows/dev-java-publish-docker.yml +++ b/.github/workflows/dev-java-publish-docker.yml @@ -18,5 +18,8 @@ jobs: runsOn: ubuntu-latest gradleVersion: '7' docker-username: ${{ github.actor }} +# FIXME: strict scanner was disabled for 4.6.4 hotfix publishing and must be removed after that + strict-scanner: false secrets: - docker-password: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + docker-password: ${{ secrets.GITHUB_TOKEN }} + nvd-api-key: ${{ secrets.NVD_APIKEY }} \ No newline at end of file diff --git a/.github/workflows/java-publish-docker.yml b/.github/workflows/java-publish-docker.yml index 755369f7..d40f3ed2 100644 --- a/.github/workflows/java-publish-docker.yml +++ b/.github/workflows/java-publish-docker.yml @@ -17,4 +17,5 @@ jobs: gradleVersion: '7' docker-username: ${{ github.actor }} secrets: - docker-password: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + docker-password: ${{ secrets.GITHUB_TOKEN }} + nvd-api-key: ${{ secrets.NVD_APIKEY }} \ No newline at end of file From 0b04c17fdb5122fc96473d9a8216daccffe26737 Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Thu, 15 Aug 2024 15:31:24 +0400 Subject: [PATCH 11/42] [TH2-5226] registered exception handler for informers --- .../manager/impl/ConfigMapEventHandler.java | 16 +++++++-- .../manager/impl/DefaultWatchManager.java | 10 ++++-- .../manager/impl/NamespaceEventHandler.java | 4 ++- .../impl/Th2DictionaryEventHandler.java | 4 ++- .../util/CustomResourceUtils.java | 14 ++++---- .../th2/infraoperator/util/WatcherUtils.kt | 33 +++++++++++++++++++ 6 files changed, 66 insertions(+), 15 deletions(-) create mode 100644 src/main/kotlin/com/exactpro/th2/infraoperator/util/WatcherUtils.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/ConfigMapEventHandler.java b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/ConfigMapEventHandler.java index e5bef611..4a4f0c3f 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/ConfigMapEventHandler.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/ConfigMapEventHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,12 +48,21 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; +import java.util.Base64; +import java.util.Collection; +import java.util.Map; +import java.util.Objects; import static com.exactpro.th2.infraoperator.configuration.OperatorConfig.RABBITMQ_SECRET_PASSWORD_KEY; -import static com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.*; +import static com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.CHECKSUM_ALIAS; +import static com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.CONFIG_ALIAS; +import static com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.CRADLE_MGR_ALIAS; +import static com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.GRPC_ROUTER_ALIAS; +import static com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.LOGGING_ALIAS; +import static com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.MQ_ROUTER_ALIAS; import static com.exactpro.th2.infraoperator.util.CustomResourceUtils.annotationFor; import static com.exactpro.th2.infraoperator.util.JsonUtils.JSON_MAPPER; +import static com.exactpro.th2.infraoperator.util.WatcherUtils.createExceptionHandler; public class ConfigMapEventHandler implements Watcher { public static final String SECRET_TYPE_OPAQUE = "Opaque"; @@ -115,6 +124,7 @@ public static ConfigMapEventHandler newInstance(SharedInformerFactory sharedInfo ConfigMap.class, CustomResourceUtils.RESYNC_TIME); + configMapInformer.exceptionHandler(createExceptionHandler(ConfigMap.class)); configMapInformer.addEventHandler(new GenericResourceEventHandler<>(res, eventQueue)); return res; } diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/DefaultWatchManager.java b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/DefaultWatchManager.java index bf5ccf39..c6b53619 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/DefaultWatchManager.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/DefaultWatchManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.KubernetesClientBuilder; import io.fabric8.kubernetes.client.Watcher; +import io.fabric8.kubernetes.client.informers.SharedIndexInformer; import io.fabric8.kubernetes.client.informers.SharedInformerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,6 +42,7 @@ import static com.exactpro.th2.infraoperator.operator.AbstractTh2Operator.REFRESH_TOKEN_ALIAS; import static com.exactpro.th2.infraoperator.util.CustomResourceUtils.annotationFor; import static com.exactpro.th2.infraoperator.util.ExtractUtils.extractName; +import static com.exactpro.th2.infraoperator.util.WatcherUtils.createExceptionHandler; public class DefaultWatchManager { @@ -157,7 +159,11 @@ private EventHandlerContext registerInformers(SharedInformerFactory sharedInform var handler = new BoxResourceEventHandler<>( helmReleaseTh2Op, eventDispatcher.getEventQueue()); - helmReleaseTh2Op.generateInformerFromFactory(getInformerFactory()).addEventHandler(handler); + + SharedIndexInformer customResourceInformer = + helmReleaseTh2Op.generateInformerFromFactory(getInformerFactory()); + customResourceInformer.exceptionHandler(createExceptionHandler(Th2CustomResource.class)); + customResourceInformer.addEventHandler(handler); context.addHandler(handler); } diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/NamespaceEventHandler.java b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/NamespaceEventHandler.java index 9a6289fb..30b5e1c0 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/NamespaceEventHandler.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/NamespaceEventHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,6 +32,7 @@ import org.slf4j.LoggerFactory; import static com.exactpro.th2.infraoperator.util.CustomResourceUtils.RESYNC_TIME; +import static com.exactpro.th2.infraoperator.util.WatcherUtils.createExceptionHandler; public class NamespaceEventHandler implements ResourceEventHandler, Watcher { private static final Logger logger = LoggerFactory.getLogger(NamespaceEventHandler.class); @@ -47,6 +48,7 @@ public static NamespaceEventHandler newInstance(SharedInformerFactory sharedInfo RESYNC_TIME); var res = new NamespaceEventHandler(eventQueue); + namespaceInformer.exceptionHandler(createExceptionHandler(Namespace.class)); namespaceInformer.addEventHandler(res); return res; } diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/Th2DictionaryEventHandler.java b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/Th2DictionaryEventHandler.java index 39638fca..67435a5c 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/Th2DictionaryEventHandler.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/Th2DictionaryEventHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,6 +47,7 @@ import static com.exactpro.th2.infraoperator.util.ExtractUtils.extractName; import static com.exactpro.th2.infraoperator.util.ExtractUtils.extractNamespace; import static com.exactpro.th2.infraoperator.util.HelmReleaseUtils.extractDictionariesConfig; +import static com.exactpro.th2.infraoperator.util.WatcherUtils.createExceptionHandler; public class Th2DictionaryEventHandler implements Watcher { @@ -72,6 +73,7 @@ public static Th2DictionaryEventHandler newInstance(SharedInformerFactory shared sharedInformerFactory.sharedIndexInformerFor( Th2Dictionary.class, RESYNC_TIME); + dictionaryInformer.exceptionHandler(createExceptionHandler(Th2Dictionary.class)); dictionaryInformer.addEventHandler(new GenericResourceEventHandler<>(res, eventQueue)); return res; } diff --git a/src/main/java/com/exactpro/th2/infraoperator/util/CustomResourceUtils.java b/src/main/java/com/exactpro/th2/infraoperator/util/CustomResourceUtils.java index b028a553..cb315b7d 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/util/CustomResourceUtils.java +++ b/src/main/java/com/exactpro/th2/infraoperator/util/CustomResourceUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,15 +16,9 @@ package com.exactpro.th2.infraoperator.util; -import static com.exactpro.th2.infraoperator.util.ExtractUtils.extractName; -import static com.exactpro.th2.infraoperator.util.ExtractUtils.extractNamespace; -import static com.exactpro.th2.infraoperator.util.ExtractUtils.extractType; - import com.exactpro.th2.infraoperator.spec.Th2CustomResource; import com.exactpro.th2.infraoperator.spec.helmrelease.HelmRelease; -import com.exactpro.th2.infraoperator.spec.shared.pin.*; import io.fabric8.kubernetes.api.model.HasMetadata; - import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,6 +28,10 @@ import java.util.List; import java.util.Objects; +import static com.exactpro.th2.infraoperator.util.ExtractUtils.extractName; +import static com.exactpro.th2.infraoperator.util.ExtractUtils.extractNamespace; +import static com.exactpro.th2.infraoperator.util.ExtractUtils.extractType; + public class CustomResourceUtils { private static final Logger logger = LoggerFactory.getLogger(CustomResourceUtils.class); @@ -86,7 +84,7 @@ private static String extractHashedFullName(Th2CustomResource customResource) { @Nullable private static String extractOwnerFullName(HelmRelease helmRelease) { var ownerReferences = helmRelease.getMetadata().getOwnerReferences(); - if (ownerReferences.size() > 0) { + if (!ownerReferences.isEmpty()) { return concatFullName(extractNamespace(helmRelease), ownerReferences.get(0).getName()); } else { logger.warn("[{}<{}>] doesn't have owner resource", extractType(helmRelease), extractFullName(helmRelease)); diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/util/WatcherUtils.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/util/WatcherUtils.kt new file mode 100644 index 00000000..370d5b65 --- /dev/null +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/util/WatcherUtils.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2024-2024 Exactpro (Exactpro Systems Limited) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:JvmName("WatcherUtils") + +package com.exactpro.th2.infraoperator.util + +import io.fabric8.kubernetes.client.WatcherException +import io.fabric8.kubernetes.client.informers.ExceptionHandler +import mu.KotlinLogging + +private val K_LOGGER = KotlinLogging.logger { } + +fun createExceptionHandler(clazz: Class<*>): ExceptionHandler { + return ExceptionHandler { isStarted: Boolean, t: Throwable -> + K_LOGGER.error(t) { "${clazz.simpleName} informer catch error, isStarted: $isStarted" } + // the code below is copied from io.fabric8.kubernetes.client.informers.impl.cache.Reflector.handler + isStarted && t !is WatcherException + } +} \ No newline at end of file From c4041b97eacb5e8e0659dfde8a8ef27844ee1ece Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Fri, 16 Aug 2024 12:12:52 +0400 Subject: [PATCH 12/42] [TH2-5226] added deleteRabbitMQRubbish method --- .../th2/infraoperator/Th2CrdController.java | 2 + .../operator/AbstractTh2Operator.java | 15 +- .../manager/impl/ConfigMapEventHandler.java | 4 +- .../manager/impl/DefaultWatchManager.java | 31 ++-- .../impl/GenericResourceEventHandler.java | 13 +- .../impl/Th2DictionaryEventHandler.java | 13 +- .../linkresolver/mq/RabbitMQContext.java | 74 ++------- .../tasks/CheckResourceCacheTask.java | 9 +- .../th2/infraoperator/util/ExtractUtils.java | 5 +- .../th2/infraoperator/util/Strings.java | 15 +- .../configuration/OperatorConfig.kt | 4 +- .../th2/infraoperator/util/KubernetesUtils.kt | 65 ++++++++ .../th2/infraoperator/util/RabbitMQUtils.kt | 152 ++++++++++++++++++ .../th2/infraoperator/util/WatcherUtils.kt | 2 +- .../configuration/ConfigurationTests.java | 16 +- .../th2/infraoperator/util/StringsTests.java | 16 +- 16 files changed, 297 insertions(+), 139 deletions(-) create mode 100644 src/main/kotlin/com/exactpro/th2/infraoperator/util/KubernetesUtils.kt create mode 100644 src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt diff --git a/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java b/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java index 104d32a2..936493ff 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java +++ b/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java @@ -27,6 +27,7 @@ import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext; import com.exactpro.th2.infraoperator.spec.strategy.redeploy.ContinuousTaskWorker; import com.exactpro.th2.infraoperator.spec.strategy.redeploy.tasks.CheckResourceCacheTask; +import com.exactpro.th2.infraoperator.util.RabbitMQUtils; import org.apache.logging.log4j.core.LoggerContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,6 +48,7 @@ public static void main(String[] args) { OperatorMetrics.resetCacheErrors(); try { RabbitMQContext.declareTopicExchange(); + RabbitMQUtils.deleteRabbitMQRubbish(); watchManager.addTarget(MstoreHelmTh2Op::new); watchManager.addTarget(EstoreHelmTh2Op::new); diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/AbstractTh2Operator.java b/src/main/java/com/exactpro/th2/infraoperator/operator/AbstractTh2Operator.java index a86da669..37db4609 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/AbstractTh2Operator.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/AbstractTh2Operator.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,9 +29,7 @@ import com.exactpro.th2.infraoperator.spec.strategy.redeploy.tasks.TriggerRedeployTask; import com.exactpro.th2.infraoperator.util.CustomResourceUtils; import com.exactpro.th2.infraoperator.util.ExtractUtils; - import io.fabric8.kubernetes.api.model.HasMetadata; -import io.fabric8.kubernetes.api.model.Namespace; import io.fabric8.kubernetes.api.model.OwnerReference; import io.fabric8.kubernetes.api.model.OwnerReferenceBuilder; import io.fabric8.kubernetes.client.KubernetesClient; @@ -52,6 +50,7 @@ import static com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.ANTECEDENT_LABEL_KEY_ALIAS; import static com.exactpro.th2.infraoperator.util.ExtractUtils.extractName; import static com.exactpro.th2.infraoperator.util.ExtractUtils.extractNamespace; +import static com.exactpro.th2.infraoperator.util.KubernetesUtils.isActive; import static io.fabric8.kubernetes.client.Watcher.Action.MODIFIED; public abstract class AbstractTh2Operator implements Watcher { @@ -97,9 +96,7 @@ public void eventReceived(Action action, CR resource) { action, resourceLabel, e); String namespace = resource.getMetadata().getNamespace(); - Namespace namespaceObj = kubClient.namespaces().withName(namespace).get(); - if (namespaceObj == null || !namespaceObj.getStatus().getPhase().equals("Active")) { - logger.info("Namespace \"{}\" deleted or not active, cancelling", namespace); + if (isActive(kubClient, namespace)) { return; } @@ -129,9 +126,7 @@ public void eventReceived(Action action, CR resource) { } catch (Exception e) { String namespace = resource.getMetadata().getNamespace(); - Namespace namespaceObj = kubClient.namespaces().withName(namespace).get(); - if (namespaceObj == null || !namespaceObj.getStatus().getPhase().equals("Active")) { - logger.info("Namespace \"{}\" deleted or not active, cancelling", namespace); + if (isActive(kubClient, namespace)) { return; } resource.getStatus().failed(e.getMessage()); @@ -260,7 +255,7 @@ protected CR updateStatus(CR resource) { } } - protected void setupAndCreateKubObj(CR resource) throws IOException { + protected void setupAndCreateKubObj(CR resource) { var kubObj = loadKubObj(); diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/ConfigMapEventHandler.java b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/ConfigMapEventHandler.java index 4a4f0c3f..656fd5fc 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/ConfigMapEventHandler.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/ConfigMapEventHandler.java @@ -29,7 +29,6 @@ import com.exactpro.th2.infraoperator.util.CustomResourceUtils; import com.exactpro.th2.infraoperator.util.ExtractUtils; import com.exactpro.th2.infraoperator.util.HelmReleaseUtils; -import com.exactpro.th2.infraoperator.util.Strings; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectReader; @@ -45,6 +44,7 @@ import io.fabric8.kubernetes.client.informers.SharedIndexInformer; import io.fabric8.kubernetes.client.informers.SharedInformerFactory; import io.prometheus.client.Histogram; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -152,7 +152,7 @@ public void eventReceived(Action action, ConfigMap resource) { RabbitMQConfig rabbitMQConfig = configMaps.getRabbitMQConfig4Namespace(namespace); String configContent = resource.getData().get(RabbitMQConfig.CONFIG_MAP_RABBITMQ_PROP_NAME); - if (Strings.isNullOrEmpty(configContent)) { + if (StringUtils.isBlank(configContent)) { logger.error("Key \"{}\" not found in \"{}\"", RabbitMQConfig.CONFIG_MAP_RABBITMQ_PROP_NAME, resourceLabel); return; diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/DefaultWatchManager.java b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/DefaultWatchManager.java index c6b53619..af18438a 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/DefaultWatchManager.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/DefaultWatchManager.java @@ -26,14 +26,17 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.KubernetesClientBuilder; import io.fabric8.kubernetes.client.Watcher; import io.fabric8.kubernetes.client.informers.SharedIndexInformer; import io.fabric8.kubernetes.client.informers.SharedInformerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import java.util.function.Supplier; @@ -41,7 +44,7 @@ import static com.exactpro.th2.infraoperator.operator.AbstractTh2Operator.REFRESH_TOKEN_ALIAS; import static com.exactpro.th2.infraoperator.util.CustomResourceUtils.annotationFor; -import static com.exactpro.th2.infraoperator.util.ExtractUtils.extractName; +import static com.exactpro.th2.infraoperator.util.KubernetesUtils.createKubernetesClient; import static com.exactpro.th2.infraoperator.util.WatcherUtils.createExceptionHandler; public class DefaultWatchManager { @@ -187,20 +190,6 @@ public void addTarget( } void refreshBoxes(String namespace) { - refreshBoxes(namespace, null, true); - } - - void refreshBoxes(String namespace, Set boxes) { - refreshBoxes(namespace, boxes, false); - } - - private void refreshBoxes(String namespace, Set boxes, boolean refreshAllBoxes) { - - if (!refreshAllBoxes && (boxes == null || boxes.size() == 0)) { - logger.warn("Empty set of boxes was given to refresh"); - return; - } - if (!isWatching()) { logger.warn("Not watching for resources yet"); return; @@ -210,10 +199,8 @@ private void refreshBoxes(String namespace, Set boxes, boolean refreshAl for (var resourceClient : resourceClients) { var mixedOperation = resourceClient.getInstance(); for (var res : mixedOperation.inNamespace(namespace).list().getItems()) { - if (refreshAllBoxes || boxes.contains(extractName(res))) { - createResource(namespace, res, resourceClient); - refreshedBoxes++; - } + createResource(namespace, res, resourceClient); + refreshedBoxes++; } } @@ -234,7 +221,7 @@ private void createResource(String linkNamespace, Th2CustomResource resource, public static synchronized DefaultWatchManager getInstance() { if (instance == null) { - instance = new DefaultWatchManager(new KubernetesClientBuilder().build()); + instance = new DefaultWatchManager(createKubernetesClient()); } return instance; diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/GenericResourceEventHandler.java b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/GenericResourceEventHandler.java index 7426b3ce..118b4970 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/GenericResourceEventHandler.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/GenericResourceEventHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,18 +29,20 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static java.util.Objects.requireNonNull; + public class GenericResourceEventHandler implements ResourceEventHandler, Watcher { private static final Logger logger = LoggerFactory.getLogger(GenericResourceEventHandler.class); - private Watcher watcher; + private final Watcher watcher; - private EventQueue eventQueue; + private final EventQueue eventQueue; private final OperatorConfig config = ConfigLoader.getConfig(); public GenericResourceEventHandler(Watcher watcher, EventQueue eventQueue) { - this.watcher = watcher; - this.eventQueue = eventQueue; + this.watcher = requireNonNull(watcher, "watcher can't be null"); + this.eventQueue = requireNonNull(eventQueue, "event queue can't be null"); } @Override @@ -146,6 +148,7 @@ public void eventReceived(Action action, T resource) { @Override public void onClose(WatcherException cause) { + logger.error("Watcher for '{}' has been closed", watcher.getClass().getSimpleName()); throw new AssertionError("This method should not be called"); } } diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/Th2DictionaryEventHandler.java b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/Th2DictionaryEventHandler.java index 67435a5c..2c3f250e 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/Th2DictionaryEventHandler.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/Th2DictionaryEventHandler.java @@ -24,7 +24,6 @@ import com.exactpro.th2.infraoperator.util.CustomResourceUtils; import com.exactpro.th2.infraoperator.util.ExtractUtils; import io.fabric8.kubernetes.api.model.ConfigMap; -import io.fabric8.kubernetes.api.model.Namespace; import io.fabric8.kubernetes.api.model.KubernetesResourceList; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.client.KubernetesClient; @@ -38,15 +37,19 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import static com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.*; +import static com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.DICTIONARIES_ALIAS; import static com.exactpro.th2.infraoperator.util.CustomResourceUtils.RESYNC_TIME; import static com.exactpro.th2.infraoperator.util.CustomResourceUtils.annotationFor; import static com.exactpro.th2.infraoperator.util.ExtractUtils.extractName; import static com.exactpro.th2.infraoperator.util.ExtractUtils.extractNamespace; import static com.exactpro.th2.infraoperator.util.HelmReleaseUtils.extractDictionariesConfig; +import static com.exactpro.th2.infraoperator.util.KubernetesUtils.isActive; import static com.exactpro.th2.infraoperator.util.WatcherUtils.createExceptionHandler; public class Th2DictionaryEventHandler implements Watcher { @@ -193,9 +196,7 @@ private ConfigMap toConfigMap(Th2Dictionary dictionary) { private void updateLinkedResources(String dictionaryName, String namespace, String checksum, Set linkedResources) { - Namespace namespaceObj = kubClient.namespaces().withName(namespace).get(); - if (namespaceObj == null || !namespaceObj.getStatus().getPhase().equals("Active")) { - logger.info("Namespace \"{}\" deleted or not active, cancelling", namespace); + if (isActive(kubClient, namespace)) { return; } for (var linkedResourceName : linkedResources) { diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java index cd81208d..e43bdea9 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java +++ b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java @@ -29,7 +29,6 @@ import com.exactpro.th2.infraoperator.spec.strategy.redeploy.tasks.RecreateQueuesAndBindings; import com.exactpro.th2.infraoperator.spec.strategy.redeploy.tasks.RetryRabbitSetup; import com.exactpro.th2.infraoperator.spec.strategy.redeploy.tasks.RetryTopicExchangeTask; -import com.exactpro.th2.infraoperator.util.Strings; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; @@ -41,6 +40,7 @@ import com.rabbitmq.http.client.domain.ExchangeInfo; import com.rabbitmq.http.client.domain.QueueInfo; import com.rabbitmq.http.client.domain.UserPermissions; +import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,6 +49,7 @@ import java.net.MalformedURLException; import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -58,9 +59,11 @@ import static com.exactpro.th2.infraoperator.spec.strategy.linkresolver.Util.createEstoreQueue; import static com.exactpro.th2.infraoperator.spec.strategy.linkresolver.Util.createMstoreQueue; import static com.exactpro.th2.infraoperator.spec.strategy.linkresolver.queue.QueueName.QUEUE_NAME_REGEXP; +import static com.exactpro.th2.infraoperator.util.Strings.anyPrefixMatch; import static java.lang.String.format; import static java.util.Collections.emptyList; import static java.util.Objects.requireNonNullElse; +import static org.apache.commons.lang3.StringUtils.isNoneBlank; public final class RabbitMQContext { @@ -119,7 +122,7 @@ private static void createUser(String namespace) throws Exception { String password = rabbitMQConfig.getPassword(); String vHostName = rabbitMQManagementConfig.getVhostName(); - if (Strings.isNullOrEmpty(namespace)) { + if (StringUtils.isBlank(namespace)) { return; } @@ -235,49 +238,6 @@ private static void removeSchemaQueues(String namespace) { } } - public static void cleanUpRabbitBeforeStart() { - try { - if (!getManagementConfig().getCleanUpOnStart()) { - logger.info("Cleanup RabbitMQ before start is skipped by config"); - return; - } - - Channel channel = getChannel(); - List namespacePrefixes = ConfigLoader.getConfig().getNamespacePrefixes(); - - List queueInfoList = getQueues(); - queueInfoList.forEach(q -> { - String queueName = q.getName(); - if (queueName != null && queueName.matches(QUEUE_NAME_REGEXP)) { - try { - channel.queueDelete(queueName); - logger.info("Deleted queue: [{}]", queueName); - } catch (IOException e) { - logger.error("Exception deleting queue: [{}]", queueName, e); - } - } - }); - - List exchangeInfoList = getExchanges(); - exchangeInfoList.forEach(e -> { - String exchangeName = e.getName(); - for (String namespacePrefix : namespacePrefixes) { - if (exchangeName.startsWith(namespacePrefix)) { - try { - channel.exchangeDelete(exchangeName); - break; - } catch (IOException ex) { - logger.error("Exception deleting exchange: [{}]", exchangeName, ex); - break; - } - } - } - }); - } catch (Exception e) { - logger.error("Exception cleaning up rabbit", e); - } - } - public static Channel getChannel() { Channel channel = getChannelContext().channel; if (!channel.isOpen()) { @@ -347,16 +307,14 @@ public static List getQueueBindings(String queue) { } public static @NotNull List getTh2Exchanges() { - List namespacePrefixes = ConfigLoader.getConfig().getNamespacePrefixes(); + Collection namespacePrefixes = ConfigLoader.getConfig().getNamespacePrefixes(); + String topicExchange = getManagementConfig().getExchangeName(); return getExchanges().stream() .filter(exchangeInfo -> { String name = exchangeInfo.getName(); - for (String namespacePrefix : namespacePrefixes) { - if (name.startsWith(namespacePrefix)) { - return true; - } - } - return false; + return isNoneBlank(name) + && (name.equals(topicExchange) || anyPrefixMatch(name, namespacePrefixes)); + }).collect(Collectors.toList()); } @@ -465,18 +423,6 @@ private static ConnectionFactory createConnectionFactory() { } } - @NotNull - private static ConnectionFactory createConnectionFactory() { - RabbitMQManagementConfig rabbitMQManagementConfig = getManagementConfig(); - ConnectionFactory connectionFactory = new ConnectionFactory(); - connectionFactory.setHost(rabbitMQManagementConfig.getHost()); - connectionFactory.setPort(rabbitMQManagementConfig.getApplicationPort()); - connectionFactory.setVirtualHost(rabbitMQManagementConfig.getVhostName()); - connectionFactory.setUsername(rabbitMQManagementConfig.getUsername()); - connectionFactory.setPassword(rabbitMQManagementConfig.getPassword()); - return connectionFactory; - } - private static class RmqClientShutdownEventListener implements ShutdownListener { @Override diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/CheckResourceCacheTask.java b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/CheckResourceCacheTask.java index 62762113..1d56f77c 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/CheckResourceCacheTask.java +++ b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/CheckResourceCacheTask.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,24 +30,25 @@ import io.fabric8.kubernetes.api.model.DefaultKubernetesResourceList; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.KubernetesClientBuilder; import io.fabric8.kubernetes.client.dsl.MixedOperation; import io.fabric8.kubernetes.client.dsl.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; +import java.util.Set; import static com.exactpro.th2.infraoperator.util.CustomResourceUtils.stamp; +import static com.exactpro.th2.infraoperator.util.KubernetesUtils.createKubernetesClient; public class CheckResourceCacheTask implements Task { private static final Logger logger = LoggerFactory.getLogger(ContinuousTaskWorker.class); private final long retryDelay; - private final KubernetesClient client = new KubernetesClientBuilder().build(); + private final KubernetesClient client = createKubernetesClient(); - private final List nsPrefixes = ConfigLoader.getConfig().getNamespacePrefixes(); + private final Set nsPrefixes = ConfigLoader.getConfig().getNamespacePrefixes(); private final List operations = List.of( client.resources(Th2Box.class), diff --git a/src/main/java/com/exactpro/th2/infraoperator/util/ExtractUtils.java b/src/main/java/com/exactpro/th2/infraoperator/util/ExtractUtils.java index 303f404c..35caad42 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/util/ExtractUtils.java +++ b/src/main/java/com/exactpro/th2/infraoperator/util/ExtractUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import java.util.Map; import io.fabric8.kubernetes.api.model.HasMetadata; +import org.apache.commons.lang3.StringUtils; public class ExtractUtils { @@ -73,7 +74,7 @@ public static String fullSourceHash(HasMetadata res) { public static String shortSourceHash(HasMetadata res) { String fullHash = fullSourceHash(res); - if (Strings.isNullOrEmpty(fullHash)) { + if (StringUtils.isBlank(fullHash)) { return fullHash; } return "[" + fullHash.substring(0, 8) + "]"; diff --git a/src/main/java/com/exactpro/th2/infraoperator/util/Strings.java b/src/main/java/com/exactpro/th2/infraoperator/util/Strings.java index 61929a65..a03ee67d 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/util/Strings.java +++ b/src/main/java/com/exactpro/th2/infraoperator/util/Strings.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import com.exactpro.th2.infraoperator.model.box.dictionary.DictionaryEntity; import org.apache.commons.text.lookup.StringLookup; -import java.util.List; +import java.util.Collection; import java.util.Map; import java.util.Set; @@ -30,14 +30,17 @@ public class Strings { private Strings() { } - public static boolean isNullOrEmpty(String s) { - return (s == null || s.isEmpty()); + public static boolean anyPrefixMatch(String namespace, Collection prefixes) { + return (namespace != null + && prefixes != null + && !prefixes.isEmpty() + && prefixes.stream().anyMatch(namespace::startsWith)); } - public static boolean nonePrefixMatch(String namespace, List prefixes) { + public static boolean nonePrefixMatch(String namespace, Collection prefixes) { return (namespace != null && prefixes != null - && prefixes.size() > 0 + && !prefixes.isEmpty() && prefixes.stream().noneMatch(namespace::startsWith)); } diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/OperatorConfig.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/OperatorConfig.kt index b66b5465..7d31bbe0 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/OperatorConfig.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/OperatorConfig.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ data class OperatorConfig( var chart: Any? = null, var rabbitMQManagement: RabbitMQManagementConfig = RabbitMQManagementConfig(), var schemaSecrets: SchemaSecrets = SchemaSecrets(), - var namespacePrefixes: List = ArrayList(), + var namespacePrefixes: Set = emptySet(), var rabbitMQConfigMapName: String = DEFAULT_RABBITMQ_CONFIGMAP_NAME, var k8sUrl: String = "", var prometheusConfiguration: PrometheusConfiguration = PrometheusConfiguration.createDefault("true"), diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/util/KubernetesUtils.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/util/KubernetesUtils.kt new file mode 100644 index 00000000..0e0f198b --- /dev/null +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/util/KubernetesUtils.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2024-2024 Exactpro (Exactpro Systems Limited) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:JvmName("KubernetesUtils") + +package com.exactpro.th2.infraoperator.util + +import com.exactpro.th2.infraoperator.spec.Th2CustomResource +import com.exactpro.th2.infraoperator.spec.box.Th2Box +import com.exactpro.th2.infraoperator.spec.corebox.Th2CoreBox +import com.exactpro.th2.infraoperator.spec.estore.Th2Estore +import com.exactpro.th2.infraoperator.spec.job.Th2Job +import com.exactpro.th2.infraoperator.spec.mstore.Th2Mstore +import io.fabric8.kubernetes.api.model.Namespace +import io.fabric8.kubernetes.client.KubernetesClient +import io.fabric8.kubernetes.client.KubernetesClientBuilder +import mu.KotlinLogging +import kotlin.streams.toList + +private val K_LOGGER = KotlinLogging.logger { } + +val CUSTOM_RESOURCE_KINDS = + setOf(Th2Estore::class.java, Th2Mstore::class.java, Th2CoreBox::class.java, Th2Box::class.java, Th2Job::class.java) + +fun createKubernetesClient(): KubernetesClient = KubernetesClientBuilder().build() + +fun KubernetesClient.namespaces(namespacePrefixes: Set): Set = + namespaces() + .list() + .items + .map { it.metadata.name } + .filter { ns -> Strings.anyPrefixMatch(ns, namespacePrefixes) } + .toSet() + +fun KubernetesClient.customResources(namespace: String): List = + CUSTOM_RESOURCE_KINDS + .stream() + .flatMap { + resources(it) + .inNamespace(namespace) + .resources() + }.map { it.get() as Th2CustomResource } + .toList() + +fun KubernetesClient.isActive(namespace: String): Boolean { + val namespaceObj: Namespace? = namespaces().withName(namespace).get() + if (namespaceObj == null || namespaceObj.status.phase != "Active") { + K_LOGGER.info { "Namespace \"$namespace\" deleted or not active, cancelling" } + return true + } + return false +} diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt new file mode 100644 index 00000000..d524c4f9 --- /dev/null +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt @@ -0,0 +1,152 @@ +/* + * Copyright 2024-2024 Exactpro (Exactpro Systems Limited) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:JvmName("RabbitMQUtils") + +package com.exactpro.th2.infraoperator.util + +import com.exactpro.th2.infraoperator.configuration.ConfigLoader +import com.exactpro.th2.infraoperator.model.box.mq.factory.MessageRouterConfigFactory +import com.exactpro.th2.infraoperator.model.box.mq.factory.MessageRouterConfigFactoryBox +import com.exactpro.th2.infraoperator.model.box.mq.factory.MessageRouterConfigFactoryEstore +import com.exactpro.th2.infraoperator.model.box.mq.factory.MessageRouterConfigFactoryMstore +import com.exactpro.th2.infraoperator.spec.Th2CustomResource +import com.exactpro.th2.infraoperator.spec.estore.Th2Estore +import com.exactpro.th2.infraoperator.spec.mstore.Th2Mstore +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext +import com.rabbitmq.http.client.domain.ExchangeInfo +import com.rabbitmq.http.client.domain.QueueInfo +import io.fabric8.kubernetes.client.KubernetesClient +import mu.KotlinLogging +import java.io.IOException + +private val K_LOGGER = KotlinLogging.logger { } + +fun deleteRabbitMQRubbish() { + val config = ConfigLoader.loadConfiguration() + val rabbitMQManagement = config.rabbitMQManagement + + if (!rabbitMQManagement.cleanUpOnStart) { + K_LOGGER.info { "Cleanup RabbitMQ before start is skipped by config" } + return + } + + val namespacePrefixes = config.namespacePrefixes + val topicExchange = rabbitMQManagement.exchangeName + + createKubernetesClient().use { kuClient -> + val namespaces = kuClient.namespaces(namespacePrefixes) + val resourceHolder = kuClient.collectRabbitMQRubbish(namespaces, topicExchange) + K_LOGGER.info { "RabbitMQ rubbish: $resourceHolder" } + deleteRabbitMQRubbish(resourceHolder) + } +} + +private fun KubernetesClient.collectRabbitMQRubbish( + namespaces: Set, + topicExchange: String, +) = ResourceHolder().apply { + val factories = createFactories() + + RabbitMQContext + .getTh2Queues() + .asSequence() + .map(QueueInfo::getName) + .forEach(queues::add) + + RabbitMQContext + .getTh2Exchanges() + .asSequence() + .map(ExchangeInfo::getName) + .forEach(exchanges::add) + + K_LOGGER.debug { "Actual set in RabbitMQ, queues: $queues, exchanges: $exchanges" } + + if (isHolderEmpty() || namespaces.isEmpty()) { + return@apply + } + + K_LOGGER.debug { "Search RabbitMQ resources in $namespaces namespaces" } + exchanges.remove(topicExchange) + + namespaces.forEach { namespace -> + customResources(namespace).forEach { cr -> + val configuration = factories[cr.javaClass]?.createConfig(cr) + ?: error("MQ config factory isn't present for ${cr.javaClass.simpleName}") + + configuration.queues.values.forEach { queueCfg -> + if (queueCfg.queueName.isNotBlank()) { + queues.remove(queueCfg.queueName) + } + exchanges.remove(queueCfg.exchange) + } + } + + K_LOGGER.debug { + "Survived RabbitMQ resources after '$namespace' namespace process, " + + "queues: $queues, exchanges: $exchanges" + } + } +} + +private fun deleteRabbitMQRubbish(resourceHolder: ResourceHolder) { + if (resourceHolder.isHolderEmpty()) { + return + } + + val channel = RabbitMQContext.getChannel() + + resourceHolder.queues.forEach { queue -> + try { + channel.queueDelete(queue) + K_LOGGER.info { "Deleted '$queue' queue" } + } catch (e: IOException) { + K_LOGGER.error(e) { "'$queue' queue delete failure" } + } + } + + resourceHolder.exchanges.forEach { exchange -> + try { + channel.exchangeDelete(exchange) + K_LOGGER.info { "Deleted '$exchange' exchange" } + } catch (e: IOException) { + K_LOGGER.error(e) { "'$exchange' queue delete failure" } + } + } +} + +private fun createFactories(): Map, MessageRouterConfigFactory> { + val defaultFactory = MessageRouterConfigFactoryBox() + return CUSTOM_RESOURCE_KINDS + .asSequence() + .map { + it to + when (it) { + Th2Mstore::class.java -> MessageRouterConfigFactoryMstore() + Th2Estore::class.java -> MessageRouterConfigFactoryEstore() + else -> defaultFactory + } + }.toMap() +} + +private data class ResourceHolder( + val queues: MutableSet = hashSetOf(), + val exchanges: MutableSet = hashSetOf(), +) { + fun isHolderEmpty() = queues.isEmpty() and exchanges.isEmpty() + + override fun toString(): String = "queues=$queues, exchanges=$exchanges" +} diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/util/WatcherUtils.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/util/WatcherUtils.kt index 370d5b65..6ae7969f 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/util/WatcherUtils.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/util/WatcherUtils.kt @@ -30,4 +30,4 @@ fun createExceptionHandler(clazz: Class<*>): ExceptionHandler { // the code below is copied from io.fabric8.kubernetes.client.informers.impl.cache.Reflector.handler isStarted && t !is WatcherException } -} \ No newline at end of file +} diff --git a/src/test/java/com/exactpro/th2/infraoperator/configuration/ConfigurationTests.java b/src/test/java/com/exactpro/th2/infraoperator/configuration/ConfigurationTests.java index 92776a39..ca6a409e 100644 --- a/src/test/java/com/exactpro/th2/infraoperator/configuration/ConfigurationTests.java +++ b/src/test/java/com/exactpro/th2/infraoperator/configuration/ConfigurationTests.java @@ -21,12 +21,14 @@ import com.exactpro.th2.infraoperator.configuration.fields.SchemaSecrets; import org.junit.jupiter.api.Test; -import java.util.Arrays; -import java.util.Collections; +import java.util.Set; import static com.exactpro.th2.infraoperator.configuration.ConfigLoader.CONFIG_FILE_SYSTEM_PROPERTY; -import static org.junit.jupiter.api.Assertions.*; -import static com.exactpro.th2.infraoperator.configuration.OperatorConfig.*; +import static com.exactpro.th2.infraoperator.configuration.OperatorConfig.DEFAULT_RABBITMQ_CONFIGMAP_NAME; +import static java.util.Collections.emptySet; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; class ConfigurationTests { @@ -70,7 +72,7 @@ void testFullConfig() { ) ); expected.setSchemaSecrets(new SchemaSecrets("rabbitMQ", "cassandra")); - expected.setNamespacePrefixes(Arrays.asList("string1", "string2")); + expected.setNamespacePrefixes(Set.of("string1", "string2")); expected.setRabbitMQConfigMapName("rabbit-mq-app"); assertEquals(expected, loadConfiguration()); @@ -80,7 +82,7 @@ void testFullConfig() { void testNsPrefixes() { beforeEach("nsPrefixesConfig.yml"); - expected.setNamespacePrefixes(Arrays.asList("string1", "string2")); + expected.setNamespacePrefixes(Set.of("string1", "string2")); assertEquals(expected, loadConfiguration()); } @@ -121,7 +123,7 @@ void testSchemaSecretsConfig() { @Test void testDefaultConfig() { OperatorConfig config = new OperatorConfig(); - assertEquals(Collections.emptyList(), + assertEquals(emptySet(), config.getNamespacePrefixes()); assertTrue(config.getRabbitMQManagement().getHost().isEmpty()); diff --git a/src/test/java/com/exactpro/th2/infraoperator/util/StringsTests.java b/src/test/java/com/exactpro/th2/infraoperator/util/StringsTests.java index c8f6e476..d8301ae7 100644 --- a/src/test/java/com/exactpro/th2/infraoperator/util/StringsTests.java +++ b/src/test/java/com/exactpro/th2/infraoperator/util/StringsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,13 +25,13 @@ class StringsTests { @Test - void isNullOrEmptyTest() { - String testStr = null; - assertTrue(Strings.isNullOrEmpty(testStr)); - testStr = ""; - assertTrue(Strings.isNullOrEmpty(testStr)); - testStr = "notNullOrEmpty"; - assertFalse(Strings.isNullOrEmpty(testStr)); + void anyPrefixMatchTest() { + List prefixes = List.of("a", "b", "c", "D"); + String namespace = "dev-someone"; + assertFalse(Strings.anyPrefixMatch(namespace, prefixes)); + prefixes = List.of("dev", "not-dev"); + assertTrue(Strings.anyPrefixMatch(namespace, prefixes)); + assertFalse(Strings.anyPrefixMatch(null, null)); } @Test From a6d9014f48fd23ce05eff10e4513eabba21da786 Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Fri, 16 Aug 2024 15:41:25 +0400 Subject: [PATCH 13/42] [TH2-5226] set true for clearOnStart by default --- README.md | 4 +- build.gradle | 1 + gradle/libs.versions.toml | 1 + .../linkresolver/mq/RabbitMQContext.java | 8 +- .../fields/RabbitMQManagementConfig.kt | 2 +- .../th2/infraoperator/util/RabbitMQUtils.kt | 84 ++++--- .../configuration/ConfigurationTests.java | 4 +- .../infraoperator/util/RabbitMQUtilsTest.kt | 210 ++++++++++++++++++ src/test/resources/log4j2.properties | 14 ++ 9 files changed, 284 insertions(+), 44 deletions(-) create mode 100644 src/test/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtilsTest.kt create mode 100644 src/test/resources/log4j2.properties diff --git a/README.md b/README.md index fab508cf..36f88952 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ rabbitMQManagement: # determines if the RabbitMQ resources are persistent or not cleanUpOnStart: false - # if option is true, operator removes all queues and exchanges from RabbitMQ on start + # if option is true, operator removes all redundant queues and exchanges from RabbitMQ on start schemaPermissions: # this section describes what permissions schema RabbitMQ user will have on its own resources @@ -148,7 +148,7 @@ openshift: ## Release notes ### 4.7.0 -+ Improved clean rubbish from RabbitMQ on start ++ Improved clean rubbish from RabbitMQ on start. This function enabled by default + Migrated to th2 plugin `0.1.1` + Updated: diff --git a/build.gradle b/build.gradle index d7366d79..fd1389f5 100644 --- a/build.gradle +++ b/build.gradle @@ -55,6 +55,7 @@ dependencies { implementation "io.prometheus:simpleclient_hotspot" testImplementation libs.mockito.core + testImplementation libs.mockito.kotlin testImplementation libs.junit.jupiter.api testRuntimeOnly libs.junit.jupiter.engine diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 79fd3716..1207fe44 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,6 +14,7 @@ http-client = { group = "com.rabbitmq", name = "http-client", version = "5.2.0" java-uuid-generator = { group = "com.fasterxml.uuid", name = "java-uuid-generator", version = "5.1.0" } mockito-core = { group = "org.mockito", name = "mockito-core", version = "5.12.0" } +mockito-kotlin = { group = "org.mockito.kotlin", name = "mockito-kotlin", version = "5.4.0" } junit-jupiter-api = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "jupiter" } junit-jupiter-engine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", version.ref = "jupiter" } diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java index e43bdea9..8780dd4a 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java +++ b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java @@ -103,7 +103,7 @@ public static void declareTopicExchange() { public static void setUpRabbitMqForNamespace(String namespace) { try { createUser(namespace); - declareExchange(namespace); + declareExchange(toExchangeName(namespace)); createStoreQueues(namespace); } catch (Exception e) { logger.error("Exception setting up rabbitMq for namespace: \"{}\"", namespace, e); @@ -185,7 +185,7 @@ private static void createStoreQueues(String namespace) throws Exception { } public static void cleanupRabbit(String namespace) throws Exception { - removeSchemaExchange(namespace); + removeSchemaExchange(toExchangeName(namespace)); removeSchemaQueues(namespace); removeSchemaUser(namespace); @@ -238,6 +238,10 @@ private static void removeSchemaQueues(String namespace) { } } + public static String toExchangeName(String namespace) { + return namespace; + } + public static Channel getChannel() { Channel channel = getChannelContext().channel; if (!channel.isOpen()) { diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQManagementConfig.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQManagementConfig.kt index 059cd646..5e727bbf 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQManagementConfig.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQManagementConfig.kt @@ -25,6 +25,6 @@ data class RabbitMQManagementConfig( val username: String = "", val password: String = "", val persistence: Boolean = false, - val cleanUpOnStart: Boolean = false, + val cleanUpOnStart: Boolean = true, val schemaPermissions: RabbitMQNamespacePermissions = RabbitMQNamespacePermissions() ) diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt index d524c4f9..2060bd48 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt @@ -19,6 +19,7 @@ package com.exactpro.th2.infraoperator.util import com.exactpro.th2.infraoperator.configuration.ConfigLoader +import com.exactpro.th2.infraoperator.model.box.mq.QueueConfiguration import com.exactpro.th2.infraoperator.model.box.mq.factory.MessageRouterConfigFactory import com.exactpro.th2.infraoperator.model.box.mq.factory.MessageRouterConfigFactoryBox import com.exactpro.th2.infraoperator.model.box.mq.factory.MessageRouterConfigFactoryEstore @@ -27,6 +28,7 @@ import com.exactpro.th2.infraoperator.spec.Th2CustomResource import com.exactpro.th2.infraoperator.spec.estore.Th2Estore import com.exactpro.th2.infraoperator.spec.mstore.Th2Mstore import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext +import com.rabbitmq.client.Channel import com.rabbitmq.http.client.domain.ExchangeInfo import com.rabbitmq.http.client.domain.QueueInfo import io.fabric8.kubernetes.client.KubernetesClient @@ -36,40 +38,47 @@ import java.io.IOException private val K_LOGGER = KotlinLogging.logger { } fun deleteRabbitMQRubbish() { - val config = ConfigLoader.loadConfiguration() - val rabbitMQManagement = config.rabbitMQManagement + try { + val config = ConfigLoader.loadConfiguration() + val rabbitMQManagement = config.rabbitMQManagement - if (!rabbitMQManagement.cleanUpOnStart) { - K_LOGGER.info { "Cleanup RabbitMQ before start is skipped by config" } - return - } - - val namespacePrefixes = config.namespacePrefixes - val topicExchange = rabbitMQManagement.exchangeName + if (!rabbitMQManagement.cleanUpOnStart) { + K_LOGGER.info { "Cleanup RabbitMQ before start is skipped by config" } + return + } - createKubernetesClient().use { kuClient -> - val namespaces = kuClient.namespaces(namespacePrefixes) - val resourceHolder = kuClient.collectRabbitMQRubbish(namespaces, topicExchange) - K_LOGGER.info { "RabbitMQ rubbish: $resourceHolder" } - deleteRabbitMQRubbish(resourceHolder) + val namespacePrefixes = config.namespacePrefixes + val topicExchange = rabbitMQManagement.exchangeName + + createKubernetesClient().use { kuClient -> + val resourceHolder = kuClient.collectRabbitMQRubbish( + namespacePrefixes, + topicExchange, + RabbitMQContext.getTh2Queues(), + RabbitMQContext.getTh2Exchanges(), + ) + K_LOGGER.info { "RabbitMQ rubbish: $resourceHolder" } + deleteRabbitMQRubbish(resourceHolder, RabbitMQContext::getChannel) + } + } catch (e: Exception) { + K_LOGGER.error(e) { "Delete RabbitMQ rubbish failure" } } } -private fun KubernetesClient.collectRabbitMQRubbish( - namespaces: Set, +internal fun KubernetesClient.collectRabbitMQRubbish( + namespacePrefixes: Set, topicExchange: String, -) = ResourceHolder().apply { - val factories = createFactories() + th2Queues: Collection, + th2Exchanges: Collection, +): ResourceHolder = ResourceHolder().apply { + val namespaces: Set = namespaces(namespacePrefixes) + val factories: Map, MessageRouterConfigFactory> = createFactories() - RabbitMQContext - .getTh2Queues() - .asSequence() + th2Queues.asSequence() .map(QueueInfo::getName) .forEach(queues::add) - RabbitMQContext - .getTh2Exchanges() - .asSequence() + th2Exchanges.asSequence() .map(ExchangeInfo::getName) .forEach(exchanges::add) @@ -83,17 +92,15 @@ private fun KubernetesClient.collectRabbitMQRubbish( exchanges.remove(topicExchange) namespaces.forEach { namespace -> - customResources(namespace).forEach { cr -> - val configuration = factories[cr.javaClass]?.createConfig(cr) - ?: error("MQ config factory isn't present for ${cr.javaClass.simpleName}") + exchanges.remove(namespace) - configuration.queues.values.forEach { queueCfg -> - if (queueCfg.queueName.isNotBlank()) { - queues.remove(queueCfg.queueName) - } - exchanges.remove(queueCfg.exchange) - } - } + customResources(namespace).asSequence() + .flatMap { cr -> + factories[cr.javaClass]?.createConfig(cr)?.queues?.values + ?: error("MQ config factory isn't present for ${cr.javaClass.simpleName}") + }.map(QueueConfiguration::getQueueName) + .filter(String::isNotBlank) + .forEach(queues::remove) K_LOGGER.debug { "Survived RabbitMQ resources after '$namespace' namespace process, " + @@ -102,12 +109,15 @@ private fun KubernetesClient.collectRabbitMQRubbish( } } -private fun deleteRabbitMQRubbish(resourceHolder: ResourceHolder) { +internal fun deleteRabbitMQRubbish( + resourceHolder: ResourceHolder, + getChannel: () -> Channel +) { if (resourceHolder.isHolderEmpty()) { return } - val channel = RabbitMQContext.getChannel() + val channel: Channel = getChannel() resourceHolder.queues.forEach { queue -> try { @@ -142,7 +152,7 @@ private fun createFactories(): Map, MessageRouterCo }.toMap() } -private data class ResourceHolder( +internal data class ResourceHolder( val queues: MutableSet = hashSetOf(), val exchanges: MutableSet = hashSetOf(), ) { diff --git a/src/test/java/com/exactpro/th2/infraoperator/configuration/ConfigurationTests.java b/src/test/java/com/exactpro/th2/infraoperator/configuration/ConfigurationTests.java index ca6a409e..5f25bab5 100644 --- a/src/test/java/com/exactpro/th2/infraoperator/configuration/ConfigurationTests.java +++ b/src/test/java/com/exactpro/th2/infraoperator/configuration/ConfigurationTests.java @@ -65,7 +65,7 @@ void testFullConfig() { "username", "password", true, - false, + true, new RabbitMQNamespacePermissions( "configure", "read", "write" ) @@ -101,7 +101,7 @@ void testRabbitMQManagementConfig() { "username", "password", true, - false, + true, new RabbitMQNamespacePermissions( "configure", "read", "write" ) diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtilsTest.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtilsTest.kt new file mode 100644 index 00000000..9a549a57 --- /dev/null +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtilsTest.kt @@ -0,0 +1,210 @@ +/* + * Copyright 2024-2024 Exactpro (Exactpro Systems Limited) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.exactpro.th2.infraoperator.util + +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext.toExchangeName +import com.rabbitmq.client.Channel +import com.rabbitmq.http.client.domain.ExchangeInfo +import com.rabbitmq.http.client.domain.QueueInfo +import io.fabric8.kubernetes.api.model.KubernetesResourceList +import io.fabric8.kubernetes.api.model.Namespace +import io.fabric8.kubernetes.api.model.NamespaceList +import io.fabric8.kubernetes.api.model.ObjectMeta +import io.fabric8.kubernetes.client.KubernetesClient +import io.fabric8.kubernetes.client.dsl.MixedOperation +import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation +import io.fabric8.kubernetes.client.dsl.Resource +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.mockito.kotlin.any +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import com.exactpro.th2.infraoperator.spec.Th2CustomResource as CR + +private const val TOPIC_EXCHANGE_NAME = "test-global-exchange" + +class RabbitMQUtilsTest { + @Test + fun `no ns and topic exchange`() { + val client: KubernetesClient = mockKubernetesClient() + val actual = + client.collectRabbitMQRubbish( + setOf("th2"), + TOPIC_EXCHANGE_NAME, + emptyList(), + listOf( + ExchangeInfo().apply { name = TOPIC_EXCHANGE_NAME }, + ), + ) + val expected = + ResourceHolder( + exchanges = hashSetOf(TOPIC_EXCHANGE_NAME), + ) + + assertEquals(expected, actual) + } + + @Test + fun `no ns and rubbish exchange`() { + val exchangeName = "th2-test-exchange" + val client: KubernetesClient = mockKubernetesClient() + val actual = + client.collectRabbitMQRubbish( + setOf("th2"), + TOPIC_EXCHANGE_NAME, + emptyList(), + listOf( + ExchangeInfo().apply { name = exchangeName }, + ), + ) + val expected = + ResourceHolder( + exchanges = hashSetOf(exchangeName), + ) + + assertEquals(expected, actual) + } + + @Test + fun `no ns but rubbish queue`() { + val queueName = "test-link[th2-test-namespace:test-component:test-pin]" + val client: KubernetesClient = mockKubernetesClient() + val actual = + client.collectRabbitMQRubbish( + setOf("th2"), + TOPIC_EXCHANGE_NAME, + listOf( + QueueInfo().apply { name = queueName }, + ), + emptyList(), + ) + val expected = + ResourceHolder( + queues = hashSetOf(queueName), + ) + + assertEquals(expected, actual) + } + + @Test + fun `one ns and rubbish exchange`() { + val exchangeName = "th2-test-exchange" + val namespaceName = "th2-test-active-namespace" + val client: KubernetesClient = + mockKubernetesClient( + setOf(namespaceName), + ) + val actual = + client.collectRabbitMQRubbish( + setOf("th2"), + TOPIC_EXCHANGE_NAME, + emptyList(), + listOf( + ExchangeInfo().apply { name = exchangeName }, + ExchangeInfo().apply { name = toExchangeName(namespaceName) }, + ExchangeInfo().apply { name = TOPIC_EXCHANGE_NAME }, + ), + ) + val expected = + ResourceHolder( + exchanges = hashSetOf(exchangeName), + ) + + assertEquals(expected, actual) + } + + @Test + fun `one ns and rubbish queue`() { + val queueName = "test-link[th2-test-namespace:test-component:test-pin]" + val namespaceName = "th2-test-active-namespace" + val client: KubernetesClient = + mockKubernetesClient( + setOf(namespaceName), + ) + val actual = + client.collectRabbitMQRubbish( + setOf("th2"), + TOPIC_EXCHANGE_NAME, + listOf( + QueueInfo().apply { name = queueName }, + ), + listOf( + ExchangeInfo().apply { name = toExchangeName(namespaceName) }, + ExchangeInfo().apply { name = TOPIC_EXCHANGE_NAME }, + ), + ) + val expected = + ResourceHolder( + queues = hashSetOf(queueName), + ) + + assertEquals(expected, actual) + } + + @Test + fun `delete rubbish`() { + val channel: Channel = mock {} + val resourceHolder = + ResourceHolder( + hashSetOf("queueA", "queueB"), + hashSetOf("exchangeA", "exchangeB"), + ) + deleteRabbitMQRubbish( + resourceHolder, + ) { channel } + + resourceHolder.queues.forEach { + verify(channel).queueDelete(it) + } + + resourceHolder.exchanges.forEach { + verify(channel).exchangeDelete(it) + } + } + + companion object { + fun mockKubernetesClient(namespaceNames: Set = emptySet()): KubernetesClient { + val namespaceList = + NamespaceList().apply { + items = + namespaceNames.map { namespaceName -> + Namespace().apply { + metadata = + ObjectMeta().apply { + name = namespaceName + } + } + } + } + val namespaces: NonNamespaceOperation> = + mock { + on { list() }.thenReturn(namespaceList) + } + + val mixedOperation: + MixedOperation, Resource> = + mock { + on { inNamespace(any()) }.thenReturn(it) + on { resources() }.thenAnswer { emptyList().stream() } + } + return mock { + on { namespaces() }.thenReturn(namespaces) + on { resources(any>()) }.thenReturn(mixedOperation) + } + } + } +} diff --git a/src/test/resources/log4j2.properties b/src/test/resources/log4j2.properties new file mode 100644 index 00000000..901ce45a --- /dev/null +++ b/src/test/resources/log4j2.properties @@ -0,0 +1,14 @@ +name = CommonJConfig +# Logging level related to initialization of Log4j +status = warn + +# Console appender configuration +appender.console.type = Console +appender.console.name = ConsoleLogger +appender.console.layout.type = PatternLayout +appender.console.layout.pattern = %d{dd MMM yyyy HH:mm:ss,SSS} %-6p [%-15t] %c - %m%n + +# Root logger level +rootLogger.level = DEBUG +# Root logger referring to console appender +rootLogger.appenderRef.stdout.ref = ConsoleLogger From ada9bf0cbd5b43a26ee1b120c36955149023fdc4 Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Fri, 16 Aug 2024 16:10:33 +0400 Subject: [PATCH 14/42] [TH2-5226] refactored --- .github/workflows/build-sanpshot.yml | 1 - .github/workflows/ci-unwelcome-words.yml | 2 +- README.md | 7 ++++--- .../th2/infraoperator/operator/AbstractTh2Operator.java | 6 +++--- .../operator/manager/impl/ConfigMapEventHandler.java | 2 +- .../operator/manager/impl/NamespaceEventHandler.java | 2 +- .../operator/manager/impl/Th2DictionaryEventHandler.java | 4 ++-- .../spec/strategy/linkresolver/mq/RabbitMQContext.java | 2 +- .../com/exactpro/th2/infraoperator/util/KubernetesUtils.kt | 2 +- .../com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt | 5 ++--- 10 files changed, 16 insertions(+), 17 deletions(-) diff --git a/.github/workflows/build-sanpshot.yml b/.github/workflows/build-sanpshot.yml index 957be287..9366a44e 100644 --- a/.github/workflows/build-sanpshot.yml +++ b/.github/workflows/build-sanpshot.yml @@ -15,7 +15,6 @@ jobs: with: build-target: 'Docker' docker-username: ${{ github.actor }} - strict-scanner: false secrets: docker-password: ${{ secrets.GITHUB_TOKEN }} nvd-api-key: ${{ secrets.NVD_APIKEY }} \ No newline at end of file diff --git a/.github/workflows/ci-unwelcome-words.yml b/.github/workflows/ci-unwelcome-words.yml index 319d8194..add8e7fd 100644 --- a/.github/workflows/ci-unwelcome-words.yml +++ b/.github/workflows/ci-unwelcome-words.yml @@ -5,7 +5,7 @@ on: jobs: test: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: diff --git a/README.md b/README.md index 36f88952..8ab66338 100644 --- a/README.md +++ b/README.md @@ -87,8 +87,8 @@ rabbitMQManagement: persistence: true # determines if the RabbitMQ resources are persistent or not - cleanUpOnStart: false - # if option is true, operator removes all redundant queues and exchanges from RabbitMQ on start + cleanUpOnStart: true + # if option is true, operator removes redundant queues and exchanges from RabbitMQ on start schemaPermissions: # this section describes what permissions schema RabbitMQ user will have on its own resources @@ -148,7 +148,8 @@ openshift: ## Release notes ### 4.7.0 -+ Improved clean rubbish from RabbitMQ on start. This function enabled by default ++ Improved clean rubbish from RabbitMQ on start to delete only redundant resources. + `cleanUpOnStart` is enabled by default + Migrated to th2 plugin `0.1.1` + Updated: diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/AbstractTh2Operator.java b/src/main/java/com/exactpro/th2/infraoperator/operator/AbstractTh2Operator.java index 37db4609..8526ed0b 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/AbstractTh2Operator.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/AbstractTh2Operator.java @@ -50,7 +50,7 @@ import static com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.ANTECEDENT_LABEL_KEY_ALIAS; import static com.exactpro.th2.infraoperator.util.ExtractUtils.extractName; import static com.exactpro.th2.infraoperator.util.ExtractUtils.extractNamespace; -import static com.exactpro.th2.infraoperator.util.KubernetesUtils.isActive; +import static com.exactpro.th2.infraoperator.util.KubernetesUtils.isNotActive; import static io.fabric8.kubernetes.client.Watcher.Action.MODIFIED; public abstract class AbstractTh2Operator implements Watcher { @@ -96,7 +96,7 @@ public void eventReceived(Action action, CR resource) { action, resourceLabel, e); String namespace = resource.getMetadata().getNamespace(); - if (isActive(kubClient, namespace)) { + if (isNotActive(kubClient, namespace)) { return; } @@ -126,7 +126,7 @@ public void eventReceived(Action action, CR resource) { } catch (Exception e) { String namespace = resource.getMetadata().getNamespace(); - if (isActive(kubClient, namespace)) { + if (isNotActive(kubClient, namespace)) { return; } resource.getStatus().failed(e.getMessage()); diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/ConfigMapEventHandler.java b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/ConfigMapEventHandler.java index 656fd5fc..913a7915 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/ConfigMapEventHandler.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/ConfigMapEventHandler.java @@ -298,7 +298,7 @@ protected void createKubObj(String namespace, HelmRelease helmRelease) { OperatorState.INSTANCE.putHelmReleaseInCache(helmRelease, namespace); } - private Map getConfigFromCR(CustomResource customResource, String key) { + private Map getConfigFromCR(CustomResource customResource, String key) { Th2Spec spec = (Th2Spec) customResource.getSpec(); switch (key) { case MQ_ROUTER_ALIAS: diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/NamespaceEventHandler.java b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/NamespaceEventHandler.java index 30b5e1c0..b2187f80 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/NamespaceEventHandler.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/NamespaceEventHandler.java @@ -37,7 +37,7 @@ public class NamespaceEventHandler implements ResourceEventHandler, Watcher { private static final Logger logger = LoggerFactory.getLogger(NamespaceEventHandler.class); - private EventQueue eventQueue; + private final EventQueue eventQueue; private final OperatorConfig config = ConfigLoader.getConfig(); diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/Th2DictionaryEventHandler.java b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/Th2DictionaryEventHandler.java index 2c3f250e..2a94d4d8 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/Th2DictionaryEventHandler.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/Th2DictionaryEventHandler.java @@ -49,7 +49,7 @@ import static com.exactpro.th2.infraoperator.util.ExtractUtils.extractName; import static com.exactpro.th2.infraoperator.util.ExtractUtils.extractNamespace; import static com.exactpro.th2.infraoperator.util.HelmReleaseUtils.extractDictionariesConfig; -import static com.exactpro.th2.infraoperator.util.KubernetesUtils.isActive; +import static com.exactpro.th2.infraoperator.util.KubernetesUtils.isNotActive; import static com.exactpro.th2.infraoperator.util.WatcherUtils.createExceptionHandler; public class Th2DictionaryEventHandler implements Watcher { @@ -196,7 +196,7 @@ private ConfigMap toConfigMap(Th2Dictionary dictionary) { private void updateLinkedResources(String dictionaryName, String namespace, String checksum, Set linkedResources) { - if (isActive(kubClient, namespace)) { + if (isNotActive(kubClient, namespace)) { return; } for (var linkedResourceName : linkedResources) { diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java index 8780dd4a..4fe40907 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java +++ b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java @@ -312,7 +312,7 @@ public static List getQueueBindings(String queue) { public static @NotNull List getTh2Exchanges() { Collection namespacePrefixes = ConfigLoader.getConfig().getNamespacePrefixes(); - String topicExchange = getManagementConfig().getExchangeName(); + String topicExchange = getTopicExchangeName(); return getExchanges().stream() .filter(exchangeInfo -> { String name = exchangeInfo.getName(); diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/util/KubernetesUtils.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/util/KubernetesUtils.kt index 0e0f198b..4982f1a9 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/util/KubernetesUtils.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/util/KubernetesUtils.kt @@ -55,7 +55,7 @@ fun KubernetesClient.customResources(namespace: String): List }.map { it.get() as Th2CustomResource } .toList() -fun KubernetesClient.isActive(namespace: String): Boolean { +fun KubernetesClient.isNotActive(namespace: String): Boolean { val namespaceObj: Namespace? = namespaces().withName(namespace).get() if (namespaceObj == null || namespaceObj.status.phase != "Active") { K_LOGGER.info { "Namespace \"$namespace\" deleted or not active, cancelling" } diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt index 2060bd48..4a194708 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt @@ -40,15 +40,14 @@ private val K_LOGGER = KotlinLogging.logger { } fun deleteRabbitMQRubbish() { try { val config = ConfigLoader.loadConfiguration() - val rabbitMQManagement = config.rabbitMQManagement - if (!rabbitMQManagement.cleanUpOnStart) { + if (!config.rabbitMQManagement.cleanUpOnStart) { K_LOGGER.info { "Cleanup RabbitMQ before start is skipped by config" } return } val namespacePrefixes = config.namespacePrefixes - val topicExchange = rabbitMQManagement.exchangeName + val topicExchange = RabbitMQContext.getTopicExchangeName() createKubernetesClient().use { kuClient -> val resourceHolder = kuClient.collectRabbitMQRubbish( From 1bc6904b504cf9521ae79db313dfdfbd41fa5ec8 Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Mon, 19 Aug 2024 11:21:25 +0400 Subject: [PATCH 15/42] [TH2-5226] corrected after review --- .github/workflows/java-publish-docker.yml | 0 README.md | 5 +---- .../configuration/fields/RabbitMQManagementConfig.kt | 1 - .../com/exactpro/th2/infraoperator/util/KubernetesUtils.kt | 2 +- .../com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt | 7 +------ .../com/exactpro/th2/infraoperator/util/WatcherUtils.kt | 4 +++- .../infraoperator/configuration/ConfigurationTests.java | 2 -- 7 files changed, 6 insertions(+), 15 deletions(-) delete mode 100644 .github/workflows/java-publish-docker.yml diff --git a/.github/workflows/java-publish-docker.yml b/.github/workflows/java-publish-docker.yml deleted file mode 100644 index e69de29b..00000000 diff --git a/README.md b/README.md index 8ab66338..acc99b77 100644 --- a/README.md +++ b/README.md @@ -87,9 +87,6 @@ rabbitMQManagement: persistence: true # determines if the RabbitMQ resources are persistent or not - cleanUpOnStart: true - # if option is true, operator removes redundant queues and exchanges from RabbitMQ on start - schemaPermissions: # this section describes what permissions schema RabbitMQ user will have on its own resources # see RabbitMQ documentation to find out how permissions are described @@ -149,7 +146,7 @@ openshift: ### 4.7.0 + Improved clean rubbish from RabbitMQ on start to delete only redundant resources. - `cleanUpOnStart` is enabled by default + The `cleanUpOnStart` option has been removed, the clean rubbish function is enabled. + Migrated to th2 plugin `0.1.1` + Updated: diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQManagementConfig.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQManagementConfig.kt index 5e727bbf..b2a371d6 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQManagementConfig.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQManagementConfig.kt @@ -25,6 +25,5 @@ data class RabbitMQManagementConfig( val username: String = "", val password: String = "", val persistence: Boolean = false, - val cleanUpOnStart: Boolean = true, val schemaPermissions: RabbitMQNamespacePermissions = RabbitMQNamespacePermissions() ) diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/util/KubernetesUtils.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/util/KubernetesUtils.kt index 4982f1a9..7dc1d040 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/util/KubernetesUtils.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/util/KubernetesUtils.kt @@ -32,7 +32,7 @@ import kotlin.streams.toList private val K_LOGGER = KotlinLogging.logger { } -val CUSTOM_RESOURCE_KINDS = +val CUSTOM_RESOURCE_KINDS: Set> = setOf(Th2Estore::class.java, Th2Mstore::class.java, Th2CoreBox::class.java, Th2Box::class.java, Th2Job::class.java) fun createKubernetesClient(): KubernetesClient = KubernetesClientBuilder().build() diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt index 4a194708..60b37d1e 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt @@ -41,11 +41,6 @@ fun deleteRabbitMQRubbish() { try { val config = ConfigLoader.loadConfiguration() - if (!config.rabbitMQManagement.cleanUpOnStart) { - K_LOGGER.info { "Cleanup RabbitMQ before start is skipped by config" } - return - } - val namespacePrefixes = config.namespacePrefixes val topicExchange = RabbitMQContext.getTopicExchangeName() @@ -71,7 +66,6 @@ internal fun KubernetesClient.collectRabbitMQRubbish( th2Exchanges: Collection, ): ResourceHolder = ResourceHolder().apply { val namespaces: Set = namespaces(namespacePrefixes) - val factories: Map, MessageRouterConfigFactory> = createFactories() th2Queues.asSequence() .map(QueueInfo::getName) @@ -90,6 +84,7 @@ internal fun KubernetesClient.collectRabbitMQRubbish( K_LOGGER.debug { "Search RabbitMQ resources in $namespaces namespaces" } exchanges.remove(topicExchange) + val factories: Map, MessageRouterConfigFactory> = createFactories() namespaces.forEach { namespace -> exchanges.remove(namespace) diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/util/WatcherUtils.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/util/WatcherUtils.kt index 6ae7969f..76dc7a11 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/util/WatcherUtils.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/util/WatcherUtils.kt @@ -27,7 +27,9 @@ private val K_LOGGER = KotlinLogging.logger { } fun createExceptionHandler(clazz: Class<*>): ExceptionHandler { return ExceptionHandler { isStarted: Boolean, t: Throwable -> K_LOGGER.error(t) { "${clazz.simpleName} informer catch error, isStarted: $isStarted" } - // the code below is copied from io.fabric8.kubernetes.client.informers.impl.cache.Reflector.handler + // Default condition copied from io.fabric8.kubernetes.client.informers.impl.cache.Reflector.handler. + // We should monitor caught errors in real cluster + // after that change the condition to maintain a component in working order isStarted && t !is WatcherException } } diff --git a/src/test/java/com/exactpro/th2/infraoperator/configuration/ConfigurationTests.java b/src/test/java/com/exactpro/th2/infraoperator/configuration/ConfigurationTests.java index 5f25bab5..ac52ed19 100644 --- a/src/test/java/com/exactpro/th2/infraoperator/configuration/ConfigurationTests.java +++ b/src/test/java/com/exactpro/th2/infraoperator/configuration/ConfigurationTests.java @@ -65,7 +65,6 @@ void testFullConfig() { "username", "password", true, - true, new RabbitMQNamespacePermissions( "configure", "read", "write" ) @@ -101,7 +100,6 @@ void testRabbitMQManagementConfig() { "username", "password", true, - true, new RabbitMQNamespacePermissions( "configure", "read", "write" ) From 2e47d42fc59486e89edc0f3021ab9cfe4aaeb1e5 Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Mon, 19 Aug 2024 14:59:01 +0400 Subject: [PATCH 16/42] [TH2-5226] refactored after review --- .../th2/infraoperator/util/RabbitMQUtils.kt | 37 ++++++++----- .../infraoperator/util/RabbitMQUtilsTest.kt | 53 ++++++++----------- 2 files changed, 44 insertions(+), 46 deletions(-) diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt index 60b37d1e..bf40514b 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt @@ -39,34 +39,36 @@ private val K_LOGGER = KotlinLogging.logger { } fun deleteRabbitMQRubbish() { try { - val config = ConfigLoader.loadConfiguration() + val resourceHolder = collectRabbitMQResources( + RabbitMQContext.getTh2Queues(), + RabbitMQContext.getTh2Exchanges(), + ) - val namespacePrefixes = config.namespacePrefixes + if (resourceHolder.isHolderEmpty()) { + return + } + + val namespacePrefixes = ConfigLoader.loadConfiguration().namespacePrefixes val topicExchange = RabbitMQContext.getTopicExchangeName() createKubernetesClient().use { kuClient -> - val resourceHolder = kuClient.collectRabbitMQRubbish( + resourceHolder.filterRubbishResources( + kuClient, namespacePrefixes, topicExchange, - RabbitMQContext.getTh2Queues(), - RabbitMQContext.getTh2Exchanges(), ) - K_LOGGER.info { "RabbitMQ rubbish: $resourceHolder" } - deleteRabbitMQRubbish(resourceHolder, RabbitMQContext::getChannel) } + K_LOGGER.info { "RabbitMQ rubbish: $resourceHolder" } + deleteRabbitMQRubbish(resourceHolder, RabbitMQContext::getChannel) } catch (e: Exception) { K_LOGGER.error(e) { "Delete RabbitMQ rubbish failure" } } } -internal fun KubernetesClient.collectRabbitMQRubbish( - namespacePrefixes: Set, - topicExchange: String, +internal fun collectRabbitMQResources( th2Queues: Collection, th2Exchanges: Collection, ): ResourceHolder = ResourceHolder().apply { - val namespaces: Set = namespaces(namespacePrefixes) - th2Queues.asSequence() .map(QueueInfo::getName) .forEach(queues::add) @@ -76,8 +78,15 @@ internal fun KubernetesClient.collectRabbitMQRubbish( .forEach(exchanges::add) K_LOGGER.debug { "Actual set in RabbitMQ, queues: $queues, exchanges: $exchanges" } +} - if (isHolderEmpty() || namespaces.isEmpty()) { +internal fun ResourceHolder.filterRubbishResources( + client: KubernetesClient, + namespacePrefixes: Set, + topicExchange: String, +): ResourceHolder = this.apply { + val namespaces: Set = client.namespaces(namespacePrefixes) + if (namespaces.isEmpty()) { return@apply } @@ -88,7 +97,7 @@ internal fun KubernetesClient.collectRabbitMQRubbish( namespaces.forEach { namespace -> exchanges.remove(namespace) - customResources(namespace).asSequence() + client.customResources(namespace).asSequence() .flatMap { cr -> factories[cr.javaClass]?.createConfig(cr)?.queues?.values ?: error("MQ config factory isn't present for ${cr.javaClass.simpleName}") diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtilsTest.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtilsTest.kt index 9a549a57..8362fdb1 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtilsTest.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtilsTest.kt @@ -18,8 +18,6 @@ package com.exactpro.th2.infraoperator.util import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext.toExchangeName import com.rabbitmq.client.Channel -import com.rabbitmq.http.client.domain.ExchangeInfo -import com.rabbitmq.http.client.domain.QueueInfo import io.fabric8.kubernetes.api.model.KubernetesResourceList import io.fabric8.kubernetes.api.model.Namespace import io.fabric8.kubernetes.api.model.NamespaceList @@ -42,13 +40,12 @@ class RabbitMQUtilsTest { fun `no ns and topic exchange`() { val client: KubernetesClient = mockKubernetesClient() val actual = - client.collectRabbitMQRubbish( + ResourceHolder( + exchanges = hashSetOf(TOPIC_EXCHANGE_NAME), + ).filterRubbishResources( + client, setOf("th2"), TOPIC_EXCHANGE_NAME, - emptyList(), - listOf( - ExchangeInfo().apply { name = TOPIC_EXCHANGE_NAME }, - ), ) val expected = ResourceHolder( @@ -63,13 +60,12 @@ class RabbitMQUtilsTest { val exchangeName = "th2-test-exchange" val client: KubernetesClient = mockKubernetesClient() val actual = - client.collectRabbitMQRubbish( + ResourceHolder( + exchanges = hashSetOf(exchangeName), + ).filterRubbishResources( + client, setOf("th2"), TOPIC_EXCHANGE_NAME, - emptyList(), - listOf( - ExchangeInfo().apply { name = exchangeName }, - ), ) val expected = ResourceHolder( @@ -84,13 +80,12 @@ class RabbitMQUtilsTest { val queueName = "test-link[th2-test-namespace:test-component:test-pin]" val client: KubernetesClient = mockKubernetesClient() val actual = - client.collectRabbitMQRubbish( + ResourceHolder( + queues = hashSetOf(queueName), + ).filterRubbishResources( + client, setOf("th2"), TOPIC_EXCHANGE_NAME, - listOf( - QueueInfo().apply { name = queueName }, - ), - emptyList(), ) val expected = ResourceHolder( @@ -109,15 +104,12 @@ class RabbitMQUtilsTest { setOf(namespaceName), ) val actual = - client.collectRabbitMQRubbish( + ResourceHolder( + exchanges = hashSetOf(exchangeName, toExchangeName(namespaceName), TOPIC_EXCHANGE_NAME), + ).filterRubbishResources( + client, setOf("th2"), TOPIC_EXCHANGE_NAME, - emptyList(), - listOf( - ExchangeInfo().apply { name = exchangeName }, - ExchangeInfo().apply { name = toExchangeName(namespaceName) }, - ExchangeInfo().apply { name = TOPIC_EXCHANGE_NAME }, - ), ) val expected = ResourceHolder( @@ -136,16 +128,13 @@ class RabbitMQUtilsTest { setOf(namespaceName), ) val actual = - client.collectRabbitMQRubbish( + ResourceHolder( + queues = hashSetOf(queueName), + exchanges = hashSetOf(toExchangeName(namespaceName), TOPIC_EXCHANGE_NAME), + ).filterRubbishResources( + client, setOf("th2"), TOPIC_EXCHANGE_NAME, - listOf( - QueueInfo().apply { name = queueName }, - ), - listOf( - ExchangeInfo().apply { name = toExchangeName(namespaceName) }, - ExchangeInfo().apply { name = TOPIC_EXCHANGE_NAME }, - ), ) val expected = ResourceHolder( From 123585d10ff1a0485550c82e5ceab442848c0bb2 Mon Sep 17 00:00:00 2001 From: Nikita Smirnov <46124551+Nikita-Smirnov-Exactpro@users.noreply.github.com> Date: Mon, 19 Aug 2024 15:55:25 +0400 Subject: [PATCH 17/42] Update src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt Co-authored-by: Oleg Smirnov --- .../kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt index bf40514b..0b4ba6cf 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt @@ -84,7 +84,7 @@ internal fun ResourceHolder.filterRubbishResources( client: KubernetesClient, namespacePrefixes: Set, topicExchange: String, -): ResourceHolder = this.apply { +): ResourceHolder = apply { val namespaces: Set = client.namespaces(namespacePrefixes) if (namespaces.isEmpty()) { return@apply From b3598821f8619afe806360ed57dd3a838e82ab80 Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Fri, 23 Aug 2024 12:17:05 +0400 Subject: [PATCH 18/42] [TH2-5226] Updated kotlin logging to 5.1.4 --- README.md | 2 +- build.gradle | 19 ++++++++++++++++++- gradle/libs.versions.toml | 4 +++- .../configuration/ConfigLoader.kt | 13 ++++++------- .../linkresolver/mq/BindQueueLinkResolver.kt | 2 +- .../th2/infraoperator/util/KubernetesUtils.kt | 2 +- .../th2/infraoperator/util/RabbitMQUtils.kt | 2 +- .../th2/infraoperator/util/WatcherUtils.kt | 2 +- 8 files changed, 32 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index acc99b77..ef055efe 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ openshift: + force logging-interceptor: `4.12.0` + http-client: `5.2.0` + java-uuid-generator: `5.1.0` - + kotlin-logging: `3.0.5` + + kotlin-logging: `5.1.4` ### 4.6.4 + Added `rabbitMQManagement.cleanUpOnStart` option \ No newline at end of file diff --git a/build.gradle b/build.gradle index fd1389f5..02add3be 100644 --- a/build.gradle +++ b/build.gradle @@ -54,6 +54,11 @@ dependencies { implementation "io.prometheus:simpleclient_httpserver" implementation "io.prometheus:simpleclient_hotspot" + testImplementation(platform(libs.testcontainers.bom)) + testImplementation 'org.testcontainers:rabbitmq' + testImplementation 'org.testcontainers:k3s' + + testImplementation libs.mockito.core testImplementation libs.mockito.kotlin testImplementation libs.junit.jupiter.api @@ -68,7 +73,19 @@ wrapper { } test { - useJUnitPlatform() + useJUnitPlatform { + excludeTags("integration-test") + } +} + +tasks.register("integrationTest", Test.class) { + group = "verification" + useJUnitPlatform { + includeTags("integration-test") + } + testLogging { + showStandardStreams = true + } } dependencyCheck { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1207fe44..54a3cf70 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ okhttp3 = "4.12.0" detekt = "1.23.6" [libraries] -kotlin-logging = { group = "io.github.microutils", name = "kotlin-logging", version = "3.0.5" } +kotlin-logging = { group = "io.github.oshai", name = "kotlin-logging", version = "5.1.4" } okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp3" } logging-interceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp3" } kubernetes-client = { group = "io.fabric8", name = "kubernetes-client", version = "6.13.1" } @@ -18,6 +18,8 @@ mockito-kotlin = { group = "org.mockito.kotlin", name = "mockito-kotlin", versio junit-jupiter-api = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "jupiter" } junit-jupiter-engine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", version.ref = "jupiter" } +testcontainers-bom = { group = "org.testcontainers", name = "testcontainers-bom", version = "1.20.1" } + detekt-formatting = { group = "io.gitlab.arturbosch.detekt", name = "detekt-formatting", version.ref = "detekt" } [plugins] diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/ConfigLoader.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/ConfigLoader.kt index 2706f041..6d5ef569 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/ConfigLoader.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/ConfigLoader.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ import com.exactpro.th2.infraoperator.util.JsonUtils.YAML_MAPPER import com.fasterxml.jackson.core.JsonParseException import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException import com.fasterxml.jackson.module.kotlin.readValue -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging import org.apache.commons.text.StringSubstitutor import org.apache.commons.text.lookup.StringLookupFactory import java.io.FileInputStream @@ -54,13 +54,12 @@ object ConfigLoader { return YAML_MAPPER.readValue(content) } } catch (e: UnrecognizedPropertyException) { - logger.error( - "Bad configuration: unknown property(\"{}\") specified in configuration file", - e.propertyName - ) + logger.error(e) { + "Bad configuration: unknown property('${e.propertyName}') specified in configuration file" + } throw e } catch (e: JsonParseException) { - logger.error("Bad configuration: exception while parsing configuration file") + logger.error { "Bad configuration: exception while parsing configuration file" } throw e } } diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt index 930a94c8..447058d0 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt @@ -31,7 +31,7 @@ import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.queue.RoutingKe import com.exactpro.th2.infraoperator.spec.strategy.redeploy.NonTerminalException import com.exactpro.th2.infraoperator.util.CustomResourceUtils import com.exactpro.th2.infraoperator.util.CustomResourceUtils.annotationFor -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging object BindQueueLinkResolver { private val logger = KotlinLogging.logger { } diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/util/KubernetesUtils.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/util/KubernetesUtils.kt index 7dc1d040..c2308918 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/util/KubernetesUtils.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/util/KubernetesUtils.kt @@ -27,7 +27,7 @@ import com.exactpro.th2.infraoperator.spec.mstore.Th2Mstore import io.fabric8.kubernetes.api.model.Namespace import io.fabric8.kubernetes.client.KubernetesClient import io.fabric8.kubernetes.client.KubernetesClientBuilder -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging import kotlin.streams.toList private val K_LOGGER = KotlinLogging.logger { } diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt index 0b4ba6cf..028c3ff4 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt @@ -32,7 +32,7 @@ import com.rabbitmq.client.Channel import com.rabbitmq.http.client.domain.ExchangeInfo import com.rabbitmq.http.client.domain.QueueInfo import io.fabric8.kubernetes.client.KubernetesClient -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging import java.io.IOException private val K_LOGGER = KotlinLogging.logger { } diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/util/WatcherUtils.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/util/WatcherUtils.kt index 76dc7a11..81626fee 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/util/WatcherUtils.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/util/WatcherUtils.kt @@ -20,7 +20,7 @@ package com.exactpro.th2.infraoperator.util import io.fabric8.kubernetes.client.WatcherException import io.fabric8.kubernetes.client.informers.ExceptionHandler -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging private val K_LOGGER = KotlinLogging.logger { } From 879d17b36691484dd229e9e6af49f082025b910b Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Fri, 23 Aug 2024 17:30:37 +0400 Subject: [PATCH 19/42] [TH2-5226] Added IntegrationTest --- build.gradle | 3 + gradle/libs.versions.toml | 1 + .../th2/infraoperator/Th2CrdController.java | 13 +- .../linkresolver/mq/RabbitMQContext.java | 18 +- .../th2/infraoperator/IntegrationTest.kt | 180 +++++ src/test/resources/crds/th2-box-crd.yaml | 712 +++++++++++++++++ src/test/resources/crds/th2-core-box-crd.yaml | 726 ++++++++++++++++++ .../resources/crds/th2-dictionary-crd.yaml | 54 ++ src/test/resources/crds/th2-estore-crd.yaml | 698 +++++++++++++++++ src/test/resources/crds/th2-job-crd.yaml | 699 +++++++++++++++++ src/test/resources/crds/th2-mstore-crd.yaml | 698 +++++++++++++++++ 11 files changed, 3792 insertions(+), 10 deletions(-) create mode 100644 src/test/kotlin/com/exactpro/th2/infraoperator/IntegrationTest.kt create mode 100644 src/test/resources/crds/th2-box-crd.yaml create mode 100644 src/test/resources/crds/th2-core-box-crd.yaml create mode 100644 src/test/resources/crds/th2-dictionary-crd.yaml create mode 100644 src/test/resources/crds/th2-estore-crd.yaml create mode 100644 src/test/resources/crds/th2-job-crd.yaml create mode 100644 src/test/resources/crds/th2-mstore-crd.yaml diff --git a/build.gradle b/build.gradle index 02add3be..0bbebce4 100644 --- a/build.gradle +++ b/build.gradle @@ -62,6 +62,9 @@ dependencies { testImplementation libs.mockito.core testImplementation libs.mockito.kotlin testImplementation libs.junit.jupiter.api + testImplementation "org.jetbrains.kotlin:kotlin-test-junit5" + testImplementation libs.bcpkix.jdk18on + testRuntimeOnly libs.junit.jupiter.engine detektPlugins libs.detekt.formatting diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 54a3cf70..b0f63495 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,6 +19,7 @@ junit-jupiter-api = { group = "org.junit.jupiter", name = "junit-jupiter-api", v junit-jupiter-engine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", version.ref = "jupiter" } testcontainers-bom = { group = "org.testcontainers", name = "testcontainers-bom", version = "1.20.1" } +bcpkix-jdk18on = { group = "org.bouncycastle", name = "bcpkix-jdk18on", version = "1.78.1" } detekt-formatting = { group = "io.gitlab.arturbosch.detekt", name = "detekt-formatting", version.ref = "detekt" } diff --git a/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java b/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java index 936493ff..bffe70f1 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java +++ b/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java @@ -39,10 +39,7 @@ public class Th2CrdController { private static final Logger LOGGER = LoggerFactory.getLogger(Th2CrdController.class); - public static void main(String[] args) { - if (args.length > 0) { - configureLogger(args[0]); - } + public void start() { var watchManager = DefaultWatchManager.getInstance(); PrometheusServer.start(); OperatorMetrics.resetCacheErrors(); @@ -68,6 +65,14 @@ public static void main(String[] args) { } } + public static void main(String[] args) { + if (args.length > 0) { + configureLogger(args[0]); + } + Th2CrdController controller = new Th2CrdController(); + controller.start(); + } + private static void configureLogger(String filePath) { Path path = Path.of(filePath); if (Files.exists(path)) { diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java index 4fe40907..19e4a47d 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java +++ b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java @@ -335,6 +335,14 @@ public static QueueInfo getQueue(String queueName) { } } + public static Client createClient(String host, int port, String username, String password) throws MalformedURLException, URISyntaxException { + return new Client(new ClientParameters() + .url(format("http://%s:%s/api", host, port)) + .username(username) + .password(password) + ); + } + private static RabbitMQManagementConfig getManagementConfig() { // we do not need to synchronize as we are assigning immutable object from singleton if (managementConfig == null) { @@ -346,12 +354,10 @@ private static RabbitMQManagementConfig getManagementConfig() { private static Client getClient() throws MalformedURLException, URISyntaxException { if (rmqClient == null) { RabbitMQManagementConfig rabbitMQMngConfig = getManagementConfig(); - String apiStr = "http://%s:%s/api"; - rmqClient = new Client(new ClientParameters() - .url(format(apiStr, rabbitMQMngConfig.getHost(), rabbitMQMngConfig.getManagementPort())) - .username(rabbitMQMngConfig.getUsername()) - .password(rabbitMQMngConfig.getPassword()) - ); + rmqClient = createClient(rabbitMQMngConfig.getHost(), + rabbitMQMngConfig.getManagementPort(), + rabbitMQMngConfig.getUsername(), + rabbitMQMngConfig.getPassword()); } return rmqClient; } diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/IntegrationTest.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/IntegrationTest.kt new file mode 100644 index 00000000..2d20c6bd --- /dev/null +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/IntegrationTest.kt @@ -0,0 +1,180 @@ +/* + * Copyright 2024-2024 Exactpro (Exactpro Systems Limited) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.exactpro.th2.infraoperator + +import com.exactpro.th2.infraoperator.configuration.ConfigLoader.CONFIG_FILE_SYSTEM_PROPERTY +import com.exactpro.th2.infraoperator.configuration.OperatorConfig +import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQManagementConfig +import com.exactpro.th2.infraoperator.spec.shared.PrometheusConfiguration +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext +import com.exactpro.th2.infraoperator.util.JsonUtils.YAML_MAPPER +import com.exactpro.th2.infraoperator.util.createKubernetesClient +import com.fasterxml.jackson.module.kotlin.readValue +import com.rabbitmq.http.client.Client +import io.fabric8.kubernetes.client.Config +import io.fabric8.kubernetes.client.KubernetesClient +import io.fabric8.kubernetes.client.dsl.NamespaceableResource +import io.github.oshai.kotlinlogging.KotlinLogging +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.io.TempDir +import org.slf4j.LoggerFactory.getLogger +import org.testcontainers.containers.RabbitMQContainer +import org.testcontainers.containers.output.Slf4jLogConsumer +import org.testcontainers.k3s.K3sContainer +import org.testcontainers.lifecycle.Startable +import org.testcontainers.utility.DockerImageName +import java.nio.file.Files +import java.nio.file.Path +import kotlin.io.path.absolutePathString +import kotlin.io.path.createDirectories +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition as CRD + +@Tag("integration-test") +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class IntegrationTest { + + private lateinit var tempDir: Path + + private lateinit var configDir: Path + + private lateinit var kubeConfig: Path + + private lateinit var operatorConfig: Path + + private lateinit var k3s: K3sContainer + + private lateinit var rabbitMQ: RabbitMQContainer + + private lateinit var kubeClient: KubernetesClient + + private lateinit var rabbitMQClient: Client + + private lateinit var controller: Th2CrdController + + + @BeforeAll + fun beforeAll(@TempDir tempDir: Path) { + this.tempDir = tempDir + configDir = tempDir.resolve("cfg") + kubeConfig = configDir.resolve("kube-config.yaml") + operatorConfig = configDir.resolve("infra-operator.yml") + configDir.createDirectories() + + k3s = K3sContainer(K3S_DOCKER_IMAGE) + .withLogConsumer(Slf4jLogConsumer(getLogger("K3S")).withSeparateOutputStreams()) + .also(Startable::start) + + rabbitMQ = RabbitMQContainer(RABBITMQ_DOCKER_IMAGE) + .withLogConsumer(Slf4jLogConsumer(getLogger("RABBIT_MQ")).withSeparateOutputStreams()) + .also(Startable::start) + + K_LOGGER.info { "RabbitMQ URL: ${rabbitMQ.httpUrl}" } + + println(k3s.kubeConfigYaml) + Files.writeString(kubeConfig, k3s.kubeConfigYaml) + YAML_MAPPER.writeValue(operatorConfig.toFile(), createConfig(rabbitMQ)) + + System.setProperty(Config.KUBERNETES_KUBECONFIG_FILE, kubeConfig.absolutePathString()) + System.setProperty(CONFIG_FILE_SYSTEM_PROPERTY, operatorConfig.absolutePathString()) + + kubeClient = createKubernetesClient().apply { configureK3s() } + rabbitMQClient = createRabbitMQClient(rabbitMQ) + controller = Th2CrdController().apply(Th2CrdController::start) + } + + @AfterAll + fun afterAll() { + if(this::kubeClient.isInitialized) { + kubeClient.close() + } + if(this::k3s.isInitialized) { + k3s.stop() + } + if(this::rabbitMQ.isInitialized) { + rabbitMQ.stop() + } + } + + @Test + fun test() { +// val controller = Th2CrdController() +// controller.start() + Thread.sleep(5_000) + +// createKubernetesClient().use { client -> +// println("NAME_SPACE ${client.namespaces().list().items.map{ it.metadata.name }}") +// } + } + + companion object { + private val K_LOGGER = KotlinLogging.logger {} + + private val CRD_RESOURCE_NAME = setOf( + "th2-box-crd.yaml", + "th2-core-box-crd.yaml", + "th2-dictionary-crd.yaml", + "th2-estore-crd.yaml", + "th2-job-crd.yaml", + "th2-mstore-crd.yaml", + ) + + private const val V_HOST = "/" + private const val TOPIC_EXCHANGE = "global-exchange" + + private val RABBITMQ_DOCKER_IMAGE = DockerImageName.parse("rabbitmq:3.12.6-management") + private val K3S_DOCKER_IMAGE = DockerImageName.parse("rancher/k3s:v1.21.3-k3s1") + + private fun createRabbitMQClient(rabbitMQ: RabbitMQContainer) = RabbitMQContext.createClient( + rabbitMQ.host, + rabbitMQ.httpPort, + rabbitMQ.adminUsername, + rabbitMQ.adminPassword, + ) + + private fun createConfig(rabbitMQ: RabbitMQContainer) = OperatorConfig( + rabbitMQManagement = RabbitMQManagementConfig( + host = rabbitMQ.host, + managementPort = rabbitMQ.httpPort, + applicationPort = rabbitMQ.amqpPort, + vhostName = V_HOST, + exchangeName = TOPIC_EXCHANGE, + username = rabbitMQ.adminUsername, + password = rabbitMQ.adminPassword, + ), + prometheusConfiguration = PrometheusConfiguration( + "0.0.0.0", + "9752", + false.toString(), + ), + ) + + private fun KubernetesClient.configureK3s() { + CRD_RESOURCE_NAME.asSequence() + .map(IntegrationTest::loadCrd) + .map(this::resource) + .forEach(NamespaceableResource::create) + } + + private fun loadCrd(resourceName: String): CRD = + requireNotNull(IntegrationTest::class.java.classLoader.getResource("crds/$resourceName")) { + "Resource '$resourceName' isn't found" + }.let(YAML_MAPPER::readValue) + } +} diff --git a/src/test/resources/crds/th2-box-crd.yaml b/src/test/resources/crds/th2-box-crd.yaml new file mode 100644 index 00000000..009fc555 --- /dev/null +++ b/src/test/resources/crds/th2-box-crd.yaml @@ -0,0 +1,712 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: th2boxes.th2.exactpro.com +spec: + conversion: + strategy: None + group: th2.exactpro.com + names: + kind: Th2Box + listKind: Th2BoxList + plural: th2boxes + singular: th2box + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.subResourceName + name: HelmRelease + type: string + - jsonPath: .spec.type + name: Type + type: string + - jsonPath: .status.phase + name: Status + type: string + - jsonPath: .status.message + name: Message + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v2 + schema: + openAPIV3Schema: + description: Th2Box defines generic Th2 component instance + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + description: Specification of desired box + properties: + customConfig: + description: this is custom configuration + type: object + x-kubernetes-preserve-unknown-fields: true + loggingConfig: + description: this is logging configuration + type: string + mqRouter: + description: this is custom configuration for router mq + type: object + x-kubernetes-preserve-unknown-fields: true + grpcRouter: + description: this is custom configuration for router grpc + type: object + x-kubernetes-preserve-unknown-fields: true + cradleManager: + description: this is custom configuration for cradle + type: object + x-kubernetes-preserve-unknown-fields: true + bookName: + description: Can be used for passing custom book for a specific CR + type: string + extendedSettings: + type: object + description: section for extended settings + properties: + entryPoint: + description: allows user to pass custom ENTRYPOINT into the docker container + type: object + properties: + command: + description: allows user to pass custom command for the docker container entrypoint + type: array + items: + type: string + args: + description: allows user to pass custom arguments for the docker container entrypoint + type: array + items: + type: string + envVariables: + description: allows us to pass specific environment variables that are going to be set into the pods. + type: object + x-kubernetes-preserve-unknown-fields: true + sharedMemory: + description: DESCRIPTION NEEDED + type: object + properties: + enabled: + description: DESCRIPTION NEEDED + type: boolean + replicas: + description: number of replicas + type: integer + k8sProbes: + description: if enabled liveness probes will be collected from pod + type: boolean + default: false + externalBox: + description: using this section we can configure boxes that are going to be run outside of kubernetes cluster + properties: + address: + description: address to the machine on which external box is running + type: string + enabled: + description: represents the state of the external box + type: boolean + endpoints: + description: internal mapping for ports + items: + properties: + name: + description: name for the endpoint + type: string + targetPort: + description: value for exposed port + type: integer + required: + - name + - targetPort + type: object + type: array + type: object + hostAliases: + description: HostAliases is an optional list of hosts and IPs that will be injected into the pod's hosts file if specified. This is only valid for non-hostNetwork pods. + type: array + items: + properties: + ip: + description: IP address of the host file entry. + type: string + hostnames: + description: Hostnames for the above IP address. + items: + type: string + type: array + required: + - ip + - hostnames + type: object + hostNetwork: + description: if the flag is set to true, pod will run on node network and kubernetes will decide which node will be used for running the box. + type: boolean + nodeSelector: + additionalProperties: + type: string + description: | + NodeSelector is a selector which must be true for the pod to fit on a node. + Selector which must match a node's labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector + type: object + mounting: + description: allows to configure persistent volume mounting for pods + items: + properties: + path: + description: a directory in pod where you mount external folder + type: string + pvcName: + description: name for the pvc + type: string + type: object + type: array + resources: + description: here we can specify resource limitations and allowances for this specific component + properties: + limits: + properties: + cpu: + type: string + memory: + type: string + type: object + requests: + properties: + cpu: + type: string + memory: + type: string + type: object + type: object + service: + type: object + properties: + enabled: + description: if enabled allows pod to be exposed using ClusterIP + type: boolean + clusterIP: + description: endpoints for cluster IP service type + type: array + items: + type: object + properties: + name: + description: name of the endpoint + type: string + containerPort: + description: allows to specify Kubernetes port for the pod. + type: integer + required: + - name + nodePort: + description: endpoints for nodePort service type + type: array + items: + type: object + properties: + name: + description: name of the endpoint + type: string + exposedPort: + description: a port on which the service will be accessible. + type: integer + containerPort: + description: allows to specify Kubernetes port for the pod. + type: integer + required: + - name + - exposedPort + loadBalancer: + description: endpoints for loadBalancer service type + type: array + items: + type: object + properties: + name: + description: name of the endpoint + type: string + containerPort: + description: allows to specify Kubernetes port for the pod. + type: integer + required: + - name + ingress: + description: Creates Ingress for the Box + type: object + properties: + urlPaths: + description: | + Path is a suffix, it is appended to namespace url, e.g for th2-test namespace: + + urlPaths: + - /my-box/ + + service will be available at /th2-test/my-box/ + type: array + items: + type: string + pattern: "^[-a-zA-Z0-9_/]+$" + disabled: + description: if set to true resource will act as if it is removed from schema without actually deleting the file. The default value is false. + type: boolean + imageName: + description: docker image repository URL + type: string + imageVersion: + description: docker image tag + type: string + versionRange: + description: when the appropriate pattern is provided, image-version will be updated with the latest tag from the image repository that satisfies that pattern. + type: string + pins: + description: consists of grpc(server, client) and mq pin sections + type: object + properties: + grpc: + description: pin section for grpc + type: object + properties: + server: + description: grpc server subsection + type: array + items: + type: object + properties: + serviceClasses: + description: list of the service classes supported by the grpc server + items: + type: string + type: array + name: + description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters + type: string + pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" + maxLength: 70 + required: + - name + - serviceClasses + client: + description: grpc client subsection + type: array + items: + type: object + properties: + attributes: + description: attributes of grpc client + items: + type: string + type: array + serviceClass: + description: name of the service class used supported by the grpc client + type: string + strategy: + description: strategy to be used for grpc communication. default value is set to robin + type: string + default: robin + filters: + description: filters for grpc client + type: array + items: + type: object + properties: + properties: + description: DESCRIPTION NEEDED + type: array + items: + type: object + properties: + expectedValue: + description: DESCRIPTION NEEDED + type: string + fieldName: + description: DESCRIPTION NEEDED + type: string + operation: + description: DESCRIPTION NEEDED + enum: + - EQUAL + - NOT_EQUAL + - EMPTY + - NOT_EMPTY + - WILDCARD + type: string + required: + - fieldName + - expectedValue + - operation + linkTo: + description: DESCRIPTION NEEDED + type: array + items: + type: object + properties: + box: + description: name of the box + type: string + pin: + description: represents the pin of the box that we want to connect + type: string + required: + - box + - pin + name: + description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters + type: string + pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" + maxLength: 70 + required: + - name + - serviceClass + mq: + type: object + properties: + subscribers: + description: section for subscriber mq pins + type: array + items: + type: object + required: + - name + properties: + name: + description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters + type: string + pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" + maxLength: 70 + attributes: + description: attributes for mq + items: + type: string + type: array + filters: + description: filters for mq + type: array + items: + type: object + properties: + properties: + description: DESCRIPTION NEEDED + type: array + items: + type: object + properties: + expectedValue: + description: DESCRIPTION NEEDED + type: string + fieldName: + description: DESCRIPTION NEEDED + type: string + operation: + description: DESCRIPTION NEEDED + enum: + - EQUAL + - NOT_EQUAL + - EMPTY + - NOT_EMPTY + - WILDCARD + type: string + required: + - fieldName + - expectedValue + - operation + message: + description: DESCRIPTION NEEDED + type: array + items: + type: object + properties: + expectedValue: + description: DESCRIPTION NEEDED + type: string + fieldName: + description: DESCRIPTION NEEDED + type: string + operation: + description: DESCRIPTION NEEDED + enum: + - EQUAL + - NOT_EQUAL + - EMPTY + - NOT_EMPTY + - WILDCARD + type: string + required: + - fieldName + - expectedValue + - operation + metadata: + description: metadata for mq pin + type: array + items: + type: object + properties: + expectedValue: + description: DESCRIPTION NEEDED + type: string + fieldName: + description: DESCRIPTION NEEDED + type: string + operation: + description: DESCRIPTION NEEDED + enum: + - EQUAL + - NOT_EQUAL + - EMPTY + - NOT_EMPTY + - WILDCARD + type: string + required: + - fieldName + - expectedValue + - operation + linkTo: + description: DESCRIPTION NEEDED + type: array + items: + type: object + properties: + box: + description: name of the box + type: string + pin: + description: represents the pin of the box that we want to connect + type: string + required: + - box + - pin + settings: + description: settings for rabbitMq queue configuration + type: object + properties: + overloadStrategy: + description: configuration for rabbit mq queue. default is set to “drop-head”. + type: string + enum: + - "drop-head" + - "reject-publish" + default: "drop-head" + queueLength: + description: configuration for rabbit mq queue. default is set to 1000 msg. queueLength isn't used if storageOnDemand is set to true. + type: integer + default: 1000 + storageOnDemand: + description: configuration for rabbit mq queue. default value is set to false + type: boolean + default: false + publishers: + description: section for subscriber mq pins + type: array + items: + required: + - name + type: object + properties: + name: + description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters + type: string + pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" + maxLength: 70 + attributes: + description: attributes for mq + items: + type: string + type: array + filters: + description: filters for mq + type: array + items: + type: object + properties: + properties: + description: DESCRIPTION NEEDED + type: array + items: + type: object + properties: + expectedValue: + description: DESCRIPTION NEEDED + type: string + fieldName: + description: DESCRIPTION NEEDED + type: string + operation: + description: DESCRIPTION NEEDED + enum: + - EQUAL + - NOT_EQUAL + - EMPTY + - NOT_EMPTY + - WILDCARD + type: string + required: + - fieldName + - expectedValue + - operation + message: + description: DESCRIPTION NEEDED + type: array + items: + type: object + properties: + expectedValue: + description: DESCRIPTION NEEDED + type: string + fieldName: + description: DESCRIPTION NEEDED + type: string + operation: + description: DESCRIPTION NEEDED + enum: + - EQUAL + - NOT_EQUAL + - EMPTY + - NOT_EMPTY + - WILDCARD + type: string + required: + - fieldName + - expectedValue + - operation + metadata: + description: metadata for mq pin + type: array + items: + type: object + properties: + expectedValue: + description: DESCRIPTION NEEDED + type: string + fieldName: + description: DESCRIPTION NEEDED + type: string + operation: + description: DESCRIPTION NEEDED + enum: + - EQUAL + - NOT_EQUAL + - EMPTY + - NOT_EMPTY + - WILDCARD + type: string + required: + - fieldName + - expectedValue + - operation + prometheus: + description: custom configuration of prometheus for microservices + properties: + enabled: + description: By default this is set to true. + type: boolean + host: + description: host for prometheus + type: string + port: + description: port for prometheus + type: integer + type: object + type: + enum: + - th2-act + - th2-codec + - th2-conn + - th2-rpt-data-provider + - th2-rpt-viewer + - th2-check1 + - th2-check2-bookcheck + - th2-check2-recon + - th2-script + - th2-sim + - th2-hand + - th2-read + - th2-util + - th2-cradle-viewer + - th2-crawler + - th2-crawler-processor + - th2-data-provider + - th2-adapter + - th2-reactive-script + - th2-monkey + type: string + required: + - imageName + - imageVersion + - type + type: object + status: + description: contains status information about this resource + properties: + conditions: + description: Conditions contains observations of the resource's state + items: + properties: + lastTransitionTime: + description: LastTransitionTime is the timestamp corresponding + to the last status change of this condition. + format: date-time + type: string + lastUpdateTime: + description: LastUpdateTime is the timestamp corresponding to + the last status update of this condition. + format: date-time + type: string + message: + description: Message is a human readable description of the + details of the last transition, complementing reason. + nullable: true + type: string + reason: + description: Reason is a brief machine readable explanation + for the condition's last transition. + type: string + status: + description: Status of the condition, one of ('True', 'False', + 'Unknown'). + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: Type of the condition + enum: + - Deployed + - Enqueued + type: string + required: + - status + - type + type: object + type: array + message: + description: Message describe current state of this resource + type: string + phase: + description: ComponentStatus is the status as given by Operator for + this resource + type: string + subResourceName: + description: SubResourceName is the name of produced helmrelease tied + with this resource + type: string + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + listKind: "" + plural: "" + singular: "" + conditions: [] + storedVersions: [] \ No newline at end of file diff --git a/src/test/resources/crds/th2-core-box-crd.yaml b/src/test/resources/crds/th2-core-box-crd.yaml new file mode 100644 index 00000000..40de1f9f --- /dev/null +++ b/src/test/resources/crds/th2-core-box-crd.yaml @@ -0,0 +1,726 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: th2coreboxes.th2.exactpro.com +spec: + conversion: + strategy: None + group: th2.exactpro.com + names: + kind: Th2CoreBox + listKind: Th2CoreBoxList + plural: th2coreboxes + singular: th2corebox + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.subResourceName + name: HelmRelease + type: string + - jsonPath: .spec.type + name: Type + type: string + - jsonPath: .status.phase + name: Status + type: string + - jsonPath: .status.message + name: Message + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v2 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + description: Specification of desired box + properties: + customConfig: + description: this is custom configuration + type: object + x-kubernetes-preserve-unknown-fields: true + loggingConfig: + description: this is logging configuration + type: string + mqRouter: + description: this is custom configuration for router mq + type: object + x-kubernetes-preserve-unknown-fields: true + grpcRouter: + description: this is custom configuration for router grpc + type: object + x-kubernetes-preserve-unknown-fields: true + cradleManager: + description: this is custom configuration for cradle + type: object + x-kubernetes-preserve-unknown-fields: true + bookName: + description: Can be used for passing custom book for a specific CR + type: string + extendedSettings: + type: object + description: section for extended settings + properties: + entryPoint: + description: allows user to pass custom ENTRYPOINT into the docker container + type: object + properties: + command: + description: allows user to pass custom command for the docker container entrypoint + type: array + items: + type: string + args: + description: allows user to pass custom arguments for the docker container entrypoint + type: array + items: + type: string + replicas: + description: number of replicas + type: integer + k8sProbes: + description: if enabled liveness probes will be collected from pod + type: boolean + default: false + envVariables: + description: allows us to pass specific environment variables that are going to be set into the pods. + type: object + x-kubernetes-preserve-unknown-fields: true + sharedMemory: + description: DESCRIPTION NEEDED + type: object + properties: + enabled: + description: DESCRIPTION NEEDED + type: boolean + externalBox: + description: using this section we can configure boxes that are going to be run outside of kubernetes cluster + properties: + address: + description: address to the machine on which external box is running + type: string + enabled: + description: represents the state of the external box + type: boolean + endpoints: + description: internal mapping for ports + items: + properties: + name: + description: name for the endpoint + type: string + targetPort: + description: value for exposed port + type: integer + required: + - name + - targetPort + type: object + type: array + type: object + hostAliases: + description: HostAliases is an optional list of hosts and IPs that will be injected into the pod's hosts file if specified. This is only valid for non-hostNetwork pods. + type: array + items: + properties: + ip: + description: IP address of the host file entry. + type: string + hostnames: + description: Hostnames for the above IP address. + items: + type: string + type: array + required: + - ip + - hostnames + type: object + hostNetwork: + description: if the flag is set to true, pod will run on node network and kubernetes will decide which node will be used for running the box. + type: boolean + nodeSelector: + additionalProperties: + type: string + description: | + NodeSelector is a selector which must be true for the pod to fit on a node. + Selector which must match a node's labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector + type: object + mounting: + description: allows to configure persistent volume mounting for pods + items: + properties: + path: + description: a directory in pod where you mount external folder + type: string + pvcName: + description: name for the pvc + type: string + type: object + type: array + resources: + description: here we can specify resource limitations and allowances for this specific component + properties: + limits: + properties: + cpu: + type: string + memory: + type: string + type: object + requests: + properties: + cpu: + type: string + memory: + type: string + type: object + type: object + service: + type: object + properties: + enabled: + description: if enabled allows pod to be exposed using ClusterIP + type: boolean + clusterIP: + description: endpoints for cluster IP service type + type: array + items: + type: object + properties: + name: + description: name of the endpoint + type: string + containerPort: + description: allows to specify Kubernetes port for the pod. + type: integer + required: + - name + nodePort: + description: endpoints for nodePort service type + type: array + items: + type: object + properties: + name: + description: name of the endpoint + type: string + exposedPort: + description: a port on which the service will be accessible. + type: integer + containerPort: + description: allows to specify Kubernetes port for the pod. + type: integer + required: + - name + - exposedPort + loadBalancer: + description: endpoints for loadBalancer service type + type: array + items: + type: object + properties: + name: + description: name of the endpoint + type: string + containerPort: + description: allows to specify Kubernetes port for the pod. + type: integer + required: + - name + ingress: + description: Creates Ingress for the Box + type: object + properties: + urlPaths: + description: | + Path is a suffix, it is appended to namespace url, e.g for th2-test namespace: + + urlPaths: + - /my-box/ + + service will be available at /th2-test/my-box/ + type: array + items: + type: string + pattern: "^[-a-zA-Z0-9_/]+$" + disabled: + description: if set to true resource will act as if it is removed from schema without actually deleting the file. The default value is false. + type: boolean + imageName: + description: docker image repository URL + type: string + imageVersion: + description: docker image tag + type: string + versionRange: + description: when the appropriate pattern is provided, image-version will be updated with the latest tag from the image repository that satisfies that pattern. + type: string + params: + description: List of generic parameters + items: + properties: + name: + description: name of parameter + type: string + value: + description: value of parameter + type: string + required: + - name + - value + type: object + type: array + pins: + description: consists of grpc(server, client) and mq pin sections + type: object + properties: + grpc: + description: pin section for grpc + type: object + properties: + server: + description: grpc server subsection + type: array + items: + type: object + properties: + serviceClasses: + description: list of the service classes supported by the grpc server + items: + type: string + type: array + name: + description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters + type: string + pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" + maxLength: 70 + required: + - name + - serviceClasses + client: + description: grpc client subsection + type: array + items: + type: object + properties: + attributes: + description: attributes of grpc client + items: + type: string + type: array + serviceClass: + description: name of the service class used supported by the grpc client + type: string + strategy: + description: strategy to be used for grpc communication. default value is set to robin + type: string + default: robin + filters: + description: filters for grpc client + type: array + items: + type: object + properties: + properties: + description: DESCRIPTION NEEDED + type: array + items: + type: object + properties: + expectedValue: + description: DESCRIPTION NEEDED + type: string + fieldName: + description: DESCRIPTION NEEDED + type: string + operation: + description: DESCRIPTION NEEDED + enum: + - EQUAL + - NOT_EQUAL + - EMPTY + - NOT_EMPTY + - WILDCARD + type: string + required: + - fieldName + - expectedValue + - operation + linkTo: + description: DESCRIPTION NEEDED + type: array + items: + type: object + properties: + box: + description: name of the box + type: string + pin: + description: represents the pin of the box that we want to connect + type: string + required: + - box + - pin + name: + description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters + type: string + pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" + maxLength: 70 + required: + - name + - serviceClass + mq: + type: object + properties: + subscribers: + description: section for subscriber mq pins + type: array + items: + type: object + required: + - name + properties: + name: + description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters + type: string + pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" + maxLength: 70 + attributes: + description: attributes for mq + items: + type: string + type: array + filters: + description: filters for mq + type: array + items: + type: object + properties: + properties: + description: DESCRIPTION NEEDED + type: array + items: + type: object + properties: + expectedValue: + description: DESCRIPTION NEEDED + type: string + fieldName: + description: DESCRIPTION NEEDED + type: string + operation: + description: DESCRIPTION NEEDED + enum: + - EQUAL + - NOT_EQUAL + - EMPTY + - NOT_EMPTY + - WILDCARD + type: string + required: + - fieldName + - expectedValue + - operation + message: + description: DESCRIPTION NEEDED + type: array + items: + type: object + properties: + expectedValue: + description: DESCRIPTION NEEDED + type: string + fieldName: + description: DESCRIPTION NEEDED + type: string + operation: + description: DESCRIPTION NEEDED + enum: + - EQUAL + - NOT_EQUAL + - EMPTY + - NOT_EMPTY + - WILDCARD + type: string + required: + - fieldName + - expectedValue + - operation + metadata: + description: metadata for mq pin + type: array + items: + type: object + properties: + expectedValue: + description: DESCRIPTION NEEDED + type: string + fieldName: + description: DESCRIPTION NEEDED + type: string + operation: + description: DESCRIPTION NEEDED + enum: + - EQUAL + - NOT_EQUAL + - EMPTY + - NOT_EMPTY + - WILDCARD + type: string + required: + - fieldName + - expectedValue + - operation + linkTo: + description: DESCRIPTION NEEDED + type: array + items: + type: object + properties: + box: + description: name of the box + type: string + pin: + description: represents the pin of the box that we want to connect + type: string + required: + - box + - pin + settings: + description: settings for rabbitMq queue configuration + type: object + properties: + overloadStrategy: + description: configuration for rabbit mq queue. default is set to “drop-head”. + type: string + enum: + - "drop-head" + - "reject-publish" + default: "drop-head" + queueLength: + description: configuration for rabbit mq queue. default is set to 1000 msg. queueLength isn't used if storageOnDemand is set to true. + type: integer + default: 1000 + storageOnDemand: + description: configuration for rabbit mq queue. default value is set to false + type: boolean + default: false + publishers: + description: section for subscriber mq pins + type: array + items: + required: + - name + type: object + properties: + name: + description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters + type: string + pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" + maxLength: 70 + attributes: + description: attributes for mq + items: + type: string + type: array + filters: + description: filters for mq + type: array + items: + type: object + properties: + properties: + description: DESCRIPTION NEEDED + type: array + items: + type: object + properties: + expectedValue: + description: DESCRIPTION NEEDED + type: string + fieldName: + description: DESCRIPTION NEEDED + type: string + operation: + description: DESCRIPTION NEEDED + enum: + - EQUAL + - NOT_EQUAL + - EMPTY + - NOT_EMPTY + - WILDCARD + type: string + required: + - fieldName + - expectedValue + - operation + message: + description: DESCRIPTION NEEDED + type: array + items: + type: object + properties: + expectedValue: + description: DESCRIPTION NEEDED + type: string + fieldName: + description: DESCRIPTION NEEDED + type: string + operation: + description: DESCRIPTION NEEDED + enum: + - EQUAL + - NOT_EQUAL + - EMPTY + - NOT_EMPTY + - WILDCARD + type: string + required: + - fieldName + - expectedValue + - operation + metadata: + description: metadata for mq pin + type: array + items: + type: object + properties: + expectedValue: + description: DESCRIPTION NEEDED + type: string + fieldName: + description: DESCRIPTION NEEDED + type: string + operation: + description: DESCRIPTION NEEDED + enum: + - EQUAL + - NOT_EQUAL + - EMPTY + - NOT_EMPTY + - WILDCARD + type: string + required: + - fieldName + - expectedValue + - operation + prometheus: + description: custom configuration of prometheus for microservices + properties: + enabled: + description: By default this is set to true. + type: boolean + host: + description: host for prometheus + type: string + port: + description: port for prometheus + type: integer + type: object + type: + enum: + - th2-act + - th2-codec + - th2-conn + - th2-rpt-data-provider + - th2-rpt-viewer + - th2-check1 + - th2-check2-bookcheck + - th2-check2-recon + - th2-script + - th2-sim + - th2-hand + - th2-read + - th2-util + - th2-cradle-viewer + - th2-crawler + - th2-crawler-processor + - th2-data-provider + - th2-adapter + - th2-reactive-script + - th2-monkey + type: string + required: + - imageName + - imageVersion + - type + type: object + status: + description: contains status information about this resource + properties: + conditions: + description: Conditions contains observations of the resource's state + items: + properties: + lastTransitionTime: + description: LastTransitionTime is the timestamp corresponding + to the last status change of this condition. + format: date-time + type: string + lastUpdateTime: + description: LastUpdateTime is the timestamp corresponding to + the last status update of this condition. + format: date-time + type: string + message: + description: Message is a human readable description of the + details of the last transition, complementing reason. + nullable: true + type: string + reason: + description: Reason is a brief machine readable explanation + for the condition's last transition. + type: string + status: + description: Status of the condition, one of ('True', 'False', + 'Unknown'). + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: Type of the condition + enum: + - Deployed + - Enqueued + type: string + required: + - status + - type + type: object + type: array + message: + description: Message describe current state of this resource + type: string + phase: + description: ComponentStatus is the status as given by Operator for + this resource + type: string + subResourceName: + description: SubResourceName is the name of produced helmrelease tied + with this resource + type: string + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + listKind: "" + plural: "" + singular: "" + conditions: [] + storedVersions: [] \ No newline at end of file diff --git a/src/test/resources/crds/th2-dictionary-crd.yaml b/src/test/resources/crds/th2-dictionary-crd.yaml new file mode 100644 index 00000000..7207685c --- /dev/null +++ b/src/test/resources/crds/th2-dictionary-crd.yaml @@ -0,0 +1,54 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + creationTimestamp: null + name: th2dictionaries.th2.exactpro.com +spec: + conversion: + strategy: None + group: th2.exactpro.com + names: + kind: Th2Dictionary + listKind: Th2DictionaryList + plural: th2dictionaries + singular: th2dictionary + scope: Namespaced + versions: + - name: v2 + schema: + openAPIV3Schema: + description: Th2Dictionary defines th2 dictionary instance + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + description: Specification of desired dictionary + properties: + compressed: + description: Indicates whether dictionary's data is already compressed. If set to true, no further compression will take place in infra. default is set to false + type: boolean + data: + description: data for the dictionary + type: string + required: + - data + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + listKind: "" + plural: "" + singular: "" + conditions: [] + storedVersions: [] \ No newline at end of file diff --git a/src/test/resources/crds/th2-estore-crd.yaml b/src/test/resources/crds/th2-estore-crd.yaml new file mode 100644 index 00000000..af92c9db --- /dev/null +++ b/src/test/resources/crds/th2-estore-crd.yaml @@ -0,0 +1,698 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: th2estores.th2.exactpro.com +spec: + conversion: + strategy: None + group: th2.exactpro.com + names: + kind: Th2Estore + listKind: Th2EstoreList + plural: th2estores + singular: th2estore + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.subResourceName + name: HelmRelease + type: string + - jsonPath: .status.phase + name: Status + type: string + - jsonPath: .status.message + name: Message + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v2 + schema: + openAPIV3Schema: + description: Th2Estore defines th2 event store instance + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + description: Specification of desired event store + properties: + customConfig: + description: this is custom configuration + type: object + x-kubernetes-preserve-unknown-fields: true + loggingConfig: + description: this is logging configuration + type: string + mqRouter: + description: this is custom configuration for router mq + type: object + x-kubernetes-preserve-unknown-fields: true + grpcRouter: + description: this is custom configuration for router grpc + type: object + x-kubernetes-preserve-unknown-fields: true + cradleManager: + description: this is custom configuration for cradle + type: object + x-kubernetes-preserve-unknown-fields: true + bookName: + description: Can be used for passing custom book for a specific CR + type: string + extendedSettings: + type: object + description: section for extended settings + properties: + entryPoint: + description: allows user to pass custom ENTRYPOINT into the docker container + type: object + properties: + command: + description: allows user to pass custom command for the docker container entrypoint + type: array + items: + type: string + args: + description: allows user to pass custom arguments for the docker container entrypoint + type: array + items: + type: string + replicas: + description: number of replicas + type: integer + k8sProbes: + description: if enabled liveness probes will be collected from pod + type: boolean + default: false + envVariables: + description: allows us to pass specific environment variables that are going to be set into the pods. + type: object + x-kubernetes-preserve-unknown-fields: true + sharedMemory: + description: DESCRIPTION NEEDED + type: object + properties: + enabled: + description: DESCRIPTION NEEDED + type: boolean + externalBox: + description: using this section we can configure boxes that are going to be run outside of kubernetes cluster + properties: + address: + description: address to the machine on which external box is running + type: string + enabled: + description: represents the state of the external box + type: boolean + endpoints: + description: internal mapping for ports + items: + properties: + name: + description: name for the endpoint + type: string + targetPort: + description: value for exposed port + type: integer + required: + - name + - targetPort + type: object + type: array + type: object + hostAliases: + description: HostAliases is an optional list of hosts and IPs that will be injected into the pod's hosts file if specified. This is only valid for non-hostNetwork pods. + type: array + items: + properties: + ip: + description: IP address of the host file entry. + type: string + hostnames: + description: Hostnames for the above IP address. + items: + type: string + type: array + required: + - ip + - hostnames + type: object + hostNetwork: + description: if the flag is set to true, pod will run on node network and kubernetes will decide which node will be used for running the box. + type: boolean + nodeSelector: + additionalProperties: + type: string + description: | + NodeSelector is a selector which must be true for the pod to fit on a node. + Selector which must match a node's labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector + type: object + mounting: + description: allows to configure persistent volume mounting for pods + items: + properties: + path: + description: a directory in pod where you mount external folder + type: string + pvcName: + description: name for the pvc + type: string + type: object + type: array + resources: + description: here we can specify resource limitations and allowances for this specific component + properties: + limits: + properties: + cpu: + type: string + memory: + type: string + type: object + requests: + properties: + cpu: + type: string + memory: + type: string + type: object + type: object + service: + type: object + properties: + enabled: + description: if enabled allows pod to be exposed using ClusterIP + type: boolean + clusterIP: + description: endpoints for cluster IP service type + type: array + items: + type: object + properties: + name: + description: name of the endpoint + type: string + containerPort: + description: allows to specify Kubernetes port for the pod. + type: integer + required: + - name + nodePort: + description: endpoints for nodePort service type + type: array + items: + type: object + properties: + name: + description: name of the endpoint + type: string + exposedPort: + description: a port on which the service will be accessible. + type: integer + containerPort: + description: allows to specify Kubernetes port for the pod. + type: integer + required: + - name + - exposedPort + loadBalancer: + description: endpoints for loadBalancer service type + type: array + items: + type: object + properties: + name: + description: name of the endpoint + type: string + containerPort: + description: allows to specify Kubernetes port for the pod. + type: integer + required: + - name + ingress: + description: section for ingress + type: object + properties: + urlPaths: + description: url paths + type: array + items: + type: string + pattern: "^[-a-zA-Z0-9_/]+$" + disabled: + description: if set to true resource will act as if it is removed from schema without actually deleting the file. The default value is false. + type: boolean + imageName: + description: docker image repository URL + type: string + imageVersion: + description: docker image tag + type: string + versionRange: + description: when the appropriate pattern is provided, image-version will be updated with the latest tag from the image repository that satisfies that pattern. + type: string + params: + description: List of generic parameters + items: + properties: + name: + description: name of parameter + type: string + value: + description: value of parameter + type: string + required: + - name + - value + type: object + type: array + pins: + description: consists of grpc(server, client) and mq pin sections + type: object + properties: + grpc: + description: pin section for grpc + type: object + properties: + server: + description: grpc server subsection + type: array + items: + type: object + properties: + serviceClasses: + description: list of the service classes supported by the grpc server + items: + type: string + type: array + name: + description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters + type: string + pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" + maxLength: 70 + required: + - name + - serviceClasses + client: + description: grpc client subsection + type: array + items: + type: object + properties: + attributes: + description: attributes of grpc client + items: + type: string + type: array + serviceClass: + description: name of the service class used supported by the grpc client + type: string + strategy: + description: strategy to be used for grpc communication. default value is set to robin + type: string + default: robin + filters: + description: filters for grpc client + type: array + items: + type: object + properties: + properties: + description: DESCRIPTION NEEDED + type: array + items: + type: object + properties: + expectedValue: + description: DESCRIPTION NEEDED + type: string + fieldName: + description: DESCRIPTION NEEDED + type: string + operation: + description: DESCRIPTION NEEDED + enum: + - EQUAL + - NOT_EQUAL + - EMPTY + - NOT_EMPTY + - WILDCARD + type: string + required: + - fieldName + - expectedValue + - operation + linkTo: + description: DESCRIPTION NEEDED + type: array + items: + type: object + properties: + box: + description: name of the box + type: string + pin: + description: represents the pin of the box that we want to connect + type: string + required: + - box + - pin + name: + description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters + type: string + pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" + maxLength: 70 + required: + - name + - serviceClass + mq: + type: object + properties: + subscribers: + description: section for subscriber mq pins + type: array + items: + type: object + required: + - name + properties: + name: + description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters + type: string + pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" + maxLength: 70 + attributes: + description: attributes for mq + items: + type: string + type: array + filters: + description: filters for mq + type: array + items: + type: object + properties: + properties: + description: DESCRIPTION NEEDED + type: array + items: + type: object + properties: + expectedValue: + description: DESCRIPTION NEEDED + type: string + fieldName: + description: DESCRIPTION NEEDED + type: string + operation: + description: DESCRIPTION NEEDED + enum: + - EQUAL + - NOT_EQUAL + - EMPTY + - NOT_EMPTY + - WILDCARD + type: string + required: + - fieldName + - expectedValue + - operation + message: + description: DESCRIPTION NEEDED + type: array + items: + type: object + properties: + expectedValue: + description: DESCRIPTION NEEDED + type: string + fieldName: + description: DESCRIPTION NEEDED + type: string + operation: + description: DESCRIPTION NEEDED + enum: + - EQUAL + - NOT_EQUAL + - EMPTY + - NOT_EMPTY + - WILDCARD + type: string + required: + - fieldName + - expectedValue + - operation + metadata: + description: metadata for mq pin + type: array + items: + type: object + properties: + expectedValue: + description: DESCRIPTION NEEDED + type: string + fieldName: + description: DESCRIPTION NEEDED + type: string + operation: + description: DESCRIPTION NEEDED + enum: + - EQUAL + - NOT_EQUAL + - EMPTY + - NOT_EMPTY + - WILDCARD + type: string + required: + - fieldName + - expectedValue + - operation + linkTo: + description: DESCRIPTION NEEDED + type: array + items: + type: object + properties: + box: + description: name of the box + type: string + pin: + description: represents the pin of the box that we want to connect + type: string + required: + - box + - pin + settings: + description: settings for rabbitMq queue configuration + type: object + properties: + overloadStrategy: + description: configuration for rabbit mq queue. default is set to “drop-head”. + type: string + enum: + - "drop-head" + - "reject-publish" + default: "drop-head" + queueLength: + description: configuration for rabbit mq queue. default is set to 1000 msg. queueLength isn't used if storageOnDemand is set to true. + type: integer + default: 1000 + storageOnDemand: + description: configuration for rabbit mq queue. default value is set to false + type: boolean + default: false + publishers: + description: section for subscriber mq pins + type: array + items: + required: + - name + type: object + properties: + name: + description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters + type: string + pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" + maxLength: 70 + attributes: + description: attributes for mq + items: + type: string + type: array + filters: + description: filters for mq + type: array + items: + type: object + properties: + properties: + description: DESCRIPTION NEEDED + type: array + items: + type: object + properties: + expectedValue: + description: DESCRIPTION NEEDED + type: string + fieldName: + description: DESCRIPTION NEEDED + type: string + operation: + description: DESCRIPTION NEEDED + enum: + - EQUAL + - NOT_EQUAL + - EMPTY + - NOT_EMPTY + - WILDCARD + type: string + required: + - fieldName + - expectedValue + - operation + message: + description: DESCRIPTION NEEDED + type: array + items: + type: object + properties: + expectedValue: + description: DESCRIPTION NEEDED + type: string + fieldName: + description: DESCRIPTION NEEDED + type: string + operation: + description: DESCRIPTION NEEDED + enum: + - EQUAL + - NOT_EQUAL + - EMPTY + - NOT_EMPTY + - WILDCARD + type: string + required: + - fieldName + - expectedValue + - operation + metadata: + description: metadata for mq pin + type: array + items: + type: object + properties: + expectedValue: + description: DESCRIPTION NEEDED + type: string + fieldName: + description: DESCRIPTION NEEDED + type: string + operation: + description: DESCRIPTION NEEDED + enum: + - EQUAL + - NOT_EQUAL + - EMPTY + - NOT_EMPTY + - WILDCARD + type: string + required: + - fieldName + - expectedValue + - operation + prometheus: + description: custom configuration of prometheus for microservices + properties: + enabled: + description: By default this is set to true. + type: boolean + host: + description: host for prometheus + type: string + port: + description: port for prometheus + type: integer + type: object + type: + enum: + - th2-estore + type: string + required: + - imageName + - imageVersion + type: object + status: + description: contains status information about this resource + properties: + conditions: + description: Conditions contains observations of the resource's state + items: + properties: + lastTransitionTime: + description: LastTransitionTime is the timestamp corresponding + to the last status change of this condition. + format: date-time + type: string + lastUpdateTime: + description: LastUpdateTime is the timestamp corresponding to + the last status update of this condition. + format: date-time + type: string + message: + description: Message is a human readable description of the + details of the last transition, complementing reason. + nullable: true + type: string + reason: + description: Reason is a brief machine readable explanation + for the condition's last transition. + type: string + status: + description: Status of the condition, one of ('True', 'False', + 'Unknown'). + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: Type of the condition + enum: + - Deployed + - Enqueued + type: string + required: + - status + - type + type: object + type: array + message: + description: Message describe current state of this resource + type: string + phase: + description: ComponentStatus is the status as given by Operator for + this resource + type: string + subResourceName: + description: SubResourceName is the name of produced helmrelease tied + with this resource + type: string + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + listKind: "" + plural: "" + singular: "" + conditions: [] + storedVersions: [] \ No newline at end of file diff --git a/src/test/resources/crds/th2-job-crd.yaml b/src/test/resources/crds/th2-job-crd.yaml new file mode 100644 index 00000000..a5874bec --- /dev/null +++ b/src/test/resources/crds/th2-job-crd.yaml @@ -0,0 +1,699 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: th2jobs.th2.exactpro.com +spec: + conversion: + strategy: None + group: th2.exactpro.com + names: + kind: Th2Job + listKind: Th2JobList + plural: th2jobs + singular: th2job + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.subResourceName + name: HelmRelease + type: string + - jsonPath: .spec.type + name: Type + type: string + - jsonPath: .status.phase + name: Status + type: string + - jsonPath: .status.message + name: Message + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v2 + schema: + openAPIV3Schema: + description: Th2Jox defines a resource for single execution tasks + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + description: Specification of desired box + properties: + customConfig: + description: this is custom configuration + type: object + x-kubernetes-preserve-unknown-fields: true + loggingConfig: + description: this is logging configuration + type: string + mqRouter: + description: this is custom configuration for router mq + type: object + x-kubernetes-preserve-unknown-fields: true + grpcRouter: + description: this is custom configuration for router grpc + type: object + x-kubernetes-preserve-unknown-fields: true + cradleManager: + description: this is custom configuration for cradle + type: object + x-kubernetes-preserve-unknown-fields: true + bookName: + description: Can be used for passing custom book for a specific CR + type: string + extendedSettings: + type: object + description: section for extended settings + properties: + entryPoint: + description: allows user to pass custom ENTRYPOINT into the docker container + type: object + properties: + command: + description: allows user to pass custom command for the docker container entrypoint + type: array + items: + type: string + args: + description: allows user to pass custom arguments for the docker container entrypoint + type: array + items: + type: string + parallelism: + type: integer + description: Specifies the maximum desired number of pods the job should run at any given time. The actual number of pods running in steady state will be less than this number when ((.spec.completions - .status.successful) < .spec.parallelism), i.e. when the work left to do is less than max parallelism + completions: + type: integer + description: Specifies the desired number of successfully finished pods the job should be run with. Setting to nil means that the success of any pod signals the success of all pods, and allows parallelism to have any positive value. Setting to 1 means that parallelism is limited to 1 and the success of that pod signals the success of the job + envVariables: + description: allows us to pass specific environment variables that are going to be set into the pods. + type: object + x-kubernetes-preserve-unknown-fields: true + sharedMemory: + description: DESCRIPTION NEEDED + type: object + properties: + enabled: + description: DESCRIPTION NEEDED + type: boolean + replicas: + description: number of replicas + type: integer + k8sProbes: + description: if enabled liveness probes will be collected from pod + type: boolean + default: false + externalBox: + description: using this section we can configure boxes that are going to be run outside of kubernetes cluster + properties: + address: + description: address to the machine on which external box is running + type: string + enabled: + description: represents the state of the external box + type: boolean + endpoints: + description: internal mapping for ports + items: + properties: + name: + description: name for the endpoint + type: string + targetPort: + description: value for exposed port + type: integer + required: + - name + - targetPort + type: object + type: array + type: object + hostAliases: + description: HostAliases is an optional list of hosts and IPs that will be injected into the pod's hosts file if specified. This is only valid for non-hostNetwork pods. + type: array + items: + properties: + ip: + description: IP address of the host file entry. + type: string + hostnames: + description: Hostnames for the above IP address. + items: + type: string + type: array + required: + - ip + - hostnames + type: object + hostNetwork: + description: if the flag is set to true, pod will run on node network and kubernetes will decide which node will be used for running the box. + type: boolean + nodeSelector: + additionalProperties: + type: string + description: | + NodeSelector is a selector which must be true for the pod to fit on a node. + Selector which must match a node's labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector + type: object + mounting: + description: allows to configure persistent volume mounting for pods + items: + properties: + path: + description: a directory in pod where you mount external folder + type: string + pvcName: + description: name for the pvc + type: string + type: object + type: array + resources: + description: here we can specify resource limitations and allowances for this specific component + properties: + limits: + properties: + cpu: + type: string + memory: + type: string + type: object + requests: + properties: + cpu: + type: string + memory: + type: string + type: object + type: object + service: + type: object + properties: + enabled: + description: if enabled allows pod to be exposed using ClusterIP + type: boolean + clusterIP: + description: endpoints for cluster IP service type + type: array + items: + type: object + properties: + name: + description: name of the endpoint + type: string + containerPort: + description: allows to specify Kubernetes port for the pod. + type: integer + required: + - name + nodePort: + description: endpoints for nodePort service type + type: array + items: + type: object + properties: + name: + description: name of the endpoint + type: string + exposedPort: + description: a port on which the service will be accessible. + type: integer + containerPort: + description: allows to specify Kubernetes port for the pod. + type: integer + required: + - name + - exposedPort + loadBalancer: + description: endpoints for loadBalancer service type + type: array + items: + type: object + properties: + name: + description: name of the endpoint + type: string + containerPort: + description: allows to specify Kubernetes port for the pod. + type: integer + required: + - name + ingress: + description: Creates Ingress for the Box + type: object + properties: + urlPaths: + description: | + Path is a suffix, it is appended to namespace url, e.g for th2-test namespace: + + urlPaths: + - /my-box/ + + service will be available at /th2-test/my-box/ + type: array + items: + type: string + pattern: "^[-a-zA-Z0-9_/]+$" + disabled: + description: if set to true resource will act as if it is removed from schema without actually deleting the file. The default value is false. + type: boolean + imageName: + description: docker image repository URL + type: string + imageVersion: + description: docker image tag + type: string + versionRange: + description: when the appropriate pattern is provided, image-version will be updated with the latest tag from the image repository that satisfies that pattern. + type: string + pins: + description: consists of grpc(server, client) and mq pin sections + type: object + properties: + grpc: + description: pin section for grpc + type: object + properties: + server: + description: grpc server subsection + type: array + items: + type: object + properties: + serviceClasses: + description: list of the service classes supported by the grpc server + items: + type: string + type: array + name: + description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters + type: string + pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" + maxLength: 70 + required: + - name + - serviceClasses + client: + description: grpc client subsection + type: array + items: + type: object + properties: + attributes: + description: attributes of grpc client + items: + type: string + type: array + serviceClass: + description: name of the service class used supported by the grpc client + type: string + strategy: + description: strategy to be used for grpc communication. default value is set to robin + type: string + default: robin + filters: + description: filters for grpc client + type: array + items: + type: object + properties: + properties: + description: DESCRIPTION NEEDED + type: array + items: + type: object + properties: + expectedValue: + description: DESCRIPTION NEEDED + type: string + fieldName: + description: DESCRIPTION NEEDED + type: string + operation: + description: DESCRIPTION NEEDED + enum: + - EQUAL + - NOT_EQUAL + - EMPTY + - NOT_EMPTY + - WILDCARD + type: string + required: + - fieldName + - expectedValue + - operation + linkTo: + description: DESCRIPTION NEEDED + type: array + items: + type: object + properties: + box: + description: name of the box + type: string + pin: + description: represents the pin of the box that we want to connect + type: string + required: + - box + - pin + name: + description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters + type: string + pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" + maxLength: 70 + required: + - name + - serviceClass + mq: + type: object + properties: + subscribers: + description: section for subscriber mq pins + type: array + items: + type: object + required: + - name + properties: + name: + description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters + type: string + pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" + maxLength: 70 + attributes: + description: attributes for mq + items: + type: string + type: array + filters: + description: filters for mq + type: array + items: + type: object + properties: + properties: + description: DESCRIPTION NEEDED + type: array + items: + type: object + properties: + expectedValue: + description: DESCRIPTION NEEDED + type: string + fieldName: + description: DESCRIPTION NEEDED + type: string + operation: + description: DESCRIPTION NEEDED + enum: + - EQUAL + - NOT_EQUAL + - EMPTY + - NOT_EMPTY + - WILDCARD + type: string + required: + - fieldName + - expectedValue + - operation + message: + description: DESCRIPTION NEEDED + type: array + items: + type: object + properties: + expectedValue: + description: DESCRIPTION NEEDED + type: string + fieldName: + description: DESCRIPTION NEEDED + type: string + operation: + description: DESCRIPTION NEEDED + enum: + - EQUAL + - NOT_EQUAL + - EMPTY + - NOT_EMPTY + - WILDCARD + type: string + required: + - fieldName + - expectedValue + - operation + metadata: + description: metadata for mq pin + type: array + items: + type: object + properties: + expectedValue: + description: DESCRIPTION NEEDED + type: string + fieldName: + description: DESCRIPTION NEEDED + type: string + operation: + description: DESCRIPTION NEEDED + enum: + - EQUAL + - NOT_EQUAL + - EMPTY + - NOT_EMPTY + - WILDCARD + type: string + required: + - fieldName + - expectedValue + - operation + linkTo: + description: DESCRIPTION NEEDED + type: array + items: + type: object + properties: + box: + description: name of the box + type: string + pin: + description: represents the pin of the box that we want to connect + type: string + required: + - box + - pin + settings: + description: settings for rabbitMq queue configuration + type: object + properties: + overloadStrategy: + description: configuration for rabbit mq queue. default is set to “drop-head”. + type: string + enum: + - "drop-head" + - "reject-publish" + default: "drop-head" + queueLength: + description: configuration for rabbit mq queue. default is set to 1000 msg. queueLength isn't used if storageOnDemand is set to true. + type: integer + default: 1000 + storageOnDemand: + description: configuration for rabbit mq queue. default value is set to false + type: boolean + default: false + publishers: + description: section for subscriber mq pins + type: array + items: + required: + - name + type: object + properties: + name: + description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters + type: string + pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" + maxLength: 70 + attributes: + description: attributes for mq + items: + type: string + type: array + filters: + description: filters for mq + type: array + items: + type: object + properties: + properties: + description: DESCRIPTION NEEDED + type: array + items: + type: object + properties: + expectedValue: + description: DESCRIPTION NEEDED + type: string + fieldName: + description: DESCRIPTION NEEDED + type: string + operation: + description: DESCRIPTION NEEDED + enum: + - EQUAL + - NOT_EQUAL + - EMPTY + - NOT_EMPTY + - WILDCARD + type: string + required: + - fieldName + - expectedValue + - operation + message: + description: DESCRIPTION NEEDED + type: array + items: + type: object + properties: + expectedValue: + description: DESCRIPTION NEEDED + type: string + fieldName: + description: DESCRIPTION NEEDED + type: string + operation: + description: DESCRIPTION NEEDED + enum: + - EQUAL + - NOT_EQUAL + - EMPTY + - NOT_EMPTY + - WILDCARD + type: string + required: + - fieldName + - expectedValue + - operation + metadata: + description: metadata for mq pin + type: array + items: + type: object + properties: + expectedValue: + description: DESCRIPTION NEEDED + type: string + fieldName: + description: DESCRIPTION NEEDED + type: string + operation: + description: DESCRIPTION NEEDED + enum: + - EQUAL + - NOT_EQUAL + - EMPTY + - NOT_EMPTY + - WILDCARD + type: string + required: + - fieldName + - expectedValue + - operation + prometheus: + description: custom configuration of prometheus for microservices + properties: + enabled: + description: By default this is set to true. + type: boolean + host: + description: host for prometheus + type: string + port: + description: port for prometheus + type: integer + type: object + type: + enum: + - th2-job + type: string + required: + - imageName + - imageVersion + - type + type: object + status: + description: contains status information about this resource + properties: + conditions: + description: Conditions contains observations of the resource's state + items: + properties: + lastTransitionTime: + description: LastTransitionTime is the timestamp corresponding + to the last status change of this condition. + format: date-time + type: string + lastUpdateTime: + description: LastUpdateTime is the timestamp corresponding to + the last status update of this condition. + format: date-time + type: string + message: + description: Message is a human readable description of the + details of the last transition, complementing reason. + nullable: true + type: string + reason: + description: Reason is a brief machine readable explanation + for the condition's last transition. + type: string + status: + description: Status of the condition, one of ('True', 'False', + 'Unknown'). + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: Type of the condition + enum: + - Deployed + - Enqueued + type: string + required: + - status + - type + type: object + type: array + message: + description: Message describe current state of this resource + type: string + phase: + description: ComponentStatus is the status as given by Operator for + this resource + type: string + subResourceName: + description: SubResourceName is the name of produced helmrelease tied + with this resource + type: string + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + listKind: "" + plural: "" + singular: "" + conditions: [] + storedVersions: [] \ No newline at end of file diff --git a/src/test/resources/crds/th2-mstore-crd.yaml b/src/test/resources/crds/th2-mstore-crd.yaml new file mode 100644 index 00000000..f3805c06 --- /dev/null +++ b/src/test/resources/crds/th2-mstore-crd.yaml @@ -0,0 +1,698 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: th2mstores.th2.exactpro.com +spec: + conversion: + strategy: None + group: th2.exactpro.com + names: + kind: Th2Mstore + listKind: Th2MstoreList + plural: th2mstores + singular: th2mstore + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.subResourceName + name: HelmRelease + type: string + - jsonPath: .status.phase + name: Status + type: string + - jsonPath: .status.message + name: Message + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v2 + schema: + openAPIV3Schema: + description: Th2Mstore defines th2 message store instance + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + description: Specification of desired message store + properties: + customConfig: + description: this is custom configuration + type: object + x-kubernetes-preserve-unknown-fields: true + loggingConfig: + description: this is logging configuration + type: string + mqRouter: + description: this is custom configuration for router mq + type: object + x-kubernetes-preserve-unknown-fields: true + grpcRouter: + description: this is custom configuration for router grpc + type: object + x-kubernetes-preserve-unknown-fields: true + cradleManager: + description: this is custom configuration for cradle + type: object + x-kubernetes-preserve-unknown-fields: true + bookName: + description: Can be used for passing custom book for a specific CR + type: string + extendedSettings: + type: object + description: section for extended settings + properties: + entryPoint: + description: allows user to pass custom ENTRYPOINT into the docker container + type: object + properties: + command: + description: allows user to pass custom command for the docker container entrypoint + type: array + items: + type: string + args: + description: allows user to pass custom arguments for the docker container entrypoint + type: array + items: + type: string + replicas: + description: number of replicas + type: integer + k8sProbes: + description: if enabled liveness probes will be collected from pod + type: boolean + default: false + envVariables: + description: allows us to pass specific environment variables that are going to be set into the pods. + type: object + x-kubernetes-preserve-unknown-fields: true + sharedMemory: + description: DESCRIPTION NEEDED + type: object + properties: + enabled: + description: DESCRIPTION NEEDED + type: boolean + externalBox: + description: using this section we can configure boxes that are going to be run outside of kubernetes cluster + properties: + address: + description: address to the machine on which external box is running + type: string + enabled: + description: represents the state of the external box + type: boolean + endpoints: + description: internal mapping for ports + items: + properties: + name: + description: name for the endpoint + type: string + targetPort: + description: value for exposed port + type: integer + required: + - name + - targetPort + type: object + type: array + type: object + hostAliases: + description: HostAliases is an optional list of hosts and IPs that will be injected into the pod's hosts file if specified. This is only valid for non-hostNetwork pods. + type: array + items: + properties: + ip: + description: IP address of the host file entry. + type: string + hostnames: + description: Hostnames for the above IP address. + items: + type: string + type: array + required: + - ip + - hostnames + type: object + hostNetwork: + description: if the flag is set to true, pod will run on node network and kubernetes will decide which node will be used for running the box. + type: boolean + nodeSelector: + additionalProperties: + type: string + description: | + NodeSelector is a selector which must be true for the pod to fit on a node. + Selector which must match a node's labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector + type: object + mounting: + description: allows to configure persistent volume mounting for pods + items: + properties: + path: + description: a directory in pod where you mount external folder + type: string + pvcName: + description: name for the pvc + type: string + type: object + type: array + resources: + description: here we can specify resource limitations and allowances for this specific component + properties: + limits: + properties: + cpu: + type: string + memory: + type: string + type: object + requests: + properties: + cpu: + type: string + memory: + type: string + type: object + type: object + service: + type: object + properties: + enabled: + description: if enabled allows pod to be exposed using ClusterIP + type: boolean + clusterIP: + description: endpoints for cluster IP service type + type: array + items: + type: object + properties: + name: + description: name of the endpoint + type: string + containerPort: + description: allows to specify Kubernetes port for the pod. + type: integer + required: + - name + nodePort: + description: endpoints for nodePort service type + type: array + items: + type: object + properties: + name: + description: name of the endpoint + type: string + exposedPort: + description: a port on which the service will be accessible. + type: integer + containerPort: + description: allows to specify Kubernetes port for the pod. + type: integer + required: + - name + - exposedPort + loadBalancer: + description: endpoints for loadBalancer service type + type: array + items: + type: object + properties: + name: + description: name of the endpoint + type: string + containerPort: + description: allows to specify Kubernetes port for the pod. + type: integer + required: + - name + ingress: + description: section for ingress + type: object + properties: + urlPaths: + description: url paths + type: array + items: + type: string + pattern: "^[-a-zA-Z0-9_/]+$" + disabled: + description: if set to true resource will act as if it is removed from schema without actually deleting the file. The default value is false. + type: boolean + imageName: + description: docker image repository URL + type: string + imageVersion: + description: docker image tag + type: string + versionRange: + description: when the appropriate pattern is provided, image-version will be updated with the latest tag from the image repository that satisfies that pattern. + type: string + params: + description: List of generic parameters + items: + properties: + name: + description: name of parameter + type: string + value: + description: value of parameter + type: string + required: + - name + - value + type: object + type: array + pins: + description: consists of grpc(server, client) and mq pin sections + type: object + properties: + grpc: + description: pin section for grpc + type: object + properties: + server: + description: grpc server subsection + type: array + items: + type: object + properties: + serviceClasses: + description: list of the service classes supported by the grpc server + items: + type: string + type: array + name: + description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters + type: string + pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" + maxLength: 70 + required: + - name + - serviceClasses + client: + description: grpc client subsection + type: array + items: + type: object + properties: + attributes: + description: attributes of grpc client + items: + type: string + type: array + serviceClass: + description: name of the service class used supported by the grpc client + type: string + strategy: + description: strategy to be used for grpc communication. default value is set to robin + type: string + default: robin + filters: + description: filters for grpc client + type: array + items: + type: object + properties: + properties: + description: DESCRIPTION NEEDED + type: array + items: + type: object + properties: + expectedValue: + description: DESCRIPTION NEEDED + type: string + fieldName: + description: DESCRIPTION NEEDED + type: string + operation: + description: DESCRIPTION NEEDED + enum: + - EQUAL + - NOT_EQUAL + - EMPTY + - NOT_EMPTY + - WILDCARD + type: string + required: + - fieldName + - expectedValue + - operation + linkTo: + description: DESCRIPTION NEEDED + type: array + items: + type: object + properties: + box: + description: name of the box + type: string + pin: + description: represents the pin of the box that we want to connect + type: string + required: + - box + - pin + name: + description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters + type: string + pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" + maxLength: 70 + required: + - name + - serviceClass + mq: + type: object + properties: + subscribers: + description: section for subscriber mq pins + type: array + items: + type: object + required: + - name + properties: + name: + description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters + type: string + pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" + maxLength: 70 + attributes: + description: attributes for mq + items: + type: string + type: array + filters: + description: filters for mq + type: array + items: + type: object + properties: + properties: + description: DESCRIPTION NEEDED + type: array + items: + type: object + properties: + expectedValue: + description: DESCRIPTION NEEDED + type: string + fieldName: + description: DESCRIPTION NEEDED + type: string + operation: + description: DESCRIPTION NEEDED + enum: + - EQUAL + - NOT_EQUAL + - EMPTY + - NOT_EMPTY + - WILDCARD + type: string + required: + - fieldName + - expectedValue + - operation + message: + description: DESCRIPTION NEEDED + type: array + items: + type: object + properties: + expectedValue: + description: DESCRIPTION NEEDED + type: string + fieldName: + description: DESCRIPTION NEEDED + type: string + operation: + description: DESCRIPTION NEEDED + enum: + - EQUAL + - NOT_EQUAL + - EMPTY + - NOT_EMPTY + - WILDCARD + type: string + required: + - fieldName + - expectedValue + - operation + metadata: + description: metadata for mq pin + type: array + items: + type: object + properties: + expectedValue: + description: DESCRIPTION NEEDED + type: string + fieldName: + description: DESCRIPTION NEEDED + type: string + operation: + description: DESCRIPTION NEEDED + enum: + - EQUAL + - NOT_EQUAL + - EMPTY + - NOT_EMPTY + - WILDCARD + type: string + required: + - fieldName + - expectedValue + - operation + linkTo: + description: DESCRIPTION NEEDED + type: array + items: + type: object + properties: + box: + description: name of the box + type: string + pin: + description: represents the pin of the box that we want to connect + type: string + required: + - box + - pin + settings: + description: settings for rabbitMq queue configuration + type: object + properties: + overloadStrategy: + description: configuration for rabbit mq queue. default is set to “drop-head”. + type: string + enum: + - "drop-head" + - "reject-publish" + default: "drop-head" + queueLength: + description: configuration for rabbit mq queue. default is set to 1000 msg. queueLength isn't used if storageOnDemand is set to true. + type: integer + default: 1000 + storageOnDemand: + description: configuration for rabbit mq queue. default value is set to false + type: boolean + default: false + publishers: + description: section for subscriber mq pins + type: array + items: + required: + - name + type: object + properties: + name: + description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters + type: string + pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" + maxLength: 70 + attributes: + description: attributes for mq + items: + type: string + type: array + filters: + description: filters for mq + type: array + items: + type: object + properties: + properties: + description: DESCRIPTION NEEDED + type: array + items: + type: object + properties: + expectedValue: + description: DESCRIPTION NEEDED + type: string + fieldName: + description: DESCRIPTION NEEDED + type: string + operation: + description: DESCRIPTION NEEDED + enum: + - EQUAL + - NOT_EQUAL + - EMPTY + - NOT_EMPTY + - WILDCARD + type: string + required: + - fieldName + - expectedValue + - operation + message: + description: DESCRIPTION NEEDED + type: array + items: + type: object + properties: + expectedValue: + description: DESCRIPTION NEEDED + type: string + fieldName: + description: DESCRIPTION NEEDED + type: string + operation: + description: DESCRIPTION NEEDED + enum: + - EQUAL + - NOT_EQUAL + - EMPTY + - NOT_EMPTY + - WILDCARD + type: string + required: + - fieldName + - expectedValue + - operation + metadata: + description: metadata for mq pin + type: array + items: + type: object + properties: + expectedValue: + description: DESCRIPTION NEEDED + type: string + fieldName: + description: DESCRIPTION NEEDED + type: string + operation: + description: DESCRIPTION NEEDED + enum: + - EQUAL + - NOT_EQUAL + - EMPTY + - NOT_EMPTY + - WILDCARD + type: string + required: + - fieldName + - expectedValue + - operation + prometheus: + description: custom configuration of prometheus for microservices + properties: + enabled: + description: By default this is set to true. + type: boolean + host: + description: host for prometheus + type: string + port: + description: port for prometheus + type: integer + type: object + type: + enum: + - th2-mstore + type: string + required: + - imageName + - imageVersion + type: object + status: + description: contains status information about this resource + properties: + conditions: + description: Conditions contains observations of the resource's state + items: + properties: + lastTransitionTime: + description: LastTransitionTime is the timestamp corresponding + to the last status change of this condition. + format: date-time + type: string + lastUpdateTime: + description: LastUpdateTime is the timestamp corresponding to + the last status update of this condition. + format: date-time + type: string + message: + description: Message is a human readable description of the + details of the last transition, complementing reason. + nullable: true + type: string + reason: + description: Reason is a brief machine readable explanation + for the condition's last transition. + type: string + status: + description: Status of the condition, one of ('True', 'False', + 'Unknown'). + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: Type of the condition + enum: + - Deployed + - Enqueued + type: string + required: + - status + - type + type: object + type: array + message: + description: Message describe current state of this resource + type: string + phase: + description: ComponentStatus is the status as given by Operator for + this resource + type: string + subResourceName: + description: SubResourceName is the name of produced helmrelease tied + with this resource + type: string + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + listKind: "" + plural: "" + singular: "" + conditions: [] + storedVersions: [] \ No newline at end of file From 152af531cfc43ff49f9ed83f67f50df19869d578 Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Fri, 23 Aug 2024 17:55:04 +0400 Subject: [PATCH 20/42] [TH2-5226] Added topic exchange test at the start of integration test --- .../linkresolver/mq/RabbitMQContext.java | 6 +- .../th2/infraoperator/util/RabbitMQUtils.kt | 3 +- .../th2/infraoperator/IntegrationTest.kt | 61 +++++++++++++------ 3 files changed, 47 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java index 19e4a47d..61f2fe2d 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java +++ b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java @@ -70,6 +70,8 @@ public final class RabbitMQContext { private static final Logger logger = LoggerFactory.getLogger(RabbitMQContext.class); private static final int RETRY_DELAY = 120; + public static final String TOPIC = "topic"; + public static final String DIRECT = "direct"; private static volatile RabbitMQManagementConfig managementConfig; @@ -90,7 +92,7 @@ public static void declareTopicExchange() { String exchangeName = getTopicExchangeName(); RabbitMQManagementConfig rabbitMQManagementConfig = getManagementConfig(); try { - getChannel().exchangeDeclare(exchangeName, "topic", rabbitMQManagementConfig.getPersistence()); + getChannel().exchangeDeclare(exchangeName, TOPIC, rabbitMQManagementConfig.getPersistence()); } catch (Exception e) { logger.error("Exception setting up exchange: \"{}\"", exchangeName, e); RetryTopicExchangeTask retryTopicExchangeTask = new RetryTopicExchangeTask(exchangeName, RETRY_DELAY); @@ -156,7 +158,7 @@ private static void createUser(String namespace) throws Exception { private static void declareExchange(String exchangeName) throws Exception { RabbitMQManagementConfig rabbitMQManagementConfig = getManagementConfig(); try { - getChannel().exchangeDeclare(exchangeName, "direct", rabbitMQManagementConfig.getPersistence()); + getChannel().exchangeDeclare(exchangeName, DIRECT, rabbitMQManagementConfig.getPersistence()); } catch (Exception e) { logger.error("Exception setting up exchange: \"{}\"", exchangeName, e); throw e; diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt index 028c3ff4..0781c4f2 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt @@ -85,13 +85,14 @@ internal fun ResourceHolder.filterRubbishResources( namespacePrefixes: Set, topicExchange: String, ): ResourceHolder = apply { + exchanges.remove(topicExchange) // FIXME: topic exchange should be declare after each namespace creation val namespaces: Set = client.namespaces(namespacePrefixes) if (namespaces.isEmpty()) { return@apply } K_LOGGER.debug { "Search RabbitMQ resources in $namespaces namespaces" } - exchanges.remove(topicExchange) +// exchanges.remove(topicExchange) FIXME: uncomment this line when topic exchange is declared after each namespace creation val factories: Map, MessageRouterConfigFactory> = createFactories() namespaces.forEach { namespace -> diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/IntegrationTest.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/IntegrationTest.kt index 2d20c6bd..33e4a0b0 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/IntegrationTest.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/IntegrationTest.kt @@ -20,6 +20,7 @@ import com.exactpro.th2.infraoperator.configuration.OperatorConfig import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQManagementConfig import com.exactpro.th2.infraoperator.spec.shared.PrometheusConfiguration import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext.TOPIC import com.exactpro.th2.infraoperator.util.JsonUtils.YAML_MAPPER import com.exactpro.th2.infraoperator.util.createKubernetesClient import com.fasterxml.jackson.module.kotlin.readValue @@ -44,6 +45,10 @@ import java.nio.file.Files import java.nio.file.Path import kotlin.io.path.absolutePathString import kotlin.io.path.createDirectories +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertTrue import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition as CRD @Tag("integration-test") @@ -58,9 +63,9 @@ class IntegrationTest { private lateinit var operatorConfig: Path - private lateinit var k3s: K3sContainer + private lateinit var k3sContainer: K3sContainer - private lateinit var rabbitMQ: RabbitMQContainer + private lateinit var rabbitMQContainer: RabbitMQContainer private lateinit var kubeClient: KubernetesClient @@ -77,26 +82,28 @@ class IntegrationTest { operatorConfig = configDir.resolve("infra-operator.yml") configDir.createDirectories() - k3s = K3sContainer(K3S_DOCKER_IMAGE) + k3sContainer = K3sContainer(K3S_DOCKER_IMAGE) .withLogConsumer(Slf4jLogConsumer(getLogger("K3S")).withSeparateOutputStreams()) .also(Startable::start) - rabbitMQ = RabbitMQContainer(RABBITMQ_DOCKER_IMAGE) + rabbitMQContainer = RabbitMQContainer(RABBITMQ_DOCKER_IMAGE) .withLogConsumer(Slf4jLogConsumer(getLogger("RABBIT_MQ")).withSeparateOutputStreams()) .also(Startable::start) - K_LOGGER.info { "RabbitMQ URL: ${rabbitMQ.httpUrl}" } + K_LOGGER.info { "RabbitMQ URL: ${rabbitMQContainer.httpUrl}" } - println(k3s.kubeConfigYaml) - Files.writeString(kubeConfig, k3s.kubeConfigYaml) - YAML_MAPPER.writeValue(operatorConfig.toFile(), createConfig(rabbitMQ)) + println(k3sContainer.kubeConfigYaml) + Files.writeString(kubeConfig, k3sContainer.kubeConfigYaml) + YAML_MAPPER.writeValue(operatorConfig.toFile(), createConfig(rabbitMQContainer)) System.setProperty(Config.KUBERNETES_KUBECONFIG_FILE, kubeConfig.absolutePathString()) System.setProperty(CONFIG_FILE_SYSTEM_PROPERTY, operatorConfig.absolutePathString()) kubeClient = createKubernetesClient().apply { configureK3s() } - rabbitMQClient = createRabbitMQClient(rabbitMQ) + rabbitMQClient = createRabbitMQClient(rabbitMQContainer) controller = Th2CrdController().apply(Th2CrdController::start) + + rabbitMQClient.assertExchange(TOPIC_EXCHANGE, TOPIC) } @AfterAll @@ -104,11 +111,11 @@ class IntegrationTest { if(this::kubeClient.isInitialized) { kubeClient.close() } - if(this::k3s.isInitialized) { - k3s.stop() + if(this::k3sContainer.isInitialized) { + k3sContainer.stop() } - if(this::rabbitMQ.isInitialized) { - rabbitMQ.stop() + if(this::rabbitMQContainer.isInitialized) { + rabbitMQContainer.stop() } } @@ -141,13 +148,6 @@ class IntegrationTest { private val RABBITMQ_DOCKER_IMAGE = DockerImageName.parse("rabbitmq:3.12.6-management") private val K3S_DOCKER_IMAGE = DockerImageName.parse("rancher/k3s:v1.21.3-k3s1") - private fun createRabbitMQClient(rabbitMQ: RabbitMQContainer) = RabbitMQContext.createClient( - rabbitMQ.host, - rabbitMQ.httpPort, - rabbitMQ.adminUsername, - rabbitMQ.adminPassword, - ) - private fun createConfig(rabbitMQ: RabbitMQContainer) = OperatorConfig( rabbitMQManagement = RabbitMQManagementConfig( host = rabbitMQ.host, @@ -157,6 +157,7 @@ class IntegrationTest { exchangeName = TOPIC_EXCHANGE, username = rabbitMQ.adminUsername, password = rabbitMQ.adminPassword, + persistence = true, ), prometheusConfiguration = PrometheusConfiguration( "0.0.0.0", @@ -165,6 +166,26 @@ class IntegrationTest { ), ) + private fun createRabbitMQClient(rabbitMQ: RabbitMQContainer) = RabbitMQContext.createClient( + rabbitMQ.host, + rabbitMQ.httpPort, + rabbitMQ.adminUsername, + rabbitMQ.adminPassword, + ) + + private fun Client.assertExchange(exchange: String, type: String) { + val exchangeInfo = exchanges.asSequence() + .filter { it.name == exchange } + .firstOrNull() + assertNotNull(exchangeInfo, "Exchange '$exchange' isn't found") + assertEquals(type, exchangeInfo.type, "Exchange '$exchange' has incorrect type") + assertEquals(V_HOST, exchangeInfo.vhost, "Exchange '$exchange' has incorrect vHost") + assertEquals(emptyMap(), exchangeInfo.arguments, "Exchange '$exchange' has arguments") + assertTrue(exchangeInfo.isDurable, "Exchange '$exchange' isn't durable") + assertFalse(exchangeInfo.isInternal, "Exchange '$exchange' is internal") + assertFalse(exchangeInfo.isAutoDelete, "Exchange '$exchange' is auto delete") + } + private fun KubernetesClient.configureK3s() { CRD_RESOURCE_NAME.asSequence() .map(IntegrationTest::loadCrd) From 0fec9a0e12b120471e70d46e782fa6ee42fe2f16 Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Fri, 23 Aug 2024 19:52:40 +0400 Subject: [PATCH 21/42] [TH2-5226] Added create namespace test --- build.gradle | 2 + gradle/libs.versions.toml | 1 + .../configuration/fields/RabbitMQConfig.kt | 2 + .../fields/RabbitMQNamespacePermissions.kt | 2 +- .../{ => integration}/IntegrationTest.kt | 63 ++++++++----- .../integration/TestKubernetesUtils.kt | 67 ++++++++++++++ .../integration/TestRabbitMQUtils.kt | 90 +++++++++++++++++++ 7 files changed, 205 insertions(+), 22 deletions(-) rename src/test/kotlin/com/exactpro/th2/infraoperator/{ => integration}/IntegrationTest.kt (75%) create mode 100644 src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt create mode 100644 src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt diff --git a/build.gradle b/build.gradle index 0bbebce4..ab73d50b 100644 --- a/build.gradle +++ b/build.gradle @@ -64,6 +64,8 @@ dependencies { testImplementation libs.junit.jupiter.api testImplementation "org.jetbrains.kotlin:kotlin-test-junit5" testImplementation libs.bcpkix.jdk18on + testImplementation libs.awaitility + testRuntimeOnly libs.junit.jupiter.engine diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b0f63495..1335677f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -20,6 +20,7 @@ junit-jupiter-engine = { group = "org.junit.jupiter", name = "junit-jupiter-engi testcontainers-bom = { group = "org.testcontainers", name = "testcontainers-bom", version = "1.20.1" } bcpkix-jdk18on = { group = "org.bouncycastle", name = "bcpkix-jdk18on", version = "1.78.1" } +awaitility = { group = "org.awaitility", name = "awaitility", version = "4.2.2" } detekt-formatting = { group = "io.gitlab.arturbosch.detekt", name = "detekt-formatting", version.ref = "detekt" } diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQConfig.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQConfig.kt index c5bc0c88..af70258d 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQConfig.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQConfig.kt @@ -16,12 +16,14 @@ package com.exactpro.th2.infraoperator.configuration.fields +import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.databind.annotation.JsonDeserialize @JsonDeserialize data class RabbitMQConfig( val port: Int, val host: String, + @get:JsonProperty("vHost") val vHost: String, val exchangeName: String, val username: String, diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQNamespacePermissions.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQNamespacePermissions.kt index 22e5f667..16a69df5 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQNamespacePermissions.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQNamespacePermissions.kt @@ -26,7 +26,7 @@ data class RabbitMQNamespacePermissions( ) { companion object { - const val DEFAULT_CONFIGURE_PERMISSION = "" + const val DEFAULT_CONFIGURE_PERMISSION = ".*" const val DEFAULT_READ_PERMISSION = ".*" const val DEFAULT_WRITE_PERMISSION = ".*" } diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/IntegrationTest.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt similarity index 75% rename from src/test/kotlin/com/exactpro/th2/infraoperator/IntegrationTest.kt rename to src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt index 33e4a0b0..7cc5f6c8 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/IntegrationTest.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt @@ -13,14 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.exactpro.th2.infraoperator +package com.exactpro.th2.infraoperator.integration +import com.exactpro.th2.infraoperator.Th2CrdController import com.exactpro.th2.infraoperator.configuration.ConfigLoader.CONFIG_FILE_SYSTEM_PROPERTY import com.exactpro.th2.infraoperator.configuration.OperatorConfig +import com.exactpro.th2.infraoperator.configuration.OperatorConfig.Companion.RABBITMQ_SECRET_PASSWORD_KEY +import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQConfig import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQManagementConfig +import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQNamespacePermissions import com.exactpro.th2.infraoperator.spec.shared.PrometheusConfiguration +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.createEstoreQueue +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.createMstoreQueue import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext.DIRECT import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext.TOPIC +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext.toExchangeName import com.exactpro.th2.infraoperator.util.JsonUtils.YAML_MAPPER import com.exactpro.th2.infraoperator.util.createKubernetesClient import com.fasterxml.jackson.module.kotlin.readValue @@ -45,10 +53,6 @@ import java.nio.file.Files import java.nio.file.Path import kotlin.io.path.absolutePathString import kotlin.io.path.createDirectories -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertNotNull -import kotlin.test.assertTrue import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition as CRD @Tag("integration-test") @@ -103,7 +107,7 @@ class IntegrationTest { rabbitMQClient = createRabbitMQClient(rabbitMQContainer) controller = Th2CrdController().apply(Th2CrdController::start) - rabbitMQClient.assertExchange(TOPIC_EXCHANGE, TOPIC) + rabbitMQClient.assertExchange(TOPIC_EXCHANGE, TOPIC, V_HOST) } @AfterAll @@ -119,11 +123,27 @@ class IntegrationTest { } } + @Test + fun `create namespace`() { + val namespace = "${PREFIX}test" + kubeClient.createNamespace(namespace) + kubeClient.createSecret("rabbitmq", namespace, mapOf(RABBITMQ_SECRET_PASSWORD_KEY to "test-pass")) + kubeClient.createRabbitMQAppConfigCfgMap( + namespace, + createRabbitMQConfig(rabbitMQContainer, namespace) + ) + + rabbitMQClient.assertUser(namespace, V_HOST, RABBIT_MQ_NAMESPACE_PERMISSIONS) + rabbitMQClient.assertExchange(toExchangeName(namespace), DIRECT, V_HOST) + rabbitMQClient.assertQueue(createMstoreQueue(namespace), QUEUE_CLASSIC_TYPE, V_HOST) + rabbitMQClient.assertQueue(createEstoreQueue(namespace), QUEUE_CLASSIC_TYPE, V_HOST) + } + @Test fun test() { // val controller = Th2CrdController() // controller.start() - Thread.sleep(5_000) +// Thread.sleep(5_000) // createKubernetesClient().use { client -> // println("NAME_SPACE ${client.namespaces().list().items.map{ it.metadata.name }}") @@ -141,7 +161,10 @@ class IntegrationTest { "th2-job-crd.yaml", "th2-mstore-crd.yaml", ) + private val RABBIT_MQ_NAMESPACE_PERMISSIONS = RabbitMQNamespacePermissions() + private const val QUEUE_CLASSIC_TYPE = "classic" + private const val PREFIX = "th2-" private const val V_HOST = "/" private const val TOPIC_EXCHANGE = "global-exchange" @@ -149,6 +172,7 @@ class IntegrationTest { private val K3S_DOCKER_IMAGE = DockerImageName.parse("rancher/k3s:v1.21.3-k3s1") private fun createConfig(rabbitMQ: RabbitMQContainer) = OperatorConfig( + namespacePrefixes = setOf(PREFIX), rabbitMQManagement = RabbitMQManagementConfig( host = rabbitMQ.host, managementPort = rabbitMQ.httpPort, @@ -158,6 +182,7 @@ class IntegrationTest { username = rabbitMQ.adminUsername, password = rabbitMQ.adminPassword, persistence = true, + schemaPermissions = RABBIT_MQ_NAMESPACE_PERMISSIONS, ), prometheusConfiguration = PrometheusConfiguration( "0.0.0.0", @@ -166,6 +191,15 @@ class IntegrationTest { ), ) + private fun createRabbitMQConfig(rabbitMQ: RabbitMQContainer, namespace: String) = RabbitMQConfig( + rabbitMQ.amqpPort, + rabbitMQ.host, + V_HOST, + toExchangeName(namespace), + namespace, + "${'$'}{RABBITMQ_PASS}" + ) + private fun createRabbitMQClient(rabbitMQ: RabbitMQContainer) = RabbitMQContext.createClient( rabbitMQ.host, rabbitMQ.httpPort, @@ -173,22 +207,9 @@ class IntegrationTest { rabbitMQ.adminPassword, ) - private fun Client.assertExchange(exchange: String, type: String) { - val exchangeInfo = exchanges.asSequence() - .filter { it.name == exchange } - .firstOrNull() - assertNotNull(exchangeInfo, "Exchange '$exchange' isn't found") - assertEquals(type, exchangeInfo.type, "Exchange '$exchange' has incorrect type") - assertEquals(V_HOST, exchangeInfo.vhost, "Exchange '$exchange' has incorrect vHost") - assertEquals(emptyMap(), exchangeInfo.arguments, "Exchange '$exchange' has arguments") - assertTrue(exchangeInfo.isDurable, "Exchange '$exchange' isn't durable") - assertFalse(exchangeInfo.isInternal, "Exchange '$exchange' is internal") - assertFalse(exchangeInfo.isAutoDelete, "Exchange '$exchange' is auto delete") - } - private fun KubernetesClient.configureK3s() { CRD_RESOURCE_NAME.asSequence() - .map(IntegrationTest::loadCrd) + .map(Companion::loadCrd) .map(this::resource) .forEach(NamespaceableResource::create) } diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt new file mode 100644 index 00000000..b9d0a66a --- /dev/null +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt @@ -0,0 +1,67 @@ +/* + * Copyright 2024-2024 Exactpro (Exactpro Systems Limited) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.exactpro.th2.infraoperator.integration + +import com.exactpro.th2.infraoperator.configuration.OperatorConfig.Companion.DEFAULT_RABBITMQ_CONFIGMAP_NAME +import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQConfig +import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQConfig.Companion.CONFIG_MAP_RABBITMQ_PROP_NAME +import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.SECRET_TYPE_OPAQUE +import com.exactpro.th2.infraoperator.util.JsonUtils.JSON_MAPPER +import io.fabric8.kubernetes.api.model.ConfigMap +import io.fabric8.kubernetes.api.model.Namespace +import io.fabric8.kubernetes.api.model.ObjectMeta +import io.fabric8.kubernetes.api.model.Secret +import io.fabric8.kubernetes.client.KubernetesClient +import java.util.Base64 + +fun KubernetesClient.createNamespace( + namespace: String, +) { + resource(Namespace().apply { + metadata = ObjectMeta().apply { + name = namespace + } + }).create() +} + +fun KubernetesClient.createSecret( + name: String, + namespace: String, + data: Map, +) { + resource(Secret().apply { + metadata = ObjectMeta().apply { + this.name = name + this.namespace = namespace + } + this.type = SECRET_TYPE_OPAQUE + this.data = data.mapValues { (_, value) -> String(Base64.getEncoder().encode(value.toByteArray())); } + }).create() +} + +fun KubernetesClient.createRabbitMQAppConfigCfgMap( + namespace: String, + data: RabbitMQConfig, +) { + resource(ConfigMap().apply { + metadata = ObjectMeta().apply { + this.name = DEFAULT_RABBITMQ_CONFIGMAP_NAME + this.namespace = namespace + } + this.data[CONFIG_MAP_RABBITMQ_PROP_NAME] = JSON_MAPPER.writeValueAsString(data) + }).create() +} \ No newline at end of file diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt new file mode 100644 index 00000000..9f82ab0e --- /dev/null +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt @@ -0,0 +1,90 @@ +/* + * Copyright 2024-2024 Exactpro (Exactpro Systems Limited) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.exactpro.th2.infraoperator.integration + +import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQNamespacePermissions +import com.rabbitmq.http.client.Client +import org.testcontainers.shaded.org.awaitility.Awaitility.await +import java.util.concurrent.TimeUnit +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +fun Client.assertUser( + user: String, + vHost: String, + permissions: RabbitMQNamespacePermissions, + timeout: Long = 500, + unit: TimeUnit = TimeUnit.MILLISECONDS, +) { + await("assertUser('$user')") + .timeout(timeout, unit) + .until { users.firstOrNull { it.name == user } != null } + + val userInfo = users.firstOrNull { it.name == user } + assertNotNull(userInfo, "User '$user' isn't found") + assertEquals(emptyList(), userInfo.tags, "User '$user' has tags") + val userPermissions = this.permissions.firstOrNull { it.user == user } + assertNotNull(userPermissions, "User permission '$user' isn't found") + assertEquals(vHost, userPermissions.vhost, "User permission '$user' has incorrect vHost") + assertEquals(permissions.configure, userPermissions.configure, "User permission '$user' has incorrect configure permission") + assertEquals(permissions.read, userPermissions.read, "User permission '$user' has incorrect read permission") + assertEquals(permissions.write, userPermissions.write, "User permission '$user' has incorrect write permission") +} + +fun Client.assertExchange( + exchange: String, + type: String, + vHost: String, + timeout: Long = 500, + unit: TimeUnit = TimeUnit.MILLISECONDS, +) { + await("assertExchange('$exchange')") + .timeout(timeout, unit) + .until { exchanges.firstOrNull { it.name == exchange } != null } + + val exchangeInfo = exchanges.firstOrNull { it.name == exchange } + assertNotNull(exchangeInfo, "Exchange '$exchange' isn't found") + assertEquals(type, exchangeInfo.type, "Exchange '$exchange' has incorrect type") + assertEquals(vHost, exchangeInfo.vhost, "Exchange '$exchange' has incorrect vHost") + assertEquals(emptyMap(), exchangeInfo.arguments, "Exchange '$exchange' has arguments") + assertTrue(exchangeInfo.isDurable, "Exchange '$exchange' isn't durable") + assertFalse(exchangeInfo.isInternal, "Exchange '$exchange' is internal") + assertFalse(exchangeInfo.isAutoDelete, "Exchange '$exchange' is auto delete") +} + +fun Client.assertQueue( + queue: String, + type: String, + vHost: String, + timeout: Long = 200, + unit: TimeUnit = TimeUnit.MILLISECONDS, +) { + await("assertQueue('$queue')") + .timeout(timeout, unit) + .until { queues.firstOrNull { it.name == queue } != null } + + val queueInfo = queues.firstOrNull { it.name == queue } + assertNotNull(queueInfo, "Queue '$queue' isn't found") + assertEquals(type, queueInfo.type, "Queue '$queue' has incorrect type") + assertEquals(vHost, queueInfo.vhost, "Queue '$queue' has incorrect vHost") + assertEquals(emptyMap(), queueInfo.arguments, "Queue '$queue' has arguments") + assertTrue(queueInfo.isDurable, "Queue '$queue' isn't durable") + assertFalse(queueInfo.isExclusive, "Queue '$queue' is exclusive") + assertFalse(queueInfo.isAutoDelete, "Queue '$queue' is auto delete") +} \ No newline at end of file From a57b19a10b659b6e699d98f27a257db15ff65e78 Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Mon, 26 Aug 2024 11:15:32 +0400 Subject: [PATCH 22/42] [TH2-5226] Delete namespace after test --- .../manager/impl/ConfigMapEventHandler.java | 2 +- .../integration/IntegrationTest.kt | 74 +++++++++++++--- .../integration/TestKubernetesUtils.kt | 85 +++++++++++++++++-- .../integration/TestRabbitMQUtils.kt | 34 +++++++- 4 files changed, 169 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/ConfigMapEventHandler.java b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/ConfigMapEventHandler.java index 913a7915..0b893ff9 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/ConfigMapEventHandler.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/ConfigMapEventHandler.java @@ -83,7 +83,7 @@ public class ConfigMapEventHandler implements Watcher { public static final String BOOK_CONFIG_CM_NAME = "book-config"; - private static final String DEFAULT_BOOK = "defaultBook"; + public static final String DEFAULT_BOOK = "defaultBook"; private static final Map cmMapping = Map.of( LOGGING_CM_NAME, new ConfigMapMeta(LOGGING_ALIAS, ""), diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt index 7cc5f6c8..9ed56211 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt @@ -18,8 +18,10 @@ package com.exactpro.th2.infraoperator.integration import com.exactpro.th2.infraoperator.Th2CrdController import com.exactpro.th2.infraoperator.configuration.ConfigLoader.CONFIG_FILE_SYSTEM_PROPERTY import com.exactpro.th2.infraoperator.configuration.OperatorConfig +import com.exactpro.th2.infraoperator.configuration.OperatorConfig.Companion.DEFAULT_RABBITMQ_CONFIGMAP_NAME import com.exactpro.th2.infraoperator.configuration.OperatorConfig.Companion.RABBITMQ_SECRET_PASSWORD_KEY import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQConfig +import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQConfig.Companion.CONFIG_MAP_RABBITMQ_PROP_NAME import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQManagementConfig import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQNamespacePermissions import com.exactpro.th2.infraoperator.spec.shared.PrometheusConfiguration @@ -29,6 +31,7 @@ import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQCont import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext.DIRECT import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext.TOPIC import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext.toExchangeName +import com.exactpro.th2.infraoperator.util.JsonUtils.JSON_MAPPER import com.exactpro.th2.infraoperator.util.JsonUtils.YAML_MAPPER import com.exactpro.th2.infraoperator.util.createKubernetesClient import com.fasterxml.jackson.module.kotlin.readValue @@ -38,7 +41,9 @@ import io.fabric8.kubernetes.client.KubernetesClient import io.fabric8.kubernetes.client.dsl.NamespaceableResource import io.github.oshai.kotlinlogging.KotlinLogging import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance @@ -51,6 +56,7 @@ import org.testcontainers.lifecycle.Startable import org.testcontainers.utility.DockerImageName import java.nio.file.Files import java.nio.file.Path +import java.util.concurrent.TimeUnit.SECONDS import kotlin.io.path.absolutePathString import kotlin.io.path.createDirectories import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition as CRD @@ -123,27 +129,37 @@ class IntegrationTest { } } - @Test - fun `create namespace`() { - val namespace = "${PREFIX}test" - kubeClient.createNamespace(namespace) - kubeClient.createSecret("rabbitmq", namespace, mapOf(RABBITMQ_SECRET_PASSWORD_KEY to "test-pass")) - kubeClient.createRabbitMQAppConfigCfgMap( - namespace, - createRabbitMQConfig(rabbitMQContainer, namespace) - ) + @BeforeEach + fun beforeEach() { + kubeClient.createNamespace(NAMESPACE) + + kubeClient.createRabbitMQSecret(NAMESPACE) + kubeClient.createRabbitMQAppConfigCfgMap(NAMESPACE, createRabbitMQConfig(rabbitMQContainer, NAMESPACE)) + rabbitMQClient.assertUser(NAMESPACE, V_HOST, RABBIT_MQ_NAMESPACE_PERMISSIONS) + rabbitMQClient.assertExchange(toExchangeName(NAMESPACE), DIRECT, V_HOST) + rabbitMQClient.assertQueue(createMstoreQueue(NAMESPACE), QUEUE_CLASSIC_TYPE, V_HOST) + rabbitMQClient.assertQueue(createEstoreQueue(NAMESPACE), QUEUE_CLASSIC_TYPE, V_HOST) - rabbitMQClient.assertUser(namespace, V_HOST, RABBIT_MQ_NAMESPACE_PERMISSIONS) - rabbitMQClient.assertExchange(toExchangeName(namespace), DIRECT, V_HOST) - rabbitMQClient.assertQueue(createMstoreQueue(namespace), QUEUE_CLASSIC_TYPE, V_HOST) - rabbitMQClient.assertQueue(createEstoreQueue(namespace), QUEUE_CLASSIC_TYPE, V_HOST) +// kubeClient.createBookConfigCfgMap(NAMESPACE, ) + } + + @AfterEach + fun afterEach() { + kubeClient.deleteRabbitMQAppConfigCfgMap(NAMESPACE) + kubeClient.deleteRabbitMQSecret(NAMESPACE) + kubeClient.deleteNamespace(NAMESPACE,10, SECONDS) + + rabbitMQClient.assertNoQueue(createEstoreQueue(NAMESPACE)) + rabbitMQClient.assertNoQueue(createMstoreQueue(NAMESPACE)) + rabbitMQClient.assertNoExchange(toExchangeName(NAMESPACE)) + rabbitMQClient.assertNoUser(NAMESPACE) } @Test fun test() { // val controller = Th2CrdController() // controller.start() -// Thread.sleep(5_000) +// Thread.sleep(1_000) // createKubernetesClient().use { client -> // println("NAME_SPACE ${client.namespaces().list().items.map{ it.metadata.name }}") @@ -165,6 +181,7 @@ class IntegrationTest { private const val QUEUE_CLASSIC_TYPE = "classic" private const val PREFIX = "th2-" + private const val NAMESPACE = "${PREFIX}test" private const val V_HOST = "/" private const val TOPIC_EXCHANGE = "global-exchange" @@ -218,5 +235,34 @@ class IntegrationTest { requireNotNull(IntegrationTest::class.java.classLoader.getResource("crds/$resourceName")) { "Resource '$resourceName' isn't found" }.let(YAML_MAPPER::readValue) + + private fun KubernetesClient.createRabbitMQSecret( + namespace: String, + ) { + createSecret(namespace, "rabbitmq", mapOf(RABBITMQ_SECRET_PASSWORD_KEY to "test-pass")) + } + + private fun KubernetesClient.deleteRabbitMQSecret( + namespace: String, + ) { + deleteSecret(namespace, "rabbitmq") + } + + private fun KubernetesClient.createRabbitMQAppConfigCfgMap( + namespace: String, + data: RabbitMQConfig, + ) { + createConfigMap( + namespace, + DEFAULT_RABBITMQ_CONFIGMAP_NAME, + mapOf(CONFIG_MAP_RABBITMQ_PROP_NAME to JSON_MAPPER.writeValueAsString(data)) + ) + } + + fun KubernetesClient.deleteRabbitMQAppConfigCfgMap( + namespace: String, + ) { + deleteConfigMap(namespace, DEFAULT_RABBITMQ_CONFIGMAP_NAME) + } } } diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt index b9d0a66a..aae26196 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt @@ -16,31 +16,50 @@ package com.exactpro.th2.infraoperator.integration -import com.exactpro.th2.infraoperator.configuration.OperatorConfig.Companion.DEFAULT_RABBITMQ_CONFIGMAP_NAME -import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQConfig -import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQConfig.Companion.CONFIG_MAP_RABBITMQ_PROP_NAME +import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.BOOK_CONFIG_CM_NAME +import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.DEFAULT_BOOK import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.SECRET_TYPE_OPAQUE -import com.exactpro.th2.infraoperator.util.JsonUtils.JSON_MAPPER import io.fabric8.kubernetes.api.model.ConfigMap import io.fabric8.kubernetes.api.model.Namespace import io.fabric8.kubernetes.api.model.ObjectMeta import io.fabric8.kubernetes.api.model.Secret import io.fabric8.kubernetes.client.KubernetesClient +import io.fabric8.kubernetes.client.dsl.Deletable +import io.github.oshai.kotlinlogging.KotlinLogging +import org.testcontainers.shaded.org.awaitility.Awaitility.await import java.util.Base64 +import java.util.concurrent.TimeUnit + +private val K_LOGGER = KotlinLogging.logger {} fun KubernetesClient.createNamespace( namespace: String, + timeout: Long = 200, + unit: TimeUnit = TimeUnit.MILLISECONDS, ) { resource(Namespace().apply { metadata = ObjectMeta().apply { name = namespace } }).create() + + await("createNamespace('$namespace')") + .timeout(timeout, unit) + .until { namespaces().withName(namespace) != null } +} + +fun KubernetesClient.deleteNamespace( + namespace: String, + timeout: Long = 200, + unit: TimeUnit = TimeUnit.MILLISECONDS, +) { + namespaces().withName(namespace) + ?.awaitDeleteResource("deleteNamespace('$namespace')", timeout, unit) } fun KubernetesClient.createSecret( - name: String, namespace: String, + name: String, data: Map, ) { resource(Secret().apply { @@ -53,15 +72,63 @@ fun KubernetesClient.createSecret( }).create() } -fun KubernetesClient.createRabbitMQAppConfigCfgMap( +fun KubernetesClient.deleteSecret( namespace: String, - data: RabbitMQConfig, + name: String, + timeout: Long = 200, + unit: TimeUnit = TimeUnit.MILLISECONDS, +) { + secrets().inNamespace(namespace)?.withName(name) + ?.awaitDeleteResource("deleteSecret('$namespace/$name')", timeout, unit) +} + +fun KubernetesClient.createConfigMap( + namespace: String, + name: String, + data: Map, ) { resource(ConfigMap().apply { metadata = ObjectMeta().apply { - this.name = DEFAULT_RABBITMQ_CONFIGMAP_NAME + this.name = name this.namespace = namespace } - this.data[CONFIG_MAP_RABBITMQ_PROP_NAME] = JSON_MAPPER.writeValueAsString(data) + this.data.putAll(data) }).create() +} + +fun KubernetesClient.deleteConfigMap( + namespace: String, + name: String, + timeout: Long = 200, + unit: TimeUnit = TimeUnit.MILLISECONDS, +) { + configMaps().inNamespace(namespace)?.withName(name) + ?.awaitDeleteResource("deleteConfigMap('$namespace/$name')", timeout, unit) +} + +fun KubernetesClient.createBookConfigCfgMap( + namespace: String, + book: String +) { + resource(ConfigMap().apply { + metadata = ObjectMeta().apply { + this.name = BOOK_CONFIG_CM_NAME + this.namespace = namespace + } + this.data[DEFAULT_BOOK] = book + }).create() +} + +private fun Deletable.awaitDeleteResource( + alias: String, + timeout: Long, + unit: TimeUnit, +) { + delete().also { + K_LOGGER.info { "Delete status ($alias): $it" } + if (it.isEmpty()) return + } + await(alias) + .timeout(timeout, unit) + .until { delete().also { K_LOGGER.info { "Delete status ($alias): $it" }}.isEmpty() } } \ No newline at end of file diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt index 9f82ab0e..5ae3a8ef 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt @@ -29,7 +29,7 @@ fun Client.assertUser( user: String, vHost: String, permissions: RabbitMQNamespacePermissions, - timeout: Long = 500, + timeout: Long = 200, unit: TimeUnit = TimeUnit.MILLISECONDS, ) { await("assertUser('$user')") @@ -47,11 +47,21 @@ fun Client.assertUser( assertEquals(permissions.write, userPermissions.write, "User permission '$user' has incorrect write permission") } +fun Client.assertNoUser( + user: String, + timeout: Long = 200, + unit: TimeUnit = TimeUnit.MILLISECONDS, +) { + await("assertNoUser('$user')") + .timeout(timeout, unit) + .until { users.firstOrNull { it.name == user } == null } +} + fun Client.assertExchange( exchange: String, type: String, vHost: String, - timeout: Long = 500, + timeout: Long = 200, unit: TimeUnit = TimeUnit.MILLISECONDS, ) { await("assertExchange('$exchange')") @@ -68,6 +78,16 @@ fun Client.assertExchange( assertFalse(exchangeInfo.isAutoDelete, "Exchange '$exchange' is auto delete") } +fun Client.assertNoExchange( + exchange: String, + timeout: Long = 200, + unit: TimeUnit = TimeUnit.MILLISECONDS, +) { + await("assertNoExchange('$exchange')") + .timeout(timeout, unit) + .until { exchanges.firstOrNull { it.name == exchange } == null } +} + fun Client.assertQueue( queue: String, type: String, @@ -87,4 +107,14 @@ fun Client.assertQueue( assertTrue(queueInfo.isDurable, "Queue '$queue' isn't durable") assertFalse(queueInfo.isExclusive, "Queue '$queue' is exclusive") assertFalse(queueInfo.isAutoDelete, "Queue '$queue' is auto delete") +} + +fun Client.assertNoQueue( + queue: String, + timeout: Long = 200, + unit: TimeUnit = TimeUnit.MILLISECONDS, +) { + await("assertQueue('$queue')") + .timeout(timeout, unit) + .until { queues.firstOrNull { it.name == queue } == null } } \ No newline at end of file From c7cf7942d54afa02a28266f89cab980e9e2b8f9f Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Mon, 26 Aug 2024 11:32:27 +0400 Subject: [PATCH 23/42] [TH2-5226] Added namespace configmaps into integration test --- .../integration/IntegrationTest.kt | 64 ++++++++++++++++++- .../integration/TestKubernetesUtils.kt | 15 ----- 2 files changed, 61 insertions(+), 18 deletions(-) diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt index 9ed56211..b41ba1ef 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt @@ -24,6 +24,12 @@ import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQConfig import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQConfig.Companion.CONFIG_MAP_RABBITMQ_PROP_NAME import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQManagementConfig import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQNamespacePermissions +import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.BOOK_CONFIG_CM_NAME +import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.CRADLE_MANAGER_CM_NAME +import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.DEFAULT_BOOK +import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.GRPC_ROUTER_CM_NAME +import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.LOGGING_CM_NAME +import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.MQ_ROUTER_CM_NAME import com.exactpro.th2.infraoperator.spec.shared.PrometheusConfiguration import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.createEstoreQueue import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.createMstoreQueue @@ -56,7 +62,7 @@ import org.testcontainers.lifecycle.Startable import org.testcontainers.utility.DockerImageName import java.nio.file.Files import java.nio.file.Path -import java.util.concurrent.TimeUnit.SECONDS +import java.util.concurrent.TimeUnit.MINUTES import kotlin.io.path.absolutePathString import kotlin.io.path.createDirectories import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition as CRD @@ -140,14 +146,24 @@ class IntegrationTest { rabbitMQClient.assertQueue(createMstoreQueue(NAMESPACE), QUEUE_CLASSIC_TYPE, V_HOST) rabbitMQClient.assertQueue(createEstoreQueue(NAMESPACE), QUEUE_CLASSIC_TYPE, V_HOST) -// kubeClient.createBookConfigCfgMap(NAMESPACE, ) + kubeClient.createBookConfigCfgMap(NAMESPACE, BOOK) + kubeClient.createLoggingCfgMap(NAMESPACE) + kubeClient.createMQRouterCfgMap(NAMESPACE) + kubeClient.createGrpcRouterCfgMap(NAMESPACE) + kubeClient.createCradleManagerCfgMap(NAMESPACE) } @AfterEach fun afterEach() { + kubeClient.deleteCradleManagerCfgMap(NAMESPACE) + kubeClient.deleteGrpcRouterCfgMap(NAMESPACE) + kubeClient.deleteMQRouterCfgMap(NAMESPACE) + kubeClient.deleteLoggingCfgMap(NAMESPACE) + kubeClient.deleteBookConfigCfgMap(NAMESPACE) + kubeClient.deleteRabbitMQAppConfigCfgMap(NAMESPACE) kubeClient.deleteRabbitMQSecret(NAMESPACE) - kubeClient.deleteNamespace(NAMESPACE,10, SECONDS) + kubeClient.deleteNamespace(NAMESPACE,1, MINUTES) rabbitMQClient.assertNoQueue(createEstoreQueue(NAMESPACE)) rabbitMQClient.assertNoQueue(createMstoreQueue(NAMESPACE)) @@ -184,6 +200,7 @@ class IntegrationTest { private const val NAMESPACE = "${PREFIX}test" private const val V_HOST = "/" private const val TOPIC_EXCHANGE = "global-exchange" + private const val BOOK = "test_book" private val RABBITMQ_DOCKER_IMAGE = DockerImageName.parse("rabbitmq:3.12.6-management") private val K3S_DOCKER_IMAGE = DockerImageName.parse("rancher/k3s:v1.21.3-k3s1") @@ -264,5 +281,46 @@ class IntegrationTest { ) { deleteConfigMap(namespace, DEFAULT_RABBITMQ_CONFIGMAP_NAME) } + + fun KubernetesClient.createBookConfigCfgMap( + namespace: String, + book: String + ) { createConfigMap(namespace, BOOK_CONFIG_CM_NAME, mapOf(DEFAULT_BOOK to book)) } + + fun KubernetesClient.deleteBookConfigCfgMap( + namespace: String, + ) { deleteConfigMap(namespace, BOOK_CONFIG_CM_NAME) } + + fun KubernetesClient.createLoggingCfgMap( + namespace: String + ) { createConfigMap(namespace, LOGGING_CM_NAME, mapOf("log4j2.properties" to "test-content")) } + + fun KubernetesClient.deleteLoggingCfgMap( + namespace: String, + ) { deleteConfigMap(namespace, LOGGING_CM_NAME) } + + fun KubernetesClient.createMQRouterCfgMap( + namespace: String + ) { createConfigMap(namespace, MQ_ROUTER_CM_NAME, mapOf("mq_router.json" to "test-content")) } + + fun KubernetesClient.deleteMQRouterCfgMap( + namespace: String, + ) { deleteConfigMap(namespace, MQ_ROUTER_CM_NAME) } + + fun KubernetesClient.createGrpcRouterCfgMap( + namespace: String + ) { createConfigMap(namespace, GRPC_ROUTER_CM_NAME, mapOf("grpc_router.json" to "test-content")) } + + fun KubernetesClient.deleteGrpcRouterCfgMap( + namespace: String, + ) { deleteConfigMap(namespace, GRPC_ROUTER_CM_NAME) } + + fun KubernetesClient.createCradleManagerCfgMap( + namespace: String + ) { createConfigMap(namespace, CRADLE_MANAGER_CM_NAME, mapOf("cradle_manager.json" to "test-content")) } + + fun KubernetesClient.deleteCradleManagerCfgMap( + namespace: String, + ) { deleteConfigMap(namespace, CRADLE_MANAGER_CM_NAME) } } } diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt index aae26196..474c5b1f 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt @@ -16,8 +16,6 @@ package com.exactpro.th2.infraoperator.integration -import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.BOOK_CONFIG_CM_NAME -import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.DEFAULT_BOOK import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.SECRET_TYPE_OPAQUE import io.fabric8.kubernetes.api.model.ConfigMap import io.fabric8.kubernetes.api.model.Namespace @@ -106,19 +104,6 @@ fun KubernetesClient.deleteConfigMap( ?.awaitDeleteResource("deleteConfigMap('$namespace/$name')", timeout, unit) } -fun KubernetesClient.createBookConfigCfgMap( - namespace: String, - book: String -) { - resource(ConfigMap().apply { - metadata = ObjectMeta().apply { - this.name = BOOK_CONFIG_CM_NAME - this.namespace = namespace - } - this.data[DEFAULT_BOOK] = book - }).create() -} - private fun Deletable.awaitDeleteResource( alias: String, timeout: Long, From c3bb543ea39cf41ce13902b8c3b88b1a17b3c9fa Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Mon, 26 Aug 2024 11:55:38 +0400 Subject: [PATCH 24/42] [TH2-5226] Refactored --- .../util/CustomResourceUtils.java | 2 +- .../th2/infraoperator/util/ExtractUtils.java | 2 +- .../integration/IntegrationTest.kt | 315 +++++++++++------- .../integration/TestKubernetesUtils.kt | 4 + 4 files changed, 200 insertions(+), 123 deletions(-) diff --git a/src/main/java/com/exactpro/th2/infraoperator/util/CustomResourceUtils.java b/src/main/java/com/exactpro/th2/infraoperator/util/CustomResourceUtils.java index cb315b7d..5be00f56 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/util/CustomResourceUtils.java +++ b/src/main/java/com/exactpro/th2/infraoperator/util/CustomResourceUtils.java @@ -38,7 +38,7 @@ public class CustomResourceUtils { public static final long RESYNC_TIME = 180000; - private static final String GIT_COMMIT_HASH = "th2.exactpro.com/git-commit-hash"; + public static final String GIT_COMMIT_HASH = "th2.exactpro.com/git-commit-hash"; private static final int SHORT_HASH_LENGTH = 8; diff --git a/src/main/java/com/exactpro/th2/infraoperator/util/ExtractUtils.java b/src/main/java/com/exactpro/th2/infraoperator/util/ExtractUtils.java index 35caad42..36b60511 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/util/ExtractUtils.java +++ b/src/main/java/com/exactpro/th2/infraoperator/util/ExtractUtils.java @@ -24,7 +24,7 @@ public class ExtractUtils { - static final String KEY_SOURCE_HASH = "th2.exactpro.com/source-hash"; + public static final String KEY_SOURCE_HASH = "th2.exactpro.com/source-hash"; public static final String REFRESH_TOKEN_ALIAS = "refresh-token"; diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt index b41ba1ef..3c57b615 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt @@ -37,6 +37,8 @@ import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQCont import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext.DIRECT import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext.TOPIC import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext.toExchangeName +import com.exactpro.th2.infraoperator.util.CustomResourceUtils.GIT_COMMIT_HASH +import com.exactpro.th2.infraoperator.util.ExtractUtils.KEY_SOURCE_HASH import com.exactpro.th2.infraoperator.util.JsonUtils.JSON_MAPPER import com.exactpro.th2.infraoperator.util.JsonUtils.YAML_MAPPER import com.exactpro.th2.infraoperator.util.createKubernetesClient @@ -63,6 +65,7 @@ import org.testcontainers.utility.DockerImageName import java.nio.file.Files import java.nio.file.Path import java.util.concurrent.TimeUnit.MINUTES +import java.util.concurrent.atomic.AtomicLong import kotlin.io.path.absolutePathString import kotlin.io.path.createDirectories import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition as CRD @@ -70,7 +73,6 @@ import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition @Tag("integration-test") @TestInstance(TestInstance.Lifecycle.PER_CLASS) class IntegrationTest { - private lateinit var tempDir: Path private lateinit var configDir: Path @@ -89,22 +91,25 @@ class IntegrationTest { private lateinit var controller: Th2CrdController - @BeforeAll - fun beforeAll(@TempDir tempDir: Path) { + fun beforeAll( + @TempDir tempDir: Path, + ) { this.tempDir = tempDir configDir = tempDir.resolve("cfg") kubeConfig = configDir.resolve("kube-config.yaml") operatorConfig = configDir.resolve("infra-operator.yml") configDir.createDirectories() - k3sContainer = K3sContainer(K3S_DOCKER_IMAGE) - .withLogConsumer(Slf4jLogConsumer(getLogger("K3S")).withSeparateOutputStreams()) - .also(Startable::start) + k3sContainer = + K3sContainer(K3S_DOCKER_IMAGE) + .withLogConsumer(Slf4jLogConsumer(getLogger("K3S")).withSeparateOutputStreams()) + .also(Startable::start) - rabbitMQContainer = RabbitMQContainer(RABBITMQ_DOCKER_IMAGE) - .withLogConsumer(Slf4jLogConsumer(getLogger("RABBIT_MQ")).withSeparateOutputStreams()) - .also(Startable::start) + rabbitMQContainer = + RabbitMQContainer(RABBITMQ_DOCKER_IMAGE) + .withLogConsumer(Slf4jLogConsumer(getLogger("RABBIT_MQ")).withSeparateOutputStreams()) + .also(Startable::start) K_LOGGER.info { "RabbitMQ URL: ${rabbitMQContainer.httpUrl}" } @@ -119,56 +124,57 @@ class IntegrationTest { rabbitMQClient = createRabbitMQClient(rabbitMQContainer) controller = Th2CrdController().apply(Th2CrdController::start) - rabbitMQClient.assertExchange(TOPIC_EXCHANGE, TOPIC, V_HOST) + rabbitMQClient.assertExchange(RABBIT_MQ_TOPIC_EXCHANGE, TOPIC, RABBIT_MQ_V_HOST) } @AfterAll fun afterAll() { - if(this::kubeClient.isInitialized) { + if (this::kubeClient.isInitialized) { kubeClient.close() } - if(this::k3sContainer.isInitialized) { + if (this::k3sContainer.isInitialized) { k3sContainer.stop() } - if(this::rabbitMQContainer.isInitialized) { + if (this::rabbitMQContainer.isInitialized) { rabbitMQContainer.stop() } } @BeforeEach fun beforeEach() { - kubeClient.createNamespace(NAMESPACE) - - kubeClient.createRabbitMQSecret(NAMESPACE) - kubeClient.createRabbitMQAppConfigCfgMap(NAMESPACE, createRabbitMQConfig(rabbitMQContainer, NAMESPACE)) - rabbitMQClient.assertUser(NAMESPACE, V_HOST, RABBIT_MQ_NAMESPACE_PERMISSIONS) - rabbitMQClient.assertExchange(toExchangeName(NAMESPACE), DIRECT, V_HOST) - rabbitMQClient.assertQueue(createMstoreQueue(NAMESPACE), QUEUE_CLASSIC_TYPE, V_HOST) - rabbitMQClient.assertQueue(createEstoreQueue(NAMESPACE), QUEUE_CLASSIC_TYPE, V_HOST) - - kubeClient.createBookConfigCfgMap(NAMESPACE, BOOK) - kubeClient.createLoggingCfgMap(NAMESPACE) - kubeClient.createMQRouterCfgMap(NAMESPACE) - kubeClient.createGrpcRouterCfgMap(NAMESPACE) - kubeClient.createCradleManagerCfgMap(NAMESPACE) + val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() + kubeClient.createNamespace(TH2_NAMESPACE) + + kubeClient.createRabbitMQSecret(TH2_NAMESPACE, gitHash) + kubeClient.createRabbitMQAppConfigCfgMap(TH2_NAMESPACE, gitHash, createRabbitMQConfig(rabbitMQContainer, TH2_NAMESPACE)) + rabbitMQClient.assertUser(TH2_NAMESPACE, RABBIT_MQ_V_HOST, RABBIT_MQ_NAMESPACE_PERMISSIONS) + rabbitMQClient.assertExchange(toExchangeName(TH2_NAMESPACE), DIRECT, RABBIT_MQ_V_HOST) + rabbitMQClient.assertQueue(createMstoreQueue(TH2_NAMESPACE), RABBIT_MQ_QUEUE_CLASSIC_TYPE, RABBIT_MQ_V_HOST) + rabbitMQClient.assertQueue(createEstoreQueue(TH2_NAMESPACE), RABBIT_MQ_QUEUE_CLASSIC_TYPE, RABBIT_MQ_V_HOST) + + kubeClient.createBookConfigCfgMap(TH2_NAMESPACE, gitHash, TH2_BOOK) + kubeClient.createLoggingCfgMap(TH2_NAMESPACE, gitHash) + kubeClient.createMQRouterCfgMap(TH2_NAMESPACE, gitHash) + kubeClient.createGrpcRouterCfgMap(TH2_NAMESPACE, gitHash) + kubeClient.createCradleManagerCfgMap(TH2_NAMESPACE, gitHash) } @AfterEach fun afterEach() { - kubeClient.deleteCradleManagerCfgMap(NAMESPACE) - kubeClient.deleteGrpcRouterCfgMap(NAMESPACE) - kubeClient.deleteMQRouterCfgMap(NAMESPACE) - kubeClient.deleteLoggingCfgMap(NAMESPACE) - kubeClient.deleteBookConfigCfgMap(NAMESPACE) - - kubeClient.deleteRabbitMQAppConfigCfgMap(NAMESPACE) - kubeClient.deleteRabbitMQSecret(NAMESPACE) - kubeClient.deleteNamespace(NAMESPACE,1, MINUTES) - - rabbitMQClient.assertNoQueue(createEstoreQueue(NAMESPACE)) - rabbitMQClient.assertNoQueue(createMstoreQueue(NAMESPACE)) - rabbitMQClient.assertNoExchange(toExchangeName(NAMESPACE)) - rabbitMQClient.assertNoUser(NAMESPACE) + kubeClient.deleteCradleManagerCfgMap(TH2_NAMESPACE) + kubeClient.deleteGrpcRouterCfgMap(TH2_NAMESPACE) + kubeClient.deleteMQRouterCfgMap(TH2_NAMESPACE) + kubeClient.deleteLoggingCfgMap(TH2_NAMESPACE) + kubeClient.deleteBookConfigCfgMap(TH2_NAMESPACE) + + kubeClient.deleteRabbitMQAppConfigCfgMap(TH2_NAMESPACE) + kubeClient.deleteRabbitMQSecret(TH2_NAMESPACE) + kubeClient.deleteNamespace(TH2_NAMESPACE, 1, MINUTES) + + rabbitMQClient.assertNoQueue(createEstoreQueue(TH2_NAMESPACE)) + rabbitMQClient.assertNoQueue(createMstoreQueue(TH2_NAMESPACE)) + rabbitMQClient.assertNoExchange(toExchangeName(TH2_NAMESPACE)) + rabbitMQClient.assertNoUser(TH2_NAMESPACE) } @Test @@ -185,64 +191,78 @@ class IntegrationTest { companion object { private val K_LOGGER = KotlinLogging.logger {} - private val CRD_RESOURCE_NAME = setOf( - "th2-box-crd.yaml", - "th2-core-box-crd.yaml", - "th2-dictionary-crd.yaml", - "th2-estore-crd.yaml", - "th2-job-crd.yaml", - "th2-mstore-crd.yaml", - ) - private val RABBIT_MQ_NAMESPACE_PERMISSIONS = RabbitMQNamespacePermissions() - - private const val QUEUE_CLASSIC_TYPE = "classic" - private const val PREFIX = "th2-" - private const val NAMESPACE = "${PREFIX}test" - private const val V_HOST = "/" - private const val TOPIC_EXCHANGE = "global-exchange" - private const val BOOK = "test_book" + private val RESOURCE_GIT_HASH_COUNTER = AtomicLong(10_000_000) private val RABBITMQ_DOCKER_IMAGE = DockerImageName.parse("rabbitmq:3.12.6-management") private val K3S_DOCKER_IMAGE = DockerImageName.parse("rancher/k3s:v1.21.3-k3s1") - private fun createConfig(rabbitMQ: RabbitMQContainer) = OperatorConfig( - namespacePrefixes = setOf(PREFIX), - rabbitMQManagement = RabbitMQManagementConfig( - host = rabbitMQ.host, - managementPort = rabbitMQ.httpPort, - applicationPort = rabbitMQ.amqpPort, - vhostName = V_HOST, - exchangeName = TOPIC_EXCHANGE, - username = rabbitMQ.adminUsername, - password = rabbitMQ.adminPassword, - persistence = true, - schemaPermissions = RABBIT_MQ_NAMESPACE_PERMISSIONS, - ), - prometheusConfiguration = PrometheusConfiguration( - "0.0.0.0", - "9752", - false.toString(), - ), - ) + private val CRD_RESOURCE_NAMES = + setOf( + "th2-box-crd.yaml", + "th2-core-box-crd.yaml", + "th2-dictionary-crd.yaml", + "th2-estore-crd.yaml", + "th2-job-crd.yaml", + "th2-mstore-crd.yaml", + ) - private fun createRabbitMQConfig(rabbitMQ: RabbitMQContainer, namespace: String) = RabbitMQConfig( + private val RABBIT_MQ_NAMESPACE_PERMISSIONS = RabbitMQNamespacePermissions() + private const val RABBIT_MQ_QUEUE_CLASSIC_TYPE = "classic" + private const val RABBIT_MQ_V_HOST = "/" + private const val RABBIT_MQ_TOPIC_EXCHANGE = "global-exchange" + + private const val TH2_PREFIX = "th2-" + private const val TH2_NAMESPACE = "${TH2_PREFIX}test" + private const val TH2_BOOK = "test_book" + + private const val TEST_CONTENT = "test-content" + + private fun createConfig(rabbitMQ: RabbitMQContainer) = + OperatorConfig( + namespacePrefixes = setOf(TH2_PREFIX), + rabbitMQManagement = + RabbitMQManagementConfig( + host = rabbitMQ.host, + managementPort = rabbitMQ.httpPort, + applicationPort = rabbitMQ.amqpPort, + vhostName = RABBIT_MQ_V_HOST, + exchangeName = RABBIT_MQ_TOPIC_EXCHANGE, + username = rabbitMQ.adminUsername, + password = rabbitMQ.adminPassword, + persistence = true, + schemaPermissions = RABBIT_MQ_NAMESPACE_PERMISSIONS, + ), + prometheusConfiguration = + PrometheusConfiguration( + "0.0.0.0", + "9752", + false.toString(), + ), + ) + + private fun createRabbitMQConfig( + rabbitMQ: RabbitMQContainer, + namespace: String, + ) = RabbitMQConfig( rabbitMQ.amqpPort, rabbitMQ.host, - V_HOST, + RABBIT_MQ_V_HOST, toExchangeName(namespace), namespace, - "${'$'}{RABBITMQ_PASS}" + "${'$'}{RABBITMQ_PASS}", ) - private fun createRabbitMQClient(rabbitMQ: RabbitMQContainer) = RabbitMQContext.createClient( - rabbitMQ.host, - rabbitMQ.httpPort, - rabbitMQ.adminUsername, - rabbitMQ.adminPassword, - ) + private fun createRabbitMQClient(rabbitMQ: RabbitMQContainer) = + RabbitMQContext.createClient( + rabbitMQ.host, + rabbitMQ.httpPort, + rabbitMQ.adminUsername, + rabbitMQ.adminPassword, + ) private fun KubernetesClient.configureK3s() { - CRD_RESOURCE_NAME.asSequence() + CRD_RESOURCE_NAMES + .asSequence() .map(Companion::loadCrd) .map(this::resource) .forEach(NamespaceableResource::create) @@ -253,74 +273,127 @@ class IntegrationTest { "Resource '$resourceName' isn't found" }.let(YAML_MAPPER::readValue) + private fun createAnnotations( + gitHash: String, + content: String, + ) = mapOf( + GIT_COMMIT_HASH to gitHash, + KEY_SOURCE_HASH to content.hashCode().toString(), + ) + private fun KubernetesClient.createRabbitMQSecret( namespace: String, + gitHash: String, ) { - createSecret(namespace, "rabbitmq", mapOf(RABBITMQ_SECRET_PASSWORD_KEY to "test-pass")) + createSecret( + namespace, + "rabbitmq", + createAnnotations(gitHash, TEST_CONTENT), + mapOf(RABBITMQ_SECRET_PASSWORD_KEY to TEST_CONTENT), + ) } - private fun KubernetesClient.deleteRabbitMQSecret( - namespace: String, - ) { + private fun KubernetesClient.deleteRabbitMQSecret(namespace: String) { deleteSecret(namespace, "rabbitmq") } private fun KubernetesClient.createRabbitMQAppConfigCfgMap( namespace: String, + gitHash: String, data: RabbitMQConfig, ) { + val content = JSON_MAPPER.writeValueAsString(data) createConfigMap( namespace, DEFAULT_RABBITMQ_CONFIGMAP_NAME, - mapOf(CONFIG_MAP_RABBITMQ_PROP_NAME to JSON_MAPPER.writeValueAsString(data)) + createAnnotations(gitHash, content), + mapOf(CONFIG_MAP_RABBITMQ_PROP_NAME to content), ) } - fun KubernetesClient.deleteRabbitMQAppConfigCfgMap( - namespace: String, - ) { + private fun KubernetesClient.deleteRabbitMQAppConfigCfgMap(namespace: String) { deleteConfigMap(namespace, DEFAULT_RABBITMQ_CONFIGMAP_NAME) } - fun KubernetesClient.createBookConfigCfgMap( - namespace: String, - book: String - ) { createConfigMap(namespace, BOOK_CONFIG_CM_NAME, mapOf(DEFAULT_BOOK to book)) } - - fun KubernetesClient.deleteBookConfigCfgMap( + private fun KubernetesClient.createBookConfigCfgMap( namespace: String, - ) { deleteConfigMap(namespace, BOOK_CONFIG_CM_NAME) } + gitHash: String, + book: String, + ) { + createConfigMap( + namespace, + BOOK_CONFIG_CM_NAME, + createAnnotations(gitHash, book), + mapOf(DEFAULT_BOOK to book), + ) + } - fun KubernetesClient.createLoggingCfgMap( - namespace: String - ) { createConfigMap(namespace, LOGGING_CM_NAME, mapOf("log4j2.properties" to "test-content")) } + private fun KubernetesClient.deleteBookConfigCfgMap(namespace: String) { + deleteConfigMap(namespace, BOOK_CONFIG_CM_NAME) + } - fun KubernetesClient.deleteLoggingCfgMap( + private fun KubernetesClient.createLoggingCfgMap( namespace: String, - ) { deleteConfigMap(namespace, LOGGING_CM_NAME) } + gitHash: String, + ) { + createConfigMap( + namespace, + LOGGING_CM_NAME, + createAnnotations(gitHash, TEST_CONTENT), + mapOf("log4j2.properties" to TEST_CONTENT), + ) + } - fun KubernetesClient.createMQRouterCfgMap( - namespace: String - ) { createConfigMap(namespace, MQ_ROUTER_CM_NAME, mapOf("mq_router.json" to "test-content")) } + private fun KubernetesClient.deleteLoggingCfgMap(namespace: String) { + deleteConfigMap(namespace, LOGGING_CM_NAME) + } - fun KubernetesClient.deleteMQRouterCfgMap( + private fun KubernetesClient.createMQRouterCfgMap( namespace: String, - ) { deleteConfigMap(namespace, MQ_ROUTER_CM_NAME) } + gitHash: String, + ) { + createConfigMap( + namespace, + MQ_ROUTER_CM_NAME, + createAnnotations(gitHash, TEST_CONTENT), + mapOf("mq_router.json" to TEST_CONTENT), + ) + } - fun KubernetesClient.createGrpcRouterCfgMap( - namespace: String - ) { createConfigMap(namespace, GRPC_ROUTER_CM_NAME, mapOf("grpc_router.json" to "test-content")) } + private fun KubernetesClient.deleteMQRouterCfgMap(namespace: String) { + deleteConfigMap(namespace, MQ_ROUTER_CM_NAME) + } - fun KubernetesClient.deleteGrpcRouterCfgMap( + private fun KubernetesClient.createGrpcRouterCfgMap( namespace: String, - ) { deleteConfigMap(namespace, GRPC_ROUTER_CM_NAME) } + gitHash: String, + ) { + createConfigMap( + namespace, + GRPC_ROUTER_CM_NAME, + createAnnotations(gitHash, TEST_CONTENT), + mapOf("grpc_router.json" to TEST_CONTENT), + ) + } - fun KubernetesClient.createCradleManagerCfgMap( - namespace: String - ) { createConfigMap(namespace, CRADLE_MANAGER_CM_NAME, mapOf("cradle_manager.json" to "test-content")) } + private fun KubernetesClient.deleteGrpcRouterCfgMap(namespace: String) { + deleteConfigMap(namespace, GRPC_ROUTER_CM_NAME) + } - fun KubernetesClient.deleteCradleManagerCfgMap( + private fun KubernetesClient.createCradleManagerCfgMap( namespace: String, - ) { deleteConfigMap(namespace, CRADLE_MANAGER_CM_NAME) } + gitHash: String, + ) { + createConfigMap( + namespace, + CRADLE_MANAGER_CM_NAME, + createAnnotations(gitHash, TEST_CONTENT), + mapOf("cradle_manager.json" to TEST_CONTENT), + ) + } + + private fun KubernetesClient.deleteCradleManagerCfgMap(namespace: String) { + deleteConfigMap(namespace, CRADLE_MANAGER_CM_NAME) + } } } diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt index 474c5b1f..dd6971df 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt @@ -58,12 +58,14 @@ fun KubernetesClient.deleteNamespace( fun KubernetesClient.createSecret( namespace: String, name: String, + annotations: Map, data: Map, ) { resource(Secret().apply { metadata = ObjectMeta().apply { this.name = name this.namespace = namespace + this.annotations.putAll(annotations) } this.type = SECRET_TYPE_OPAQUE this.data = data.mapValues { (_, value) -> String(Base64.getEncoder().encode(value.toByteArray())); } @@ -83,12 +85,14 @@ fun KubernetesClient.deleteSecret( fun KubernetesClient.createConfigMap( namespace: String, name: String, + annotations: Map, data: Map, ) { resource(ConfigMap().apply { metadata = ObjectMeta().apply { this.name = name this.namespace = namespace + this.annotations.putAll(annotations) } this.data.putAll(data) }).create() From 5dff0eec1dcd2fcb7cfeb308f3e5cb2e686a1309 Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Tue, 10 Sep 2024 13:16:10 +0400 Subject: [PATCH 25/42] [TH2-5226] Prepared base IntegrationTest --- .../manager/impl/ConfigMapEventHandler.java | 2 +- .../manager/impl/NamespaceEventHandler.java | 2 +- .../linkresolver/mq/RabbitMQContext.java | 1 + .../integration/IntegrationTest.kt | 48 +++++++++--- .../integration/TestKubernetesUtils.kt | 78 ++++++++++++------- 5 files changed, 90 insertions(+), 41 deletions(-) diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/ConfigMapEventHandler.java b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/ConfigMapEventHandler.java index 0b893ff9..1978a5c1 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/ConfigMapEventHandler.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/ConfigMapEventHandler.java @@ -172,7 +172,7 @@ public void eventReceived(Action action, ConfigMap resource) { logger.info("box-definition(s) have been updated"); processTimer.observeDuration(); } else { - logger.info("RabbitMQ ConfigMap data hasn't changed"); + logger.info("RabbitMQ ConfigMap data hasn't changed"); } } finally { lock.unlock(); diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/NamespaceEventHandler.java b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/NamespaceEventHandler.java index b2187f80..ab380670 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/NamespaceEventHandler.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/NamespaceEventHandler.java @@ -114,7 +114,7 @@ public void eventReceived(Action action, Namespace resource) { try { lock.lock(); - logger.debug("Processing {} event for namespace: \"{}\"", action, namespaceName); + logger.info("Processing {} event for namespace: \"{}\"", action, namespaceName); RabbitMQContext.cleanupRabbit(namespaceName); logger.info("Deleted namespace {}", namespaceName); } catch (Exception e) { diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java index 61f2fe2d..a5ec5bad 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java +++ b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java @@ -105,6 +105,7 @@ public static void declareTopicExchange() { public static void setUpRabbitMqForNamespace(String namespace) { try { createUser(namespace); + declareTopicExchange(); declareExchange(toExchangeName(namespace)); createStoreQueues(namespace); } catch (Exception e) { diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt index 3c57b615..5c6a7bb2 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt @@ -64,6 +64,7 @@ import org.testcontainers.lifecycle.Startable import org.testcontainers.utility.DockerImageName import java.nio.file.Files import java.nio.file.Path +import java.util.UUID import java.util.concurrent.TimeUnit.MINUTES import java.util.concurrent.atomic.AtomicLong import kotlin.io.path.absolutePathString @@ -144,9 +145,9 @@ class IntegrationTest { fun beforeEach() { val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() kubeClient.createNamespace(TH2_NAMESPACE) - kubeClient.createRabbitMQSecret(TH2_NAMESPACE, gitHash) kubeClient.createRabbitMQAppConfigCfgMap(TH2_NAMESPACE, gitHash, createRabbitMQConfig(rabbitMQContainer, TH2_NAMESPACE)) + rabbitMQClient.assertUser(TH2_NAMESPACE, RABBIT_MQ_V_HOST, RABBIT_MQ_NAMESPACE_PERMISSIONS) rabbitMQClient.assertExchange(toExchangeName(TH2_NAMESPACE), DIRECT, RABBIT_MQ_V_HOST) rabbitMQClient.assertQueue(createMstoreQueue(TH2_NAMESPACE), RABBIT_MQ_QUEUE_CLASSIC_TYPE, RABBIT_MQ_V_HOST) @@ -161,14 +162,6 @@ class IntegrationTest { @AfterEach fun afterEach() { - kubeClient.deleteCradleManagerCfgMap(TH2_NAMESPACE) - kubeClient.deleteGrpcRouterCfgMap(TH2_NAMESPACE) - kubeClient.deleteMQRouterCfgMap(TH2_NAMESPACE) - kubeClient.deleteLoggingCfgMap(TH2_NAMESPACE) - kubeClient.deleteBookConfigCfgMap(TH2_NAMESPACE) - - kubeClient.deleteRabbitMQAppConfigCfgMap(TH2_NAMESPACE) - kubeClient.deleteRabbitMQSecret(TH2_NAMESPACE) kubeClient.deleteNamespace(TH2_NAMESPACE, 1, MINUTES) rabbitMQClient.assertNoQueue(createEstoreQueue(TH2_NAMESPACE)) @@ -178,7 +171,40 @@ class IntegrationTest { } @Test - fun test() { + fun `add mstore`() { +// val controller = Th2CrdController() +// controller.start() +// Thread.sleep(1_000) + +// createKubernetesClient().use { client -> +// println("NAME_SPACE ${client.namespaces().list().items.map{ it.metadata.name }}") +// } + } + + @Test + fun `add estore`() { +// val controller = Th2CrdController() +// controller.start() +// Thread.sleep(1_000) + +// createKubernetesClient().use { client -> +// println("NAME_SPACE ${client.namespaces().list().items.map{ it.metadata.name }}") +// } + } + + @Test + fun `add core component`() { +// val controller = Th2CrdController() +// controller.start() +// Thread.sleep(1_000) + +// createKubernetesClient().use { client -> +// println("NAME_SPACE ${client.namespaces().list().items.map{ it.metadata.name }}") +// } + } + + @Test + fun `add component`() { // val controller = Th2CrdController() // controller.start() // Thread.sleep(1_000) @@ -289,7 +315,7 @@ class IntegrationTest { namespace, "rabbitmq", createAnnotations(gitHash, TEST_CONTENT), - mapOf(RABBITMQ_SECRET_PASSWORD_KEY to TEST_CONTENT), + mapOf(RABBITMQ_SECRET_PASSWORD_KEY to UUID.randomUUID().toString()), ) } diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt index dd6971df..3b9acf7d 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt @@ -25,8 +25,11 @@ import io.fabric8.kubernetes.client.KubernetesClient import io.fabric8.kubernetes.client.dsl.Deletable import io.github.oshai.kotlinlogging.KotlinLogging import org.testcontainers.shaded.org.awaitility.Awaitility.await +import java.time.Instant import java.util.Base64 +import java.util.UUID import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicInteger private val K_LOGGER = KotlinLogging.logger {} @@ -35,11 +38,11 @@ fun KubernetesClient.createNamespace( timeout: Long = 200, unit: TimeUnit = TimeUnit.MILLISECONDS, ) { - resource(Namespace().apply { - metadata = ObjectMeta().apply { - name = namespace - } - }).create() + resource( + Namespace().apply { + metadata = createMeta(namespace, null) + }, + ).create() await("createNamespace('$namespace')") .timeout(timeout, unit) @@ -51,7 +54,8 @@ fun KubernetesClient.deleteNamespace( timeout: Long = 200, unit: TimeUnit = TimeUnit.MILLISECONDS, ) { - namespaces().withName(namespace) + namespaces() + .withName(namespace) ?.awaitDeleteResource("deleteNamespace('$namespace')", timeout, unit) } @@ -61,15 +65,16 @@ fun KubernetesClient.createSecret( annotations: Map, data: Map, ) { - resource(Secret().apply { - metadata = ObjectMeta().apply { - this.name = name - this.namespace = namespace - this.annotations.putAll(annotations) - } - this.type = SECRET_TYPE_OPAQUE - this.data = data.mapValues { (_, value) -> String(Base64.getEncoder().encode(value.toByteArray())); } - }).create() + resource( + Secret().apply { + metadata = createMeta(name, namespace, annotations) + this.type = SECRET_TYPE_OPAQUE + this.data = + data.mapValues { (_, value) -> + String(Base64.getEncoder().encode(value.toByteArray())) + } + }, + ).create() } fun KubernetesClient.deleteSecret( @@ -78,7 +83,9 @@ fun KubernetesClient.deleteSecret( timeout: Long = 200, unit: TimeUnit = TimeUnit.MILLISECONDS, ) { - secrets().inNamespace(namespace)?.withName(name) + secrets() + .inNamespace(namespace) + ?.withName(name) ?.awaitDeleteResource("deleteSecret('$namespace/$name')", timeout, unit) } @@ -88,14 +95,12 @@ fun KubernetesClient.createConfigMap( annotations: Map, data: Map, ) { - resource(ConfigMap().apply { - metadata = ObjectMeta().apply { - this.name = name - this.namespace = namespace - this.annotations.putAll(annotations) - } - this.data.putAll(data) - }).create() + resource( + ConfigMap().apply { + metadata = createMeta(name, namespace, annotations) + this.data.putAll(data) + }, + ).create() } fun KubernetesClient.deleteConfigMap( @@ -104,7 +109,9 @@ fun KubernetesClient.deleteConfigMap( timeout: Long = 200, unit: TimeUnit = TimeUnit.MILLISECONDS, ) { - configMaps().inNamespace(namespace)?.withName(name) + configMaps() + .inNamespace(namespace) + ?.withName(name) ?.awaitDeleteResource("deleteConfigMap('$namespace/$name')", timeout, unit) } @@ -114,10 +121,25 @@ private fun Deletable.awaitDeleteResource( unit: TimeUnit, ) { delete().also { - K_LOGGER.info { "Delete status ($alias): $it" } + K_LOGGER.info { "Deleted ($alias)" } if (it.isEmpty()) return } + + val counter = AtomicInteger(0) await(alias) .timeout(timeout, unit) - .until { delete().also { K_LOGGER.info { "Delete status ($alias): $it" }}.isEmpty() } -} \ No newline at end of file + .until { delete().also { counter.incrementAndGet() }.isEmpty() } + K_LOGGER.info { "Deleted ($alias) after $counter iterations" } +} + +private fun createMeta( + name: String, + namespace: String?, + annotations: Map = emptyMap(), +): ObjectMeta = ObjectMeta().apply { + this.name = name + this.namespace = namespace + this.annotations.putAll(annotations) + this.uid = UUID.randomUUID().toString() + this.creationTimestamp = Instant.now().toString() +} From 8119b3421a5678569e918da789ad0b0d7e4dc32c Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Thu, 12 Sep 2024 15:14:46 +0400 Subject: [PATCH 26/42] [TH2-5226] Updated test --- build.gradle | 7 +- gradle/libs.versions.toml | 6 +- .../metrics/OperatorMetrics.java | 32 +- .../operator/HelmReleaseTh2Op.java | 18 +- .../util/CustomResourceUtils.java | 2 +- .../integration/IntegrationTest.kt | 276 ++++++++++-- .../integration/TestKubernetesUtils.kt | 106 +++++ .../integration/TestRabbitMQUtils.kt | 111 ++++- src/test/resources/crds/helmreleases-crd.yaml | 422 ++++++++++++++++++ .../resources/crds/th2-dictionary-crd.yaml | 1 - src/test/resources/log4j2.properties | 5 +- 11 files changed, 896 insertions(+), 90 deletions(-) create mode 100644 src/test/resources/crds/helmreleases-crd.yaml diff --git a/build.gradle b/build.gradle index ab73d50b..e14e6b00 100644 --- a/build.gradle +++ b/build.gradle @@ -61,13 +61,12 @@ dependencies { testImplementation libs.mockito.core testImplementation libs.mockito.kotlin - testImplementation libs.junit.jupiter.api + testImplementation libs.junit.jupiter + testImplementation libs.junit.jupiter.params testImplementation "org.jetbrains.kotlin:kotlin-test-junit5" testImplementation libs.bcpkix.jdk18on testImplementation libs.awaitility - - - testRuntimeOnly libs.junit.jupiter.engine + testImplementation libs.strikt.core detektPlugins libs.detekt.formatting } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1335677f..3a430794 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,6 +4,7 @@ th2-plugin = "0.1.1" jupiter = "5.10.3" okhttp3 = "4.12.0" detekt = "1.23.6" +strikt = "0.34.1" [libraries] kotlin-logging = { group = "io.github.oshai", name = "kotlin-logging", version = "5.1.4" } @@ -15,9 +16,10 @@ java-uuid-generator = { group = "com.fasterxml.uuid", name = "java-uuid-generato mockito-core = { group = "org.mockito", name = "mockito-core", version = "5.12.0" } mockito-kotlin = { group = "org.mockito.kotlin", name = "mockito-kotlin", version = "5.4.0" } -junit-jupiter-api = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "jupiter" } -junit-jupiter-engine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", version.ref = "jupiter" } +junit-jupiter = { group = "org.junit.jupiter", name = "junit-jupiter", version.ref = "jupiter" } +junit-jupiter-params = { group = "org.junit.jupiter", name = "junit-jupiter-params", version.ref = "jupiter" } +strikt-core = { group = "io.strikt", name = "strikt-core", version.ref = "strikt" } testcontainers-bom = { group = "org.testcontainers", name = "testcontainers-bom", version = "1.20.1" } bcpkix-jdk18on = { group = "org.bouncycastle", name = "bcpkix-jdk18on", version = "1.78.1" } awaitility = { group = "org.awaitility", name = "awaitility", version = "4.2.2" } diff --git a/src/main/java/com/exactpro/th2/infraoperator/metrics/OperatorMetrics.java b/src/main/java/com/exactpro/th2/infraoperator/metrics/OperatorMetrics.java index ffcd66ca..3a9c5556 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/metrics/OperatorMetrics.java +++ b/src/main/java/com/exactpro/th2/infraoperator/metrics/OperatorMetrics.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,35 +25,35 @@ public class OperatorMetrics { private static final double[] TOTAL_PROCESSING_TIME_BUCKETS = {5, 10, 20, 30, 40, 50, 60, 70, 80, 120}; - private static final String KEY_DETECTION_TIME = "th2.exactpro.com/detection-time"; + public static final String KEY_DETECTION_TIME = "th2.exactpro.com/detection-time"; private static final double MILLIS_PER_SECOND = 1000; //local metrics - private static Gauge eventCounter = Gauge + private static final Gauge EVENT_COUNTER = Gauge .build("th2_infra_operator_event_queue", "Amount of events to be processed") .labelNames("exported_namespace", "category") .register(); - private static Gauge resourceCacheErrors = Gauge + private static final Gauge RESOURCE_CACHE_ERRORS = Gauge .build("th2_infra_operator_resource_cache_errors", "Amount of errors in operator resource cache") .register(); - private static Histogram crEventProcessingTime = Histogram + private static final Histogram CR_EVENT_PROCESSING_TIME = Histogram .build("th2_infra_operator_custom_resource_event_processing_time", "Time it took to process specific event by operator") .buckets(LOCAL_PROCESSING_TIME_BUCKETS) .labelNames("exported_namespace", "kind", "resName") .register(); - private static Histogram cmEventProcessingTime = Histogram + private static final Histogram CM_EVENT_PROCESSING_TIME = Histogram .build("th2_infra_operator_config_map_event_processing_time", "Time it took to process specific config map") .buckets(LOCAL_PROCESSING_TIME_BUCKETS) .labelNames("exported_namespace", "resName") .register(); - private static Histogram dictionaryEventProcessingTime = Histogram + private static final Histogram DICTIONARY_EVENT_PROCESSING_TIME = Histogram .build("th2_infra_operator_dictionary_event_processing_time", "Time it took to process dictionary") .buckets(LOCAL_PROCESSING_TIME_BUCKETS) @@ -61,7 +61,7 @@ public class OperatorMetrics { .register(); //total processing time metric - private static Histogram eventProcessingTimeTotal = Histogram + private static final Histogram EVENT_PROCESSING_TIME_TOTAL = Histogram .build("th2_infra_event_processing_total_time", "Time it took to process specific event by both manager and operator") .buckets(TOTAL_PROCESSING_TIME_BUCKETS) @@ -69,38 +69,38 @@ public class OperatorMetrics { .register(); public static void setPriorityEventCount(int value, String exportedNamespace) { - eventCounter.labels(exportedNamespace, "priority").set(value); + EVENT_COUNTER.labels(exportedNamespace, "priority").set(value); } public static void setRegularEventCount(int value, String exportedNamespace) { - eventCounter.labels(exportedNamespace, "regular").set(value); + EVENT_COUNTER.labels(exportedNamespace, "regular").set(value); } public static Histogram.Timer getCustomResourceEventTimer(HasMetadata resource) { String exportedNamespace = resource.getMetadata().getNamespace(); String resName = resource.getMetadata().getName(); String kind = resource.getKind(); - return crEventProcessingTime.labels(exportedNamespace, kind, resName).startTimer(); + return CR_EVENT_PROCESSING_TIME.labels(exportedNamespace, kind, resName).startTimer(); } public static Histogram.Timer getConfigMapEventTimer(HasMetadata resource) { String exportedNamespace = resource.getMetadata().getNamespace(); String resName = resource.getMetadata().getName(); - return cmEventProcessingTime.labels(exportedNamespace, resName).startTimer(); + return CM_EVENT_PROCESSING_TIME.labels(exportedNamespace, resName).startTimer(); } public static Histogram.Timer getDictionaryEventTimer(HasMetadata resource) { String exportedNamespace = resource.getMetadata().getNamespace(); String resName = resource.getMetadata().getName(); - return dictionaryEventProcessingTime.labels(exportedNamespace, resName).startTimer(); + return DICTIONARY_EVENT_PROCESSING_TIME.labels(exportedNamespace, resName).startTimer(); } public static void resetCacheErrors() { - resourceCacheErrors.set(0); + RESOURCE_CACHE_ERRORS.set(0); } public static void incrementCacheErrors() { - resourceCacheErrors.inc(); + RESOURCE_CACHE_ERRORS.inc(); } public static void observeTotal(HasMetadata resource) { @@ -113,6 +113,6 @@ public static void observeTotal(HasMetadata resource) { String resName = resource.getMetadata().getName(); long detectionTime = Long.parseLong(detectionTimeStr); double duration = (System.currentTimeMillis() - detectionTime) / MILLIS_PER_SECOND; - eventProcessingTimeTotal.labels(exportedNamespace, kind, resName).observe(duration); + EVENT_PROCESSING_TIME_TOTAL.labels(exportedNamespace, kind, resName).observe(duration); } } diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/HelmReleaseTh2Op.java b/src/main/java/com/exactpro/th2/infraoperator/operator/HelmReleaseTh2Op.java index 02bf247c..77ea675c 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/HelmReleaseTh2Op.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/HelmReleaseTh2Op.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -65,25 +65,25 @@ public abstract class HelmReleaseTh2Op extends Abs public static final String ANNOTATIONS_ALIAS = "commonAnnotations"; //component section - private static final String ROOTLESS_ALIAS = "rootless"; + public static final String ROOTLESS_ALIAS = "rootless"; - private static final String COMPONENT_NAME_ALIAS = "name"; + public static final String COMPONENT_NAME_ALIAS = "name"; - private static final String DOCKER_IMAGE_ALIAS = "image"; + public static final String DOCKER_IMAGE_ALIAS = "image"; - private static final String CUSTOM_CONFIG_ALIAS = "custom"; + public static final String CUSTOM_CONFIG_ALIAS = "custom"; - private static final String SECRET_VALUES_CONFIG_ALIAS = "secretValuesConfig"; + public static final String SECRET_VALUES_CONFIG_ALIAS = "secretValuesConfig"; - private static final String SECRET_PATHS_CONFIG_ALIAS = "secretPathsConfig"; + public static final String SECRET_PATHS_CONFIG_ALIAS = "secretPathsConfig"; - private static final String PROMETHEUS_CONFIG_ALIAS = "prometheus"; + public static final String PROMETHEUS_CONFIG_ALIAS = "prometheus"; public static final String DICTIONARIES_ALIAS = "dictionaries"; public static final String MQ_QUEUE_CONFIG_ALIAS = "mq"; - private static final String GRPC_P2P_CONFIG_ALIAS = "grpc"; + public static final String GRPC_P2P_CONFIG_ALIAS = "grpc"; public static final String MQ_ROUTER_ALIAS = "mqRouter"; diff --git a/src/main/java/com/exactpro/th2/infraoperator/util/CustomResourceUtils.java b/src/main/java/com/exactpro/th2/infraoperator/util/CustomResourceUtils.java index 5be00f56..a832eb6a 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/util/CustomResourceUtils.java +++ b/src/main/java/com/exactpro/th2/infraoperator/util/CustomResourceUtils.java @@ -100,7 +100,7 @@ private static String concatFullName(String namespace, String name) { return namespace + "." + name; } - private static String hashNameIfNeeded(String resName) { + public static String hashNameIfNeeded(String resName) { if (resName.length() >= HelmRelease.NAME_LENGTH_LIMIT) { return digest(resName); } diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt index 5c6a7bb2..e8579141 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt @@ -20,16 +20,45 @@ import com.exactpro.th2.infraoperator.configuration.ConfigLoader.CONFIG_FILE_SYS import com.exactpro.th2.infraoperator.configuration.OperatorConfig import com.exactpro.th2.infraoperator.configuration.OperatorConfig.Companion.DEFAULT_RABBITMQ_CONFIGMAP_NAME import com.exactpro.th2.infraoperator.configuration.OperatorConfig.Companion.RABBITMQ_SECRET_PASSWORD_KEY +import com.exactpro.th2.infraoperator.configuration.fields.ChartSpec import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQConfig import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQConfig.Companion.CONFIG_MAP_RABBITMQ_PROP_NAME import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQManagementConfig import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQNamespacePermissions +import com.exactpro.th2.infraoperator.metrics.OperatorMetrics.KEY_DETECTION_TIME +import com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.BOOK_CONFIG_ALIAS +import com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.BOOK_NAME_ALIAS +import com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.CHECKSUM_ALIAS +import com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.COMPONENT_NAME_ALIAS +import com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.CONFIG_ALIAS +import com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.CRADLE_MGR_ALIAS +import com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.CUSTOM_CONFIG_ALIAS +import com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.DICTIONARIES_ALIAS +import com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.DOCKER_IMAGE_ALIAS +import com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.EXTENDED_SETTINGS_ALIAS +import com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.GRPC_P2P_CONFIG_ALIAS +import com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.GRPC_ROUTER_ALIAS +import com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.IS_JOB_ALIAS +import com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.LOGGING_ALIAS +import com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.MQ_QUEUE_CONFIG_ALIAS +import com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.MQ_ROUTER_ALIAS +import com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.PROMETHEUS_CONFIG_ALIAS +import com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.PULL_SECRETS_ALIAS +import com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.ROOTLESS_ALIAS +import com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.SCHEMA_SECRETS_ALIAS +import com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.SECRET_PATHS_CONFIG_ALIAS +import com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.SECRET_VALUES_CONFIG_ALIAS +import com.exactpro.th2.infraoperator.operator.impl.EstoreHelmTh2Op.EVENT_STORAGE_BOX_ALIAS +import com.exactpro.th2.infraoperator.operator.impl.EstoreHelmTh2Op.EVENT_STORAGE_PIN_ALIAS +import com.exactpro.th2.infraoperator.operator.impl.MstoreHelmTh2Op.MESSAGE_STORAGE_BOX_ALIAS import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.BOOK_CONFIG_CM_NAME import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.CRADLE_MANAGER_CM_NAME import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.DEFAULT_BOOK import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.GRPC_ROUTER_CM_NAME import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.LOGGING_CM_NAME import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.MQ_ROUTER_CM_NAME +import com.exactpro.th2.infraoperator.spec.helmrelease.HelmRelease +import com.exactpro.th2.infraoperator.spec.helmrelease.HelmRelease.NAME_LENGTH_LIMIT import com.exactpro.th2.infraoperator.spec.shared.PrometheusConfiguration import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.createEstoreQueue import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.createMstoreQueue @@ -38,6 +67,7 @@ import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQCont import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext.TOPIC import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext.toExchangeName import com.exactpro.th2.infraoperator.util.CustomResourceUtils.GIT_COMMIT_HASH +import com.exactpro.th2.infraoperator.util.CustomResourceUtils.hashNameIfNeeded import com.exactpro.th2.infraoperator.util.ExtractUtils.KEY_SOURCE_HASH import com.exactpro.th2.infraoperator.util.JsonUtils.JSON_MAPPER import com.exactpro.th2.infraoperator.util.JsonUtils.YAML_MAPPER @@ -46,7 +76,6 @@ import com.fasterxml.jackson.module.kotlin.readValue import com.rabbitmq.http.client.Client import io.fabric8.kubernetes.client.Config import io.fabric8.kubernetes.client.KubernetesClient -import io.fabric8.kubernetes.client.dsl.NamespaceableResource import io.github.oshai.kotlinlogging.KotlinLogging import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.AfterEach @@ -56,16 +85,27 @@ import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.io.TempDir +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource import org.slf4j.LoggerFactory.getLogger import org.testcontainers.containers.RabbitMQContainer import org.testcontainers.containers.output.Slf4jLogConsumer import org.testcontainers.k3s.K3sContainer import org.testcontainers.lifecycle.Startable import org.testcontainers.utility.DockerImageName +import strikt.api.expectThat +import strikt.assertions.getValue +import strikt.assertions.hasSize +import strikt.assertions.isA +import strikt.assertions.isEmpty +import strikt.assertions.isEqualTo +import strikt.assertions.isNotNull +import strikt.assertions.isNull import java.nio.file.Files import java.nio.file.Path import java.util.UUID import java.util.concurrent.TimeUnit.MINUTES +import java.util.concurrent.TimeUnit.SECONDS import java.util.concurrent.atomic.AtomicLong import kotlin.io.path.absolutePathString import kotlin.io.path.createDirectories @@ -116,7 +156,7 @@ class IntegrationTest { println(k3sContainer.kubeConfigYaml) Files.writeString(kubeConfig, k3sContainer.kubeConfigYaml) - YAML_MAPPER.writeValue(operatorConfig.toFile(), createConfig(rabbitMQContainer)) + YAML_MAPPER.writeValue(operatorConfig.toFile(), createOperatorConfig(rabbitMQContainer)) System.setProperty(Config.KUBERNETES_KUBECONFIG_FILE, kubeConfig.absolutePathString()) System.setProperty(CONFIG_FILE_SYSTEM_PROPERTY, operatorConfig.absolutePathString()) @@ -163,55 +203,119 @@ class IntegrationTest { @AfterEach fun afterEach() { kubeClient.deleteNamespace(TH2_NAMESPACE, 1, MINUTES) + // FIXME: Secret not found "th2-test:Secret/rabbitMQ" - rabbitMQClient.assertNoQueue(createEstoreQueue(TH2_NAMESPACE)) - rabbitMQClient.assertNoQueue(createMstoreQueue(TH2_NAMESPACE)) + rabbitMQClient.assertNoQueue(createEstoreQueue(TH2_NAMESPACE), 1, SECONDS) + rabbitMQClient.assertNoQueue(createMstoreQueue(TH2_NAMESPACE), 1, SECONDS) rabbitMQClient.assertNoExchange(toExchangeName(TH2_NAMESPACE)) rabbitMQClient.assertNoUser(TH2_NAMESPACE) + + kubeClient.awaitNoHelmRelease(TH2_NAMESPACE) } @Test fun `add mstore`() { -// val controller = Th2CrdController() -// controller.start() -// Thread.sleep(1_000) - -// createKubernetesClient().use { client -> -// println("NAME_SPACE ${client.namespaces().list().items.map{ it.metadata.name }}") -// } + val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() + kubeClient.createTh2Mstore(TH2_NAMESPACE, MESSAGE_STORAGE_BOX_ALIAS, createAnnotations(gitHash, TEST_CONTENT)) + kubeClient.awaitHelmRelease(TH2_NAMESPACE, MESSAGE_STORAGE_BOX_ALIAS) + // FIXME: estore should have binding +// println("Bindings: ${rabbitMQClient.getQueueBindings(RABBIT_MQ_V_HOST, createEstoreQueue(TH2_NAMESPACE))}") } @Test fun `add estore`() { -// val controller = Th2CrdController() -// controller.start() -// Thread.sleep(1_000) + val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() + kubeClient.createTh2Estore(TH2_NAMESPACE, EVENT_STORAGE_BOX_ALIAS, createAnnotations(gitHash, TEST_CONTENT)) + kubeClient.awaitHelmRelease(TH2_NAMESPACE, EVENT_STORAGE_BOX_ALIAS) + rabbitMQClient.assertBindings( + createEstoreQueue(TH2_NAMESPACE), + RABBIT_MQ_V_HOST, + setOf( + "link[$TH2_NAMESPACE:$EVENT_STORAGE_BOX_ALIAS:$EVENT_STORAGE_PIN_ALIAS]", + ) + ) + } + + @ParameterizedTest + @ValueSource(strings = ["th2-core-component", "th2-core-component-more-than-$NAME_LENGTH_LIMIT-character"] ) + /** + * Max name length is limited by [HelmRelease.NAME_LENGTH_LIMIT] + */ + fun `add core component (min configuration)`(name: String) { + val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() + val spec = """ + imageName: $IMAGE + imageVersion: $VERSION + type: th2-rpt-data-provider + """.trimIndent() + + kubeClient.createTh2CoreBox( + TH2_NAMESPACE, + name, + createAnnotations(gitHash, spec.hashCode().toString()), + spec + ) + kubeClient.awaitHelmRelease(TH2_NAMESPACE, hashNameIfNeeded(name)).assertMinCfg(name) + rabbitMQClient.assertBindings( + createEstoreQueue(TH2_NAMESPACE), + RABBIT_MQ_V_HOST, + setOf( + "link[$TH2_NAMESPACE:$EVENT_STORAGE_BOX_ALIAS:$EVENT_STORAGE_PIN_ALIAS]", + "key[$TH2_NAMESPACE:$name:$EVENT_STORAGE_PIN_ALIAS]" + ) + ) + } -// createKubernetesClient().use { client -> -// println("NAME_SPACE ${client.namespaces().list().items.map{ it.metadata.name }}") -// } + @ParameterizedTest + @ValueSource(strings = ["th2-component", "th2-component-more-than-$NAME_LENGTH_LIMIT-character"] ) + /** + * Max name length is limited by [HelmRelease.NAME_LENGTH_LIMIT] + */ + fun `add component (min configuration)`(name: String) { + val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() + val spec = """ + imageName: $IMAGE + imageVersion: $VERSION + type: th2-codec + """.trimIndent() + + kubeClient.createTh2Box( + TH2_NAMESPACE, + name, + createAnnotations(gitHash, spec.hashCode().toString()), + spec + ) + kubeClient.awaitHelmRelease(TH2_NAMESPACE, hashNameIfNeeded(name)).assertMinCfg(name) + rabbitMQClient.assertBindings( + createEstoreQueue(TH2_NAMESPACE), + RABBIT_MQ_V_HOST, + setOf( + "link[$TH2_NAMESPACE:$EVENT_STORAGE_BOX_ALIAS:$EVENT_STORAGE_PIN_ALIAS]", + "key[$TH2_NAMESPACE:$name:$EVENT_STORAGE_PIN_ALIAS]" + ) + ) } - @Test - fun `add core component`() { -// val controller = Th2CrdController() -// controller.start() -// Thread.sleep(1_000) - -// createKubernetesClient().use { client -> -// println("NAME_SPACE ${client.namespaces().list().items.map{ it.metadata.name }}") -// } +// @Test // FIXME Failure executing: POST at: https://localhost:33114/apis/th2.exactpro.com/v2/th2dictionaries. Message: Not Found. + fun `add dictionary (short name)`() { + val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() + val name = "th2-dictionary" + val spec = """ + data: $DICTIONARY_CONTENT + """.trimIndent() + + kubeClient.createTh2Dictionary( + TH2_NAMESPACE, + name, + createAnnotations(gitHash, spec.hashCode().toString()), + spec + ) + kubeClient.awaitHelmRelease(TH2_NAMESPACE, name).assertMinCfg(name) } - @Test - fun `add component`() { -// val controller = Th2CrdController() -// controller.start() -// Thread.sleep(1_000) - -// createKubernetesClient().use { client -> -// println("NAME_SPACE ${client.namespaces().list().items.map{ it.metadata.name }}") -// } +// @Test + fun `create job`() { + } companion object { @@ -224,6 +328,7 @@ class IntegrationTest { private val CRD_RESOURCE_NAMES = setOf( + "helmreleases-crd.yaml", "th2-box-crd.yaml", "th2-core-box-crd.yaml", "th2-dictionary-crd.yaml", @@ -235,16 +340,22 @@ class IntegrationTest { private val RABBIT_MQ_NAMESPACE_PERMISSIONS = RabbitMQNamespacePermissions() private const val RABBIT_MQ_QUEUE_CLASSIC_TYPE = "classic" private const val RABBIT_MQ_V_HOST = "/" - private const val RABBIT_MQ_TOPIC_EXCHANGE = "global-exchange" + private const val RABBIT_MQ_TOPIC_EXCHANGE = "test-global-exchange" private const val TH2_PREFIX = "th2-" private const val TH2_NAMESPACE = "${TH2_PREFIX}test" private const val TH2_BOOK = "test_book" + private const val CFG_FIELD = "test-cfg-field" + private const val CFG_VALUE = "test-cfg-value" + private const val IMAGE = "ghcr.io/th2-net/th2-estore" + private const val VERSION = "0.0.0" + private const val DICTIONARY_CONTENT = "test-dictionary-content" private const val TEST_CONTENT = "test-content" - private fun createConfig(rabbitMQ: RabbitMQContainer) = + private fun createOperatorConfig(rabbitMQ: RabbitMQContainer) = OperatorConfig( + chart = ChartSpec(), namespacePrefixes = setOf(TH2_PREFIX), rabbitMQManagement = RabbitMQManagementConfig( @@ -291,7 +402,10 @@ class IntegrationTest { .asSequence() .map(Companion::loadCrd) .map(this::resource) - .forEach(NamespaceableResource::create) + .forEach { crd -> + crd.create() + K_LOGGER.info { "Applied CRD: ${crd.get().metadata.name}" } + } } private fun loadCrd(resourceName: String): CRD = @@ -301,10 +415,11 @@ class IntegrationTest { private fun createAnnotations( gitHash: String, - content: String, + sourceHash: String, ) = mapOf( + KEY_DETECTION_TIME to System.currentTimeMillis().toString(), GIT_COMMIT_HASH to gitHash, - KEY_SOURCE_HASH to content.hashCode().toString(), + KEY_SOURCE_HASH to sourceHash.hashCode().toString(), ) private fun KubernetesClient.createRabbitMQSecret( @@ -421,5 +536,86 @@ class IntegrationTest { private fun KubernetesClient.deleteCradleManagerCfgMap(namespace: String) { deleteConfigMap(namespace, CRADLE_MANAGER_CM_NAME) } + + private fun HelmRelease.assertMinCfg(name: String) { + expectThat(componentValuesSection) { + getValue(BOOK_CONFIG_ALIAS).isA>().and { + hasSize(1) + getValue(BOOK_NAME_ALIAS) isEqualTo TH2_BOOK + } + getValue(CRADLE_MGR_ALIAS).isA>().and { + hasSize(2) + getValue(CHECKSUM_ALIAS).isNotNull() + getValue(CONFIG_ALIAS).isNull() // FIXME: shouldn't be null + } + getValue(CUSTOM_CONFIG_ALIAS).isA>().isEmpty() + getValue(DICTIONARIES_ALIAS).isA>().isEmpty() // FIXME + getValue(PULL_SECRETS_ALIAS).isA>().isEmpty() // FIXME + getValue(EXTENDED_SETTINGS_ALIAS).isA>().and { + isEmpty() // FIXME: add extendedSettings + } + getValue(GRPC_P2P_CONFIG_ALIAS).isA>().and { + hasSize(2) + getValue("server").isA>().and { + getValue("attributes").isNull() // FIXME: add attributes + getValue("host").isNull() // FIXME: add host + getValue("port") isEqualTo 8080 + getValue("workers") isEqualTo 5 + } + getValue("services").isA>().isEmpty() + + } + getValue(IS_JOB_ALIAS) isEqualTo false + getValue(SECRET_PATHS_CONFIG_ALIAS).isA>().isEmpty() + getValue(SECRET_VALUES_CONFIG_ALIAS).isA>().isEmpty() + getValue(SCHEMA_SECRETS_ALIAS).isA>().and { + getValue("cassandra") isEqualTo "cassandra" + getValue("rabbitMQ") isEqualTo "rabbitMQ" + } + getValue(GRPC_ROUTER_ALIAS).isA>().and { + hasSize(2) + getValue(CHECKSUM_ALIAS).isNotNull() + getValue(CONFIG_ALIAS).isNull() // FIXME + } + getValue(DOCKER_IMAGE_ALIAS) isEqualTo "$IMAGE:$VERSION" + getValue(COMPONENT_NAME_ALIAS) isEqualTo name + getValue(ROOTLESS_ALIAS).isA>().and { + hasSize(1) + getValue("enabled") isEqualTo false // FIXME + } + getValue(PROMETHEUS_CONFIG_ALIAS).isA>().and { + hasSize(1) + getValue("enabled") isEqualTo true // FIXME + } + getValue(LOGGING_ALIAS).isA>().and { + hasSize(2) + getValue(CHECKSUM_ALIAS).isNotNull() + getValue(CONFIG_ALIAS).isNull() // FIXME + } + getValue(MQ_ROUTER_ALIAS).isA>().and { + hasSize(2) + getValue(CHECKSUM_ALIAS).isNotNull() + getValue(CONFIG_ALIAS).isNull() // FIXME + } + getValue(MQ_QUEUE_CONFIG_ALIAS).isA>().and { + hasSize(2) + getValue("globalNotification").isA>().and { + hasSize(1) + getValue("exchange") isEqualTo RABBIT_MQ_TOPIC_EXCHANGE + } + getValue("queues").isA>().and { + hasSize(1) + getValue(EVENT_STORAGE_PIN_ALIAS).isA>().and { + hasSize(5) + getValue("attributes").isA>() isEqualTo listOf("publish", "event") + getValue("exchange") isEqualTo toExchangeName(TH2_NAMESPACE) + getValue("filters").isA>().isEmpty() // FIXME + getValue("name") isEqualTo "key[$TH2_NAMESPACE:$name:$EVENT_STORAGE_PIN_ALIAS]" + getValue("queue").isA().isEmpty() + } + } + } + } + } } } diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt index 3b9acf7d..fdc887f3 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt @@ -17,6 +17,15 @@ package com.exactpro.th2.infraoperator.integration import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.SECRET_TYPE_OPAQUE +import com.exactpro.th2.infraoperator.spec.Th2Spec +import com.exactpro.th2.infraoperator.spec.box.Th2Box +import com.exactpro.th2.infraoperator.spec.corebox.Th2CoreBox +import com.exactpro.th2.infraoperator.spec.dictionary.Th2Dictionary +import com.exactpro.th2.infraoperator.spec.dictionary.Th2DictionarySpec +import com.exactpro.th2.infraoperator.spec.estore.Th2Estore +import com.exactpro.th2.infraoperator.spec.helmrelease.HelmRelease +import com.exactpro.th2.infraoperator.spec.mstore.Th2Mstore +import com.exactpro.th2.infraoperator.util.JsonUtils.YAML_MAPPER import io.fabric8.kubernetes.api.model.ConfigMap import io.fabric8.kubernetes.api.model.Namespace import io.fabric8.kubernetes.api.model.ObjectMeta @@ -115,6 +124,103 @@ fun KubernetesClient.deleteConfigMap( ?.awaitDeleteResource("deleteConfigMap('$namespace/$name')", timeout, unit) } +fun KubernetesClient.createTh2Mstore( + namespace: String, + name: String, + annotations: Map, +) { + resource( + Th2Mstore().apply { + metadata = createMeta(name, namespace, annotations) + spec = YAML_MAPPER.readValue(""" + imageName: ghcr.io/th2-net/th2-mstore + imageVersion: 0.0.0 + """.trimIndent(), Th2Spec::class.java) + } + ).create() +} + +fun KubernetesClient.createTh2Estore( + namespace: String, + name: String, + annotations: Map, +) { + resource( + Th2Estore().apply { + metadata = createMeta(name, namespace, annotations) + spec = YAML_MAPPER.readValue(""" + imageName: ghcr.io/th2-net/th2-estore + imageVersion: 0.0.0 + """.trimIndent(), Th2Spec::class.java) + } + ).create() +} + +fun KubernetesClient.createTh2CoreBox( + namespace: String, + name: String, + annotations: Map, + spec: String, +) { + resource( + Th2CoreBox().apply { + this.metadata = createMeta(name, namespace, annotations) + this.spec = YAML_MAPPER.readValue(spec, Th2Spec::class.java) + } + ).create() +} + +fun KubernetesClient.createTh2Box( + namespace: String, + name: String, + annotations: Map, + spec: String, +) { + resource( + Th2Box().apply { + this.metadata = createMeta(name, namespace, annotations) + this.spec = YAML_MAPPER.readValue(spec, Th2Spec::class.java) + } + ).create() +} + +fun KubernetesClient.createTh2Dictionary( + namespace: String, + name: String, + annotations: Map, + spec: String, +) { + resource( + Th2Dictionary().apply { + this.metadata = createMeta(name, namespace, annotations) + this.spec = YAML_MAPPER.readValue(spec, Th2DictionarySpec::class.java) + } + ).create() +} + +fun KubernetesClient.awaitHelmRelease( + namespace: String, + name: String, + timeout: Long = 2000, + unit: TimeUnit = TimeUnit.MILLISECONDS, +): HelmRelease { + await("awaitHelmRelease ($name)") + .timeout(timeout, unit) + .until { resources(HelmRelease::class.java).inNamespace(namespace).withName(name).get() != null } + + return resources(HelmRelease::class.java).inNamespace(namespace).withName(name).get() +} + +fun KubernetesClient.awaitNoHelmRelease( + namespace: String, + timeout: Long = 2000, + unit: TimeUnit = TimeUnit.MILLISECONDS, +) { + await("awaitNoHelmRelease") + .timeout(timeout, unit) + .until { resources(HelmRelease::class.java).inNamespace(namespace).list().items.isEmpty() } +} + private fun Deletable.awaitDeleteResource( alias: String, timeout: Long, diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt index 5ae3a8ef..c5dbb937 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt @@ -18,6 +18,9 @@ package com.exactpro.th2.infraoperator.integration import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQNamespacePermissions import com.rabbitmq.http.client.Client +import com.rabbitmq.http.client.domain.DestinationType +import com.rabbitmq.http.client.domain.QueueInfo +import org.junit.jupiter.api.assertAll import org.testcontainers.shaded.org.awaitility.Awaitility.await import java.util.concurrent.TimeUnit import kotlin.test.assertEquals @@ -29,7 +32,7 @@ fun Client.assertUser( user: String, vHost: String, permissions: RabbitMQNamespacePermissions, - timeout: Long = 200, + timeout: Long = 1_000, unit: TimeUnit = TimeUnit.MILLISECONDS, ) { await("assertUser('$user')") @@ -49,7 +52,7 @@ fun Client.assertUser( fun Client.assertNoUser( user: String, - timeout: Long = 200, + timeout: Long = 1_000, unit: TimeUnit = TimeUnit.MILLISECONDS, ) { await("assertNoUser('$user')") @@ -61,7 +64,7 @@ fun Client.assertExchange( exchange: String, type: String, vHost: String, - timeout: Long = 200, + timeout: Long = 1_000, unit: TimeUnit = TimeUnit.MILLISECONDS, ) { await("assertExchange('$exchange')") @@ -80,7 +83,7 @@ fun Client.assertExchange( fun Client.assertNoExchange( exchange: String, - timeout: Long = 200, + timeout: Long = 1_000, unit: TimeUnit = TimeUnit.MILLISECONDS, ) { await("assertNoExchange('$exchange')") @@ -92,26 +95,102 @@ fun Client.assertQueue( queue: String, type: String, vHost: String, - timeout: Long = 200, + timeout: Long = 1_000, unit: TimeUnit = TimeUnit.MILLISECONDS, -) { +): QueueInfo { await("assertQueue('$queue')") .timeout(timeout, unit) - .until { queues.firstOrNull { it.name == queue } != null } + .until { getQueue(vHost, queue) != null } + + return assertNotNull(getQueue(vHost, queue), "Queue '$queue' isn't found") + .also { queueInfo -> + assertEquals(type, queueInfo.type, "Queue '$queue' has incorrect type") + assertEquals(emptyMap(), queueInfo.arguments, "Queue '$queue' has arguments") + assertTrue(queueInfo.isDurable, "Queue '$queue' isn't durable") + assertFalse(queueInfo.isExclusive, "Queue '$queue' is exclusive") + assertFalse(queueInfo.isAutoDelete, "Queue '$queue' is auto delete") + } +} - val queueInfo = queues.firstOrNull { it.name == queue } - assertNotNull(queueInfo, "Queue '$queue' isn't found") - assertEquals(type, queueInfo.type, "Queue '$queue' has incorrect type") - assertEquals(vHost, queueInfo.vhost, "Queue '$queue' has incorrect vHost") - assertEquals(emptyMap(), queueInfo.arguments, "Queue '$queue' has arguments") - assertTrue(queueInfo.isDurable, "Queue '$queue' isn't durable") - assertFalse(queueInfo.isExclusive, "Queue '$queue' is exclusive") - assertFalse(queueInfo.isAutoDelete, "Queue '$queue' is auto delete") +fun Client.assertBindings( + queue: String, + vHost: String, + routingKeys: Set = emptySet(), + timeout: Long = 1_000, + unit: TimeUnit = TimeUnit.MILLISECONDS, +) { + await("assertBindings('$queue'), bindings: $routingKeys") + .timeout(timeout, unit) + .untilAsserted { + val queueBindings = getQueueBindings(vHost, queue) + when { + routingKeys.isEmpty() -> assertTrue( + queueBindings.isEmpty(), + "Bindings isn't empty for queue '$queue', actual: $queueBindings" + ) + else -> assertAll( + buildList { + add { + assertEquals( + routingKeys.size, + queueBindings.size, + "Bindings number is incorrect for queue '$queue', actual: $queueBindings" + ) + } + routingKeys.forEach { routingKey -> + add { + val queueBinding = assertNotNull( + queueBindings.singleOrNull { it.routingKey == routingKey }, + "Queue '$queue' doesn't contain routing key, actual: $queueBindings" + ) + assertAll( + { + assertEquals( + vHost, + queueBinding.vhost, + "Binding has incorrect vHost for routing key '$routingKey' in queue '$queue'", + ) + }, + { + assertEquals( + emptyMap(), + queueBinding.arguments, + "Binding has arguments for routing key '$routingKey' in queue '$queue'", + ) + }, + { + assertEquals( + routingKey.replace("[", "%5B").replace(":", "%3A").replace("]", "%5D"), + queueBinding.propertiesKey, + "Binding has 'propertiesKey' for routing key '$routingKey' in queue '$queue'", + ) + }, + { + assertEquals( + queue, + queueBinding.destination, + "Binding has incorrect 'destination' for routing key '$routingKey' in queue '$queue'", + ) + }, + { + assertEquals( + DestinationType.QUEUE, + queueBinding.destinationType, + "Binding has incorrect 'destinationType' for routing key '$routingKey' in queue '$queue'", + ) + }, + ) + } + } + } + ) + } + } } fun Client.assertNoQueue( queue: String, - timeout: Long = 200, + timeout: Long = 1_000, unit: TimeUnit = TimeUnit.MILLISECONDS, ) { await("assertQueue('$queue')") diff --git a/src/test/resources/crds/helmreleases-crd.yaml b/src/test/resources/crds/helmreleases-crd.yaml new file mode 100644 index 00000000..35ec7a94 --- /dev/null +++ b/src/test/resources/crds/helmreleases-crd.yaml @@ -0,0 +1,422 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: helmreleases.helm.fluxcd.io +spec: + conversion: + strategy: None + group: helm.fluxcd.io + names: + kind: HelmRelease + listKind: HelmReleaseList + plural: helmreleases + shortNames: + - hr + - hrs + singular: helmrelease + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Release is the name of the Helm release, as given by Helm. + jsonPath: .status.releaseName + name: Release + type: string + - description: Phase is the current release phase being performed for the HelmRelease. + jsonPath: .status.phase + name: Phase + type: string + - description: ReleaseStatus is the status of the Helm release, as given by Helm. + jsonPath: .status.releaseStatus + name: ReleaseStatus + type: string + - jsonPath: .status.conditions[?(@.type=="Released")].message + name: Message + type: string + - description: CreationTimestamp is a timestamp representing the server time when + this object was created. It is not guaranteed to be set in happens-before + order across separate operations. Clients may not set this value. It is represented + in RFC3339 form and is in UTC. + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: HelmRelease is a type to represent a Helm release. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + chart: + properties: + chartPullSecret: + description: ChartPullSecret holds the reference to the authentication + secret for accessing the Helm repository using HTTPS basic auth. + NOT IMPLEMENTED! + properties: + name: + type: string + required: + - name + type: object + git: + description: Git URL is the URL of the Git repository, e.g. `git@github.com:org/repo`, + `http://github.com/org/repo`, or `ssh://git@example.com:2222/org/repo.git`. + type: string + name: + description: Name is the name of the Helm chart _without_ an alias, + e.g. redis (for `helm upgrade [flags] stable/redis`). + type: string + path: + description: Path is the path to the chart relative to the repository + root. + type: string + ref: + description: Ref is the Git branch (or other reference) to use. + Defaults to 'master', or the configured default Git ref. + type: string + repository: + description: RepoURL is the URL of the Helm repository, e.g. `https://kubernetes-charts.storage.googleapis.com` + or `https://charts.example.com`. + type: string + secretRef: + description: SecretRef holds the authentication secret for accessing + the Git repository (over HTTPS). The credentials will be added + to an HTTPS GitURL before the mirror is started. + properties: + name: + type: string + namespace: + type: string + required: + - name + type: object + skipDepUpdate: + description: SkipDepUpdate will tell the operator to skip running + 'helm dep update' before installing or upgrading the chart, + the chart dependencies _must_ be present for this to succeed. + type: boolean + version: + description: Version is the targeted Helm chart version, e.g. + 7.0.1. + type: string + type: object + disableOpenAPIValidation: + description: DisableOpenAPIValidation controls whether OpenAPI validation + is enforced. + type: boolean + forceUpgrade: + description: Force will mark this Helm release to `--force` upgrades. + This forces the resource updates through delete/recreate if needed. + type: boolean + helmVersion: + description: 'HelmVersion is the version of Helm to target. If not + supplied, the lowest _enabled Helm version_ will be targeted. Valid + HelmVersion values are: "v2", "v3"' + enum: + - v2 + - v3 + type: string + maxHistory: + description: MaxHistory is the maximum amount of revisions to keep + for the Helm release. If not supplied, it defaults to 10. + type: integer + releaseName: + description: ReleaseName is the name of the The Helm release. If not + supplied, it will be generated by affixing the namespace to the + resource name. + type: string + resetValues: + description: ResetValues will mark this Helm release to reset the + values to the defaults of the targeted chart before performing an + upgrade. Not explicitly setting this to `false` equals to `true` + due to the declarative nature of the operator. + type: boolean + rollback: + description: The rollback settings for this Helm release. + properties: + disableHooks: + description: DisableHooks will mark this Helm release to prevent + hooks from running during the rollback. + type: boolean + enable: + description: Enable will mark this Helm release for rollbacks. + type: boolean + force: + description: Force will mark this Helm release to `--force` rollbacks. + This forces the resource updates through delete/recreate if + needed. + type: boolean + maxRetries: + description: MaxRetries is the maximum amount of upgrade retries + the operator should make before bailing. + format: int64 + type: integer + recreate: + description: Recreate will mark this Helm release to `--recreate-pods` + for if applicable. This performs pod restarts. + type: boolean + retry: + description: Retry will mark this Helm release for upgrade retries + after a rollback. + type: boolean + timeout: + description: Timeout is the time to wait for any individual Kubernetes + operation (like Jobs for hooks) during rollback. + format: int64 + type: integer + wait: + description: Wait will mark this Helm release to wait until all + Pods, PVCs, Services, and minimum number of Pods of a Deployment, + StatefulSet, or ReplicaSet are in a ready state before marking + the release as successful. + type: boolean + type: object + skipCRDs: + description: SkipCRDs will mark this Helm release to skip the creation + of CRDs during a Helm 3 installation. + type: boolean + targetNamespace: + description: TargetNamespace overrides the targeted namespace for + the Helm release. The default namespace equals to the namespace + of the HelmRelease resource. + type: string + test: + description: The test settings for this Helm release. + properties: + cleanup: + description: Cleanup, when targeting Helm 2, determines whether + to delete test pods between each test run initiated by the Helm + Operator. + type: boolean + enable: + description: Enable will mark this Helm release for tests. + type: boolean + ignoreFailures: + description: IgnoreFailures will cause a Helm release to be rolled + back if it fails otherwise it will be left in a released state + type: boolean + timeout: + description: Timeout is the time to wait for any individual Kubernetes + operation (like Jobs for hooks) during test. + format: int64 + type: integer + type: object + timeout: + description: Timeout is the time to wait for any individual Kubernetes + operation (like Jobs for hooks) during installation and upgrade + operations. + format: int64 + type: integer + valueFileSecrets: + description: ValueFileSecrets holds the local name references to secrets. + DEPRECATED, use ValuesFrom.secretKeyRef instead. + items: + properties: + name: + type: string + required: + - name + type: object + type: array + values: + description: Values holds the values for this Helm release. + x-kubernetes-preserve-unknown-fields: true + valuesFrom: + items: + properties: + chartFileRef: + description: The reference to a local chart file with release + values. + properties: + optional: + description: Optional will mark this ChartFileSelector as + optional. The result of this are that operations are permitted + without the source, due to it e.g. being temporarily unavailable. + type: boolean + path: + description: Path is the file path to the source relative + to the chart root. + type: string + required: + - path + type: object + configMapKeyRef: + description: The reference to a config map with release values. + properties: + key: + type: string + name: + type: string + namespace: + type: string + optional: + type: boolean + required: + - name + type: object + externalSourceRef: + description: The reference to an external source with release + values. + properties: + optional: + description: Optional will mark this ExternalSourceSelector + as optional. The result of this are that operations are + permitted without the source, due to it e.g. being temporarily + unavailable. + type: boolean + url: + description: URL is the URL of the external source. + type: string + required: + - url + type: object + secretKeyRef: + description: The reference to a secret with release values. + properties: + key: + type: string + name: + type: string + namespace: + type: string + optional: + type: boolean + required: + - name + type: object + type: object + type: array + wait: + description: Wait will mark this Helm release to wait until all Pods, + PVCs, Services, and minimum number of Pods of a Deployment, StatefulSet, + or ReplicaSet are in a ready state before marking the release as + successful. + type: boolean + required: + - chart + type: object + status: + description: HelmReleaseStatus contains status information about an HelmRelease. + properties: + conditions: + description: Conditions contains observations of the resource's state, + e.g., has the chart which it refers to been fetched. + items: + properties: + lastTransitionTime: + description: LastTransitionTime is the timestamp corresponding + to the last status change of this condition. + format: date-time + type: string + lastUpdateTime: + description: LastUpdateTime is the timestamp corresponding to + the last status update of this condition. + format: date-time + type: string + message: + description: Message is a human readable description of the + details of the last transition, complementing reason. + type: string + reason: + description: Reason is a brief machine readable explanation + for the condition's last transition. + type: string + status: + description: Status of the condition, one of ('True', 'False', + 'Unknown'). + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: Type of the condition, one of ('ChartFetched', + 'Deployed', 'Released', 'RolledBack', 'Tested'). + enum: + - ChartFetched + - Deployed + - Released + - RolledBack + - Tested + type: string + required: + - status + - type + type: object + type: array + lastAttemptedRevision: + description: LastAttemptedRevision is the revision of the latest chart + sync, and may be of a failed release. + type: string + observedGeneration: + description: ObservedGeneration is the most recent generation observed + by the operator. + format: int64 + type: integer + phase: + description: Phase the release is in, one of ('ChartFetched', 'ChartFetchFailed', + 'Installing', 'Upgrading', 'Deployed', 'DeployFailed', 'Testing', + 'TestFailed', 'Tested', 'Succeeded', 'RollingBack', 'RolledBack', + 'RollbackFailed') + enum: + - ChartFetched + - ChartFetchFailed + - Installing + - Upgrading + - Deployed + - DeployFailed + - Testing + - TestFailed + - Tested + - Succeeded + - Failed + - RollingBack + - RolledBack + - RollbackFailed + type: string + releaseName: + description: ReleaseName is the name as either supplied or generated. + type: string + releaseStatus: + description: ReleaseStatus is the status as given by Helm for the + release managed by this resource. + type: string + revision: + description: Revision holds the Git hash or version of the chart currently + deployed. + type: string + rollbackCount: + description: RollbackCount records the amount of rollback attempts + made, it is incremented after a rollback failure and reset after + a successful upgrade or revision change. + format: int64 + type: integer + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + listKind: "" + plural: "" + singular: "" + conditions: [] + storedVersions: [] \ No newline at end of file diff --git a/src/test/resources/crds/th2-dictionary-crd.yaml b/src/test/resources/crds/th2-dictionary-crd.yaml index 7207685c..a6928fe3 100644 --- a/src/test/resources/crds/th2-dictionary-crd.yaml +++ b/src/test/resources/crds/th2-dictionary-crd.yaml @@ -1,7 +1,6 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: - creationTimestamp: null name: th2dictionaries.th2.exactpro.com spec: conversion: diff --git a/src/test/resources/log4j2.properties b/src/test/resources/log4j2.properties index 901ce45a..8009fbc8 100644 --- a/src/test/resources/log4j2.properties +++ b/src/test/resources/log4j2.properties @@ -8,7 +8,10 @@ appender.console.name = ConsoleLogger appender.console.layout.type = PatternLayout appender.console.layout.pattern = %d{dd MMM yyyy HH:mm:ss,SSS} %-6p [%-15t] %c - %m%n +logger.th2.name= com.exactpro.th2 +logger.th2.level= DEBUG + # Root logger level -rootLogger.level = DEBUG +rootLogger.level = INFO # Root logger referring to console appender rootLogger.appenderRef.stdout.ref = ConsoleLogger From 9986b5e87d61a19be080f1a34833f71cf6148e33 Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Thu, 12 Sep 2024 15:39:44 +0400 Subject: [PATCH 27/42] [TH2-5226] added integration-tests.yml --- .github/workflows/integration-tests.yml | 26 ++++++++ build.gradle | 22 +++---- .../th2/infraoperator/Th2CrdController.java | 2 +- .../th2/infraoperator/util/RabbitMQUtils.kt | 2 +- .../integration/IntegrationTest.kt | 65 +++++++++++-------- .../integration/TestKubernetesUtils.kt | 16 +++-- .../integration/TestRabbitMQUtils.kt | 8 ++- 7 files changed, 95 insertions(+), 46 deletions(-) create mode 100644 .github/workflows/integration-tests.yml diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml new file mode 100644 index 00000000..0f113e34 --- /dev/null +++ b/.github/workflows/integration-tests.yml @@ -0,0 +1,26 @@ +name: "Run integration tests for LDP" + +on: + push: + branches: + - '*' + +jobs: + tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 'zulu' '11' + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: '11' + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + - name: Build with Gradle + run: ./gradlew --info clean integrationTest + - uses: actions/upload-artifact@v3 + if: failure() + with: + name: integration-test-results + path: build/reports/tests/integrationTest/ \ No newline at end of file diff --git a/build.gradle b/build.gradle index e14e6b00..a8949b10 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ plugins { id 'application' - id 'checkstyle' - alias(libs.plugins.detekt) +// id 'checkstyle' +// alias(libs.plugins.detekt) alias(libs.plugins.kotlin) alias(libs.plugins.th2.component) } @@ -17,15 +17,15 @@ repositories { mavenCentral() } -checkstyle { - toolVersion = "10.12.4" -} +//checkstyle { +// toolVersion = "10.12.4" +//} -detekt { - buildUponDefaultConfig = true - autoCorrect = true - config.setFrom("$rootDir/config/detekt/detekt.yml") -} +//detekt { +// buildUponDefaultConfig = true +// autoCorrect = true +// config.setFrom("$rootDir/config/detekt/detekt.yml") +//} dependencies { implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml" @@ -68,7 +68,7 @@ dependencies { testImplementation libs.awaitility testImplementation libs.strikt.core - detektPlugins libs.detekt.formatting +// detektPlugins libs.detekt.formatting } wrapper { diff --git a/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java b/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java index bffe70f1..f97d8b66 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java +++ b/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java @@ -44,8 +44,8 @@ public void start() { PrometheusServer.start(); OperatorMetrics.resetCacheErrors(); try { - RabbitMQContext.declareTopicExchange(); RabbitMQUtils.deleteRabbitMQRubbish(); + RabbitMQContext.declareTopicExchange(); watchManager.addTarget(MstoreHelmTh2Op::new); watchManager.addTarget(EstoreHelmTh2Op::new); diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt index 0781c4f2..a14b9b1a 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt @@ -85,11 +85,11 @@ internal fun ResourceHolder.filterRubbishResources( namespacePrefixes: Set, topicExchange: String, ): ResourceHolder = apply { - exchanges.remove(topicExchange) // FIXME: topic exchange should be declare after each namespace creation val namespaces: Set = client.namespaces(namespacePrefixes) if (namespaces.isEmpty()) { return@apply } + exchanges.remove(topicExchange) // FIXME: topic exchange should be declare after each namespace creation K_LOGGER.debug { "Search RabbitMQ resources in $namespaces namespaces" } // exchanges.remove(topicExchange) FIXME: uncomment this line when topic exchange is declared after each namespace creation diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt index e8579141..07cbed71 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt @@ -84,6 +84,7 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.Timeout import org.junit.jupiter.api.io.TempDir import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.ValueSource @@ -133,6 +134,7 @@ class IntegrationTest { private lateinit var controller: Th2CrdController @BeforeAll + @Timeout(30_000) fun beforeAll( @TempDir tempDir: Path, ) { @@ -169,6 +171,7 @@ class IntegrationTest { } @AfterAll + @Timeout(30_000) fun afterAll() { if (this::kubeClient.isInitialized) { kubeClient.close() @@ -182,11 +185,16 @@ class IntegrationTest { } @BeforeEach + @Timeout(30_000) fun beforeEach() { val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() kubeClient.createNamespace(TH2_NAMESPACE) kubeClient.createRabbitMQSecret(TH2_NAMESPACE, gitHash) - kubeClient.createRabbitMQAppConfigCfgMap(TH2_NAMESPACE, gitHash, createRabbitMQConfig(rabbitMQContainer, TH2_NAMESPACE)) + kubeClient.createRabbitMQAppConfigCfgMap( + TH2_NAMESPACE, + gitHash, + createRabbitMQConfig(rabbitMQContainer, TH2_NAMESPACE) + ) rabbitMQClient.assertUser(TH2_NAMESPACE, RABBIT_MQ_V_HOST, RABBIT_MQ_NAMESPACE_PERMISSIONS) rabbitMQClient.assertExchange(toExchangeName(TH2_NAMESPACE), DIRECT, RABBIT_MQ_V_HOST) @@ -201,6 +209,7 @@ class IntegrationTest { } @AfterEach + @Timeout(30_000) fun afterEach() { kubeClient.deleteNamespace(TH2_NAMESPACE, 1, MINUTES) // FIXME: Secret not found "th2-test:Secret/rabbitMQ" @@ -214,6 +223,7 @@ class IntegrationTest { } @Test + @Timeout(30_000) fun `add mstore`() { val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() kubeClient.createTh2Mstore(TH2_NAMESPACE, MESSAGE_STORAGE_BOX_ALIAS, createAnnotations(gitHash, TEST_CONTENT)) @@ -223,6 +233,7 @@ class IntegrationTest { } @Test + @Timeout(30_000) fun `add estore`() { val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() kubeClient.createTh2Estore(TH2_NAMESPACE, EVENT_STORAGE_BOX_ALIAS, createAnnotations(gitHash, TEST_CONTENT)) @@ -236,18 +247,19 @@ class IntegrationTest { ) } - @ParameterizedTest - @ValueSource(strings = ["th2-core-component", "th2-core-component-more-than-$NAME_LENGTH_LIMIT-character"] ) /** * Max name length is limited by [HelmRelease.NAME_LENGTH_LIMIT] */ + @Timeout(30_000) + @ParameterizedTest + @ValueSource(strings = ["th2-core-component", "th2-core-component-more-than-$NAME_LENGTH_LIMIT-character"]) fun `add core component (min configuration)`(name: String) { val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() val spec = """ imageName: $IMAGE imageVersion: $VERSION type: th2-rpt-data-provider - """.trimIndent() + """.trimIndent() kubeClient.createTh2CoreBox( TH2_NAMESPACE, @@ -266,18 +278,19 @@ class IntegrationTest { ) } - @ParameterizedTest - @ValueSource(strings = ["th2-component", "th2-component-more-than-$NAME_LENGTH_LIMIT-character"] ) /** * Max name length is limited by [HelmRelease.NAME_LENGTH_LIMIT] */ + @Timeout(30_000) + @ParameterizedTest + @ValueSource(strings = ["th2-component", "th2-component-more-than-$NAME_LENGTH_LIMIT-character"]) fun `add component (min configuration)`(name: String) { val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() val spec = """ imageName: $IMAGE imageVersion: $VERSION type: th2-codec - """.trimIndent() + """.trimIndent() kubeClient.createTh2Box( TH2_NAMESPACE, @@ -297,12 +310,13 @@ class IntegrationTest { } // @Test // FIXME Failure executing: POST at: https://localhost:33114/apis/th2.exactpro.com/v2/th2dictionaries. Message: Not Found. + @Timeout(30_000) fun `add dictionary (short name)`() { val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() val name = "th2-dictionary" val spec = """ data: $DICTIONARY_CONTENT - """.trimIndent() + """.trimIndent() kubeClient.createTh2Dictionary( TH2_NAMESPACE, @@ -314,8 +328,8 @@ class IntegrationTest { } // @Test + @Timeout(30_000) fun `create job`() { - } companion object { @@ -358,23 +372,23 @@ class IntegrationTest { chart = ChartSpec(), namespacePrefixes = setOf(TH2_PREFIX), rabbitMQManagement = - RabbitMQManagementConfig( - host = rabbitMQ.host, - managementPort = rabbitMQ.httpPort, - applicationPort = rabbitMQ.amqpPort, - vhostName = RABBIT_MQ_V_HOST, - exchangeName = RABBIT_MQ_TOPIC_EXCHANGE, - username = rabbitMQ.adminUsername, - password = rabbitMQ.adminPassword, - persistence = true, - schemaPermissions = RABBIT_MQ_NAMESPACE_PERMISSIONS, - ), + RabbitMQManagementConfig( + host = rabbitMQ.host, + managementPort = rabbitMQ.httpPort, + applicationPort = rabbitMQ.amqpPort, + vhostName = RABBIT_MQ_V_HOST, + exchangeName = RABBIT_MQ_TOPIC_EXCHANGE, + username = rabbitMQ.adminUsername, + password = rabbitMQ.adminPassword, + persistence = true, + schemaPermissions = RABBIT_MQ_NAMESPACE_PERMISSIONS, + ), prometheusConfiguration = - PrometheusConfiguration( - "0.0.0.0", - "9752", - false.toString(), - ), + PrometheusConfiguration( + "0.0.0.0", + "9752", + false.toString(), + ), ) private fun createRabbitMQConfig( @@ -563,7 +577,6 @@ class IntegrationTest { getValue("workers") isEqualTo 5 } getValue("services").isA>().isEmpty() - } getValue(IS_JOB_ALIAS) isEqualTo false getValue(SECRET_PATHS_CONFIG_ALIAS).isA>().isEmpty() diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt index fdc887f3..54679a82 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt @@ -132,10 +132,13 @@ fun KubernetesClient.createTh2Mstore( resource( Th2Mstore().apply { metadata = createMeta(name, namespace, annotations) - spec = YAML_MAPPER.readValue(""" + spec = YAML_MAPPER.readValue( + """ imageName: ghcr.io/th2-net/th2-mstore imageVersion: 0.0.0 - """.trimIndent(), Th2Spec::class.java) + """.trimIndent(), + Th2Spec::class.java + ) } ).create() } @@ -148,10 +151,13 @@ fun KubernetesClient.createTh2Estore( resource( Th2Estore().apply { metadata = createMeta(name, namespace, annotations) - spec = YAML_MAPPER.readValue(""" + spec = YAML_MAPPER.readValue( + """ imageName: ghcr.io/th2-net/th2-estore imageVersion: 0.0.0 - """.trimIndent(), Th2Spec::class.java) + """.trimIndent(), + Th2Spec::class.java + ) } ).create() } @@ -201,7 +207,7 @@ fun KubernetesClient.createTh2Dictionary( fun KubernetesClient.awaitHelmRelease( namespace: String, name: String, - timeout: Long = 2000, + timeout: Long = 5_000, unit: TimeUnit = TimeUnit.MILLISECONDS, ): HelmRelease { await("awaitHelmRelease ($name)") diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt index c5dbb937..c09793c1 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt @@ -45,7 +45,11 @@ fun Client.assertUser( val userPermissions = this.permissions.firstOrNull { it.user == user } assertNotNull(userPermissions, "User permission '$user' isn't found") assertEquals(vHost, userPermissions.vhost, "User permission '$user' has incorrect vHost") - assertEquals(permissions.configure, userPermissions.configure, "User permission '$user' has incorrect configure permission") + assertEquals( + permissions.configure, + userPermissions.configure, + "User permission '$user' has incorrect configure permission" + ) assertEquals(permissions.read, userPermissions.read, "User permission '$user' has incorrect read permission") assertEquals(permissions.write, userPermissions.write, "User permission '$user' has incorrect write permission") } @@ -196,4 +200,4 @@ fun Client.assertNoQueue( await("assertQueue('$queue')") .timeout(timeout, unit) .until { queues.firstOrNull { it.name == queue } == null } -} \ No newline at end of file +} From 3410e217a05d7ae2a295a9a63d622845e73168ce Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Thu, 12 Sep 2024 18:26:29 +0400 Subject: [PATCH 28/42] [TH2-5226] added dictionary test --- .../spec/dictionary/Th2Dictionary.java | 5 ++-- .../integration/IntegrationTest.kt | 23 +++++++++++---- .../integration/TestKubernetesUtils.kt | 29 +++++++++++++++++-- 3 files changed, 47 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/dictionary/Th2Dictionary.java b/src/main/java/com/exactpro/th2/infraoperator/spec/dictionary/Th2Dictionary.java index ae5cb859..0f88387a 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/spec/dictionary/Th2Dictionary.java +++ b/src/main/java/com/exactpro/th2/infraoperator/spec/dictionary/Th2Dictionary.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package com.exactpro.th2.infraoperator.spec.dictionary; import com.exactpro.th2.infraoperator.spec.helmrelease.InstantiableMap; +import io.fabric8.kubernetes.api.model.Namespaced; import io.fabric8.kubernetes.client.CustomResource; import io.fabric8.kubernetes.model.annotation.Group; import io.fabric8.kubernetes.model.annotation.Kind; @@ -25,6 +26,6 @@ @Group("th2.exactpro.com") @Version("v2") @Kind("Th2Dictionary") -public class Th2Dictionary extends CustomResource { +public class Th2Dictionary extends CustomResource implements Namespaced { } diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt index 07cbed71..dc11124f 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt @@ -57,6 +57,7 @@ import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandle import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.GRPC_ROUTER_CM_NAME import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.LOGGING_CM_NAME import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.MQ_ROUTER_CM_NAME +import com.exactpro.th2.infraoperator.operator.manager.impl.Th2DictionaryEventHandler.DICTIONARY_SUFFIX import com.exactpro.th2.infraoperator.spec.helmrelease.HelmRelease import com.exactpro.th2.infraoperator.spec.helmrelease.HelmRelease.NAME_LENGTH_LIMIT import com.exactpro.th2.infraoperator.spec.shared.PrometheusConfiguration @@ -220,6 +221,7 @@ class IntegrationTest { rabbitMQClient.assertNoUser(TH2_NAMESPACE) kubeClient.awaitNoHelmRelease(TH2_NAMESPACE) + kubeClient.awaitNoConfigMap(TH2_NAMESPACE) } @Test @@ -252,7 +254,7 @@ class IntegrationTest { */ @Timeout(30_000) @ParameterizedTest - @ValueSource(strings = ["th2-core-component", "th2-core-component-more-than-$NAME_LENGTH_LIMIT-character"]) + @ValueSource(strings = ["th2-core-component", "th2-core-component-more-than-$NAME_LENGTH_LIMIT-characters"]) fun `add core component (min configuration)`(name: String) { val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() val spec = """ @@ -283,7 +285,7 @@ class IntegrationTest { */ @Timeout(30_000) @ParameterizedTest - @ValueSource(strings = ["th2-component", "th2-component-more-than-$NAME_LENGTH_LIMIT-character"]) + @ValueSource(strings = ["th2-component", "th2-component-more-than-$NAME_LENGTH_LIMIT-characters"]) fun `add component (min configuration)`(name: String) { val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() val spec = """ @@ -309,7 +311,7 @@ class IntegrationTest { ) } -// @Test // FIXME Failure executing: POST at: https://localhost:33114/apis/th2.exactpro.com/v2/th2dictionaries. Message: Not Found. + @Test @Timeout(30_000) fun `add dictionary (short name)`() { val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() @@ -318,13 +320,24 @@ class IntegrationTest { data: $DICTIONARY_CONTENT """.trimIndent() + val annotations = createAnnotations(gitHash, spec.hashCode().toString()) kubeClient.createTh2Dictionary( TH2_NAMESPACE, name, - createAnnotations(gitHash, spec.hashCode().toString()), + annotations, spec ) - kubeClient.awaitHelmRelease(TH2_NAMESPACE, name).assertMinCfg(name) + kubeClient.awaitConfigMap(TH2_NAMESPACE, "$name$DICTIONARY_SUFFIX").also { configMap -> + expectThat(configMap) { + get { metadata }.and { + get { this.annotations } isEqualTo annotations + } + get { data }.isA>().and { + hasSize(1) + getValue("$name$DICTIONARY_SUFFIX") isEqualTo DICTIONARY_CONTENT + } + } + } } // @Test diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt index 54679a82..f6f515dc 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt @@ -112,6 +112,29 @@ fun KubernetesClient.createConfigMap( ).create() } +fun KubernetesClient.awaitConfigMap( + namespace: String, + name: String, + timeout: Long = 5_000, + unit: TimeUnit = TimeUnit.MILLISECONDS, +): ConfigMap { + await("awaitConfigMap ($name)") + .timeout(timeout, unit) + .until { resources(ConfigMap::class.java).inNamespace(namespace).withName(name).get() != null } + + return resources(ConfigMap::class.java).inNamespace(namespace).withName(name).get() +} + +fun KubernetesClient.awaitNoConfigMap( + namespace: String, + timeout: Long = 5_000, + unit: TimeUnit = TimeUnit.MILLISECONDS, +) { + await("awaitNoConfigMap") + .timeout(timeout, unit) + .until { resources(ConfigMap::class.java).inNamespace(namespace).list().items.isEmpty() } +} + fun KubernetesClient.deleteConfigMap( namespace: String, name: String, @@ -198,9 +221,9 @@ fun KubernetesClient.createTh2Dictionary( ) { resource( Th2Dictionary().apply { - this.metadata = createMeta(name, namespace, annotations) - this.spec = YAML_MAPPER.readValue(spec, Th2DictionarySpec::class.java) - } + this.metadata = createMeta(name, namespace, annotations) + this.spec = YAML_MAPPER.readValue(spec, Th2DictionarySpec::class.java) + } ).create() } From a8d0ef3a2f53e07addec62eb0ca2e2a4e8110a10 Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Fri, 13 Sep 2024 12:01:03 +0400 Subject: [PATCH 29/42] [TH2-5226] added 'disable component (min configuration)' test --- .../operator/AbstractTh2Operator.java | 11 +- .../infraoperator/util/HelmReleaseUtils.java | 7 +- .../integration/IntegrationTest.kt | 73 +++++++--- .../integration/TestKubernetesUtils.kt | 89 +++++++++++- .../integration/TestRabbitMQUtils.kt | 127 ++++++++---------- 5 files changed, 207 insertions(+), 100 deletions(-) diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/AbstractTh2Operator.java b/src/main/java/com/exactpro/th2/infraoperator/operator/AbstractTh2Operator.java index 8526ed0b..a798f283 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/AbstractTh2Operator.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/AbstractTh2Operator.java @@ -36,6 +36,7 @@ import io.fabric8.kubernetes.client.KubernetesClientException; import io.fabric8.kubernetes.client.Watcher; import io.fabric8.kubernetes.client.WatcherException; +import io.fabric8.kubernetes.client.dsl.Resource; import io.prometheus.client.Histogram; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -153,13 +154,15 @@ protected void processEvent(Action action, CR resource) throws IOException { if (resource.getSpec().getDisabled()) { try { - logger.info("Resource \"{}\" has been disabled, executing DELETE action", resourceLabel); deletedEvent(resource); - kubClient.resources(HelmRelease.class) - .inNamespace(extractNamespace(resource)).withName(extractName(resource)).delete(); + Resource helmRelease = kubClient.resources(HelmRelease.class) + .inNamespace(extractNamespace(resource)).withName(extractName(resource)); + String helmReleaseLabel = CustomResourceUtils.annotationFor(helmRelease.get()); + helmRelease.delete(); + logger.info("Resource \"{}\" has been deleted", helmReleaseLabel); resource.getStatus().disabled("Resource has been disabled"); + logger.info("Resource \"{}\" has been disabled, executing DELETE action", resourceLabel); updateStatus(resource); - logger.info("Resource \"{}\" has been deleted", resourceLabel); } catch (Exception e) { resource.getStatus().failed("Unknown error"); updateStatus(resource); diff --git a/src/main/java/com/exactpro/th2/infraoperator/util/HelmReleaseUtils.java b/src/main/java/com/exactpro/th2/infraoperator/util/HelmReleaseUtils.java index e2b697dc..2803e7b8 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/util/HelmReleaseUtils.java +++ b/src/main/java/com/exactpro/th2/infraoperator/util/HelmReleaseUtils.java @@ -39,7 +39,12 @@ import java.util.Set; import java.util.stream.Collectors; -import static com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.*; +import static com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.DICTIONARIES_ALIAS; +import static com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.ENABLED_ALIAS; +import static com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.EXTENDED_SETTINGS_ALIAS; +import static com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.EXTERNAL_BOX_ALIAS; +import static com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.MQ_QUEUE_CONFIG_ALIAS; +import static com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.SERVICE_ALIAS; import static com.exactpro.th2.infraoperator.util.CustomResourceUtils.annotationFor; import static com.exactpro.th2.infraoperator.util.JsonUtils.JSON_MAPPER; import static com.exactpro.th2.infraoperator.util.JsonUtils.YAML_MAPPER; diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt index dc11124f..583e5416 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt @@ -61,6 +61,7 @@ import com.exactpro.th2.infraoperator.operator.manager.impl.Th2DictionaryEventHa import com.exactpro.th2.infraoperator.spec.helmrelease.HelmRelease import com.exactpro.th2.infraoperator.spec.helmrelease.HelmRelease.NAME_LENGTH_LIMIT import com.exactpro.th2.infraoperator.spec.shared.PrometheusConfiguration +import com.exactpro.th2.infraoperator.spec.shared.status.RolloutPhase import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.createEstoreQueue import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.createMstoreQueue import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext @@ -107,7 +108,6 @@ import java.nio.file.Files import java.nio.file.Path import java.util.UUID import java.util.concurrent.TimeUnit.MINUTES -import java.util.concurrent.TimeUnit.SECONDS import java.util.concurrent.atomic.AtomicLong import kotlin.io.path.absolutePathString import kotlin.io.path.createDirectories @@ -136,9 +136,7 @@ class IntegrationTest { @BeforeAll @Timeout(30_000) - fun beforeAll( - @TempDir tempDir: Path, - ) { + fun beforeAll(@TempDir tempDir: Path) { this.tempDir = tempDir configDir = tempDir.resolve("cfg") kubeConfig = configDir.resolve("kube-config.yaml") @@ -215,13 +213,17 @@ class IntegrationTest { kubeClient.deleteNamespace(TH2_NAMESPACE, 1, MINUTES) // FIXME: Secret not found "th2-test:Secret/rabbitMQ" - rabbitMQClient.assertNoQueue(createEstoreQueue(TH2_NAMESPACE), 1, SECONDS) - rabbitMQClient.assertNoQueue(createMstoreQueue(TH2_NAMESPACE), 1, SECONDS) + rabbitMQClient.assertNoQueue(createEstoreQueue(TH2_NAMESPACE)) + rabbitMQClient.assertNoQueue(createMstoreQueue(TH2_NAMESPACE)) rabbitMQClient.assertNoExchange(toExchangeName(TH2_NAMESPACE)) rabbitMQClient.assertNoUser(TH2_NAMESPACE) - kubeClient.awaitNoHelmRelease(TH2_NAMESPACE) - kubeClient.awaitNoConfigMap(TH2_NAMESPACE) + kubeClient.awaitNoHelmReleases(TH2_NAMESPACE) + kubeClient.awaitNoConfigMaps(TH2_NAMESPACE) + kubeClient.awaitNoTh2Estores(TH2_NAMESPACE) + kubeClient.awaitNoTh2Mstores(TH2_NAMESPACE) + kubeClient.awaitNoTh2CoreBoxes(TH2_NAMESPACE) + kubeClient.awaitNoTh2Boxes(TH2_NAMESPACE) } @Test @@ -249,9 +251,6 @@ class IntegrationTest { ) } - /** - * Max name length is limited by [HelmRelease.NAME_LENGTH_LIMIT] - */ @Timeout(30_000) @ParameterizedTest @ValueSource(strings = ["th2-core-component", "th2-core-component-more-than-$NAME_LENGTH_LIMIT-characters"]) @@ -280,9 +279,6 @@ class IntegrationTest { ) } - /** - * Max name length is limited by [HelmRelease.NAME_LENGTH_LIMIT] - */ @Timeout(30_000) @ParameterizedTest @ValueSource(strings = ["th2-component", "th2-component-more-than-$NAME_LENGTH_LIMIT-characters"]) @@ -294,12 +290,31 @@ class IntegrationTest { type: th2-codec """.trimIndent() - kubeClient.createTh2Box( - TH2_NAMESPACE, - name, - createAnnotations(gitHash, spec.hashCode().toString()), - spec + kubeClient.createTh2Box(TH2_NAMESPACE, name, createAnnotations(gitHash, spec.hashCode().toString()), spec) + kubeClient.awaitHelmRelease(TH2_NAMESPACE, hashNameIfNeeded(name)).assertMinCfg(name) + rabbitMQClient.assertBindings( + createEstoreQueue(TH2_NAMESPACE), + RABBIT_MQ_V_HOST, + setOf( + "link[$TH2_NAMESPACE:$EVENT_STORAGE_BOX_ALIAS:$EVENT_STORAGE_PIN_ALIAS]", + "key[$TH2_NAMESPACE:$name:$EVENT_STORAGE_PIN_ALIAS]" + ) ) + } + + @Timeout(30_000) + @ParameterizedTest + @ValueSource(strings = ["th2-component", "th2-component-more-than-$NAME_LENGTH_LIMIT-characters"]) + fun `disable component (min configuration)`(name: String) { + var gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() + var spec = """ + imageName: $IMAGE + imageVersion: $VERSION + type: th2-codec + disabled: false + """.trimIndent() + + kubeClient.createTh2Box(TH2_NAMESPACE, name, createAnnotations(gitHash, spec.hashCode().toString()), spec) kubeClient.awaitHelmRelease(TH2_NAMESPACE, hashNameIfNeeded(name)).assertMinCfg(name) rabbitMQClient.assertBindings( createEstoreQueue(TH2_NAMESPACE), @@ -309,6 +324,26 @@ class IntegrationTest { "key[$TH2_NAMESPACE:$name:$EVENT_STORAGE_PIN_ALIAS]" ) ) + + gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() + spec = """ + imageName: $IMAGE + imageVersion: $VERSION + type: th2-codec + disabled: true + """.trimIndent() + + kubeClient.modifyTh2Box(TH2_NAMESPACE, name, createAnnotations(gitHash, spec.hashCode().toString()), spec) + kubeClient.awaitNoHelmRelease(TH2_NAMESPACE, name) + kubeClient.awaitPhase(TH2_NAMESPACE, name, RolloutPhase.DISABLED) + rabbitMQClient.assertBindings( + createEstoreQueue(TH2_NAMESPACE), + RABBIT_MQ_V_HOST, + setOf( + "link[$TH2_NAMESPACE:$EVENT_STORAGE_BOX_ALIAS:$EVENT_STORAGE_PIN_ALIAS]", + "key[$TH2_NAMESPACE:$name:$EVENT_STORAGE_PIN_ALIAS]" + ) + ) } @Test diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt index f6f515dc..e60a3f17 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt @@ -25,6 +25,7 @@ import com.exactpro.th2.infraoperator.spec.dictionary.Th2DictionarySpec import com.exactpro.th2.infraoperator.spec.estore.Th2Estore import com.exactpro.th2.infraoperator.spec.helmrelease.HelmRelease import com.exactpro.th2.infraoperator.spec.mstore.Th2Mstore +import com.exactpro.th2.infraoperator.spec.shared.status.RolloutPhase import com.exactpro.th2.infraoperator.util.JsonUtils.YAML_MAPPER import io.fabric8.kubernetes.api.model.ConfigMap import io.fabric8.kubernetes.api.model.Namespace @@ -125,12 +126,12 @@ fun KubernetesClient.awaitConfigMap( return resources(ConfigMap::class.java).inNamespace(namespace).withName(name).get() } -fun KubernetesClient.awaitNoConfigMap( +fun KubernetesClient.awaitNoConfigMaps( namespace: String, timeout: Long = 5_000, unit: TimeUnit = TimeUnit.MILLISECONDS, ) { - await("awaitNoConfigMap") + await("awaitNoConfigMaps") .timeout(timeout, unit) .until { resources(ConfigMap::class.java).inNamespace(namespace).list().items.isEmpty() } } @@ -147,6 +148,18 @@ fun KubernetesClient.deleteConfigMap( ?.awaitDeleteResource("deleteConfigMap('$namespace/$name')", timeout, unit) } +fun KubernetesClient.awaitPhase( + namespace: String, + name: String, + phase: RolloutPhase, + timeout: Long = 5_000, + unit: TimeUnit = TimeUnit.MILLISECONDS, +) { + await("awaitStatus ($name $phase)") + .timeout(timeout, unit) + .until { resources(Th2Box::class.java)?.inNamespace(namespace)?.withName(name)?.get()?.status?.phase == phase } +} + fun KubernetesClient.createTh2Mstore( namespace: String, name: String, @@ -166,6 +179,16 @@ fun KubernetesClient.createTh2Mstore( ).create() } +fun KubernetesClient.awaitNoTh2Mstores( + namespace: String, + timeout: Long = 5_000, + unit: TimeUnit = TimeUnit.MILLISECONDS, +) { + await("awaitNoTh2Mstores") + .timeout(timeout, unit) + .until { resources(Th2Mstore::class.java).inNamespace(namespace).list().items.isEmpty() } +} + fun KubernetesClient.createTh2Estore( namespace: String, name: String, @@ -185,6 +208,16 @@ fun KubernetesClient.createTh2Estore( ).create() } +fun KubernetesClient.awaitNoTh2Estores( + namespace: String, + timeout: Long = 5_000, + unit: TimeUnit = TimeUnit.MILLISECONDS, +) { + await("awaitNoTh2Estores") + .timeout(timeout, unit) + .until { resources(Th2Estore::class.java).inNamespace(namespace).list().items.isEmpty() } +} + fun KubernetesClient.createTh2CoreBox( namespace: String, name: String, @@ -199,6 +232,16 @@ fun KubernetesClient.createTh2CoreBox( ).create() } +fun KubernetesClient.awaitNoTh2CoreBoxes( + namespace: String, + timeout: Long = 5_000, + unit: TimeUnit = TimeUnit.MILLISECONDS, +) { + await("awaitNoTh2CoreBoxes") + .timeout(timeout, unit) + .until { resources(Th2CoreBox::class.java).inNamespace(namespace).list().items.isEmpty() } +} + fun KubernetesClient.createTh2Box( namespace: String, name: String, @@ -213,6 +256,31 @@ fun KubernetesClient.createTh2Box( ).create() } +fun KubernetesClient.modifyTh2Box( + namespace: String, + name: String, + annotations: Map, + spec: String, +) { + resource( + resources(Th2Box::class.java).inNamespace(namespace).withName(name).get().apply { + this.metadata.annotations.putAll(annotations) + this.metadata.generation += 1 + this.spec = YAML_MAPPER.readValue(spec, Th2Spec::class.java) + } + ).update() +} + +fun KubernetesClient.awaitNoTh2Boxes( + namespace: String, + timeout: Long = 5_000, + unit: TimeUnit = TimeUnit.MILLISECONDS, +) { + await("awaitNoTh2Boxes") + .timeout(timeout, unit) + .until { resources(Th2Box::class.java).inNamespace(namespace).list().items.isEmpty() } +} + fun KubernetesClient.createTh2Dictionary( namespace: String, name: String, @@ -240,16 +308,27 @@ fun KubernetesClient.awaitHelmRelease( return resources(HelmRelease::class.java).inNamespace(namespace).withName(name).get() } -fun KubernetesClient.awaitNoHelmRelease( +fun KubernetesClient.awaitNoHelmReleases( namespace: String, - timeout: Long = 2000, + timeout: Long = 5_000, unit: TimeUnit = TimeUnit.MILLISECONDS, ) { - await("awaitNoHelmRelease") + await("awaitNoHelmReleases") .timeout(timeout, unit) .until { resources(HelmRelease::class.java).inNamespace(namespace).list().items.isEmpty() } } +fun KubernetesClient.awaitNoHelmRelease( + namespace: String, + name: String, + timeout: Long = 5_000, + unit: TimeUnit = TimeUnit.MILLISECONDS, +) { + await("awaitNoHelmRelease ($name)") + .timeout(timeout, unit) + .until { resources(HelmRelease::class.java).inNamespace(namespace).withName(name).get() == null } +} + private fun Deletable.awaitDeleteResource( alias: String, timeout: Long, diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt index c09793c1..00ce9b4a 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt @@ -32,7 +32,7 @@ fun Client.assertUser( user: String, vHost: String, permissions: RabbitMQNamespacePermissions, - timeout: Long = 1_000, + timeout: Long = 5_000, unit: TimeUnit = TimeUnit.MILLISECONDS, ) { await("assertUser('$user')") @@ -48,7 +48,7 @@ fun Client.assertUser( assertEquals( permissions.configure, userPermissions.configure, - "User permission '$user' has incorrect configure permission" + "User permission '$user' has incorrect configure permission", ) assertEquals(permissions.read, userPermissions.read, "User permission '$user' has incorrect read permission") assertEquals(permissions.write, userPermissions.write, "User permission '$user' has incorrect write permission") @@ -56,7 +56,7 @@ fun Client.assertUser( fun Client.assertNoUser( user: String, - timeout: Long = 1_000, + timeout: Long = 5_000, unit: TimeUnit = TimeUnit.MILLISECONDS, ) { await("assertNoUser('$user')") @@ -68,7 +68,7 @@ fun Client.assertExchange( exchange: String, type: String, vHost: String, - timeout: Long = 1_000, + timeout: Long = 5_000, unit: TimeUnit = TimeUnit.MILLISECONDS, ) { await("assertExchange('$exchange')") @@ -87,7 +87,7 @@ fun Client.assertExchange( fun Client.assertNoExchange( exchange: String, - timeout: Long = 1_000, + timeout: Long = 5_000, unit: TimeUnit = TimeUnit.MILLISECONDS, ) { await("assertNoExchange('$exchange')") @@ -99,7 +99,7 @@ fun Client.assertQueue( queue: String, type: String, vHost: String, - timeout: Long = 1_000, + timeout: Long = 5_000, unit: TimeUnit = TimeUnit.MILLISECONDS, ): QueueInfo { await("assertQueue('$queue')") @@ -120,81 +120,66 @@ fun Client.assertBindings( queue: String, vHost: String, routingKeys: Set = emptySet(), - timeout: Long = 1_000, + timeout: Long = 5_000, unit: TimeUnit = TimeUnit.MILLISECONDS, ) { await("assertBindings('$queue'), bindings: $routingKeys") .timeout(timeout, unit) - .untilAsserted { - val queueBindings = getQueueBindings(vHost, queue) - when { - routingKeys.isEmpty() -> assertTrue( - queueBindings.isEmpty(), - "Bindings isn't empty for queue '$queue', actual: $queueBindings" - ) - else -> assertAll( - buildList { - add { - assertEquals( - routingKeys.size, - queueBindings.size, - "Bindings number is incorrect for queue '$queue', actual: $queueBindings" - ) - } - routingKeys.forEach { routingKey -> - add { - val queueBinding = assertNotNull( - queueBindings.singleOrNull { it.routingKey == routingKey }, - "Queue '$queue' doesn't contain routing key, actual: $queueBindings" - ) - assertAll( - { - assertEquals( - vHost, - queueBinding.vhost, - "Binding has incorrect vHost for routing key '$routingKey' in queue '$queue'", - ) - }, - { - assertEquals( - emptyMap(), - queueBinding.arguments, - "Binding has arguments for routing key '$routingKey' in queue '$queue'", - ) - }, - { - assertEquals( - routingKey.replace("[", "%5B").replace(":", "%3A").replace("]", "%5D"), - queueBinding.propertiesKey, - "Binding has 'propertiesKey' for routing key '$routingKey' in queue '$queue'", - ) - }, - { - assertEquals( - queue, - queueBinding.destination, - "Binding has incorrect 'destination' for routing key '$routingKey' in queue '$queue'", - ) - }, - { - assertEquals( - DestinationType.QUEUE, - queueBinding.destinationType, - "Binding has incorrect 'destinationType' for routing key '$routingKey' in queue '$queue'", - ) - }, - ) - } - } - } + .until { getQueueBindings(vHost, queue).size == routingKeys.size } + + val queueBindings = getQueueBindings(vHost, queue) + assertAll(routingKeys.map { routingKey -> + { + val queueBinding = + assertNotNull( + queueBindings.singleOrNull { it.routingKey == routingKey }, + "Queue '$queue' doesn't contain routing key, actual: $queueBindings", + ) + assertAll( + { + assertEquals( + vHost, + queueBinding.vhost, + "Binding has incorrect vHost for routing key '$routingKey' in queue '$queue'", + ) + }, + { + assertEquals( + emptyMap(), + queueBinding.arguments, + "Binding has arguments for routing key '$routingKey' in queue '$queue'", + ) + }, + { + assertEquals( + routingKey.replace("[", "%5B").replace(":", "%3A").replace("]", "%5D"), + queueBinding.propertiesKey, + "Binding has 'propertiesKey' for routing key '$routingKey' in queue '$queue'", + ) + }, + { + assertEquals( + queue, + queueBinding.destination, + "Binding has incorrect 'destination' for routing key '$routingKey' in queue '$queue'", + ) + }, + { + assertEquals( + DestinationType.QUEUE, + queueBinding.destinationType, + "Binding has incorrect 'destinationType' for routing key '$routingKey' in queue '$queue'", + ) + }, ) } - } + }, + ) } fun Client.assertNoQueue( queue: String, - timeout: Long = 1_000, + timeout: Long = 5_000, unit: TimeUnit = TimeUnit.MILLISECONDS, ) { await("assertQueue('$queue')") From 39704a4fe8d1982e29e194c1eff4c194827520c9 Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Fri, 13 Sep 2024 15:14:42 +0400 Subject: [PATCH 30/42] [TH2-5226] added 'enable component (min configuration)' test --- .../operator/AbstractTh2Operator.java | 50 ++++++++------- .../util/CustomResourceUtils.java | 46 ++------------ .../util/CustomResourceUtilsTests.java | 62 ++++-------------- .../integration/IntegrationTest.kt | 63 ++++++++++++++++--- .../integration/TestKubernetesUtils.kt | 4 +- .../integration/TestRabbitMQUtils.kt | 2 +- 6 files changed, 104 insertions(+), 123 deletions(-) diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/AbstractTh2Operator.java b/src/main/java/com/exactpro/th2/infraoperator/operator/AbstractTh2Operator.java index dd9eb554..8eb5bd82 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/AbstractTh2Operator.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/AbstractTh2Operator.java @@ -27,7 +27,6 @@ import com.exactpro.th2.infraoperator.spec.strategy.redeploy.NonTerminalException; import com.exactpro.th2.infraoperator.spec.strategy.redeploy.RetryableTaskQueue; import com.exactpro.th2.infraoperator.spec.strategy.redeploy.tasks.TriggerRedeployTask; -import com.exactpro.th2.infraoperator.util.CustomResourceUtils; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.OwnerReference; import io.fabric8.kubernetes.api.model.OwnerReferenceBuilder; @@ -48,6 +47,8 @@ import java.util.concurrent.ConcurrentHashMap; import static com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.ANTECEDENT_LABEL_KEY_ALIAS; +import static com.exactpro.th2.infraoperator.util.CustomResourceUtils.annotationFor; +import static com.exactpro.th2.infraoperator.util.CustomResourceUtils.extractHashedName; import static com.exactpro.th2.infraoperator.util.ExtractUtils.extractName; import static com.exactpro.th2.infraoperator.util.ExtractUtils.extractNamespace; import static com.exactpro.th2.infraoperator.util.KubernetesUtils.isNotActive; @@ -74,7 +75,7 @@ protected AbstractTh2Operator(KubernetesClient kubClient) { @Override public void eventReceived(Action action, CR resource) { - String resourceLabel = CustomResourceUtils.annotationFor(resource); + String resourceLabel = annotationFor(resource); try { var cachedFingerprint = fingerprints.get(resourceLabel); @@ -148,7 +149,7 @@ protected HelmRelease loadKubObj() { protected void processEvent(Action action, CR resource) throws IOException { - String resourceLabel = CustomResourceUtils.annotationFor(resource); + String resourceLabel = annotationFor(resource); logger.debug("Processing event {} for \"{}\"", action, resourceLabel); if (resource.getSpec().getDisabled()) { @@ -156,13 +157,20 @@ protected void processEvent(Action action, CR resource) throws IOException { deletedEvent(resource); // TODO: work with Th2CustomResource should be encapsulated somewhere // to void issues with choosing the right function to extract the name - Resource helmRelease = kubClient.resources(HelmRelease.class) - .inNamespace(extractNamespace(resource)) + String namespace = extractNamespace(resource); + String helmReleaseName = extractHashedName(resource); + Resource helmReleaseResource = kubClient.resources(HelmRelease.class) + .inNamespace(namespace) // name must be hashed if it exceeds the limit - .withName(CustomResourceUtils.extractHashedName(resource)); - String helmReleaseLabel = CustomResourceUtils.annotationFor(helmRelease.get()); - helmRelease.delete(); - logger.info("Resource \"{}\" has been deleted", helmReleaseLabel); + .withName(helmReleaseName); + HelmRelease helmRelease = helmReleaseResource.get(); + if (helmRelease == null) { + logger.info("Resource \"{}\" hasn't been deleted because it already doesn't exist", annotationFor(namespace, helmReleaseName, HelmRelease.class.getSimpleName())); + } else { + String helmReleaseLabel = annotationFor(helmRelease); + helmReleaseResource.delete(); + logger.info("Resource \"{}\" has been deleted", helmReleaseLabel); + } resource.getStatus().disabled("Resource has been disabled"); logger.info("Resource \"{}\" has been disabled, executing DELETE action", resourceLabel); updateStatus(resource); @@ -215,23 +223,23 @@ protected void deletedEvent(CR resource) { // kubernetes objects will be removed when custom resource removed (through 'OwnerReference') - String resourceLabel = CustomResourceUtils.annotationFor(resource); + String resourceLabel = annotationFor(resource); fingerprints.remove(resourceLabel); // The HelmRelease name is hashed if Th2CustomResource name exceeds the limit OperatorState.INSTANCE.removeHelmReleaseFromCache( - CustomResourceUtils.extractHashedName(resource), + extractHashedName(resource), extractNamespace(resource) ); } protected void errorEvent(CR resource) { - String resourceLabel = CustomResourceUtils.annotationFor(resource); + String resourceLabel = annotationFor(resource); fingerprints.remove(resourceLabel); } protected CR updateStatus(CR resource) { - String resourceLabel = CustomResourceUtils.annotationFor(resource); + String resourceLabel = annotationFor(resource); var resClient = getResourceClient().getInstance(); try { @@ -273,9 +281,7 @@ protected void setupAndCreateKubObj(CR resource) { createKubObj(extractNamespace(resource), kubObj); - logger.info("Generated \"{}\" based on \"{}\"" - , CustomResourceUtils.annotationFor(kubObj) - , CustomResourceUtils.annotationFor(resource)); + logger.info("Generated \"{}\" based on \"{}\"" , annotationFor(kubObj) , annotationFor(resource)); String kubObjType = kubObj.getClass().getSimpleName(); @@ -289,14 +295,14 @@ protected void setupKubObj(CR resource, HelmRelease helmRelease) { mapProperties(resource, helmRelease); logger.info("Generated additional properties from \"{}\" for the resource \"{}\"" - , CustomResourceUtils.annotationFor(resource) - , CustomResourceUtils.annotationFor(helmRelease)); + , annotationFor(resource) + , annotationFor(helmRelease)); helmRelease.getMetadata().setOwnerReferences(List.of(createOwnerReference(resource))); logger.info("Property \"OwnerReference\" with reference to \"{}\" has been set for the resource \"{}\"" - , CustomResourceUtils.annotationFor(resource) - , CustomResourceUtils.annotationFor(helmRelease)); + , annotationFor(resource) + , annotationFor(helmRelease)); } @@ -305,8 +311,8 @@ protected void mapProperties(CR resource, HelmRelease helmRelease) { var kubObjMD = helmRelease.getMetadata(); var resMD = resource.getMetadata(); String resName = resMD.getName(); - String annotation = CustomResourceUtils.annotationFor(resource); - String finalName = CustomResourceUtils.extractHashedName(resource); + String annotation = annotationFor(resource); + String finalName = extractHashedName(resource); if (!finalName.equals(resName)) { logger.info("Name of resource \"{}\" exceeds limitations. Will be substituted with \"{}\"", diff --git a/src/main/java/com/exactpro/th2/infraoperator/util/CustomResourceUtils.java b/src/main/java/com/exactpro/th2/infraoperator/util/CustomResourceUtils.java index a832eb6a..b458e2bd 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/util/CustomResourceUtils.java +++ b/src/main/java/com/exactpro/th2/infraoperator/util/CustomResourceUtils.java @@ -19,18 +19,14 @@ import com.exactpro.th2.infraoperator.spec.Th2CustomResource; import com.exactpro.th2.infraoperator.spec.helmrelease.HelmRelease; import io.fabric8.kubernetes.api.model.HasMetadata; -import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.List; -import java.util.Objects; import static com.exactpro.th2.infraoperator.util.ExtractUtils.extractName; -import static com.exactpro.th2.infraoperator.util.ExtractUtils.extractNamespace; -import static com.exactpro.th2.infraoperator.util.ExtractUtils.extractType; public class CustomResourceUtils { @@ -53,7 +49,7 @@ public static String annotationFor(String namespace, String kind, String resourc return String.format("%s:%s/%s(commit-%s)", namespace, kind, resourceName, commitHash); } - public static String annotationFor(HasMetadata resource) { + public static String annotationFor(@NotNull HasMetadata resource) { return annotationFor( resource.getMetadata().getNamespace(), resource.getKind(), @@ -62,47 +58,15 @@ public static String annotationFor(HasMetadata resource) { ); } - @Nullable - public static HelmRelease search(List helmReleases, Th2CustomResource resource) { - String resFullName = extractHashedFullName(resource); - return helmReleases.stream() - .filter(hr -> { - var owner = extractOwnerFullName(hr); - return Objects.nonNull(owner) && owner.equals(resFullName); - }).findFirst() - .orElse(null); - } - public static String extractHashedName(Th2CustomResource customResource) { return hashNameIfNeeded(extractName(customResource)); } - private static String extractHashedFullName(Th2CustomResource customResource) { - return concatFullName(extractNamespace(customResource), extractHashedName(customResource)); - } - - @Nullable - private static String extractOwnerFullName(HelmRelease helmRelease) { - var ownerReferences = helmRelease.getMetadata().getOwnerReferences(); - if (!ownerReferences.isEmpty()) { - return concatFullName(extractNamespace(helmRelease), ownerReferences.get(0).getName()); - } else { - logger.warn("[{}<{}>] doesn't have owner resource", extractType(helmRelease), extractFullName(helmRelease)); - return null; - } - } - - private static String extractFullName(HasMetadata obj) { - return concatFullName(extractNamespace(obj), extractName(obj)); - } - - private static String concatFullName(String namespace, String name) { - return namespace + "." + name; - } - public static String hashNameIfNeeded(String resName) { if (resName.length() >= HelmRelease.NAME_LENGTH_LIMIT) { - return digest(resName); + String result = digest(resName); + logger.debug("Resource '{}' name has been hashed to '{}'", resName, result); + return result; } return resName; } diff --git a/src/test/java/com/exactpro/th2/infraoperator/util/CustomResourceUtilsTests.java b/src/test/java/com/exactpro/th2/infraoperator/util/CustomResourceUtilsTests.java index de96bf7c..96c0259b 100644 --- a/src/test/java/com/exactpro/th2/infraoperator/util/CustomResourceUtilsTests.java +++ b/src/test/java/com/exactpro/th2/infraoperator/util/CustomResourceUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2021-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,74 +16,36 @@ package com.exactpro.th2.infraoperator.util; +import com.exactpro.th2.infraoperator.spec.Th2CustomResource; +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.ObjectMeta; +import org.junit.jupiter.api.Test; + import static com.exactpro.th2.infraoperator.spec.helmrelease.HelmRelease.NAME_LENGTH_LIMIT; import static com.exactpro.th2.infraoperator.util.CustomResourceUtils.digest; import static com.exactpro.th2.infraoperator.util.CustomResourceUtils.extractHashedName; -import static com.exactpro.th2.infraoperator.util.CustomResourceUtils.search; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import java.util.ArrayList; -import java.util.List; - -import org.junit.jupiter.api.Test; - -import com.exactpro.th2.infraoperator.spec.Th2CustomResource; -import com.exactpro.th2.infraoperator.spec.helmrelease.HelmRelease; - -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.fabric8.kubernetes.api.model.ObjectMeta; -import io.fabric8.kubernetes.api.model.OwnerReference; - class CustomResourceUtilsTests { - private final String sourceNamespace = "namespace"; + private static final String SOURCE_NAMESPACE = "namespace"; - private final String sourceName = "123456789_123456789_123456789"; + private static final String SOURCE_NAME = "123456789_123456789_123456789"; @Test void extractHashedNameTest() { - var currentName = sourceName.substring(0, NAME_LENGTH_LIMIT - 1); + var currentName = SOURCE_NAME.substring(0, NAME_LENGTH_LIMIT - 1); assertEquals(currentName, extractHashedName(createTh2CustomResource(currentName))); - currentName = sourceName.substring(0, NAME_LENGTH_LIMIT); + currentName = SOURCE_NAME.substring(0, NAME_LENGTH_LIMIT); assertEquals(digest(currentName), extractHashedName(createTh2CustomResource(currentName))); - currentName = sourceName.substring(0, NAME_LENGTH_LIMIT + 1); + currentName = SOURCE_NAME.substring(0, NAME_LENGTH_LIMIT + 1); assertEquals(digest(currentName), extractHashedName(createTh2CustomResource(currentName))); } - @Test - void searchTest() { - List helmReleases = new ArrayList<>(); - helmReleases.add(createHelmRelease(sourceName.substring(0, NAME_LENGTH_LIMIT - 1))); - helmReleases.add(createHelmRelease(digest(sourceName.substring(0, NAME_LENGTH_LIMIT)))); - helmReleases.add(createHelmRelease(digest(sourceName.substring(0, NAME_LENGTH_LIMIT + 1)))); - - var th2CustomResource = createTh2CustomResource(sourceName.substring(0, NAME_LENGTH_LIMIT - 1)); - assertEquals(helmReleases.get(0), search(helmReleases, th2CustomResource)); - - th2CustomResource = createTh2CustomResource(sourceName.substring(0, NAME_LENGTH_LIMIT)); - assertEquals(helmReleases.get(1), search(helmReleases, th2CustomResource)); - - th2CustomResource = createTh2CustomResource(sourceName.substring(0, NAME_LENGTH_LIMIT + 1)); - assertEquals(helmReleases.get(2), search(helmReleases, th2CustomResource)); - } - - private HelmRelease createHelmRelease(String name) { - HelmRelease helmRelease = createResource(HelmRelease.class, name); - - List ownerReferences = new ArrayList<>(); - var ownerReference = new OwnerReference(); - ownerReference.setName(name); - ownerReferences.add(ownerReference); - - when(helmRelease.getMetadata().getOwnerReferences()).thenReturn(ownerReferences); - - return helmRelease; - } - private Th2CustomResource createTh2CustomResource(String name) { return createResource(Th2CustomResource.class, name); } @@ -91,7 +53,7 @@ private Th2CustomResource createTh2CustomResource(String name) { private T createResource(Class instanceClass, String name) { T resource = mock(instanceClass); ObjectMeta metaData = mock(ObjectMeta.class); - when(metaData.getNamespace()).thenReturn(sourceNamespace); + when(metaData.getNamespace()).thenReturn(SOURCE_NAMESPACE); when(metaData.getName()).thenReturn(name); when(resource.getMetadata()).thenReturn(metaData); diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt index 583e5416..c71c9b77 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt @@ -58,10 +58,15 @@ import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandle import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.LOGGING_CM_NAME import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.MQ_ROUTER_CM_NAME import com.exactpro.th2.infraoperator.operator.manager.impl.Th2DictionaryEventHandler.DICTIONARY_SUFFIX +import com.exactpro.th2.infraoperator.spec.box.Th2Box +import com.exactpro.th2.infraoperator.spec.corebox.Th2CoreBox +import com.exactpro.th2.infraoperator.spec.estore.Th2Estore import com.exactpro.th2.infraoperator.spec.helmrelease.HelmRelease import com.exactpro.th2.infraoperator.spec.helmrelease.HelmRelease.NAME_LENGTH_LIMIT +import com.exactpro.th2.infraoperator.spec.mstore.Th2Mstore import com.exactpro.th2.infraoperator.spec.shared.PrometheusConfiguration -import com.exactpro.th2.infraoperator.spec.shared.status.RolloutPhase +import com.exactpro.th2.infraoperator.spec.shared.status.RolloutPhase.DISABLED +import com.exactpro.th2.infraoperator.spec.shared.status.RolloutPhase.SUCCEEDED import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.createEstoreQueue import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.createMstoreQueue import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext @@ -231,6 +236,7 @@ class IntegrationTest { fun `add mstore`() { val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() kubeClient.createTh2Mstore(TH2_NAMESPACE, MESSAGE_STORAGE_BOX_ALIAS, createAnnotations(gitHash, TEST_CONTENT)) + kubeClient.awaitPhase(TH2_NAMESPACE, MESSAGE_STORAGE_BOX_ALIAS, Th2Mstore::class.java, SUCCEEDED) kubeClient.awaitHelmRelease(TH2_NAMESPACE, MESSAGE_STORAGE_BOX_ALIAS) // FIXME: estore should have binding // println("Bindings: ${rabbitMQClient.getQueueBindings(RABBIT_MQ_V_HOST, createEstoreQueue(TH2_NAMESPACE))}") @@ -241,6 +247,7 @@ class IntegrationTest { fun `add estore`() { val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() kubeClient.createTh2Estore(TH2_NAMESPACE, EVENT_STORAGE_BOX_ALIAS, createAnnotations(gitHash, TEST_CONTENT)) + kubeClient.awaitPhase(TH2_NAMESPACE, EVENT_STORAGE_BOX_ALIAS, Th2Estore::class.java, SUCCEEDED) kubeClient.awaitHelmRelease(TH2_NAMESPACE, EVENT_STORAGE_BOX_ALIAS) rabbitMQClient.assertBindings( createEstoreQueue(TH2_NAMESPACE), @@ -262,12 +269,8 @@ class IntegrationTest { type: th2-rpt-data-provider """.trimIndent() - kubeClient.createTh2CoreBox( - TH2_NAMESPACE, - name, - createAnnotations(gitHash, spec.hashCode().toString()), - spec - ) + kubeClient.createTh2CoreBox(TH2_NAMESPACE, name, createAnnotations(gitHash, spec.hashCode().toString()), spec) + kubeClient.awaitPhase(TH2_NAMESPACE, name, Th2CoreBox::class.java, SUCCEEDED) kubeClient.awaitHelmRelease(TH2_NAMESPACE, hashNameIfNeeded(name)).assertMinCfg(name) rabbitMQClient.assertBindings( createEstoreQueue(TH2_NAMESPACE), @@ -291,6 +294,7 @@ class IntegrationTest { """.trimIndent() kubeClient.createTh2Box(TH2_NAMESPACE, name, createAnnotations(gitHash, spec.hashCode().toString()), spec) + kubeClient.awaitPhase(TH2_NAMESPACE, name, Th2Box::class.java, SUCCEEDED) kubeClient.awaitHelmRelease(TH2_NAMESPACE, hashNameIfNeeded(name)).assertMinCfg(name) rabbitMQClient.assertBindings( createEstoreQueue(TH2_NAMESPACE), @@ -315,6 +319,7 @@ class IntegrationTest { """.trimIndent() kubeClient.createTh2Box(TH2_NAMESPACE, name, createAnnotations(gitHash, spec.hashCode().toString()), spec) + kubeClient.awaitPhase(TH2_NAMESPACE, name, Th2Box::class.java, SUCCEEDED) kubeClient.awaitHelmRelease(TH2_NAMESPACE, hashNameIfNeeded(name)).assertMinCfg(name) rabbitMQClient.assertBindings( createEstoreQueue(TH2_NAMESPACE), @@ -334,8 +339,50 @@ class IntegrationTest { """.trimIndent() kubeClient.modifyTh2Box(TH2_NAMESPACE, name, createAnnotations(gitHash, spec.hashCode().toString()), spec) + kubeClient.awaitPhase(TH2_NAMESPACE, name, Th2Box::class.java, DISABLED) + kubeClient.awaitNoHelmRelease(TH2_NAMESPACE, name) + rabbitMQClient.assertBindings( + createEstoreQueue(TH2_NAMESPACE), + RABBIT_MQ_V_HOST, + setOf( + "link[$TH2_NAMESPACE:$EVENT_STORAGE_BOX_ALIAS:$EVENT_STORAGE_PIN_ALIAS]", + "key[$TH2_NAMESPACE:$name:$EVENT_STORAGE_PIN_ALIAS]" + ) + ) + } + + @Timeout(30_000) + @ParameterizedTest + @ValueSource(strings = ["th2-component", "th2-component-more-than-$NAME_LENGTH_LIMIT-characters"]) + fun `enabled component (min configuration)`(name: String) { + var gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() + var spec = """ + imageName: $IMAGE + imageVersion: $VERSION + type: th2-codec + disabled: true + """.trimIndent() + + kubeClient.createTh2Box(TH2_NAMESPACE, name, createAnnotations(gitHash, spec.hashCode().toString()), spec) + kubeClient.awaitPhase(TH2_NAMESPACE, name, Th2Box::class.java, DISABLED) kubeClient.awaitNoHelmRelease(TH2_NAMESPACE, name) - kubeClient.awaitPhase(TH2_NAMESPACE, name, RolloutPhase.DISABLED) + rabbitMQClient.assertBindings( + createEstoreQueue(TH2_NAMESPACE), + RABBIT_MQ_V_HOST, + setOf("link[$TH2_NAMESPACE:$EVENT_STORAGE_BOX_ALIAS:$EVENT_STORAGE_PIN_ALIAS]") + ) + + gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() + spec = """ + imageName: $IMAGE + imageVersion: $VERSION + type: th2-codec + disabled: false + """.trimIndent() + + kubeClient.modifyTh2Box(TH2_NAMESPACE, name, createAnnotations(gitHash, spec.hashCode().toString()), spec) + kubeClient.awaitPhase(TH2_NAMESPACE, name, Th2Box::class.java, SUCCEEDED) + kubeClient.awaitHelmRelease(TH2_NAMESPACE, hashNameIfNeeded(name)).assertMinCfg(name) rabbitMQClient.assertBindings( createEstoreQueue(TH2_NAMESPACE), RABBIT_MQ_V_HOST, diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt index e60a3f17..3377aeff 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt @@ -17,6 +17,7 @@ package com.exactpro.th2.infraoperator.integration import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.SECRET_TYPE_OPAQUE +import com.exactpro.th2.infraoperator.spec.Th2CustomResource import com.exactpro.th2.infraoperator.spec.Th2Spec import com.exactpro.th2.infraoperator.spec.box.Th2Box import com.exactpro.th2.infraoperator.spec.corebox.Th2CoreBox @@ -151,13 +152,14 @@ fun KubernetesClient.deleteConfigMap( fun KubernetesClient.awaitPhase( namespace: String, name: String, + resourceType: Class, phase: RolloutPhase, timeout: Long = 5_000, unit: TimeUnit = TimeUnit.MILLISECONDS, ) { await("awaitStatus ($name $phase)") .timeout(timeout, unit) - .until { resources(Th2Box::class.java)?.inNamespace(namespace)?.withName(name)?.get()?.status?.phase == phase } + .until { resources(resourceType)?.inNamespace(namespace)?.withName(name)?.get()?.status?.phase == phase } } fun KubernetesClient.createTh2Mstore( diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt index 00ce9b4a..7de1ed76 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt @@ -123,7 +123,7 @@ fun Client.assertBindings( timeout: Long = 5_000, unit: TimeUnit = TimeUnit.MILLISECONDS, ) { - await("assertBindings('$queue'), bindings: $routingKeys") + await("assertBindings('$queue'), routing keys: $routingKeys") .timeout(timeout, unit) .until { getQueueBindings(vHost, queue).size == routingKeys.size } From ff3beb6a523fef24a067fe6d3b4e1ea5617a452d Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Fri, 13 Sep 2024 16:16:55 +0400 Subject: [PATCH 31/42] [TH2-5226] refactored --- .../integration/IntegrationTest.kt | 339 +++++++++--------- .../integration/TestKubernetesUtils.kt | 193 +++------- 2 files changed, 221 insertions(+), 311 deletions(-) diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt index c71c9b77..bb644553 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt @@ -25,7 +25,6 @@ import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQConfig import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQConfig.Companion.CONFIG_MAP_RABBITMQ_PROP_NAME import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQManagementConfig import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQNamespacePermissions -import com.exactpro.th2.infraoperator.metrics.OperatorMetrics.KEY_DETECTION_TIME import com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.BOOK_CONFIG_ALIAS import com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.BOOK_NAME_ALIAS import com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.CHECKSUM_ALIAS @@ -73,14 +72,13 @@ import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQCont import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext.DIRECT import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext.TOPIC import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext.toExchangeName -import com.exactpro.th2.infraoperator.util.CustomResourceUtils.GIT_COMMIT_HASH import com.exactpro.th2.infraoperator.util.CustomResourceUtils.hashNameIfNeeded -import com.exactpro.th2.infraoperator.util.ExtractUtils.KEY_SOURCE_HASH import com.exactpro.th2.infraoperator.util.JsonUtils.JSON_MAPPER import com.exactpro.th2.infraoperator.util.JsonUtils.YAML_MAPPER import com.exactpro.th2.infraoperator.util.createKubernetesClient import com.fasterxml.jackson.module.kotlin.readValue import com.rabbitmq.http.client.Client +import io.fabric8.kubernetes.api.model.ConfigMap import io.fabric8.kubernetes.client.Config import io.fabric8.kubernetes.client.KubernetesClient import io.github.oshai.kotlinlogging.KotlinLogging @@ -88,6 +86,7 @@ import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance @@ -223,208 +222,235 @@ class IntegrationTest { rabbitMQClient.assertNoExchange(toExchangeName(TH2_NAMESPACE)) rabbitMQClient.assertNoUser(TH2_NAMESPACE) - kubeClient.awaitNoHelmReleases(TH2_NAMESPACE) - kubeClient.awaitNoConfigMaps(TH2_NAMESPACE) - kubeClient.awaitNoTh2Estores(TH2_NAMESPACE) - kubeClient.awaitNoTh2Mstores(TH2_NAMESPACE) - kubeClient.awaitNoTh2CoreBoxes(TH2_NAMESPACE) - kubeClient.awaitNoTh2Boxes(TH2_NAMESPACE) + kubeClient.awaitNoResources(TH2_NAMESPACE) + kubeClient.awaitNoResources(TH2_NAMESPACE) + kubeClient.awaitNoResources(TH2_NAMESPACE) + kubeClient.awaitNoResources(TH2_NAMESPACE) + kubeClient.awaitNoResources(TH2_NAMESPACE) + kubeClient.awaitNoResources(TH2_NAMESPACE) } - @Test - @Timeout(30_000) - fun `add mstore`() { - val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() - kubeClient.createTh2Mstore(TH2_NAMESPACE, MESSAGE_STORAGE_BOX_ALIAS, createAnnotations(gitHash, TEST_CONTENT)) - kubeClient.awaitPhase(TH2_NAMESPACE, MESSAGE_STORAGE_BOX_ALIAS, Th2Mstore::class.java, SUCCEEDED) - kubeClient.awaitHelmRelease(TH2_NAMESPACE, MESSAGE_STORAGE_BOX_ALIAS) - // FIXME: estore should have binding + @Nested + inner class Mstore { + @Test + @Timeout(30_000) + fun `add mstore`() { + val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() + val spec = """ + imageName: ghcr.io/th2-net/th2-mstore + imageVersion: 0.0.0 + """.trimIndent() + kubeClient.createTh2CustomResource(TH2_NAMESPACE, MESSAGE_STORAGE_BOX_ALIAS, gitHash, spec, ::Th2Mstore) + kubeClient.awaitPhase(TH2_NAMESPACE, MESSAGE_STORAGE_BOX_ALIAS, SUCCEEDED) + kubeClient.awaitResource(TH2_NAMESPACE, MESSAGE_STORAGE_BOX_ALIAS) + // FIXME: estore should have binding // println("Bindings: ${rabbitMQClient.getQueueBindings(RABBIT_MQ_V_HOST, createEstoreQueue(TH2_NAMESPACE))}") + } } - @Test - @Timeout(30_000) - fun `add estore`() { - val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() - kubeClient.createTh2Estore(TH2_NAMESPACE, EVENT_STORAGE_BOX_ALIAS, createAnnotations(gitHash, TEST_CONTENT)) - kubeClient.awaitPhase(TH2_NAMESPACE, EVENT_STORAGE_BOX_ALIAS, Th2Estore::class.java, SUCCEEDED) - kubeClient.awaitHelmRelease(TH2_NAMESPACE, EVENT_STORAGE_BOX_ALIAS) - rabbitMQClient.assertBindings( - createEstoreQueue(TH2_NAMESPACE), - RABBIT_MQ_V_HOST, - setOf( - "link[$TH2_NAMESPACE:$EVENT_STORAGE_BOX_ALIAS:$EVENT_STORAGE_PIN_ALIAS]", + @Nested + inner class Estore { + @Test + @Timeout(30_000) + fun `add estore`() { + val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() + val spec = """ + imageName: ghcr.io/th2-net/th2-estore + imageVersion: 0.0.0 + """.trimIndent() + kubeClient.createTh2CustomResource(TH2_NAMESPACE, EVENT_STORAGE_BOX_ALIAS, gitHash, spec, ::Th2Estore) + kubeClient.awaitPhase(TH2_NAMESPACE, EVENT_STORAGE_BOX_ALIAS, SUCCEEDED) + kubeClient.awaitResource(TH2_NAMESPACE, EVENT_STORAGE_BOX_ALIAS) + rabbitMQClient.assertBindings( + createEstoreQueue(TH2_NAMESPACE), + RABBIT_MQ_V_HOST, + setOf( + "link[$TH2_NAMESPACE:$EVENT_STORAGE_BOX_ALIAS:$EVENT_STORAGE_PIN_ALIAS]", + ) ) - ) + } } - @Timeout(30_000) - @ParameterizedTest - @ValueSource(strings = ["th2-core-component", "th2-core-component-more-than-$NAME_LENGTH_LIMIT-characters"]) - fun `add core component (min configuration)`(name: String) { - val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() - val spec = """ + @Nested + inner class CoreComponent { + @Timeout(30_000) + @ParameterizedTest + @ValueSource(strings = ["th2-core-component", "th2-core-component-more-than-$NAME_LENGTH_LIMIT-characters"]) + fun `add core component (min configuration)`(name: String) { + val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() + val spec = """ imageName: $IMAGE imageVersion: $VERSION type: th2-rpt-data-provider - """.trimIndent() - - kubeClient.createTh2CoreBox(TH2_NAMESPACE, name, createAnnotations(gitHash, spec.hashCode().toString()), spec) - kubeClient.awaitPhase(TH2_NAMESPACE, name, Th2CoreBox::class.java, SUCCEEDED) - kubeClient.awaitHelmRelease(TH2_NAMESPACE, hashNameIfNeeded(name)).assertMinCfg(name) - rabbitMQClient.assertBindings( - createEstoreQueue(TH2_NAMESPACE), - RABBIT_MQ_V_HOST, - setOf( - "link[$TH2_NAMESPACE:$EVENT_STORAGE_BOX_ALIAS:$EVENT_STORAGE_PIN_ALIAS]", - "key[$TH2_NAMESPACE:$name:$EVENT_STORAGE_PIN_ALIAS]" + """.trimIndent() + + kubeClient.createTh2CustomResource(TH2_NAMESPACE, name, gitHash, spec, ::Th2CoreBox) + kubeClient.awaitPhase(TH2_NAMESPACE, name, SUCCEEDED) + kubeClient.awaitResource(TH2_NAMESPACE, hashNameIfNeeded(name)).assertMinCfg(name) + rabbitMQClient.assertBindings( + createEstoreQueue(TH2_NAMESPACE), + RABBIT_MQ_V_HOST, + setOf( + "link[$TH2_NAMESPACE:$EVENT_STORAGE_BOX_ALIAS:$EVENT_STORAGE_PIN_ALIAS]", + "key[$TH2_NAMESPACE:$name:$EVENT_STORAGE_PIN_ALIAS]" + ) ) - ) + } } - @Timeout(30_000) - @ParameterizedTest - @ValueSource(strings = ["th2-component", "th2-component-more-than-$NAME_LENGTH_LIMIT-characters"]) - fun `add component (min configuration)`(name: String) { - val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() - val spec = """ + @Nested + inner class Component { + @Timeout(30_000) + @ParameterizedTest + @ValueSource(strings = ["th2-component", "th2-component-more-than-$NAME_LENGTH_LIMIT-characters"]) + fun `add component (min configuration)`(name: String) { + val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() + val spec = """ imageName: $IMAGE imageVersion: $VERSION type: th2-codec - """.trimIndent() - - kubeClient.createTh2Box(TH2_NAMESPACE, name, createAnnotations(gitHash, spec.hashCode().toString()), spec) - kubeClient.awaitPhase(TH2_NAMESPACE, name, Th2Box::class.java, SUCCEEDED) - kubeClient.awaitHelmRelease(TH2_NAMESPACE, hashNameIfNeeded(name)).assertMinCfg(name) - rabbitMQClient.assertBindings( - createEstoreQueue(TH2_NAMESPACE), - RABBIT_MQ_V_HOST, - setOf( - "link[$TH2_NAMESPACE:$EVENT_STORAGE_BOX_ALIAS:$EVENT_STORAGE_PIN_ALIAS]", - "key[$TH2_NAMESPACE:$name:$EVENT_STORAGE_PIN_ALIAS]" + """.trimIndent() + + kubeClient.createTh2CustomResource(TH2_NAMESPACE, name, gitHash, spec, ::Th2Box) + kubeClient.awaitPhase(TH2_NAMESPACE, name, SUCCEEDED) + kubeClient.awaitResource(TH2_NAMESPACE, hashNameIfNeeded(name)).assertMinCfg(name) + rabbitMQClient.assertBindings( + createEstoreQueue(TH2_NAMESPACE), + RABBIT_MQ_V_HOST, + setOf( + "link[$TH2_NAMESPACE:$EVENT_STORAGE_BOX_ALIAS:$EVENT_STORAGE_PIN_ALIAS]", + "key[$TH2_NAMESPACE:$name:$EVENT_STORAGE_PIN_ALIAS]" + ) ) - ) - } + } - @Timeout(30_000) - @ParameterizedTest - @ValueSource(strings = ["th2-component", "th2-component-more-than-$NAME_LENGTH_LIMIT-characters"]) - fun `disable component (min configuration)`(name: String) { - var gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() - var spec = """ + @Timeout(30_000) + @ParameterizedTest + @ValueSource(strings = ["th2-component", "th2-component-more-than-$NAME_LENGTH_LIMIT-characters"]) + fun `disable component (min configuration)`(name: String) { + var gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() + var spec = """ imageName: $IMAGE imageVersion: $VERSION type: th2-codec disabled: false - """.trimIndent() - - kubeClient.createTh2Box(TH2_NAMESPACE, name, createAnnotations(gitHash, spec.hashCode().toString()), spec) - kubeClient.awaitPhase(TH2_NAMESPACE, name, Th2Box::class.java, SUCCEEDED) - kubeClient.awaitHelmRelease(TH2_NAMESPACE, hashNameIfNeeded(name)).assertMinCfg(name) - rabbitMQClient.assertBindings( - createEstoreQueue(TH2_NAMESPACE), - RABBIT_MQ_V_HOST, - setOf( - "link[$TH2_NAMESPACE:$EVENT_STORAGE_BOX_ALIAS:$EVENT_STORAGE_PIN_ALIAS]", - "key[$TH2_NAMESPACE:$name:$EVENT_STORAGE_PIN_ALIAS]" + """.trimIndent() + + kubeClient.createTh2CustomResource(TH2_NAMESPACE, name, gitHash, spec, ::Th2Box) + kubeClient.awaitPhase(TH2_NAMESPACE, name, SUCCEEDED) + kubeClient.awaitResource(TH2_NAMESPACE, hashNameIfNeeded(name)).assertMinCfg(name) + rabbitMQClient.assertBindings( + createEstoreQueue(TH2_NAMESPACE), + RABBIT_MQ_V_HOST, + setOf( + "link[$TH2_NAMESPACE:$EVENT_STORAGE_BOX_ALIAS:$EVENT_STORAGE_PIN_ALIAS]", + "key[$TH2_NAMESPACE:$name:$EVENT_STORAGE_PIN_ALIAS]" + ) ) - ) - gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() - spec = """ + gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() + spec = """ imageName: $IMAGE imageVersion: $VERSION type: th2-codec disabled: true - """.trimIndent() - - kubeClient.modifyTh2Box(TH2_NAMESPACE, name, createAnnotations(gitHash, spec.hashCode().toString()), spec) - kubeClient.awaitPhase(TH2_NAMESPACE, name, Th2Box::class.java, DISABLED) - kubeClient.awaitNoHelmRelease(TH2_NAMESPACE, name) - rabbitMQClient.assertBindings( - createEstoreQueue(TH2_NAMESPACE), - RABBIT_MQ_V_HOST, - setOf( - "link[$TH2_NAMESPACE:$EVENT_STORAGE_BOX_ALIAS:$EVENT_STORAGE_PIN_ALIAS]", - "key[$TH2_NAMESPACE:$name:$EVENT_STORAGE_PIN_ALIAS]" + """.trimIndent() + + kubeClient.modifyTh2CustomResource(TH2_NAMESPACE, name, gitHash, spec) + kubeClient.awaitPhase(TH2_NAMESPACE, name, DISABLED) + kubeClient.awaitNoResource(TH2_NAMESPACE, name) + rabbitMQClient.assertBindings( + createEstoreQueue(TH2_NAMESPACE), + RABBIT_MQ_V_HOST, + setOf( + "link[$TH2_NAMESPACE:$EVENT_STORAGE_BOX_ALIAS:$EVENT_STORAGE_PIN_ALIAS]", + "key[$TH2_NAMESPACE:$name:$EVENT_STORAGE_PIN_ALIAS]" + ) ) - ) - } + } - @Timeout(30_000) - @ParameterizedTest - @ValueSource(strings = ["th2-component", "th2-component-more-than-$NAME_LENGTH_LIMIT-characters"]) - fun `enabled component (min configuration)`(name: String) { - var gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() - var spec = """ + @Timeout(30_000) + @ParameterizedTest + @ValueSource(strings = ["th2-component", "th2-component-more-than-$NAME_LENGTH_LIMIT-characters"]) + fun `enabled component (min configuration)`(name: String) { + var gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() + var spec = """ imageName: $IMAGE imageVersion: $VERSION type: th2-codec disabled: true """.trimIndent() - kubeClient.createTh2Box(TH2_NAMESPACE, name, createAnnotations(gitHash, spec.hashCode().toString()), spec) - kubeClient.awaitPhase(TH2_NAMESPACE, name, Th2Box::class.java, DISABLED) - kubeClient.awaitNoHelmRelease(TH2_NAMESPACE, name) - rabbitMQClient.assertBindings( - createEstoreQueue(TH2_NAMESPACE), - RABBIT_MQ_V_HOST, - setOf("link[$TH2_NAMESPACE:$EVENT_STORAGE_BOX_ALIAS:$EVENT_STORAGE_PIN_ALIAS]") - ) + kubeClient.createTh2CustomResource(TH2_NAMESPACE, name, gitHash, spec, ::Th2Box) + kubeClient.awaitPhase(TH2_NAMESPACE, name, DISABLED) + kubeClient.awaitNoResource(TH2_NAMESPACE, name) + rabbitMQClient.assertBindings( + createEstoreQueue(TH2_NAMESPACE), + RABBIT_MQ_V_HOST, + setOf("link[$TH2_NAMESPACE:$EVENT_STORAGE_BOX_ALIAS:$EVENT_STORAGE_PIN_ALIAS]") + ) - gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() - spec = """ + gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() + spec = """ imageName: $IMAGE imageVersion: $VERSION type: th2-codec disabled: false - """.trimIndent() - - kubeClient.modifyTh2Box(TH2_NAMESPACE, name, createAnnotations(gitHash, spec.hashCode().toString()), spec) - kubeClient.awaitPhase(TH2_NAMESPACE, name, Th2Box::class.java, SUCCEEDED) - kubeClient.awaitHelmRelease(TH2_NAMESPACE, hashNameIfNeeded(name)).assertMinCfg(name) - rabbitMQClient.assertBindings( - createEstoreQueue(TH2_NAMESPACE), - RABBIT_MQ_V_HOST, - setOf( - "link[$TH2_NAMESPACE:$EVENT_STORAGE_BOX_ALIAS:$EVENT_STORAGE_PIN_ALIAS]", - "key[$TH2_NAMESPACE:$name:$EVENT_STORAGE_PIN_ALIAS]" + """.trimIndent() + + kubeClient.modifyTh2CustomResource(TH2_NAMESPACE, name, gitHash, spec) + kubeClient.awaitPhase(TH2_NAMESPACE, name, SUCCEEDED) + kubeClient.awaitResource(TH2_NAMESPACE, hashNameIfNeeded(name)).assertMinCfg(name) + rabbitMQClient.assertBindings( + createEstoreQueue(TH2_NAMESPACE), + RABBIT_MQ_V_HOST, + setOf( + "link[$TH2_NAMESPACE:$EVENT_STORAGE_BOX_ALIAS:$EVENT_STORAGE_PIN_ALIAS]", + "key[$TH2_NAMESPACE:$name:$EVENT_STORAGE_PIN_ALIAS]" + ) ) - ) + } } - @Test - @Timeout(30_000) - fun `add dictionary (short name)`() { - val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() - val name = "th2-dictionary" - val spec = """ + @Nested + inner class Dictionary { + + @Test + @Timeout(30_000) + fun `add dictionary (short name)`() { + val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() + val name = "th2-dictionary" + val spec = """ data: $DICTIONARY_CONTENT """.trimIndent() - val annotations = createAnnotations(gitHash, spec.hashCode().toString()) - kubeClient.createTh2Dictionary( - TH2_NAMESPACE, - name, - annotations, - spec - ) - kubeClient.awaitConfigMap(TH2_NAMESPACE, "$name$DICTIONARY_SUFFIX").also { configMap -> - expectThat(configMap) { - get { metadata }.and { - get { this.annotations } isEqualTo annotations - } - get { data }.isA>().and { - hasSize(1) - getValue("$name$DICTIONARY_SUFFIX") isEqualTo DICTIONARY_CONTENT + val annotations = createAnnotations(gitHash, spec.hashCode().toString()) + kubeClient.createTh2Dictionary( + TH2_NAMESPACE, + name, + annotations, + spec + ) + kubeClient.awaitResource(TH2_NAMESPACE, "$name$DICTIONARY_SUFFIX").also { configMap -> + expectThat(configMap) { + get { metadata }.and { + get { this.annotations } isEqualTo annotations + } + get { data }.isA>().and { + hasSize(1) + getValue("$name$DICTIONARY_SUFFIX") isEqualTo DICTIONARY_CONTENT + } } } } } -// @Test - @Timeout(30_000) - fun `create job`() { + @Nested + inner class Job { + // @Test + @Timeout(30_000) + fun `create job`() { + } } companion object { @@ -522,15 +548,6 @@ class IntegrationTest { "Resource '$resourceName' isn't found" }.let(YAML_MAPPER::readValue) - private fun createAnnotations( - gitHash: String, - sourceHash: String, - ) = mapOf( - KEY_DETECTION_TIME to System.currentTimeMillis().toString(), - GIT_COMMIT_HASH to gitHash, - KEY_SOURCE_HASH to sourceHash.hashCode().toString(), - ) - private fun KubernetesClient.createRabbitMQSecret( namespace: String, gitHash: String, diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt index 3377aeff..f02f4b12 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt @@ -16,19 +16,18 @@ package com.exactpro.th2.infraoperator.integration +import com.exactpro.th2.infraoperator.metrics.OperatorMetrics.KEY_DETECTION_TIME import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.SECRET_TYPE_OPAQUE import com.exactpro.th2.infraoperator.spec.Th2CustomResource import com.exactpro.th2.infraoperator.spec.Th2Spec -import com.exactpro.th2.infraoperator.spec.box.Th2Box -import com.exactpro.th2.infraoperator.spec.corebox.Th2CoreBox import com.exactpro.th2.infraoperator.spec.dictionary.Th2Dictionary import com.exactpro.th2.infraoperator.spec.dictionary.Th2DictionarySpec -import com.exactpro.th2.infraoperator.spec.estore.Th2Estore -import com.exactpro.th2.infraoperator.spec.helmrelease.HelmRelease -import com.exactpro.th2.infraoperator.spec.mstore.Th2Mstore import com.exactpro.th2.infraoperator.spec.shared.status.RolloutPhase +import com.exactpro.th2.infraoperator.util.CustomResourceUtils.GIT_COMMIT_HASH +import com.exactpro.th2.infraoperator.util.ExtractUtils.KEY_SOURCE_HASH import com.exactpro.th2.infraoperator.util.JsonUtils.YAML_MAPPER import io.fabric8.kubernetes.api.model.ConfigMap +import io.fabric8.kubernetes.api.model.HasMetadata import io.fabric8.kubernetes.api.model.Namespace import io.fabric8.kubernetes.api.model.ObjectMeta import io.fabric8.kubernetes.api.model.Secret @@ -114,175 +113,94 @@ fun KubernetesClient.createConfigMap( ).create() } -fun KubernetesClient.awaitConfigMap( +inline fun KubernetesClient.awaitResource( namespace: String, name: String, timeout: Long = 5_000, unit: TimeUnit = TimeUnit.MILLISECONDS, -): ConfigMap { - await("awaitConfigMap ($name)") +): T { + await("awaitResource ($name ${T::class.java})") .timeout(timeout, unit) - .until { resources(ConfigMap::class.java).inNamespace(namespace).withName(name).get() != null } + .until { resources(T::class.java).inNamespace(namespace).withName(name).get() != null } - return resources(ConfigMap::class.java).inNamespace(namespace).withName(name).get() + return resources(T::class.java).inNamespace(namespace).withName(name).get() } -fun KubernetesClient.awaitNoConfigMaps( +inline fun KubernetesClient.awaitNoResources( namespace: String, timeout: Long = 5_000, unit: TimeUnit = TimeUnit.MILLISECONDS, ) { - await("awaitNoConfigMaps") + await("awaitNoConfigMaps (${T::class.java})") .timeout(timeout, unit) - .until { resources(ConfigMap::class.java).inNamespace(namespace).list().items.isEmpty() } + .until { resources(T::class.java).inNamespace(namespace).list().items.isEmpty() } } -fun KubernetesClient.deleteConfigMap( - namespace: String, - name: String, - timeout: Long = 200, - unit: TimeUnit = TimeUnit.MILLISECONDS, -) { - configMaps() - .inNamespace(namespace) - ?.withName(name) - ?.awaitDeleteResource("deleteConfigMap('$namespace/$name')", timeout, unit) -} - -fun KubernetesClient.awaitPhase( +inline fun KubernetesClient.awaitNoResource( namespace: String, name: String, - resourceType: Class, - phase: RolloutPhase, timeout: Long = 5_000, unit: TimeUnit = TimeUnit.MILLISECONDS, ) { - await("awaitStatus ($name $phase)") + await("awaitNoResource ($name ${T::class.java})") .timeout(timeout, unit) - .until { resources(resourceType)?.inNamespace(namespace)?.withName(name)?.get()?.status?.phase == phase } -} - -fun KubernetesClient.createTh2Mstore( - namespace: String, - name: String, - annotations: Map, -) { - resource( - Th2Mstore().apply { - metadata = createMeta(name, namespace, annotations) - spec = YAML_MAPPER.readValue( - """ - imageName: ghcr.io/th2-net/th2-mstore - imageVersion: 0.0.0 - """.trimIndent(), - Th2Spec::class.java - ) - } - ).create() + .until { resources(T::class.java).inNamespace(namespace).withName(name).get() == null } } -fun KubernetesClient.awaitNoTh2Mstores( - namespace: String, - timeout: Long = 5_000, - unit: TimeUnit = TimeUnit.MILLISECONDS, -) { - await("awaitNoTh2Mstores") - .timeout(timeout, unit) - .until { resources(Th2Mstore::class.java).inNamespace(namespace).list().items.isEmpty() } -} - -fun KubernetesClient.createTh2Estore( +fun KubernetesClient.deleteConfigMap( namespace: String, name: String, - annotations: Map, -) { - resource( - Th2Estore().apply { - metadata = createMeta(name, namespace, annotations) - spec = YAML_MAPPER.readValue( - """ - imageName: ghcr.io/th2-net/th2-estore - imageVersion: 0.0.0 - """.trimIndent(), - Th2Spec::class.java - ) - } - ).create() -} - -fun KubernetesClient.awaitNoTh2Estores( - namespace: String, - timeout: Long = 5_000, + timeout: Long = 200, unit: TimeUnit = TimeUnit.MILLISECONDS, ) { - await("awaitNoTh2Estores") - .timeout(timeout, unit) - .until { resources(Th2Estore::class.java).inNamespace(namespace).list().items.isEmpty() } + configMaps() + .inNamespace(namespace) + ?.withName(name) + ?.awaitDeleteResource("deleteConfigMap('$namespace/$name')", timeout, unit) } -fun KubernetesClient.createTh2CoreBox( +inline fun KubernetesClient.awaitPhase( namespace: String, name: String, - annotations: Map, - spec: String, -) { - resource( - Th2CoreBox().apply { - this.metadata = createMeta(name, namespace, annotations) - this.spec = YAML_MAPPER.readValue(spec, Th2Spec::class.java) - } - ).create() -} - -fun KubernetesClient.awaitNoTh2CoreBoxes( - namespace: String, + phase: RolloutPhase, timeout: Long = 5_000, unit: TimeUnit = TimeUnit.MILLISECONDS, ) { - await("awaitNoTh2CoreBoxes") + await("awaitStatus ($name ${T::class.java} $phase)") .timeout(timeout, unit) - .until { resources(Th2CoreBox::class.java).inNamespace(namespace).list().items.isEmpty() } + .until { resources(T::class.java)?.inNamespace(namespace)?.withName(name)?.get()?.status?.phase == phase } } -fun KubernetesClient.createTh2Box( +fun KubernetesClient.createTh2CustomResource( namespace: String, name: String, - annotations: Map, + gitHash: String, spec: String, + create: () -> Th2CustomResource, ) { resource( - Th2Box().apply { - this.metadata = createMeta(name, namespace, annotations) + create().apply { + this.metadata = createMeta(name, namespace, createAnnotations(gitHash, spec.hashCode().toString())) this.spec = YAML_MAPPER.readValue(spec, Th2Spec::class.java) } ).create() } -fun KubernetesClient.modifyTh2Box( +inline fun KubernetesClient.modifyTh2CustomResource( namespace: String, name: String, - annotations: Map, - spec: String, + gitHash: String, + spec: String ) { resource( - resources(Th2Box::class.java).inNamespace(namespace).withName(name).get().apply { - this.metadata.annotations.putAll(annotations) + resources(T::class.java).inNamespace(namespace).withName(name).get().apply { + this.metadata.annotations.putAll(createAnnotations(gitHash, spec.hashCode().toString())) this.metadata.generation += 1 this.spec = YAML_MAPPER.readValue(spec, Th2Spec::class.java) } ).update() } -fun KubernetesClient.awaitNoTh2Boxes( - namespace: String, - timeout: Long = 5_000, - unit: TimeUnit = TimeUnit.MILLISECONDS, -) { - await("awaitNoTh2Boxes") - .timeout(timeout, unit) - .until { resources(Th2Box::class.java).inNamespace(namespace).list().items.isEmpty() } -} - fun KubernetesClient.createTh2Dictionary( namespace: String, name: String, @@ -297,39 +215,14 @@ fun KubernetesClient.createTh2Dictionary( ).create() } -fun KubernetesClient.awaitHelmRelease( - namespace: String, - name: String, - timeout: Long = 5_000, - unit: TimeUnit = TimeUnit.MILLISECONDS, -): HelmRelease { - await("awaitHelmRelease ($name)") - .timeout(timeout, unit) - .until { resources(HelmRelease::class.java).inNamespace(namespace).withName(name).get() != null } - - return resources(HelmRelease::class.java).inNamespace(namespace).withName(name).get() -} - -fun KubernetesClient.awaitNoHelmReleases( - namespace: String, - timeout: Long = 5_000, - unit: TimeUnit = TimeUnit.MILLISECONDS, -) { - await("awaitNoHelmReleases") - .timeout(timeout, unit) - .until { resources(HelmRelease::class.java).inNamespace(namespace).list().items.isEmpty() } -} - -fun KubernetesClient.awaitNoHelmRelease( - namespace: String, - name: String, - timeout: Long = 5_000, - unit: TimeUnit = TimeUnit.MILLISECONDS, -) { - await("awaitNoHelmRelease ($name)") - .timeout(timeout, unit) - .until { resources(HelmRelease::class.java).inNamespace(namespace).withName(name).get() == null } -} +fun createAnnotations( + gitHash: String, + sourceHash: String, +) = mapOf( + KEY_DETECTION_TIME to System.currentTimeMillis().toString(), + GIT_COMMIT_HASH to gitHash, + KEY_SOURCE_HASH to sourceHash.hashCode().toString(), +) private fun Deletable.awaitDeleteResource( alias: String, From c1c9dcaeb0694e8e380624d3b304156660e524ad Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Fri, 13 Sep 2024 16:39:46 +0400 Subject: [PATCH 32/42] [TH2-5226] corrected after review --- .../operator/HelmReleaseTh2Op.java | 7 ++- .../operator/StoreHelmTh2Op.java | 3 +- .../manager/impl/ConfigMapEventHandler.java | 44 +++++++++---------- .../manager/impl/NamespaceEventHandler.java | 23 +++++----- .../integration/IntegrationTest.kt | 1 - 5 files changed, 37 insertions(+), 41 deletions(-) diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/HelmReleaseTh2Op.java b/src/main/java/com/exactpro/th2/infraoperator/operator/HelmReleaseTh2Op.java index 77ea675c..3af2a828 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/HelmReleaseTh2Op.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/HelmReleaseTh2Op.java @@ -324,8 +324,8 @@ protected void addedEvent(CR resource) throws IOException { String namespace = extractNamespace(resource); var lock = OperatorState.INSTANCE.getLock(namespace); + lock.lock(); try { - lock.lock(); DeclareQueueResolver.resolveAdd(resource); BindQueueLinkResolver.resolveDeclaredLinks(resource); BindQueueLinkResolver.resolveHiddenLinks(resource); @@ -342,9 +342,8 @@ protected void modifiedEvent(CR resource) throws IOException { String namespace = extractNamespace(resource); var lock = OperatorState.INSTANCE.getLock(namespace); + lock.lock(); try { - lock.lock(); - DeclareQueueResolver.resolveAdd(resource); BindQueueLinkResolver.resolveDeclaredLinks(resource); BindQueueLinkResolver.resolveHiddenLinks(resource); @@ -359,8 +358,8 @@ protected void modifiedEvent(CR resource) throws IOException { protected void deletedEvent(CR resource) { var lock = OperatorState.INSTANCE.getLock(extractNamespace(resource)); + lock.lock(); try { - lock.lock(); super.deletedEvent(resource); DeclareQueueResolver.resolveDelete(resource); } finally { diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/StoreHelmTh2Op.java b/src/main/java/com/exactpro/th2/infraoperator/operator/StoreHelmTh2Op.java index 02d03b38..dd9d293a 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/StoreHelmTh2Op.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/StoreHelmTh2Op.java @@ -38,9 +38,8 @@ private void nameCheck(CR resource) throws IOException { var msNamespace = extractNamespace(resource); var lock = OperatorState.INSTANCE.getLock(msNamespace); + lock.lock(); try { - lock.lock(); - var msName = extractName(resource); var stName = getStorageName(); diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/ConfigMapEventHandler.java b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/ConfigMapEventHandler.java index 1978a5c1..f5d14fca 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/ConfigMapEventHandler.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/ConfigMapEventHandler.java @@ -103,7 +103,7 @@ private ConfigMapMeta(String alias, String dataFileName) { } } - private static final Logger logger = LoggerFactory.getLogger(ConfigMapEventHandler.class); + private static final Logger LOGGER = LoggerFactory.getLogger(ConfigMapEventHandler.class); private KubernetesClient client; @@ -142,10 +142,10 @@ public void eventReceived(Action action, ConfigMap resource) { if (configMapName.equals(ConfigLoader.getConfig().getRabbitMQConfigMapName())) { try { - logger.info("Processing {} event for \"{}\"", action, resourceLabel); + LOGGER.info("Processing {} event for \"{}\"", action, resourceLabel); var lock = OperatorState.INSTANCE.getLock(namespace); + lock.lock(); try { - lock.lock(); OperatorConfig opConfig = ConfigLoader.getConfig(); ConfigMaps configMaps = ConfigMaps.INSTANCE; @@ -153,7 +153,7 @@ public void eventReceived(Action action, ConfigMap resource) { String configContent = resource.getData().get(RabbitMQConfig.CONFIG_MAP_RABBITMQ_PROP_NAME); if (StringUtils.isBlank(configContent)) { - logger.error("Key \"{}\" not found in \"{}\"", RabbitMQConfig.CONFIG_MAP_RABBITMQ_PROP_NAME, + LOGGER.error("Key \"{}\" not found in \"{}\"", RabbitMQConfig.CONFIG_MAP_RABBITMQ_PROP_NAME, resourceLabel); return; } @@ -166,19 +166,19 @@ public void eventReceived(Action action, ConfigMap resource) { Histogram.Timer processTimer = OperatorMetrics.getConfigMapEventTimer(resource); configMaps.setRabbitMQConfig4Namespace(namespace, newRabbitMQConfig); RabbitMQContext.setUpRabbitMqForNamespace(namespace); - logger.info("RabbitMQ ConfigMap has been updated in namespace \"{}\". Updating all boxes", + LOGGER.info("RabbitMQ ConfigMap has been updated in namespace \"{}\". Updating all boxes", namespace); DefaultWatchManager.getInstance().refreshBoxes(namespace); - logger.info("box-definition(s) have been updated"); + LOGGER.info("box-definition(s) have been updated"); processTimer.observeDuration(); } else { - logger.info("RabbitMQ ConfigMap data hasn't changed"); + LOGGER.info("RabbitMQ ConfigMap data hasn't changed"); } } finally { lock.unlock(); } } catch (Exception e) { - logger.error("Exception processing {} event for \"{}\"", action, resourceLabel, e); + LOGGER.error("Exception processing {} event for \"{}\"", action, resourceLabel, e); } } else if (configMapName.equals(BOOK_CONFIG_CM_NAME)) { updateDefaultBookName(action, namespace, resource, resourceLabel); @@ -192,7 +192,7 @@ public void eventReceived(Action action, ConfigMap resource) { private void updateConfigMap(Action action, String namespace, ConfigMap resource, final String cmName, String resourceLabel) { try { - logger.info("Processing {} event for \"{}\"", action, resourceLabel); + LOGGER.info("Processing {} event for \"{}\"", action, resourceLabel); if (isActionInvalid(action, resourceLabel)) { return; } @@ -202,8 +202,8 @@ private void updateConfigMap(Action action, String namespace, ConfigMap resource String dataFileName = titles.dataFileName; var lock = OperatorState.INSTANCE.getLock(namespace); + lock.lock(); try { - lock.lock(); String oldChecksum = OperatorState.INSTANCE.getConfigChecksum(namespace, alias); String newChecksum = ExtractUtils.fullSourceHash(resource); if (!newChecksum.equals(oldChecksum)) { @@ -214,27 +214,27 @@ private void updateConfigMap(Action action, String namespace, ConfigMap resource cmData = resource.getData().get(dataFileName); OperatorState.INSTANCE.putConfigData(namespace, alias, cmData); } - logger.info("\"{}\" has been updated. Updating all boxes", resourceLabel); + LOGGER.info("\"{}\" has been updated. Updating all boxes", resourceLabel); int refreshedBoxesCount = updateResourceChecksumAndData(namespace, newChecksum, cmData, alias); - logger.info("{} HelmRelease(s) have been updated", refreshedBoxesCount); + LOGGER.info("{} HelmRelease(s) have been updated", refreshedBoxesCount); processTimer.observeDuration(); } } finally { lock.unlock(); } } catch (Exception e) { - logger.error("Exception processing {} event for \"{}\"", action, resourceLabel, e); + LOGGER.error("Exception processing {} event for \"{}\"", action, resourceLabel, e); } } private boolean isActionInvalid(Action action, String resourceLabel) { boolean isInvalid = false; if (action == Action.DELETED) { - logger.error("DELETED action is not supported for \"{}\". ", resourceLabel); + LOGGER.error("DELETED action is not supported for \"{}\". ", resourceLabel); isInvalid = true; } else if (action == Action.ERROR) { - logger.error("Received ERROR action for \"{}\" Canceling update", resourceLabel); + LOGGER.error("Received ERROR action for \"{}\" Canceling update", resourceLabel); isInvalid = true; } return isInvalid; @@ -242,29 +242,29 @@ private boolean isActionInvalid(Action action, String resourceLabel) { private void updateDefaultBookName(Action action, String namespace, ConfigMap resource, String resourceLabel) { try { - logger.info("Processing {} event for \"{}\"", action, resourceLabel); + LOGGER.info("Processing {} event for \"{}\"", action, resourceLabel); if (isActionInvalid(action, resourceLabel)) { return; } var lock = OperatorState.INSTANCE.getLock(namespace); + lock.lock(); try { - lock.lock(); String oldBookName = OperatorState.INSTANCE.getBookName(namespace); String newBookName = resource.getData().get(DEFAULT_BOOK); if (!newBookName.equals(oldBookName)) { Histogram.Timer processTimer = OperatorMetrics.getConfigMapEventTimer(resource); OperatorState.INSTANCE.setBookName(namespace, newBookName); - logger.info("\"{}\" has been updated. Updating all boxes", resourceLabel); + LOGGER.info("\"{}\" has been updated. Updating all boxes", resourceLabel); DefaultWatchManager.getInstance().refreshBoxes(namespace); - logger.info("box-definition(s) have been updated"); + LOGGER.info("box-definition(s) have been updated"); processTimer.observeDuration(); } } finally { lock.unlock(); } } catch (Exception e) { - logger.error("Exception processing {} event for \"{}\"", action, resourceLabel, e); + LOGGER.error("Exception processing {} event for \"{}\"", action, resourceLabel, e); } } @@ -286,9 +286,9 @@ private int updateResourceChecksumAndData(String namespace, String checksum, Str config.put(CHECKSUM_ALIAS, checksum); hr.addComponentValue(key, config); - logger.debug("Updating \"{}\" resource", CustomResourceUtils.annotationFor(hr)); + LOGGER.debug("Updating \"{}\" resource", CustomResourceUtils.annotationFor(hr)); createKubObj(namespace, hr); - logger.debug("\"{}\" Updated", CustomResourceUtils.annotationFor(hr)); + LOGGER.debug("\"{}\" Updated", CustomResourceUtils.annotationFor(hr)); } return helmReleases.size(); } diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/NamespaceEventHandler.java b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/NamespaceEventHandler.java index ab380670..886328d1 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/NamespaceEventHandler.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/NamespaceEventHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2024Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,7 +35,7 @@ import static com.exactpro.th2.infraoperator.util.WatcherUtils.createExceptionHandler; public class NamespaceEventHandler implements ResourceEventHandler, Watcher { - private static final Logger logger = LoggerFactory.getLogger(NamespaceEventHandler.class); + private static final Logger LOGGER = LoggerFactory.getLogger(NamespaceEventHandler.class); private final EventQueue eventQueue; @@ -64,7 +64,7 @@ public void onAdd(Namespace namespace) { return; } - logger.debug("Received ADDED event for namespace: \"{}\"", namespace.getMetadata().getName()); + LOGGER.debug("Received ADDED event for namespace: \"{}\"", namespace.getMetadata().getName()); } @Override @@ -76,7 +76,7 @@ public void onUpdate(Namespace oldNamespace, Namespace newNamespace) { return; } - logger.debug("Received MODIFIED event for namespace: \"{}\"", newNamespace.getMetadata().getName()); + LOGGER.debug("Received MODIFIED event for namespace: \"{}\"", newNamespace.getMetadata().getName()); } @Override @@ -89,7 +89,7 @@ public void onDelete(Namespace namespace, boolean deletedFinalStateUnknown) { String resourceLabel = String.format("namespace:%s", namespaceName); String eventId = EventCounter.newEvent(); - logger.debug("Received DELETED event for namespace: \"{}\"", namespaceName); + LOGGER.debug("Received DELETED event for namespace: \"{}\"", namespaceName); eventQueue.addEvent(EventQueue.generateEvent( eventId, @@ -111,23 +111,22 @@ public void eventReceived(Action action, Namespace resource) { String resourceLabel = String.format("namespace:%s", namespaceName); + lock.lock(); try { - lock.lock(); - - logger.info("Processing {} event for namespace: \"{}\"", action, namespaceName); + LOGGER.info("Processing {} event for namespace: \"{}\"", action, namespaceName); RabbitMQContext.cleanupRabbit(namespaceName); - logger.info("Deleted namespace {}", namespaceName); + LOGGER.info("Deleted namespace {}", namespaceName); } catch (Exception e) { - logger.error("Exception processing event for \"{}\"", resourceLabel, e); + LOGGER.error("Exception processing event for \"{}\"", resourceLabel, e); } finally { lock.unlock(); } long duration = System.currentTimeMillis() - startDateTime; - logger.info("Event for \"{}\" processed in {}ms", resourceLabel, duration); + LOGGER.info("Event for \"{}\" processed in {}ms", resourceLabel, duration); } catch (Exception e) { - logger.error("Exception processing event", e); + LOGGER.error("Exception processing event", e); } } diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt index bb644553..f3508b8f 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt @@ -159,7 +159,6 @@ class IntegrationTest { K_LOGGER.info { "RabbitMQ URL: ${rabbitMQContainer.httpUrl}" } - println(k3sContainer.kubeConfigYaml) Files.writeString(kubeConfig, k3sContainer.kubeConfigYaml) YAML_MAPPER.writeValue(operatorConfig.toFile(), createOperatorConfig(rabbitMQContainer)) From bb4731cde0133c731cfbcd3088df61aec4174cfd Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Fri, 13 Sep 2024 18:59:16 +0400 Subject: [PATCH 33/42] [TH2-5226] refactored --- .../integration/IntegrationTest.kt | 266 +++++++++++------- .../integration/TestKubernetesUtils.kt | 27 +- 2 files changed, 181 insertions(+), 112 deletions(-) diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt index f3508b8f..8fa1f65d 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt @@ -57,11 +57,13 @@ import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandle import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.LOGGING_CM_NAME import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.MQ_ROUTER_CM_NAME import com.exactpro.th2.infraoperator.operator.manager.impl.Th2DictionaryEventHandler.DICTIONARY_SUFFIX +import com.exactpro.th2.infraoperator.spec.Th2CustomResource import com.exactpro.th2.infraoperator.spec.box.Th2Box import com.exactpro.th2.infraoperator.spec.corebox.Th2CoreBox import com.exactpro.th2.infraoperator.spec.estore.Th2Estore import com.exactpro.th2.infraoperator.spec.helmrelease.HelmRelease import com.exactpro.th2.infraoperator.spec.helmrelease.HelmRelease.NAME_LENGTH_LIMIT +import com.exactpro.th2.infraoperator.spec.job.Th2Job import com.exactpro.th2.infraoperator.spec.mstore.Th2Mstore import com.exactpro.th2.infraoperator.spec.shared.PrometheusConfiguration import com.exactpro.th2.infraoperator.spec.shared.status.RolloutPhase.DISABLED @@ -229,90 +231,31 @@ class IntegrationTest { kubeClient.awaitNoResources(TH2_NAMESPACE) } - @Nested - inner class Mstore { - @Test - @Timeout(30_000) - fun `add mstore`() { - val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() - val spec = """ - imageName: ghcr.io/th2-net/th2-mstore - imageVersion: 0.0.0 - """.trimIndent() - kubeClient.createTh2CustomResource(TH2_NAMESPACE, MESSAGE_STORAGE_BOX_ALIAS, gitHash, spec, ::Th2Mstore) - kubeClient.awaitPhase(TH2_NAMESPACE, MESSAGE_STORAGE_BOX_ALIAS, SUCCEEDED) - kubeClient.awaitResource(TH2_NAMESPACE, MESSAGE_STORAGE_BOX_ALIAS) - // FIXME: estore should have binding -// println("Bindings: ${rabbitMQClient.getQueueBindings(RABBIT_MQ_V_HOST, createEstoreQueue(TH2_NAMESPACE))}") - } + interface StoreComponentTest { + fun `add component`() } - @Nested - inner class Estore { - @Test - @Timeout(30_000) - fun `add estore`() { - val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() - val spec = """ - imageName: ghcr.io/th2-net/th2-estore - imageVersion: 0.0.0 - """.trimIndent() - kubeClient.createTh2CustomResource(TH2_NAMESPACE, EVENT_STORAGE_BOX_ALIAS, gitHash, spec, ::Th2Estore) - kubeClient.awaitPhase(TH2_NAMESPACE, EVENT_STORAGE_BOX_ALIAS, SUCCEEDED) - kubeClient.awaitResource(TH2_NAMESPACE, EVENT_STORAGE_BOX_ALIAS) - rabbitMQClient.assertBindings( - createEstoreQueue(TH2_NAMESPACE), - RABBIT_MQ_V_HOST, - setOf( - "link[$TH2_NAMESPACE:$EVENT_STORAGE_BOX_ALIAS:$EVENT_STORAGE_PIN_ALIAS]", - ) - ) - } - } + abstract inner class ComponentTest { + abstract val resourceClass: Class + abstract val specType: String + abstract val runAsJob: Boolean + abstract fun createResources(): T - @Nested - inner class CoreComponent { - @Timeout(30_000) - @ParameterizedTest - @ValueSource(strings = ["th2-core-component", "th2-core-component-more-than-$NAME_LENGTH_LIMIT-characters"]) - fun `add core component (min configuration)`(name: String) { - val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() - val spec = """ - imageName: $IMAGE - imageVersion: $VERSION - type: th2-rpt-data-provider - """.trimIndent() + abstract fun add(name: String) + abstract fun disable(name: String) + abstract fun enable(name: String) - kubeClient.createTh2CustomResource(TH2_NAMESPACE, name, gitHash, spec, ::Th2CoreBox) - kubeClient.awaitPhase(TH2_NAMESPACE, name, SUCCEEDED) - kubeClient.awaitResource(TH2_NAMESPACE, hashNameIfNeeded(name)).assertMinCfg(name) - rabbitMQClient.assertBindings( - createEstoreQueue(TH2_NAMESPACE), - RABBIT_MQ_V_HOST, - setOf( - "link[$TH2_NAMESPACE:$EVENT_STORAGE_BOX_ALIAS:$EVENT_STORAGE_PIN_ALIAS]", - "key[$TH2_NAMESPACE:$name:$EVENT_STORAGE_PIN_ALIAS]" - ) - ) - } - } - - @Nested - inner class Component { - @Timeout(30_000) - @ParameterizedTest - @ValueSource(strings = ["th2-component", "th2-component-more-than-$NAME_LENGTH_LIMIT-characters"]) - fun `add component (min configuration)`(name: String) { + protected fun addTest(name: String) { val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() val spec = """ imageName: $IMAGE imageVersion: $VERSION - type: th2-codec + type: $specType """.trimIndent() - kubeClient.createTh2CustomResource(TH2_NAMESPACE, name, gitHash, spec, ::Th2Box) - kubeClient.awaitPhase(TH2_NAMESPACE, name, SUCCEEDED) - kubeClient.awaitResource(TH2_NAMESPACE, hashNameIfNeeded(name)).assertMinCfg(name) + kubeClient.createTh2CustomResource(TH2_NAMESPACE, name, gitHash, spec, this::createResources) + kubeClient.awaitPhase(TH2_NAMESPACE, name, SUCCEEDED, resourceClass) + kubeClient.awaitResource(TH2_NAMESPACE, hashNameIfNeeded(name)).assertMinCfg(name, runAsJob) rabbitMQClient.assertBindings( createEstoreQueue(TH2_NAMESPACE), RABBIT_MQ_V_HOST, @@ -323,21 +266,18 @@ class IntegrationTest { ) } - @Timeout(30_000) - @ParameterizedTest - @ValueSource(strings = ["th2-component", "th2-component-more-than-$NAME_LENGTH_LIMIT-characters"]) - fun `disable component (min configuration)`(name: String) { + protected fun disableTest(name: String) { var gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() var spec = """ imageName: $IMAGE imageVersion: $VERSION - type: th2-codec + type: $specType disabled: false """.trimIndent() - kubeClient.createTh2CustomResource(TH2_NAMESPACE, name, gitHash, spec, ::Th2Box) - kubeClient.awaitPhase(TH2_NAMESPACE, name, SUCCEEDED) - kubeClient.awaitResource(TH2_NAMESPACE, hashNameIfNeeded(name)).assertMinCfg(name) + kubeClient.createTh2CustomResource(TH2_NAMESPACE, name, gitHash, spec, this::createResources) + kubeClient.awaitPhase(TH2_NAMESPACE, name, SUCCEEDED, resourceClass) + kubeClient.awaitResource(TH2_NAMESPACE, hashNameIfNeeded(name)).assertMinCfg(name, runAsJob) rabbitMQClient.assertBindings( createEstoreQueue(TH2_NAMESPACE), RABBIT_MQ_V_HOST, @@ -351,12 +291,12 @@ class IntegrationTest { spec = """ imageName: $IMAGE imageVersion: $VERSION - type: th2-codec + type: $specType disabled: true """.trimIndent() - kubeClient.modifyTh2CustomResource(TH2_NAMESPACE, name, gitHash, spec) - kubeClient.awaitPhase(TH2_NAMESPACE, name, DISABLED) + kubeClient.modifyTh2CustomResource(TH2_NAMESPACE, name, gitHash, spec, resourceClass) + kubeClient.awaitPhase(TH2_NAMESPACE, name, DISABLED, resourceClass) kubeClient.awaitNoResource(TH2_NAMESPACE, name) rabbitMQClient.assertBindings( createEstoreQueue(TH2_NAMESPACE), @@ -368,20 +308,17 @@ class IntegrationTest { ) } - @Timeout(30_000) - @ParameterizedTest - @ValueSource(strings = ["th2-component", "th2-component-more-than-$NAME_LENGTH_LIMIT-characters"]) - fun `enabled component (min configuration)`(name: String) { + protected fun enableTest(name: String) { var gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() var spec = """ imageName: $IMAGE imageVersion: $VERSION - type: th2-codec + type: $specType disabled: true - """.trimIndent() + """.trimIndent() - kubeClient.createTh2CustomResource(TH2_NAMESPACE, name, gitHash, spec, ::Th2Box) - kubeClient.awaitPhase(TH2_NAMESPACE, name, DISABLED) + kubeClient.createTh2CustomResource(TH2_NAMESPACE, name, gitHash, spec, this::createResources) + kubeClient.awaitPhase(TH2_NAMESPACE, name, DISABLED, resourceClass) kubeClient.awaitNoResource(TH2_NAMESPACE, name) rabbitMQClient.assertBindings( createEstoreQueue(TH2_NAMESPACE), @@ -393,13 +330,13 @@ class IntegrationTest { spec = """ imageName: $IMAGE imageVersion: $VERSION - type: th2-codec + type: $specType disabled: false """.trimIndent() - kubeClient.modifyTh2CustomResource(TH2_NAMESPACE, name, gitHash, spec) - kubeClient.awaitPhase(TH2_NAMESPACE, name, SUCCEEDED) - kubeClient.awaitResource(TH2_NAMESPACE, hashNameIfNeeded(name)).assertMinCfg(name) + kubeClient.modifyTh2CustomResource(TH2_NAMESPACE, name, gitHash, spec, resourceClass) + kubeClient.awaitPhase(TH2_NAMESPACE, name, SUCCEEDED, resourceClass) + kubeClient.awaitResource(TH2_NAMESPACE, hashNameIfNeeded(name)).assertMinCfg(name, runAsJob) rabbitMQClient.assertBindings( createEstoreQueue(TH2_NAMESPACE), RABBIT_MQ_V_HOST, @@ -411,6 +348,129 @@ class IntegrationTest { } } + @Nested + inner class Mstore: StoreComponentTest { + + @Test + @Timeout(30_000) + override fun `add component`() { + val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() + val spec = """ + imageName: ghcr.io/th2-net/th2-mstore + imageVersion: 0.0.0 + """.trimIndent() + kubeClient.createTh2CustomResource(TH2_NAMESPACE, MESSAGE_STORAGE_BOX_ALIAS, gitHash, spec, ::Th2Mstore) + kubeClient.awaitPhase(TH2_NAMESPACE, MESSAGE_STORAGE_BOX_ALIAS, SUCCEEDED) + kubeClient.awaitResource(TH2_NAMESPACE, MESSAGE_STORAGE_BOX_ALIAS) + // FIXME: estore should have binding +// println("Bindings: ${rabbitMQClient.getQueueBindings(RABBIT_MQ_V_HOST, createEstoreQueue(TH2_NAMESPACE))}") + } + } + + @Nested + inner class Estore: StoreComponentTest { + @Test + @Timeout(30_000) + override fun `add component`() { + val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() + val spec = """ + imageName: ghcr.io/th2-net/th2-estore + imageVersion: 0.0.0 + """.trimIndent() + kubeClient.createTh2CustomResource(TH2_NAMESPACE, EVENT_STORAGE_BOX_ALIAS, gitHash, spec, ::Th2Estore) + kubeClient.awaitPhase(TH2_NAMESPACE, EVENT_STORAGE_BOX_ALIAS, SUCCEEDED) + kubeClient.awaitResource(TH2_NAMESPACE, EVENT_STORAGE_BOX_ALIAS) + rabbitMQClient.assertBindings( + createEstoreQueue(TH2_NAMESPACE), + RABBIT_MQ_V_HOST, + setOf( + "link[$TH2_NAMESPACE:$EVENT_STORAGE_BOX_ALIAS:$EVENT_STORAGE_PIN_ALIAS]", + ) + ) + } + } + + @Nested + inner class CoreComponent: ComponentTest() { + override val resourceClass: Class + get() = Th2CoreBox::class.java + override val specType: String + get() = "th2-rpt-data-provider" + override val runAsJob: Boolean + get() = false + + override fun createResources(): Th2CoreBox = Th2CoreBox() + + @Timeout(30_000) + @ParameterizedTest + @ValueSource(strings = ["th2-core-component", "th2-core-component-more-than-$NAME_LENGTH_LIMIT-characters"]) + override fun add(name: String) = addTest(name) + + @Timeout(30_000) + @ParameterizedTest + @ValueSource(strings = ["th2-core-component", "th2-core-component-more-than-$NAME_LENGTH_LIMIT-characters"]) + override fun disable(name: String) = disableTest(name) + + @Timeout(30_000) + @ParameterizedTest + @ValueSource(strings = ["th2-core-component", "th2-core-component-more-than-$NAME_LENGTH_LIMIT-characters"]) + override fun enable(name: String) = enableTest(name) + } + + @Nested + inner class Component: ComponentTest() { + override val resourceClass: Class + get() = Th2Box::class.java + override val specType: String + get() = "th2-codec" + override val runAsJob: Boolean + get() = false + + override fun createResources(): Th2Box = Th2Box() + + @Timeout(30_000) + @ParameterizedTest + @ValueSource(strings = ["th2-component", "th2-component-more-than-$NAME_LENGTH_LIMIT-characters"]) + override fun add(name: String) = addTest(name) + + @Timeout(30_000) + @ParameterizedTest + @ValueSource(strings = ["th2-component", "th2-component-more-than-$NAME_LENGTH_LIMIT-characters"]) + override fun disable(name: String) = disableTest(name) + + @Timeout(30_000) + @ParameterizedTest + @ValueSource(strings = ["th2-component", "th2-component-more-than-$NAME_LENGTH_LIMIT-characters"]) + override fun enable(name: String) = enableTest(name) + } + + @Nested + inner class Job: ComponentTest() { + override val resourceClass: Class + get() = Th2Job::class.java + override val specType: String + get() = "th2-job" // supported values: "th2-job" + override val runAsJob: Boolean + get() = true + + override fun createResources(): Th2Job = Th2Job() + + @Timeout(30_000) + @ParameterizedTest + @ValueSource(strings = ["th2-job", "th2-job-more-than-$NAME_LENGTH_LIMIT-characters"]) + override fun add(name: String) = addTest(name) + + @Timeout(30_000) + @ParameterizedTest + @ValueSource(strings = ["th2-job", "th2-job-more-than-$NAME_LENGTH_LIMIT-characters"]) + override fun disable(name: String) = disableTest(name) + + @Timeout(30_000) + @ParameterizedTest + @ValueSource(strings = ["th2-job", "th2-job-more-than-$NAME_LENGTH_LIMIT-characters"]) + override fun enable(name: String) = enableTest(name) + } + @Nested inner class Dictionary { @@ -444,14 +504,6 @@ class IntegrationTest { } } - @Nested - inner class Job { - // @Test - @Timeout(30_000) - fun `create job`() { - } - } - companion object { private val K_LOGGER = KotlinLogging.logger {} @@ -662,7 +714,7 @@ class IntegrationTest { deleteConfigMap(namespace, CRADLE_MANAGER_CM_NAME) } - private fun HelmRelease.assertMinCfg(name: String) { + private fun HelmRelease.assertMinCfg(name: String, runAsJob: Boolean) { expectThat(componentValuesSection) { getValue(BOOK_CONFIG_ALIAS).isA>().and { hasSize(1) @@ -689,7 +741,7 @@ class IntegrationTest { } getValue("services").isA>().isEmpty() } - getValue(IS_JOB_ALIAS) isEqualTo false + getValue(IS_JOB_ALIAS) isEqualTo runAsJob getValue(SECRET_PATHS_CONFIG_ALIAS).isA>().isEmpty() getValue(SECRET_VALUES_CONFIG_ALIAS).isA>().isEmpty() getValue(SCHEMA_SECRETS_ALIAS).isA>().and { diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt index f02f4b12..6a1ccb68 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt @@ -165,10 +165,19 @@ inline fun KubernetesClient.awaitPhase( phase: RolloutPhase, timeout: Long = 5_000, unit: TimeUnit = TimeUnit.MILLISECONDS, +) = awaitPhase(namespace, name, phase, T::class.java, timeout, unit) + +fun KubernetesClient.awaitPhase( + namespace: String, + name: String, + phase: RolloutPhase, + resourceType: Class, + timeout: Long = 5_000, + unit: TimeUnit = TimeUnit.MILLISECONDS, ) { - await("awaitStatus ($name ${T::class.java} $phase)") + await("awaitStatus ($name $resourceType $phase)") .timeout(timeout, unit) - .until { resources(T::class.java)?.inNamespace(namespace)?.withName(name)?.get()?.status?.phase == phase } + .until { resources(resourceType)?.inNamespace(namespace)?.withName(name)?.get()?.status?.phase == phase } } fun KubernetesClient.createTh2CustomResource( @@ -186,14 +195,15 @@ fun KubernetesClient.createTh2CustomResource( ).create() } -inline fun KubernetesClient.modifyTh2CustomResource( +fun KubernetesClient.modifyTh2CustomResource( namespace: String, name: String, gitHash: String, - spec: String + spec: String, + resourceType: Class, ) { resource( - resources(T::class.java).inNamespace(namespace).withName(name).get().apply { + resources(resourceType).inNamespace(namespace).withName(name).get().apply { this.metadata.annotations.putAll(createAnnotations(gitHash, spec.hashCode().toString())) this.metadata.generation += 1 this.spec = YAML_MAPPER.readValue(spec, Th2Spec::class.java) @@ -201,6 +211,13 @@ inline fun KubernetesClient.modifyTh2CustomResour ).update() } +inline fun KubernetesClient.modifyTh2CustomResource( + namespace: String, + name: String, + gitHash: String, + spec: String +) = modifyTh2CustomResource(namespace, name, gitHash, spec, T::class.java) + fun KubernetesClient.createTh2Dictionary( namespace: String, name: String, From 87c6370e462e5697803e64f7ca335f81f3b2dca4 Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Mon, 16 Sep 2024 10:12:10 +0400 Subject: [PATCH 34/42] [TH2-5226] corrected after review --- .../operator/AbstractTh2Operator.java | 46 ++++++++++--------- .../impl/Th2DictionaryEventHandler.java | 37 +++++++-------- .../redeploy/tasks/TriggerRedeployTask.java | 5 +- .../th2/infraoperator/util/KubernetesUtils.kt | 9 +--- 4 files changed, 47 insertions(+), 50 deletions(-) diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/AbstractTh2Operator.java b/src/main/java/com/exactpro/th2/infraoperator/operator/AbstractTh2Operator.java index 8eb5bd82..bd0aa89b 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/AbstractTh2Operator.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/AbstractTh2Operator.java @@ -56,7 +56,7 @@ public abstract class AbstractTh2Operator implements Watcher { - private static final Logger logger = LoggerFactory.getLogger(AbstractTh2Operator.class); + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractTh2Operator.class); private static final int REDEPLOY_DELAY = 120; @@ -83,21 +83,22 @@ public void eventReceived(Action action, CR resource) { if (cachedFingerprint != null && action.equals(MODIFIED) && cachedFingerprint.equals(resourceFingerprint)) { - logger.debug("No changes detected for \"{}\"", resourceLabel); + LOGGER.debug("No changes detected for \"{}\"", resourceLabel); return; } Histogram.Timer processTimer = OperatorMetrics.getCustomResourceEventTimer(resource); try { - logger.debug("refresh-token={}", resourceFingerprint.refreshToken); + LOGGER.debug("refresh-token={}", resourceFingerprint.refreshToken); processEvent(action, resource); } catch (NonTerminalException e) { - logger.error("Non-terminal Exception processing {} event for \"{}\". Will try to redeploy.", + LOGGER.error("Non-terminal Exception processing {} event for \"{}\". Will try to redeploy.", action, resourceLabel, e); String namespace = resource.getMetadata().getNamespace(); if (isNotActive(kubClient, namespace)) { + LOGGER.info("Namespace \"{}\" deleted or not active, cancelling", namespace); return; } @@ -115,7 +116,7 @@ public void eventReceived(Action action, CR resource) { ); retryableTaskQueue.add(triggerRedeployTask, true); - logger.info("Task \"{}\" added to scheduler, with delay \"{}\" seconds", + LOGGER.info("Task \"{}\" added to scheduler, with delay \"{}\" seconds", triggerRedeployTask.getName(), REDEPLOY_DELAY); } finally { fingerprints.put(resourceLabel, resourceFingerprint); @@ -128,11 +129,12 @@ public void eventReceived(Action action, CR resource) { } catch (Exception e) { String namespace = resource.getMetadata().getNamespace(); if (isNotActive(kubClient, namespace)) { + LOGGER.info("Namespace \"{}\" deleted or not active, cancelling", namespace); return; } resource.getStatus().failed(e.getMessage()); updateStatus(resource); - logger.error("Terminal Exception processing {} event for {}. Will not try to redeploy", + LOGGER.error("Terminal Exception processing {} event for {}. Will not try to redeploy", action, resourceLabel, e); } } @@ -150,7 +152,7 @@ protected HelmRelease loadKubObj() { protected void processEvent(Action action, CR resource) throws IOException { String resourceLabel = annotationFor(resource); - logger.debug("Processing event {} for \"{}\"", action, resourceLabel); + LOGGER.debug("Processing event {} for \"{}\"", action, resourceLabel); if (resource.getSpec().getDisabled()) { try { @@ -165,19 +167,19 @@ protected void processEvent(Action action, CR resource) throws IOException { .withName(helmReleaseName); HelmRelease helmRelease = helmReleaseResource.get(); if (helmRelease == null) { - logger.info("Resource \"{}\" hasn't been deleted because it already doesn't exist", annotationFor(namespace, helmReleaseName, HelmRelease.class.getSimpleName())); + LOGGER.info("Resource \"{}\" hasn't been deleted because it already doesn't exist", annotationFor(namespace, helmReleaseName, HelmRelease.class.getSimpleName())); } else { String helmReleaseLabel = annotationFor(helmRelease); helmReleaseResource.delete(); - logger.info("Resource \"{}\" has been deleted", helmReleaseLabel); + LOGGER.info("Resource \"{}\" has been deleted", helmReleaseLabel); } resource.getStatus().disabled("Resource has been disabled"); - logger.info("Resource \"{}\" has been disabled, executing DELETE action", resourceLabel); + LOGGER.info("Resource \"{}\" has been disabled, executing DELETE action", resourceLabel); updateStatus(resource); } catch (Exception e) { resource.getStatus().failed("Unknown error"); updateStatus(resource); - logger.error("Exception while processing disable feature for: \"{}\"", resourceLabel, e); + LOGGER.error("Exception while processing disable feature for: \"{}\"", resourceLabel, e); } return; } @@ -187,23 +189,23 @@ protected void processEvent(Action action, CR resource) throws IOException { resource.getStatus().installing(); resource = updateStatus(resource); addedEvent(resource); - logger.info("Resource \"{}\" has been added", resourceLabel); + LOGGER.info("Resource \"{}\" has been added", resourceLabel); break; case MODIFIED: resource.getStatus().upgrading(); resource = updateStatus(resource); modifiedEvent(resource); - logger.info("Resource \"{}\" has been modified", resourceLabel); + LOGGER.info("Resource \"{}\" has been modified", resourceLabel); break; case DELETED: deletedEvent(resource); - logger.info("Resource \"{}\" has been deleted", resourceLabel); + LOGGER.info("Resource \"{}\" has been deleted", resourceLabel); break; case ERROR: - logger.warn("Error while processing \"{}\"", resourceLabel); + LOGGER.warn("Error while processing \"{}\"", resourceLabel); resource.getStatus().failed("Unknown error from kubernetes"); resource = updateStatus(resource); errorEvent(resource); @@ -248,7 +250,7 @@ protected CR updateStatus(CR resource) { } catch (KubernetesClientException e) { if (HttpCode.ofCode(e.getCode()) == HttpCode.SERVER_CONFLICT) { - logger.warn("Failed to update status for \"{}\" to \"{}\" because it has been " + + LOGGER.warn("Failed to update status for \"{}\" to \"{}\" because it has been " + "already changed on the server. Trying to sync a resource...", resourceLabel, resource.getStatus().getPhase()); var freshRes = resClient.inNamespace(extractNamespace(resource)).list().getItems().stream() @@ -259,11 +261,11 @@ protected CR updateStatus(CR resource) { freshRes.setStatus(resource.getStatus()); var updatedRes = updateStatus(freshRes); fingerprints.put(resourceLabel, new ResourceFingerprint(updatedRes)); - logger.info("Status for \"{}\" resource successfully updated to \"{}\"", + LOGGER.info("Status for \"{}\" resource successfully updated to \"{}\"", resourceLabel, resource.getStatus().getPhase()); return updatedRes; } else { - logger.warn("Unable to update status for \"{}\" resource to \"{}\": resource not present", + LOGGER.warn("Unable to update status for \"{}\" resource to \"{}\": resource not present", resourceLabel, resource.getStatus().getPhase()); return resource; } @@ -281,7 +283,7 @@ protected void setupAndCreateKubObj(CR resource) { createKubObj(extractNamespace(resource), kubObj); - logger.info("Generated \"{}\" based on \"{}\"" , annotationFor(kubObj) , annotationFor(resource)); + LOGGER.info("Generated \"{}\" based on \"{}\"" , annotationFor(kubObj) , annotationFor(resource)); String kubObjType = kubObj.getClass().getSimpleName(); @@ -294,13 +296,13 @@ protected void setupKubObj(CR resource, HelmRelease helmRelease) { mapProperties(resource, helmRelease); - logger.info("Generated additional properties from \"{}\" for the resource \"{}\"" + LOGGER.info("Generated additional properties from \"{}\" for the resource \"{}\"" , annotationFor(resource) , annotationFor(helmRelease)); helmRelease.getMetadata().setOwnerReferences(List.of(createOwnerReference(resource))); - logger.info("Property \"OwnerReference\" with reference to \"{}\" has been set for the resource \"{}\"" + LOGGER.info("Property \"OwnerReference\" with reference to \"{}\" has been set for the resource \"{}\"" , annotationFor(resource) , annotationFor(helmRelease)); @@ -315,7 +317,7 @@ protected void mapProperties(CR resource, HelmRelease helmRelease) { String finalName = extractHashedName(resource); if (!finalName.equals(resName)) { - logger.info("Name of resource \"{}\" exceeds limitations. Will be substituted with \"{}\"", + LOGGER.info("Name of resource \"{}\" exceeds limitations. Will be substituted with \"{}\"", annotation, finalName); } diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/Th2DictionaryEventHandler.java b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/Th2DictionaryEventHandler.java index 2a94d4d8..0a9b8266 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/Th2DictionaryEventHandler.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/Th2DictionaryEventHandler.java @@ -54,7 +54,7 @@ public class Th2DictionaryEventHandler implements Watcher { - private static final Logger logger = LoggerFactory.getLogger(Th2DictionaryEventHandler.class); + private static final Logger LOGGER = LoggerFactory.getLogger(Th2DictionaryEventHandler.class); private KubernetesClient kubClient; @@ -100,7 +100,7 @@ public void eventReceived(Action action, Th2Dictionary dictionary) { } } catch (Exception e) { String resourceLabel = annotationFor(dictionary); - logger.error("Terminal Exception processing {} event for {}. Will not try to redeploy", + LOGGER.error("Terminal Exception processing {} event for {}. Will not try to redeploy", action, resourceLabel, e); } finally { //observe event processing time for only operator @@ -114,9 +114,9 @@ private void processAdded(Th2Dictionary dictionary) { String newChecksum = ExtractUtils.fullSourceHash(dictionary); //create or replace corresponding config map from Kubernetes - logger.debug("Creating config map for: \"{}\"", resourceLabel); + LOGGER.debug("Creating config map for: \"{}\"", resourceLabel); kubClient.resource(toConfigMap(dictionary)).inNamespace(namespace).createOrReplace(); - logger.debug("Created config map for: \"{}\"", resourceLabel); + LOGGER.debug("Created config map for: \"{}\"", resourceLabel); sourceHashes.put(resourceLabel, newChecksum); } @@ -128,25 +128,25 @@ private void processModified(Th2Dictionary dictionary) { String oldChecksum = sourceHashes.get(resourceLabel); if (oldChecksum != null && oldChecksum.equals(newChecksum)) { - logger.info("Dictionary: \"{}\" has not been changed", resourceLabel); + LOGGER.info("Dictionary: \"{}\" has not been changed", resourceLabel); return; } //update corresponding config map from Kubernetes - logger.debug("Updating config map for: \"{}\"", resourceLabel); + LOGGER.debug("Updating config map for: \"{}\"", resourceLabel); kubClient.resource(toConfigMap(dictionary)).inNamespace(namespace).createOrReplace(); - logger.debug("Updated config map for: \"{}\"", resourceLabel); + LOGGER.debug("Updated config map for: \"{}\"", resourceLabel); sourceHashes.put(resourceLabel, newChecksum); - logger.info("Checking bindings for \"{}\"", resourceLabel); + LOGGER.info("Checking bindings for \"{}\"", resourceLabel); var linkedResources = getLinkedResources(dictionary); int items = linkedResources.size(); if (items == 0) { - logger.info("No boxes needs to be updated"); + LOGGER.info("No boxes needs to be updated"); } else { - logger.info("{} box(es) needs to be updated", items); + LOGGER.info("{} box(es) needs to be updated", items); updateLinkedResources(dictionaryName, namespace, newChecksum, linkedResources); } } @@ -157,10 +157,10 @@ private void processDeleted(Th2Dictionary dictionary) { String resourceLabel = annotationFor(dictionary); //delete corresponding config map from Kubernetes - logger.debug("Deleting config map for: \"{}\"", resourceLabel); + LOGGER.debug("Deleting config map for: \"{}\"", resourceLabel); kubClient.configMaps().inNamespace(namespace).withName(dictionaryName).delete(); sourceHashes.remove(resourceLabel); - logger.debug("Deleted config map for: \"{}\"", resourceLabel); + LOGGER.debug("Deleted config map for: \"{}\"", resourceLabel); } private Set getLinkedResources(Th2Dictionary dictionary) { @@ -197,17 +197,18 @@ private void updateLinkedResources(String dictionaryName, String namespace, String checksum, Set linkedResources) { if (isNotActive(kubClient, namespace)) { + LOGGER.info("Namespace \"{}\" deleted or not active, cancelling", namespace); return; } for (var linkedResourceName : linkedResources) { - logger.debug("Checking linked resource: '{}.{}'", namespace, linkedResourceName); + LOGGER.debug("Checking linked resource: '{}.{}'", namespace, linkedResourceName); var hr = OperatorState.INSTANCE.getHelmReleaseFromCache(linkedResourceName, namespace); if (hr == null) { - logger.info("HelmRelease of '{}.{}' resource not found in cache", namespace, linkedResourceName); + LOGGER.info("HelmRelease of '{}.{}' resource not found in cache", namespace, linkedResourceName); continue; } else { - logger.debug("Found HelmRelease \"{}\"", CustomResourceUtils.annotationFor(hr)); + LOGGER.debug("Found HelmRelease \"{}\"", CustomResourceUtils.annotationFor(hr)); } Collection dictionaryConfig = extractDictionariesConfig(hr); @@ -218,11 +219,11 @@ private void updateLinkedResources(String dictionaryName, String namespace, } } hr.addComponentValue(DICTIONARIES_ALIAS, dictionaryConfig); - logger.debug("Updating \"{}\"", CustomResourceUtils.annotationFor(hr)); + LOGGER.debug("Updating \"{}\"", CustomResourceUtils.annotationFor(hr)); createKubObj(namespace, hr); - logger.debug("Updated \"{}\"", CustomResourceUtils.annotationFor(hr)); + LOGGER.debug("Updated \"{}\"", CustomResourceUtils.annotationFor(hr)); } else { - logger.info("Dictionaries config for resource of '{}.{}' was null", namespace, linkedResourceName); + LOGGER.info("Dictionaries config for resource of '{}.{}' was null", namespace, linkedResourceName); } } } diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/TriggerRedeployTask.java b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/TriggerRedeployTask.java index b764765d..457739bd 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/TriggerRedeployTask.java +++ b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/TriggerRedeployTask.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,13 +30,12 @@ import java.util.HashMap; import static com.exactpro.th2.infraoperator.operator.AbstractTh2Operator.REFRESH_TOKEN_ALIAS; +import static com.exactpro.th2.infraoperator.util.KubernetesUtils.PHASE_ACTIVE; public class TriggerRedeployTask implements Task { private static final Logger logger = LoggerFactory.getLogger(TriggerRedeployTask.class); - public static final String PHASE_ACTIVE = "Active"; - private final ResourceClient resourceClient; private final KubernetesClient kubClient; diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/util/KubernetesUtils.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/util/KubernetesUtils.kt index c2308918..5f0ac77b 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/util/KubernetesUtils.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/util/KubernetesUtils.kt @@ -27,10 +27,9 @@ import com.exactpro.th2.infraoperator.spec.mstore.Th2Mstore import io.fabric8.kubernetes.api.model.Namespace import io.fabric8.kubernetes.client.KubernetesClient import io.fabric8.kubernetes.client.KubernetesClientBuilder -import io.github.oshai.kotlinlogging.KotlinLogging import kotlin.streams.toList -private val K_LOGGER = KotlinLogging.logger { } +const val PHASE_ACTIVE = "Active" val CUSTOM_RESOURCE_KINDS: Set> = setOf(Th2Estore::class.java, Th2Mstore::class.java, Th2CoreBox::class.java, Th2Box::class.java, Th2Job::class.java) @@ -57,9 +56,5 @@ fun KubernetesClient.customResources(namespace: String): List fun KubernetesClient.isNotActive(namespace: String): Boolean { val namespaceObj: Namespace? = namespaces().withName(namespace).get() - if (namespaceObj == null || namespaceObj.status.phase != "Active") { - K_LOGGER.info { "Namespace \"$namespace\" deleted or not active, cancelling" } - return true - } - return false + return namespaceObj == null || namespaceObj.status.phase != PHASE_ACTIVE } From 0a45dffdb92cf2d27032fc03926bc2f082cc3820 Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Mon, 16 Sep 2024 10:33:09 +0400 Subject: [PATCH 35/42] [TH2-5226] corrected after review --- .../operator/StoreHelmTh2Op.java | 8 ++++ .../operator/impl/EstoreHelmTh2Op.java | 6 +-- .../operator/impl/MstoreHelmTh2Op.java | 6 +-- .../factory/MessageRouterConfigFactoryBox.kt | 2 +- .../MessageRouterConfigFactoryEstore.kt | 2 +- .../MessageRouterConfigFactoryMstore.kt | 4 +- .../spec/strategy/linkresolver/Util.kt | 8 ++-- .../linkresolver/mq/BindQueueLinkResolver.kt | 41 ++++++++----------- .../integration/IntegrationTest.kt | 6 +-- 9 files changed, 39 insertions(+), 44 deletions(-) diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/StoreHelmTh2Op.java b/src/main/java/com/exactpro/th2/infraoperator/operator/StoreHelmTh2Op.java index dd9d293a..b29432c8 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/StoreHelmTh2Op.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/StoreHelmTh2Op.java @@ -30,6 +30,14 @@ public abstract class StoreHelmTh2Op extends HelmR private static final Logger LOGGER = LoggerFactory.getLogger(StoreHelmTh2Op.class); + public static final String EVENT_STORAGE_PIN_ALIAS = "estore-pin"; + + public static final String EVENT_STORAGE_BOX_ALIAS = "estore"; + + public static final String MESSAGE_STORAGE_PIN_ALIAS = "mstore-pin"; + + public static final String MESSAGE_STORAGE_BOX_ALIAS = "mstore"; + public StoreHelmTh2Op(KubernetesClient client) { super(client); } diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/impl/EstoreHelmTh2Op.java b/src/main/java/com/exactpro/th2/infraoperator/operator/impl/EstoreHelmTh2Op.java index 4031c015..fa863d54 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/impl/EstoreHelmTh2Op.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/impl/EstoreHelmTh2Op.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,10 +29,6 @@ public class EstoreHelmTh2Op extends StoreHelmTh2Op { - public static final String EVENT_STORAGE_PIN_ALIAS = "estore-pin"; - - public static final String EVENT_STORAGE_BOX_ALIAS = "estore"; - private final EstoreClient estoreClient; public EstoreHelmTh2Op(KubernetesClient client) { diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/impl/MstoreHelmTh2Op.java b/src/main/java/com/exactpro/th2/infraoperator/operator/impl/MstoreHelmTh2Op.java index 2db5c8ef..e0763f9f 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/impl/MstoreHelmTh2Op.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/impl/MstoreHelmTh2Op.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,9 +28,6 @@ import io.fabric8.kubernetes.client.informers.SharedInformerFactory; public class MstoreHelmTh2Op extends StoreHelmTh2Op { - public static final String MESSAGE_STORAGE_PIN_ALIAS = "mstore-pin"; - - public static final String MESSAGE_STORAGE_BOX_ALIAS = "mstore"; private final MstoreClient mstoreClient; @@ -60,4 +57,5 @@ protected MessageRouterConfigFactory getMqConfigFactory() { protected String getStorageName() { return MESSAGE_STORAGE_BOX_ALIAS; } + } diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryBox.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryBox.kt index 27f4fa4f..3f1986d0 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryBox.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryBox.kt @@ -19,7 +19,7 @@ package com.exactpro.th2.infraoperator.model.box.mq.factory import com.exactpro.th2.infraoperator.configuration.ConfigLoader import com.exactpro.th2.infraoperator.model.box.mq.MessageRouterConfiguration import com.exactpro.th2.infraoperator.model.box.mq.QueueConfiguration -import com.exactpro.th2.infraoperator.operator.impl.EstoreHelmTh2Op.EVENT_STORAGE_PIN_ALIAS +import com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op.EVENT_STORAGE_PIN_ALIAS import com.exactpro.th2.infraoperator.spec.Th2CustomResource import com.exactpro.th2.infraoperator.spec.shared.pin.PinSpec import com.exactpro.th2.infraoperator.util.ExtractUtils diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryEstore.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryEstore.kt index 1ea2ee4a..578f31c4 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryEstore.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryEstore.kt @@ -20,7 +20,7 @@ import com.exactpro.th2.infraoperator.configuration.ConfigLoader import com.exactpro.th2.infraoperator.model.LinkDescription import com.exactpro.th2.infraoperator.model.box.mq.MessageRouterConfiguration import com.exactpro.th2.infraoperator.model.box.mq.QueueConfiguration -import com.exactpro.th2.infraoperator.operator.impl.EstoreHelmTh2Op.EVENT_STORAGE_PIN_ALIAS +import com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op.EVENT_STORAGE_PIN_ALIAS import com.exactpro.th2.infraoperator.spec.Th2CustomResource import com.exactpro.th2.infraoperator.spec.shared.PinAttribute import com.exactpro.th2.infraoperator.spec.shared.pin.PinSpec diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryMstore.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryMstore.kt index 7d82d51c..70bc4a5d 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryMstore.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/model/box/mq/factory/MessageRouterConfigFactoryMstore.kt @@ -20,8 +20,8 @@ import com.exactpro.th2.infraoperator.configuration.ConfigLoader import com.exactpro.th2.infraoperator.model.LinkDescription import com.exactpro.th2.infraoperator.model.box.mq.MessageRouterConfiguration import com.exactpro.th2.infraoperator.model.box.mq.QueueConfiguration -import com.exactpro.th2.infraoperator.operator.impl.EstoreHelmTh2Op.EVENT_STORAGE_PIN_ALIAS -import com.exactpro.th2.infraoperator.operator.impl.MstoreHelmTh2Op.MESSAGE_STORAGE_PIN_ALIAS +import com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op.EVENT_STORAGE_PIN_ALIAS +import com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op.MESSAGE_STORAGE_PIN_ALIAS import com.exactpro.th2.infraoperator.spec.Th2CustomResource import com.exactpro.th2.infraoperator.spec.shared.PinAttribute import com.exactpro.th2.infraoperator.spec.shared.pin.PinSpec diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/Util.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/Util.kt index b004174b..a676d3cd 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/Util.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/Util.kt @@ -18,10 +18,10 @@ package com.exactpro.th2.infraoperator.spec.strategy.linkresolver -import com.exactpro.th2.infraoperator.operator.impl.EstoreHelmTh2Op.EVENT_STORAGE_BOX_ALIAS -import com.exactpro.th2.infraoperator.operator.impl.EstoreHelmTh2Op.EVENT_STORAGE_PIN_ALIAS -import com.exactpro.th2.infraoperator.operator.impl.MstoreHelmTh2Op.MESSAGE_STORAGE_BOX_ALIAS -import com.exactpro.th2.infraoperator.operator.impl.MstoreHelmTh2Op.MESSAGE_STORAGE_PIN_ALIAS +import com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op.EVENT_STORAGE_BOX_ALIAS +import com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op.EVENT_STORAGE_PIN_ALIAS +import com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op.MESSAGE_STORAGE_BOX_ALIAS +import com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op.MESSAGE_STORAGE_PIN_ALIAS import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.queue.QueueName import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.queue.RoutingKeyName diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt index 447058d0..14868e5c 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt @@ -17,8 +17,8 @@ package com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq import com.exactpro.th2.infraoperator.model.LinkDescription -import com.exactpro.th2.infraoperator.operator.impl.EstoreHelmTh2Op.EVENT_STORAGE_BOX_ALIAS -import com.exactpro.th2.infraoperator.operator.impl.MstoreHelmTh2Op.MESSAGE_STORAGE_BOX_ALIAS +import com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op.EVENT_STORAGE_BOX_ALIAS +import com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op.MESSAGE_STORAGE_BOX_ALIAS import com.exactpro.th2.infraoperator.spec.Th2CustomResource import com.exactpro.th2.infraoperator.spec.shared.PinAttribute import com.exactpro.th2.infraoperator.spec.shared.pin.Link @@ -34,7 +34,7 @@ import com.exactpro.th2.infraoperator.util.CustomResourceUtils.annotationFor import io.github.oshai.kotlinlogging.KotlinLogging object BindQueueLinkResolver { - private val logger = KotlinLogging.logger { } + private val K_LOGGER = KotlinLogging.logger { } @JvmStatic fun resolveDeclaredLinks(resource: Th2CustomResource) { @@ -94,19 +94,15 @@ object BindQueueLinkResolver { return false } if (attributes.contains(PinAttribute.parsed.name)) { - logger.warn( - "Detected a pin: {}:{} with incorrect store configuration. attribute 'parsed' not allowed", - resourceLabel, - pinName - ) + K_LOGGER.warn { + "Detected a pin: $resourceLabel:$pinName with incorrect store configuration. attribute 'parsed' not allowed" + } return false } if (!attributes.contains(PinAttribute.raw.name)) { - logger.warn( - "Detected a pin: {}:{} with incorrect store configuration. attribute 'raw' is missing", - resourceLabel, - pinName - ) + K_LOGGER.warn { + "Detected a pin: $resourceLabel:$pinName with incorrect store configuration. attribute 'raw' is missing" + } return false } return true @@ -118,19 +114,16 @@ object BindQueueLinkResolver { val queueName = queue.queueName.toString() val currentQueue = RabbitMQContext.getQueue(queueName) if (currentQueue == null) { - logger.info("Queue '{}' does not yet exist. skipping binding", queueName) + K_LOGGER.info {"Queue '$queueName' does not yet exist. skipping binding" } return } channel.queueBind(queue.queueName.toString(), queue.exchange, queue.routingKey.toString()) - logger.info( - "Queue '{}' successfully bound to '{}' (commit-{})", - queueName, - queue.routingKey.toString(), - commitHash - ) + K_LOGGER.info { + "Queue '$queueName' successfully bound to '${queue.routingKey}' (commit-$commitHash)" + } } catch (e: Exception) { val message = "Exception while working with rabbitMq" - logger.error(message, e) + K_LOGGER.error(e) { message } throw NonTerminalException(message, e) } } @@ -157,16 +150,16 @@ object BindQueueLinkResolver { if (!currentBindings.contains(it)) { val currentQueue = RabbitMQContext.getQueue(queueName) if (currentQueue == null) { - logger.info("Queue '{}' already removed. skipping unbinding", queueName) + K_LOGGER.info { "Queue '$queueName' already removed. skipping unbinding" } return } channel.queueUnbind(queueName, queue.namespace, it) - logger.info("Unbind queue '{}' -> '{}'. (commit-{})", it, queueName, commitHash) + K_LOGGER.info { "Unbind queue '$it' -> '$queueName'. (commit-$commitHash)" } } } } catch (e: Exception) { val message = "Exception while removing extinct bindings" - logger.error(message, e) + K_LOGGER.error(e) { message } throw NonTerminalException(message, e) } } diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt index 8fa1f65d..c307026e 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt @@ -47,9 +47,9 @@ import com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.ROOTLESS_ALIAS import com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.SCHEMA_SECRETS_ALIAS import com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.SECRET_PATHS_CONFIG_ALIAS import com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.SECRET_VALUES_CONFIG_ALIAS -import com.exactpro.th2.infraoperator.operator.impl.EstoreHelmTh2Op.EVENT_STORAGE_BOX_ALIAS -import com.exactpro.th2.infraoperator.operator.impl.EstoreHelmTh2Op.EVENT_STORAGE_PIN_ALIAS -import com.exactpro.th2.infraoperator.operator.impl.MstoreHelmTh2Op.MESSAGE_STORAGE_BOX_ALIAS +import com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op.EVENT_STORAGE_BOX_ALIAS +import com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op.EVENT_STORAGE_PIN_ALIAS +import com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op.MESSAGE_STORAGE_BOX_ALIAS import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.BOOK_CONFIG_CM_NAME import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.CRADLE_MANAGER_CM_NAME import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.DEFAULT_BOOK From f2f35d68ab66f0846a2326c2c75a929aaafccac0 Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Mon, 16 Sep 2024 11:30:05 +0400 Subject: [PATCH 36/42] [TH2-5226] corrected after review --- README.md | 2 +- build.gradle | 17 +- gradle/libs.versions.toml | 3 +- src/test/resources/crds/th2-box-crd.yaml | 712 ----------------- src/test/resources/crds/th2-core-box-crd.yaml | 726 ------------------ .../resources/crds/th2-dictionary-crd.yaml | 53 -- src/test/resources/crds/th2-estore-crd.yaml | 698 ----------------- src/test/resources/crds/th2-job-crd.yaml | 699 ----------------- src/test/resources/crds/th2-mstore-crd.yaml | 698 ----------------- 9 files changed, 19 insertions(+), 3589 deletions(-) delete mode 100644 src/test/resources/crds/th2-box-crd.yaml delete mode 100644 src/test/resources/crds/th2-core-box-crd.yaml delete mode 100644 src/test/resources/crds/th2-dictionary-crd.yaml delete mode 100644 src/test/resources/crds/th2-estore-crd.yaml delete mode 100644 src/test/resources/crds/th2-job-crd.yaml delete mode 100644 src/test/resources/crds/th2-mstore-crd.yaml diff --git a/README.md b/README.md index ac502ba0..e9daf809 100644 --- a/README.md +++ b/README.md @@ -147,7 +147,7 @@ openshift: ### 4.7.0 + Improved clean rubbish from RabbitMQ on start to delete only redundant resources. The `cleanUpOnStart` option has been removed, the clean rubbish function is enabled. -+ Migrated to th2 plugin `0.1.1` ++ Migrated to th2 plugin `0.1.2` + Updated: + bom: `4.6.1` diff --git a/build.gradle b/build.gradle index a8949b10..57b8e862 100644 --- a/build.gradle +++ b/build.gradle @@ -4,6 +4,7 @@ plugins { // alias(libs.plugins.detekt) alias(libs.plugins.kotlin) alias(libs.plugins.th2.component) + alias(libs.plugins.download) } group = 'com.exactpro.th2' @@ -82,8 +83,22 @@ test { } } -tasks.register("integrationTest", Test.class) { +tasks.register("downloadCRDs", Download) { group = "verification" + src([ + 'https://raw.githubusercontent.com/th2-net/th2-infra/master/chart/crds/th2-box-crd.yaml', + 'https://raw.githubusercontent.com/th2-net/th2-infra/master/chart/crds/th2-core-box-crd.yaml', + 'https://raw.githubusercontent.com/th2-net/th2-infra/master/chart/crds/th2-dictionary-crd.yaml', + 'https://raw.githubusercontent.com/th2-net/th2-infra/master/chart/crds/th2-estore-crd.yaml', + 'https://raw.githubusercontent.com/th2-net/th2-infra/master/chart/crds/th2-job-crd.yaml', + 'https://raw.githubusercontent.com/th2-net/th2-infra/master/chart/crds/th2-mstore-crd.yaml', + ]) + dest layout.buildDirectory.dir('resources/test/crds').get() +} + +tasks.register("integrationTest", Test) { + group = "verification" + dependsOn("downloadCRDs") useJUnitPlatform { includeTags("integration-test") } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3a430794..cd746e8e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] kotlin = "1.8.22" -th2-plugin = "0.1.1" +th2-plugin = "0.1.2" jupiter = "5.10.3" okhttp3 = "4.12.0" detekt = "1.23.6" @@ -29,5 +29,6 @@ detekt-formatting = { group = "io.gitlab.arturbosch.detekt", name = "detekt-form [plugins] kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } +download = { id = "de.undercouch.download", version = "5.6.0" } th2-component = { id = "com.exactpro.th2.gradle.component", version.ref = "th2-plugin" } \ No newline at end of file diff --git a/src/test/resources/crds/th2-box-crd.yaml b/src/test/resources/crds/th2-box-crd.yaml deleted file mode 100644 index 009fc555..00000000 --- a/src/test/resources/crds/th2-box-crd.yaml +++ /dev/null @@ -1,712 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: th2boxes.th2.exactpro.com -spec: - conversion: - strategy: None - group: th2.exactpro.com - names: - kind: Th2Box - listKind: Th2BoxList - plural: th2boxes - singular: th2box - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .status.subResourceName - name: HelmRelease - type: string - - jsonPath: .spec.type - name: Type - type: string - - jsonPath: .status.phase - name: Status - type: string - - jsonPath: .status.message - name: Message - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2 - schema: - openAPIV3Schema: - description: Th2Box defines generic Th2 component instance - properties: - apiVersion: - type: string - kind: - type: string - metadata: - type: object - spec: - description: Specification of desired box - properties: - customConfig: - description: this is custom configuration - type: object - x-kubernetes-preserve-unknown-fields: true - loggingConfig: - description: this is logging configuration - type: string - mqRouter: - description: this is custom configuration for router mq - type: object - x-kubernetes-preserve-unknown-fields: true - grpcRouter: - description: this is custom configuration for router grpc - type: object - x-kubernetes-preserve-unknown-fields: true - cradleManager: - description: this is custom configuration for cradle - type: object - x-kubernetes-preserve-unknown-fields: true - bookName: - description: Can be used for passing custom book for a specific CR - type: string - extendedSettings: - type: object - description: section for extended settings - properties: - entryPoint: - description: allows user to pass custom ENTRYPOINT into the docker container - type: object - properties: - command: - description: allows user to pass custom command for the docker container entrypoint - type: array - items: - type: string - args: - description: allows user to pass custom arguments for the docker container entrypoint - type: array - items: - type: string - envVariables: - description: allows us to pass specific environment variables that are going to be set into the pods. - type: object - x-kubernetes-preserve-unknown-fields: true - sharedMemory: - description: DESCRIPTION NEEDED - type: object - properties: - enabled: - description: DESCRIPTION NEEDED - type: boolean - replicas: - description: number of replicas - type: integer - k8sProbes: - description: if enabled liveness probes will be collected from pod - type: boolean - default: false - externalBox: - description: using this section we can configure boxes that are going to be run outside of kubernetes cluster - properties: - address: - description: address to the machine on which external box is running - type: string - enabled: - description: represents the state of the external box - type: boolean - endpoints: - description: internal mapping for ports - items: - properties: - name: - description: name for the endpoint - type: string - targetPort: - description: value for exposed port - type: integer - required: - - name - - targetPort - type: object - type: array - type: object - hostAliases: - description: HostAliases is an optional list of hosts and IPs that will be injected into the pod's hosts file if specified. This is only valid for non-hostNetwork pods. - type: array - items: - properties: - ip: - description: IP address of the host file entry. - type: string - hostnames: - description: Hostnames for the above IP address. - items: - type: string - type: array - required: - - ip - - hostnames - type: object - hostNetwork: - description: if the flag is set to true, pod will run on node network and kubernetes will decide which node will be used for running the box. - type: boolean - nodeSelector: - additionalProperties: - type: string - description: | - NodeSelector is a selector which must be true for the pod to fit on a node. - Selector which must match a node's labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector - type: object - mounting: - description: allows to configure persistent volume mounting for pods - items: - properties: - path: - description: a directory in pod where you mount external folder - type: string - pvcName: - description: name for the pvc - type: string - type: object - type: array - resources: - description: here we can specify resource limitations and allowances for this specific component - properties: - limits: - properties: - cpu: - type: string - memory: - type: string - type: object - requests: - properties: - cpu: - type: string - memory: - type: string - type: object - type: object - service: - type: object - properties: - enabled: - description: if enabled allows pod to be exposed using ClusterIP - type: boolean - clusterIP: - description: endpoints for cluster IP service type - type: array - items: - type: object - properties: - name: - description: name of the endpoint - type: string - containerPort: - description: allows to specify Kubernetes port for the pod. - type: integer - required: - - name - nodePort: - description: endpoints for nodePort service type - type: array - items: - type: object - properties: - name: - description: name of the endpoint - type: string - exposedPort: - description: a port on which the service will be accessible. - type: integer - containerPort: - description: allows to specify Kubernetes port for the pod. - type: integer - required: - - name - - exposedPort - loadBalancer: - description: endpoints for loadBalancer service type - type: array - items: - type: object - properties: - name: - description: name of the endpoint - type: string - containerPort: - description: allows to specify Kubernetes port for the pod. - type: integer - required: - - name - ingress: - description: Creates Ingress for the Box - type: object - properties: - urlPaths: - description: | - Path is a suffix, it is appended to namespace url, e.g for th2-test namespace: - - urlPaths: - - /my-box/ - - service will be available at /th2-test/my-box/ - type: array - items: - type: string - pattern: "^[-a-zA-Z0-9_/]+$" - disabled: - description: if set to true resource will act as if it is removed from schema without actually deleting the file. The default value is false. - type: boolean - imageName: - description: docker image repository URL - type: string - imageVersion: - description: docker image tag - type: string - versionRange: - description: when the appropriate pattern is provided, image-version will be updated with the latest tag from the image repository that satisfies that pattern. - type: string - pins: - description: consists of grpc(server, client) and mq pin sections - type: object - properties: - grpc: - description: pin section for grpc - type: object - properties: - server: - description: grpc server subsection - type: array - items: - type: object - properties: - serviceClasses: - description: list of the service classes supported by the grpc server - items: - type: string - type: array - name: - description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters - type: string - pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" - maxLength: 70 - required: - - name - - serviceClasses - client: - description: grpc client subsection - type: array - items: - type: object - properties: - attributes: - description: attributes of grpc client - items: - type: string - type: array - serviceClass: - description: name of the service class used supported by the grpc client - type: string - strategy: - description: strategy to be used for grpc communication. default value is set to robin - type: string - default: robin - filters: - description: filters for grpc client - type: array - items: - type: object - properties: - properties: - description: DESCRIPTION NEEDED - type: array - items: - type: object - properties: - expectedValue: - description: DESCRIPTION NEEDED - type: string - fieldName: - description: DESCRIPTION NEEDED - type: string - operation: - description: DESCRIPTION NEEDED - enum: - - EQUAL - - NOT_EQUAL - - EMPTY - - NOT_EMPTY - - WILDCARD - type: string - required: - - fieldName - - expectedValue - - operation - linkTo: - description: DESCRIPTION NEEDED - type: array - items: - type: object - properties: - box: - description: name of the box - type: string - pin: - description: represents the pin of the box that we want to connect - type: string - required: - - box - - pin - name: - description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters - type: string - pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" - maxLength: 70 - required: - - name - - serviceClass - mq: - type: object - properties: - subscribers: - description: section for subscriber mq pins - type: array - items: - type: object - required: - - name - properties: - name: - description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters - type: string - pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" - maxLength: 70 - attributes: - description: attributes for mq - items: - type: string - type: array - filters: - description: filters for mq - type: array - items: - type: object - properties: - properties: - description: DESCRIPTION NEEDED - type: array - items: - type: object - properties: - expectedValue: - description: DESCRIPTION NEEDED - type: string - fieldName: - description: DESCRIPTION NEEDED - type: string - operation: - description: DESCRIPTION NEEDED - enum: - - EQUAL - - NOT_EQUAL - - EMPTY - - NOT_EMPTY - - WILDCARD - type: string - required: - - fieldName - - expectedValue - - operation - message: - description: DESCRIPTION NEEDED - type: array - items: - type: object - properties: - expectedValue: - description: DESCRIPTION NEEDED - type: string - fieldName: - description: DESCRIPTION NEEDED - type: string - operation: - description: DESCRIPTION NEEDED - enum: - - EQUAL - - NOT_EQUAL - - EMPTY - - NOT_EMPTY - - WILDCARD - type: string - required: - - fieldName - - expectedValue - - operation - metadata: - description: metadata for mq pin - type: array - items: - type: object - properties: - expectedValue: - description: DESCRIPTION NEEDED - type: string - fieldName: - description: DESCRIPTION NEEDED - type: string - operation: - description: DESCRIPTION NEEDED - enum: - - EQUAL - - NOT_EQUAL - - EMPTY - - NOT_EMPTY - - WILDCARD - type: string - required: - - fieldName - - expectedValue - - operation - linkTo: - description: DESCRIPTION NEEDED - type: array - items: - type: object - properties: - box: - description: name of the box - type: string - pin: - description: represents the pin of the box that we want to connect - type: string - required: - - box - - pin - settings: - description: settings for rabbitMq queue configuration - type: object - properties: - overloadStrategy: - description: configuration for rabbit mq queue. default is set to “drop-head”. - type: string - enum: - - "drop-head" - - "reject-publish" - default: "drop-head" - queueLength: - description: configuration for rabbit mq queue. default is set to 1000 msg. queueLength isn't used if storageOnDemand is set to true. - type: integer - default: 1000 - storageOnDemand: - description: configuration for rabbit mq queue. default value is set to false - type: boolean - default: false - publishers: - description: section for subscriber mq pins - type: array - items: - required: - - name - type: object - properties: - name: - description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters - type: string - pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" - maxLength: 70 - attributes: - description: attributes for mq - items: - type: string - type: array - filters: - description: filters for mq - type: array - items: - type: object - properties: - properties: - description: DESCRIPTION NEEDED - type: array - items: - type: object - properties: - expectedValue: - description: DESCRIPTION NEEDED - type: string - fieldName: - description: DESCRIPTION NEEDED - type: string - operation: - description: DESCRIPTION NEEDED - enum: - - EQUAL - - NOT_EQUAL - - EMPTY - - NOT_EMPTY - - WILDCARD - type: string - required: - - fieldName - - expectedValue - - operation - message: - description: DESCRIPTION NEEDED - type: array - items: - type: object - properties: - expectedValue: - description: DESCRIPTION NEEDED - type: string - fieldName: - description: DESCRIPTION NEEDED - type: string - operation: - description: DESCRIPTION NEEDED - enum: - - EQUAL - - NOT_EQUAL - - EMPTY - - NOT_EMPTY - - WILDCARD - type: string - required: - - fieldName - - expectedValue - - operation - metadata: - description: metadata for mq pin - type: array - items: - type: object - properties: - expectedValue: - description: DESCRIPTION NEEDED - type: string - fieldName: - description: DESCRIPTION NEEDED - type: string - operation: - description: DESCRIPTION NEEDED - enum: - - EQUAL - - NOT_EQUAL - - EMPTY - - NOT_EMPTY - - WILDCARD - type: string - required: - - fieldName - - expectedValue - - operation - prometheus: - description: custom configuration of prometheus for microservices - properties: - enabled: - description: By default this is set to true. - type: boolean - host: - description: host for prometheus - type: string - port: - description: port for prometheus - type: integer - type: object - type: - enum: - - th2-act - - th2-codec - - th2-conn - - th2-rpt-data-provider - - th2-rpt-viewer - - th2-check1 - - th2-check2-bookcheck - - th2-check2-recon - - th2-script - - th2-sim - - th2-hand - - th2-read - - th2-util - - th2-cradle-viewer - - th2-crawler - - th2-crawler-processor - - th2-data-provider - - th2-adapter - - th2-reactive-script - - th2-monkey - type: string - required: - - imageName - - imageVersion - - type - type: object - status: - description: contains status information about this resource - properties: - conditions: - description: Conditions contains observations of the resource's state - items: - properties: - lastTransitionTime: - description: LastTransitionTime is the timestamp corresponding - to the last status change of this condition. - format: date-time - type: string - lastUpdateTime: - description: LastUpdateTime is the timestamp corresponding to - the last status update of this condition. - format: date-time - type: string - message: - description: Message is a human readable description of the - details of the last transition, complementing reason. - nullable: true - type: string - reason: - description: Reason is a brief machine readable explanation - for the condition's last transition. - type: string - status: - description: Status of the condition, one of ('True', 'False', - 'Unknown'). - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: Type of the condition - enum: - - Deployed - - Enqueued - type: string - required: - - status - - type - type: object - type: array - message: - description: Message describe current state of this resource - type: string - phase: - description: ComponentStatus is the status as given by Operator for - this resource - type: string - subResourceName: - description: SubResourceName is the name of produced helmrelease tied - with this resource - type: string - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - listKind: "" - plural: "" - singular: "" - conditions: [] - storedVersions: [] \ No newline at end of file diff --git a/src/test/resources/crds/th2-core-box-crd.yaml b/src/test/resources/crds/th2-core-box-crd.yaml deleted file mode 100644 index 40de1f9f..00000000 --- a/src/test/resources/crds/th2-core-box-crd.yaml +++ /dev/null @@ -1,726 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: th2coreboxes.th2.exactpro.com -spec: - conversion: - strategy: None - group: th2.exactpro.com - names: - kind: Th2CoreBox - listKind: Th2CoreBoxList - plural: th2coreboxes - singular: th2corebox - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .status.subResourceName - name: HelmRelease - type: string - - jsonPath: .spec.type - name: Type - type: string - - jsonPath: .status.phase - name: Status - type: string - - jsonPath: .status.message - name: Message - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2 - schema: - openAPIV3Schema: - properties: - apiVersion: - type: string - kind: - type: string - metadata: - type: object - spec: - description: Specification of desired box - properties: - customConfig: - description: this is custom configuration - type: object - x-kubernetes-preserve-unknown-fields: true - loggingConfig: - description: this is logging configuration - type: string - mqRouter: - description: this is custom configuration for router mq - type: object - x-kubernetes-preserve-unknown-fields: true - grpcRouter: - description: this is custom configuration for router grpc - type: object - x-kubernetes-preserve-unknown-fields: true - cradleManager: - description: this is custom configuration for cradle - type: object - x-kubernetes-preserve-unknown-fields: true - bookName: - description: Can be used for passing custom book for a specific CR - type: string - extendedSettings: - type: object - description: section for extended settings - properties: - entryPoint: - description: allows user to pass custom ENTRYPOINT into the docker container - type: object - properties: - command: - description: allows user to pass custom command for the docker container entrypoint - type: array - items: - type: string - args: - description: allows user to pass custom arguments for the docker container entrypoint - type: array - items: - type: string - replicas: - description: number of replicas - type: integer - k8sProbes: - description: if enabled liveness probes will be collected from pod - type: boolean - default: false - envVariables: - description: allows us to pass specific environment variables that are going to be set into the pods. - type: object - x-kubernetes-preserve-unknown-fields: true - sharedMemory: - description: DESCRIPTION NEEDED - type: object - properties: - enabled: - description: DESCRIPTION NEEDED - type: boolean - externalBox: - description: using this section we can configure boxes that are going to be run outside of kubernetes cluster - properties: - address: - description: address to the machine on which external box is running - type: string - enabled: - description: represents the state of the external box - type: boolean - endpoints: - description: internal mapping for ports - items: - properties: - name: - description: name for the endpoint - type: string - targetPort: - description: value for exposed port - type: integer - required: - - name - - targetPort - type: object - type: array - type: object - hostAliases: - description: HostAliases is an optional list of hosts and IPs that will be injected into the pod's hosts file if specified. This is only valid for non-hostNetwork pods. - type: array - items: - properties: - ip: - description: IP address of the host file entry. - type: string - hostnames: - description: Hostnames for the above IP address. - items: - type: string - type: array - required: - - ip - - hostnames - type: object - hostNetwork: - description: if the flag is set to true, pod will run on node network and kubernetes will decide which node will be used for running the box. - type: boolean - nodeSelector: - additionalProperties: - type: string - description: | - NodeSelector is a selector which must be true for the pod to fit on a node. - Selector which must match a node's labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector - type: object - mounting: - description: allows to configure persistent volume mounting for pods - items: - properties: - path: - description: a directory in pod where you mount external folder - type: string - pvcName: - description: name for the pvc - type: string - type: object - type: array - resources: - description: here we can specify resource limitations and allowances for this specific component - properties: - limits: - properties: - cpu: - type: string - memory: - type: string - type: object - requests: - properties: - cpu: - type: string - memory: - type: string - type: object - type: object - service: - type: object - properties: - enabled: - description: if enabled allows pod to be exposed using ClusterIP - type: boolean - clusterIP: - description: endpoints for cluster IP service type - type: array - items: - type: object - properties: - name: - description: name of the endpoint - type: string - containerPort: - description: allows to specify Kubernetes port for the pod. - type: integer - required: - - name - nodePort: - description: endpoints for nodePort service type - type: array - items: - type: object - properties: - name: - description: name of the endpoint - type: string - exposedPort: - description: a port on which the service will be accessible. - type: integer - containerPort: - description: allows to specify Kubernetes port for the pod. - type: integer - required: - - name - - exposedPort - loadBalancer: - description: endpoints for loadBalancer service type - type: array - items: - type: object - properties: - name: - description: name of the endpoint - type: string - containerPort: - description: allows to specify Kubernetes port for the pod. - type: integer - required: - - name - ingress: - description: Creates Ingress for the Box - type: object - properties: - urlPaths: - description: | - Path is a suffix, it is appended to namespace url, e.g for th2-test namespace: - - urlPaths: - - /my-box/ - - service will be available at /th2-test/my-box/ - type: array - items: - type: string - pattern: "^[-a-zA-Z0-9_/]+$" - disabled: - description: if set to true resource will act as if it is removed from schema without actually deleting the file. The default value is false. - type: boolean - imageName: - description: docker image repository URL - type: string - imageVersion: - description: docker image tag - type: string - versionRange: - description: when the appropriate pattern is provided, image-version will be updated with the latest tag from the image repository that satisfies that pattern. - type: string - params: - description: List of generic parameters - items: - properties: - name: - description: name of parameter - type: string - value: - description: value of parameter - type: string - required: - - name - - value - type: object - type: array - pins: - description: consists of grpc(server, client) and mq pin sections - type: object - properties: - grpc: - description: pin section for grpc - type: object - properties: - server: - description: grpc server subsection - type: array - items: - type: object - properties: - serviceClasses: - description: list of the service classes supported by the grpc server - items: - type: string - type: array - name: - description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters - type: string - pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" - maxLength: 70 - required: - - name - - serviceClasses - client: - description: grpc client subsection - type: array - items: - type: object - properties: - attributes: - description: attributes of grpc client - items: - type: string - type: array - serviceClass: - description: name of the service class used supported by the grpc client - type: string - strategy: - description: strategy to be used for grpc communication. default value is set to robin - type: string - default: robin - filters: - description: filters for grpc client - type: array - items: - type: object - properties: - properties: - description: DESCRIPTION NEEDED - type: array - items: - type: object - properties: - expectedValue: - description: DESCRIPTION NEEDED - type: string - fieldName: - description: DESCRIPTION NEEDED - type: string - operation: - description: DESCRIPTION NEEDED - enum: - - EQUAL - - NOT_EQUAL - - EMPTY - - NOT_EMPTY - - WILDCARD - type: string - required: - - fieldName - - expectedValue - - operation - linkTo: - description: DESCRIPTION NEEDED - type: array - items: - type: object - properties: - box: - description: name of the box - type: string - pin: - description: represents the pin of the box that we want to connect - type: string - required: - - box - - pin - name: - description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters - type: string - pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" - maxLength: 70 - required: - - name - - serviceClass - mq: - type: object - properties: - subscribers: - description: section for subscriber mq pins - type: array - items: - type: object - required: - - name - properties: - name: - description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters - type: string - pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" - maxLength: 70 - attributes: - description: attributes for mq - items: - type: string - type: array - filters: - description: filters for mq - type: array - items: - type: object - properties: - properties: - description: DESCRIPTION NEEDED - type: array - items: - type: object - properties: - expectedValue: - description: DESCRIPTION NEEDED - type: string - fieldName: - description: DESCRIPTION NEEDED - type: string - operation: - description: DESCRIPTION NEEDED - enum: - - EQUAL - - NOT_EQUAL - - EMPTY - - NOT_EMPTY - - WILDCARD - type: string - required: - - fieldName - - expectedValue - - operation - message: - description: DESCRIPTION NEEDED - type: array - items: - type: object - properties: - expectedValue: - description: DESCRIPTION NEEDED - type: string - fieldName: - description: DESCRIPTION NEEDED - type: string - operation: - description: DESCRIPTION NEEDED - enum: - - EQUAL - - NOT_EQUAL - - EMPTY - - NOT_EMPTY - - WILDCARD - type: string - required: - - fieldName - - expectedValue - - operation - metadata: - description: metadata for mq pin - type: array - items: - type: object - properties: - expectedValue: - description: DESCRIPTION NEEDED - type: string - fieldName: - description: DESCRIPTION NEEDED - type: string - operation: - description: DESCRIPTION NEEDED - enum: - - EQUAL - - NOT_EQUAL - - EMPTY - - NOT_EMPTY - - WILDCARD - type: string - required: - - fieldName - - expectedValue - - operation - linkTo: - description: DESCRIPTION NEEDED - type: array - items: - type: object - properties: - box: - description: name of the box - type: string - pin: - description: represents the pin of the box that we want to connect - type: string - required: - - box - - pin - settings: - description: settings for rabbitMq queue configuration - type: object - properties: - overloadStrategy: - description: configuration for rabbit mq queue. default is set to “drop-head”. - type: string - enum: - - "drop-head" - - "reject-publish" - default: "drop-head" - queueLength: - description: configuration for rabbit mq queue. default is set to 1000 msg. queueLength isn't used if storageOnDemand is set to true. - type: integer - default: 1000 - storageOnDemand: - description: configuration for rabbit mq queue. default value is set to false - type: boolean - default: false - publishers: - description: section for subscriber mq pins - type: array - items: - required: - - name - type: object - properties: - name: - description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters - type: string - pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" - maxLength: 70 - attributes: - description: attributes for mq - items: - type: string - type: array - filters: - description: filters for mq - type: array - items: - type: object - properties: - properties: - description: DESCRIPTION NEEDED - type: array - items: - type: object - properties: - expectedValue: - description: DESCRIPTION NEEDED - type: string - fieldName: - description: DESCRIPTION NEEDED - type: string - operation: - description: DESCRIPTION NEEDED - enum: - - EQUAL - - NOT_EQUAL - - EMPTY - - NOT_EMPTY - - WILDCARD - type: string - required: - - fieldName - - expectedValue - - operation - message: - description: DESCRIPTION NEEDED - type: array - items: - type: object - properties: - expectedValue: - description: DESCRIPTION NEEDED - type: string - fieldName: - description: DESCRIPTION NEEDED - type: string - operation: - description: DESCRIPTION NEEDED - enum: - - EQUAL - - NOT_EQUAL - - EMPTY - - NOT_EMPTY - - WILDCARD - type: string - required: - - fieldName - - expectedValue - - operation - metadata: - description: metadata for mq pin - type: array - items: - type: object - properties: - expectedValue: - description: DESCRIPTION NEEDED - type: string - fieldName: - description: DESCRIPTION NEEDED - type: string - operation: - description: DESCRIPTION NEEDED - enum: - - EQUAL - - NOT_EQUAL - - EMPTY - - NOT_EMPTY - - WILDCARD - type: string - required: - - fieldName - - expectedValue - - operation - prometheus: - description: custom configuration of prometheus for microservices - properties: - enabled: - description: By default this is set to true. - type: boolean - host: - description: host for prometheus - type: string - port: - description: port for prometheus - type: integer - type: object - type: - enum: - - th2-act - - th2-codec - - th2-conn - - th2-rpt-data-provider - - th2-rpt-viewer - - th2-check1 - - th2-check2-bookcheck - - th2-check2-recon - - th2-script - - th2-sim - - th2-hand - - th2-read - - th2-util - - th2-cradle-viewer - - th2-crawler - - th2-crawler-processor - - th2-data-provider - - th2-adapter - - th2-reactive-script - - th2-monkey - type: string - required: - - imageName - - imageVersion - - type - type: object - status: - description: contains status information about this resource - properties: - conditions: - description: Conditions contains observations of the resource's state - items: - properties: - lastTransitionTime: - description: LastTransitionTime is the timestamp corresponding - to the last status change of this condition. - format: date-time - type: string - lastUpdateTime: - description: LastUpdateTime is the timestamp corresponding to - the last status update of this condition. - format: date-time - type: string - message: - description: Message is a human readable description of the - details of the last transition, complementing reason. - nullable: true - type: string - reason: - description: Reason is a brief machine readable explanation - for the condition's last transition. - type: string - status: - description: Status of the condition, one of ('True', 'False', - 'Unknown'). - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: Type of the condition - enum: - - Deployed - - Enqueued - type: string - required: - - status - - type - type: object - type: array - message: - description: Message describe current state of this resource - type: string - phase: - description: ComponentStatus is the status as given by Operator for - this resource - type: string - subResourceName: - description: SubResourceName is the name of produced helmrelease tied - with this resource - type: string - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - listKind: "" - plural: "" - singular: "" - conditions: [] - storedVersions: [] \ No newline at end of file diff --git a/src/test/resources/crds/th2-dictionary-crd.yaml b/src/test/resources/crds/th2-dictionary-crd.yaml deleted file mode 100644 index a6928fe3..00000000 --- a/src/test/resources/crds/th2-dictionary-crd.yaml +++ /dev/null @@ -1,53 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: th2dictionaries.th2.exactpro.com -spec: - conversion: - strategy: None - group: th2.exactpro.com - names: - kind: Th2Dictionary - listKind: Th2DictionaryList - plural: th2dictionaries - singular: th2dictionary - scope: Namespaced - versions: - - name: v2 - schema: - openAPIV3Schema: - description: Th2Dictionary defines th2 dictionary instance - properties: - apiVersion: - type: string - kind: - type: string - metadata: - type: object - spec: - description: Specification of desired dictionary - properties: - compressed: - description: Indicates whether dictionary's data is already compressed. If set to true, no further compression will take place in infra. default is set to false - type: boolean - data: - description: data for the dictionary - type: string - required: - - data - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - listKind: "" - plural: "" - singular: "" - conditions: [] - storedVersions: [] \ No newline at end of file diff --git a/src/test/resources/crds/th2-estore-crd.yaml b/src/test/resources/crds/th2-estore-crd.yaml deleted file mode 100644 index af92c9db..00000000 --- a/src/test/resources/crds/th2-estore-crd.yaml +++ /dev/null @@ -1,698 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: th2estores.th2.exactpro.com -spec: - conversion: - strategy: None - group: th2.exactpro.com - names: - kind: Th2Estore - listKind: Th2EstoreList - plural: th2estores - singular: th2estore - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .status.subResourceName - name: HelmRelease - type: string - - jsonPath: .status.phase - name: Status - type: string - - jsonPath: .status.message - name: Message - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2 - schema: - openAPIV3Schema: - description: Th2Estore defines th2 event store instance - properties: - apiVersion: - type: string - kind: - type: string - metadata: - type: object - spec: - description: Specification of desired event store - properties: - customConfig: - description: this is custom configuration - type: object - x-kubernetes-preserve-unknown-fields: true - loggingConfig: - description: this is logging configuration - type: string - mqRouter: - description: this is custom configuration for router mq - type: object - x-kubernetes-preserve-unknown-fields: true - grpcRouter: - description: this is custom configuration for router grpc - type: object - x-kubernetes-preserve-unknown-fields: true - cradleManager: - description: this is custom configuration for cradle - type: object - x-kubernetes-preserve-unknown-fields: true - bookName: - description: Can be used for passing custom book for a specific CR - type: string - extendedSettings: - type: object - description: section for extended settings - properties: - entryPoint: - description: allows user to pass custom ENTRYPOINT into the docker container - type: object - properties: - command: - description: allows user to pass custom command for the docker container entrypoint - type: array - items: - type: string - args: - description: allows user to pass custom arguments for the docker container entrypoint - type: array - items: - type: string - replicas: - description: number of replicas - type: integer - k8sProbes: - description: if enabled liveness probes will be collected from pod - type: boolean - default: false - envVariables: - description: allows us to pass specific environment variables that are going to be set into the pods. - type: object - x-kubernetes-preserve-unknown-fields: true - sharedMemory: - description: DESCRIPTION NEEDED - type: object - properties: - enabled: - description: DESCRIPTION NEEDED - type: boolean - externalBox: - description: using this section we can configure boxes that are going to be run outside of kubernetes cluster - properties: - address: - description: address to the machine on which external box is running - type: string - enabled: - description: represents the state of the external box - type: boolean - endpoints: - description: internal mapping for ports - items: - properties: - name: - description: name for the endpoint - type: string - targetPort: - description: value for exposed port - type: integer - required: - - name - - targetPort - type: object - type: array - type: object - hostAliases: - description: HostAliases is an optional list of hosts and IPs that will be injected into the pod's hosts file if specified. This is only valid for non-hostNetwork pods. - type: array - items: - properties: - ip: - description: IP address of the host file entry. - type: string - hostnames: - description: Hostnames for the above IP address. - items: - type: string - type: array - required: - - ip - - hostnames - type: object - hostNetwork: - description: if the flag is set to true, pod will run on node network and kubernetes will decide which node will be used for running the box. - type: boolean - nodeSelector: - additionalProperties: - type: string - description: | - NodeSelector is a selector which must be true for the pod to fit on a node. - Selector which must match a node's labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector - type: object - mounting: - description: allows to configure persistent volume mounting for pods - items: - properties: - path: - description: a directory in pod where you mount external folder - type: string - pvcName: - description: name for the pvc - type: string - type: object - type: array - resources: - description: here we can specify resource limitations and allowances for this specific component - properties: - limits: - properties: - cpu: - type: string - memory: - type: string - type: object - requests: - properties: - cpu: - type: string - memory: - type: string - type: object - type: object - service: - type: object - properties: - enabled: - description: if enabled allows pod to be exposed using ClusterIP - type: boolean - clusterIP: - description: endpoints for cluster IP service type - type: array - items: - type: object - properties: - name: - description: name of the endpoint - type: string - containerPort: - description: allows to specify Kubernetes port for the pod. - type: integer - required: - - name - nodePort: - description: endpoints for nodePort service type - type: array - items: - type: object - properties: - name: - description: name of the endpoint - type: string - exposedPort: - description: a port on which the service will be accessible. - type: integer - containerPort: - description: allows to specify Kubernetes port for the pod. - type: integer - required: - - name - - exposedPort - loadBalancer: - description: endpoints for loadBalancer service type - type: array - items: - type: object - properties: - name: - description: name of the endpoint - type: string - containerPort: - description: allows to specify Kubernetes port for the pod. - type: integer - required: - - name - ingress: - description: section for ingress - type: object - properties: - urlPaths: - description: url paths - type: array - items: - type: string - pattern: "^[-a-zA-Z0-9_/]+$" - disabled: - description: if set to true resource will act as if it is removed from schema without actually deleting the file. The default value is false. - type: boolean - imageName: - description: docker image repository URL - type: string - imageVersion: - description: docker image tag - type: string - versionRange: - description: when the appropriate pattern is provided, image-version will be updated with the latest tag from the image repository that satisfies that pattern. - type: string - params: - description: List of generic parameters - items: - properties: - name: - description: name of parameter - type: string - value: - description: value of parameter - type: string - required: - - name - - value - type: object - type: array - pins: - description: consists of grpc(server, client) and mq pin sections - type: object - properties: - grpc: - description: pin section for grpc - type: object - properties: - server: - description: grpc server subsection - type: array - items: - type: object - properties: - serviceClasses: - description: list of the service classes supported by the grpc server - items: - type: string - type: array - name: - description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters - type: string - pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" - maxLength: 70 - required: - - name - - serviceClasses - client: - description: grpc client subsection - type: array - items: - type: object - properties: - attributes: - description: attributes of grpc client - items: - type: string - type: array - serviceClass: - description: name of the service class used supported by the grpc client - type: string - strategy: - description: strategy to be used for grpc communication. default value is set to robin - type: string - default: robin - filters: - description: filters for grpc client - type: array - items: - type: object - properties: - properties: - description: DESCRIPTION NEEDED - type: array - items: - type: object - properties: - expectedValue: - description: DESCRIPTION NEEDED - type: string - fieldName: - description: DESCRIPTION NEEDED - type: string - operation: - description: DESCRIPTION NEEDED - enum: - - EQUAL - - NOT_EQUAL - - EMPTY - - NOT_EMPTY - - WILDCARD - type: string - required: - - fieldName - - expectedValue - - operation - linkTo: - description: DESCRIPTION NEEDED - type: array - items: - type: object - properties: - box: - description: name of the box - type: string - pin: - description: represents the pin of the box that we want to connect - type: string - required: - - box - - pin - name: - description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters - type: string - pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" - maxLength: 70 - required: - - name - - serviceClass - mq: - type: object - properties: - subscribers: - description: section for subscriber mq pins - type: array - items: - type: object - required: - - name - properties: - name: - description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters - type: string - pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" - maxLength: 70 - attributes: - description: attributes for mq - items: - type: string - type: array - filters: - description: filters for mq - type: array - items: - type: object - properties: - properties: - description: DESCRIPTION NEEDED - type: array - items: - type: object - properties: - expectedValue: - description: DESCRIPTION NEEDED - type: string - fieldName: - description: DESCRIPTION NEEDED - type: string - operation: - description: DESCRIPTION NEEDED - enum: - - EQUAL - - NOT_EQUAL - - EMPTY - - NOT_EMPTY - - WILDCARD - type: string - required: - - fieldName - - expectedValue - - operation - message: - description: DESCRIPTION NEEDED - type: array - items: - type: object - properties: - expectedValue: - description: DESCRIPTION NEEDED - type: string - fieldName: - description: DESCRIPTION NEEDED - type: string - operation: - description: DESCRIPTION NEEDED - enum: - - EQUAL - - NOT_EQUAL - - EMPTY - - NOT_EMPTY - - WILDCARD - type: string - required: - - fieldName - - expectedValue - - operation - metadata: - description: metadata for mq pin - type: array - items: - type: object - properties: - expectedValue: - description: DESCRIPTION NEEDED - type: string - fieldName: - description: DESCRIPTION NEEDED - type: string - operation: - description: DESCRIPTION NEEDED - enum: - - EQUAL - - NOT_EQUAL - - EMPTY - - NOT_EMPTY - - WILDCARD - type: string - required: - - fieldName - - expectedValue - - operation - linkTo: - description: DESCRIPTION NEEDED - type: array - items: - type: object - properties: - box: - description: name of the box - type: string - pin: - description: represents the pin of the box that we want to connect - type: string - required: - - box - - pin - settings: - description: settings for rabbitMq queue configuration - type: object - properties: - overloadStrategy: - description: configuration for rabbit mq queue. default is set to “drop-head”. - type: string - enum: - - "drop-head" - - "reject-publish" - default: "drop-head" - queueLength: - description: configuration for rabbit mq queue. default is set to 1000 msg. queueLength isn't used if storageOnDemand is set to true. - type: integer - default: 1000 - storageOnDemand: - description: configuration for rabbit mq queue. default value is set to false - type: boolean - default: false - publishers: - description: section for subscriber mq pins - type: array - items: - required: - - name - type: object - properties: - name: - description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters - type: string - pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" - maxLength: 70 - attributes: - description: attributes for mq - items: - type: string - type: array - filters: - description: filters for mq - type: array - items: - type: object - properties: - properties: - description: DESCRIPTION NEEDED - type: array - items: - type: object - properties: - expectedValue: - description: DESCRIPTION NEEDED - type: string - fieldName: - description: DESCRIPTION NEEDED - type: string - operation: - description: DESCRIPTION NEEDED - enum: - - EQUAL - - NOT_EQUAL - - EMPTY - - NOT_EMPTY - - WILDCARD - type: string - required: - - fieldName - - expectedValue - - operation - message: - description: DESCRIPTION NEEDED - type: array - items: - type: object - properties: - expectedValue: - description: DESCRIPTION NEEDED - type: string - fieldName: - description: DESCRIPTION NEEDED - type: string - operation: - description: DESCRIPTION NEEDED - enum: - - EQUAL - - NOT_EQUAL - - EMPTY - - NOT_EMPTY - - WILDCARD - type: string - required: - - fieldName - - expectedValue - - operation - metadata: - description: metadata for mq pin - type: array - items: - type: object - properties: - expectedValue: - description: DESCRIPTION NEEDED - type: string - fieldName: - description: DESCRIPTION NEEDED - type: string - operation: - description: DESCRIPTION NEEDED - enum: - - EQUAL - - NOT_EQUAL - - EMPTY - - NOT_EMPTY - - WILDCARD - type: string - required: - - fieldName - - expectedValue - - operation - prometheus: - description: custom configuration of prometheus for microservices - properties: - enabled: - description: By default this is set to true. - type: boolean - host: - description: host for prometheus - type: string - port: - description: port for prometheus - type: integer - type: object - type: - enum: - - th2-estore - type: string - required: - - imageName - - imageVersion - type: object - status: - description: contains status information about this resource - properties: - conditions: - description: Conditions contains observations of the resource's state - items: - properties: - lastTransitionTime: - description: LastTransitionTime is the timestamp corresponding - to the last status change of this condition. - format: date-time - type: string - lastUpdateTime: - description: LastUpdateTime is the timestamp corresponding to - the last status update of this condition. - format: date-time - type: string - message: - description: Message is a human readable description of the - details of the last transition, complementing reason. - nullable: true - type: string - reason: - description: Reason is a brief machine readable explanation - for the condition's last transition. - type: string - status: - description: Status of the condition, one of ('True', 'False', - 'Unknown'). - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: Type of the condition - enum: - - Deployed - - Enqueued - type: string - required: - - status - - type - type: object - type: array - message: - description: Message describe current state of this resource - type: string - phase: - description: ComponentStatus is the status as given by Operator for - this resource - type: string - subResourceName: - description: SubResourceName is the name of produced helmrelease tied - with this resource - type: string - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - listKind: "" - plural: "" - singular: "" - conditions: [] - storedVersions: [] \ No newline at end of file diff --git a/src/test/resources/crds/th2-job-crd.yaml b/src/test/resources/crds/th2-job-crd.yaml deleted file mode 100644 index a5874bec..00000000 --- a/src/test/resources/crds/th2-job-crd.yaml +++ /dev/null @@ -1,699 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: th2jobs.th2.exactpro.com -spec: - conversion: - strategy: None - group: th2.exactpro.com - names: - kind: Th2Job - listKind: Th2JobList - plural: th2jobs - singular: th2job - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .status.subResourceName - name: HelmRelease - type: string - - jsonPath: .spec.type - name: Type - type: string - - jsonPath: .status.phase - name: Status - type: string - - jsonPath: .status.message - name: Message - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2 - schema: - openAPIV3Schema: - description: Th2Jox defines a resource for single execution tasks - properties: - apiVersion: - type: string - kind: - type: string - metadata: - type: object - spec: - description: Specification of desired box - properties: - customConfig: - description: this is custom configuration - type: object - x-kubernetes-preserve-unknown-fields: true - loggingConfig: - description: this is logging configuration - type: string - mqRouter: - description: this is custom configuration for router mq - type: object - x-kubernetes-preserve-unknown-fields: true - grpcRouter: - description: this is custom configuration for router grpc - type: object - x-kubernetes-preserve-unknown-fields: true - cradleManager: - description: this is custom configuration for cradle - type: object - x-kubernetes-preserve-unknown-fields: true - bookName: - description: Can be used for passing custom book for a specific CR - type: string - extendedSettings: - type: object - description: section for extended settings - properties: - entryPoint: - description: allows user to pass custom ENTRYPOINT into the docker container - type: object - properties: - command: - description: allows user to pass custom command for the docker container entrypoint - type: array - items: - type: string - args: - description: allows user to pass custom arguments for the docker container entrypoint - type: array - items: - type: string - parallelism: - type: integer - description: Specifies the maximum desired number of pods the job should run at any given time. The actual number of pods running in steady state will be less than this number when ((.spec.completions - .status.successful) < .spec.parallelism), i.e. when the work left to do is less than max parallelism - completions: - type: integer - description: Specifies the desired number of successfully finished pods the job should be run with. Setting to nil means that the success of any pod signals the success of all pods, and allows parallelism to have any positive value. Setting to 1 means that parallelism is limited to 1 and the success of that pod signals the success of the job - envVariables: - description: allows us to pass specific environment variables that are going to be set into the pods. - type: object - x-kubernetes-preserve-unknown-fields: true - sharedMemory: - description: DESCRIPTION NEEDED - type: object - properties: - enabled: - description: DESCRIPTION NEEDED - type: boolean - replicas: - description: number of replicas - type: integer - k8sProbes: - description: if enabled liveness probes will be collected from pod - type: boolean - default: false - externalBox: - description: using this section we can configure boxes that are going to be run outside of kubernetes cluster - properties: - address: - description: address to the machine on which external box is running - type: string - enabled: - description: represents the state of the external box - type: boolean - endpoints: - description: internal mapping for ports - items: - properties: - name: - description: name for the endpoint - type: string - targetPort: - description: value for exposed port - type: integer - required: - - name - - targetPort - type: object - type: array - type: object - hostAliases: - description: HostAliases is an optional list of hosts and IPs that will be injected into the pod's hosts file if specified. This is only valid for non-hostNetwork pods. - type: array - items: - properties: - ip: - description: IP address of the host file entry. - type: string - hostnames: - description: Hostnames for the above IP address. - items: - type: string - type: array - required: - - ip - - hostnames - type: object - hostNetwork: - description: if the flag is set to true, pod will run on node network and kubernetes will decide which node will be used for running the box. - type: boolean - nodeSelector: - additionalProperties: - type: string - description: | - NodeSelector is a selector which must be true for the pod to fit on a node. - Selector which must match a node's labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector - type: object - mounting: - description: allows to configure persistent volume mounting for pods - items: - properties: - path: - description: a directory in pod where you mount external folder - type: string - pvcName: - description: name for the pvc - type: string - type: object - type: array - resources: - description: here we can specify resource limitations and allowances for this specific component - properties: - limits: - properties: - cpu: - type: string - memory: - type: string - type: object - requests: - properties: - cpu: - type: string - memory: - type: string - type: object - type: object - service: - type: object - properties: - enabled: - description: if enabled allows pod to be exposed using ClusterIP - type: boolean - clusterIP: - description: endpoints for cluster IP service type - type: array - items: - type: object - properties: - name: - description: name of the endpoint - type: string - containerPort: - description: allows to specify Kubernetes port for the pod. - type: integer - required: - - name - nodePort: - description: endpoints for nodePort service type - type: array - items: - type: object - properties: - name: - description: name of the endpoint - type: string - exposedPort: - description: a port on which the service will be accessible. - type: integer - containerPort: - description: allows to specify Kubernetes port for the pod. - type: integer - required: - - name - - exposedPort - loadBalancer: - description: endpoints for loadBalancer service type - type: array - items: - type: object - properties: - name: - description: name of the endpoint - type: string - containerPort: - description: allows to specify Kubernetes port for the pod. - type: integer - required: - - name - ingress: - description: Creates Ingress for the Box - type: object - properties: - urlPaths: - description: | - Path is a suffix, it is appended to namespace url, e.g for th2-test namespace: - - urlPaths: - - /my-box/ - - service will be available at /th2-test/my-box/ - type: array - items: - type: string - pattern: "^[-a-zA-Z0-9_/]+$" - disabled: - description: if set to true resource will act as if it is removed from schema without actually deleting the file. The default value is false. - type: boolean - imageName: - description: docker image repository URL - type: string - imageVersion: - description: docker image tag - type: string - versionRange: - description: when the appropriate pattern is provided, image-version will be updated with the latest tag from the image repository that satisfies that pattern. - type: string - pins: - description: consists of grpc(server, client) and mq pin sections - type: object - properties: - grpc: - description: pin section for grpc - type: object - properties: - server: - description: grpc server subsection - type: array - items: - type: object - properties: - serviceClasses: - description: list of the service classes supported by the grpc server - items: - type: string - type: array - name: - description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters - type: string - pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" - maxLength: 70 - required: - - name - - serviceClasses - client: - description: grpc client subsection - type: array - items: - type: object - properties: - attributes: - description: attributes of grpc client - items: - type: string - type: array - serviceClass: - description: name of the service class used supported by the grpc client - type: string - strategy: - description: strategy to be used for grpc communication. default value is set to robin - type: string - default: robin - filters: - description: filters for grpc client - type: array - items: - type: object - properties: - properties: - description: DESCRIPTION NEEDED - type: array - items: - type: object - properties: - expectedValue: - description: DESCRIPTION NEEDED - type: string - fieldName: - description: DESCRIPTION NEEDED - type: string - operation: - description: DESCRIPTION NEEDED - enum: - - EQUAL - - NOT_EQUAL - - EMPTY - - NOT_EMPTY - - WILDCARD - type: string - required: - - fieldName - - expectedValue - - operation - linkTo: - description: DESCRIPTION NEEDED - type: array - items: - type: object - properties: - box: - description: name of the box - type: string - pin: - description: represents the pin of the box that we want to connect - type: string - required: - - box - - pin - name: - description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters - type: string - pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" - maxLength: 70 - required: - - name - - serviceClass - mq: - type: object - properties: - subscribers: - description: section for subscriber mq pins - type: array - items: - type: object - required: - - name - properties: - name: - description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters - type: string - pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" - maxLength: 70 - attributes: - description: attributes for mq - items: - type: string - type: array - filters: - description: filters for mq - type: array - items: - type: object - properties: - properties: - description: DESCRIPTION NEEDED - type: array - items: - type: object - properties: - expectedValue: - description: DESCRIPTION NEEDED - type: string - fieldName: - description: DESCRIPTION NEEDED - type: string - operation: - description: DESCRIPTION NEEDED - enum: - - EQUAL - - NOT_EQUAL - - EMPTY - - NOT_EMPTY - - WILDCARD - type: string - required: - - fieldName - - expectedValue - - operation - message: - description: DESCRIPTION NEEDED - type: array - items: - type: object - properties: - expectedValue: - description: DESCRIPTION NEEDED - type: string - fieldName: - description: DESCRIPTION NEEDED - type: string - operation: - description: DESCRIPTION NEEDED - enum: - - EQUAL - - NOT_EQUAL - - EMPTY - - NOT_EMPTY - - WILDCARD - type: string - required: - - fieldName - - expectedValue - - operation - metadata: - description: metadata for mq pin - type: array - items: - type: object - properties: - expectedValue: - description: DESCRIPTION NEEDED - type: string - fieldName: - description: DESCRIPTION NEEDED - type: string - operation: - description: DESCRIPTION NEEDED - enum: - - EQUAL - - NOT_EQUAL - - EMPTY - - NOT_EMPTY - - WILDCARD - type: string - required: - - fieldName - - expectedValue - - operation - linkTo: - description: DESCRIPTION NEEDED - type: array - items: - type: object - properties: - box: - description: name of the box - type: string - pin: - description: represents the pin of the box that we want to connect - type: string - required: - - box - - pin - settings: - description: settings for rabbitMq queue configuration - type: object - properties: - overloadStrategy: - description: configuration for rabbit mq queue. default is set to “drop-head”. - type: string - enum: - - "drop-head" - - "reject-publish" - default: "drop-head" - queueLength: - description: configuration for rabbit mq queue. default is set to 1000 msg. queueLength isn't used if storageOnDemand is set to true. - type: integer - default: 1000 - storageOnDemand: - description: configuration for rabbit mq queue. default value is set to false - type: boolean - default: false - publishers: - description: section for subscriber mq pins - type: array - items: - required: - - name - type: object - properties: - name: - description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters - type: string - pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" - maxLength: 70 - attributes: - description: attributes for mq - items: - type: string - type: array - filters: - description: filters for mq - type: array - items: - type: object - properties: - properties: - description: DESCRIPTION NEEDED - type: array - items: - type: object - properties: - expectedValue: - description: DESCRIPTION NEEDED - type: string - fieldName: - description: DESCRIPTION NEEDED - type: string - operation: - description: DESCRIPTION NEEDED - enum: - - EQUAL - - NOT_EQUAL - - EMPTY - - NOT_EMPTY - - WILDCARD - type: string - required: - - fieldName - - expectedValue - - operation - message: - description: DESCRIPTION NEEDED - type: array - items: - type: object - properties: - expectedValue: - description: DESCRIPTION NEEDED - type: string - fieldName: - description: DESCRIPTION NEEDED - type: string - operation: - description: DESCRIPTION NEEDED - enum: - - EQUAL - - NOT_EQUAL - - EMPTY - - NOT_EMPTY - - WILDCARD - type: string - required: - - fieldName - - expectedValue - - operation - metadata: - description: metadata for mq pin - type: array - items: - type: object - properties: - expectedValue: - description: DESCRIPTION NEEDED - type: string - fieldName: - description: DESCRIPTION NEEDED - type: string - operation: - description: DESCRIPTION NEEDED - enum: - - EQUAL - - NOT_EQUAL - - EMPTY - - NOT_EMPTY - - WILDCARD - type: string - required: - - fieldName - - expectedValue - - operation - prometheus: - description: custom configuration of prometheus for microservices - properties: - enabled: - description: By default this is set to true. - type: boolean - host: - description: host for prometheus - type: string - port: - description: port for prometheus - type: integer - type: object - type: - enum: - - th2-job - type: string - required: - - imageName - - imageVersion - - type - type: object - status: - description: contains status information about this resource - properties: - conditions: - description: Conditions contains observations of the resource's state - items: - properties: - lastTransitionTime: - description: LastTransitionTime is the timestamp corresponding - to the last status change of this condition. - format: date-time - type: string - lastUpdateTime: - description: LastUpdateTime is the timestamp corresponding to - the last status update of this condition. - format: date-time - type: string - message: - description: Message is a human readable description of the - details of the last transition, complementing reason. - nullable: true - type: string - reason: - description: Reason is a brief machine readable explanation - for the condition's last transition. - type: string - status: - description: Status of the condition, one of ('True', 'False', - 'Unknown'). - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: Type of the condition - enum: - - Deployed - - Enqueued - type: string - required: - - status - - type - type: object - type: array - message: - description: Message describe current state of this resource - type: string - phase: - description: ComponentStatus is the status as given by Operator for - this resource - type: string - subResourceName: - description: SubResourceName is the name of produced helmrelease tied - with this resource - type: string - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - listKind: "" - plural: "" - singular: "" - conditions: [] - storedVersions: [] \ No newline at end of file diff --git a/src/test/resources/crds/th2-mstore-crd.yaml b/src/test/resources/crds/th2-mstore-crd.yaml deleted file mode 100644 index f3805c06..00000000 --- a/src/test/resources/crds/th2-mstore-crd.yaml +++ /dev/null @@ -1,698 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: th2mstores.th2.exactpro.com -spec: - conversion: - strategy: None - group: th2.exactpro.com - names: - kind: Th2Mstore - listKind: Th2MstoreList - plural: th2mstores - singular: th2mstore - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .status.subResourceName - name: HelmRelease - type: string - - jsonPath: .status.phase - name: Status - type: string - - jsonPath: .status.message - name: Message - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2 - schema: - openAPIV3Schema: - description: Th2Mstore defines th2 message store instance - properties: - apiVersion: - type: string - kind: - type: string - metadata: - type: object - spec: - description: Specification of desired message store - properties: - customConfig: - description: this is custom configuration - type: object - x-kubernetes-preserve-unknown-fields: true - loggingConfig: - description: this is logging configuration - type: string - mqRouter: - description: this is custom configuration for router mq - type: object - x-kubernetes-preserve-unknown-fields: true - grpcRouter: - description: this is custom configuration for router grpc - type: object - x-kubernetes-preserve-unknown-fields: true - cradleManager: - description: this is custom configuration for cradle - type: object - x-kubernetes-preserve-unknown-fields: true - bookName: - description: Can be used for passing custom book for a specific CR - type: string - extendedSettings: - type: object - description: section for extended settings - properties: - entryPoint: - description: allows user to pass custom ENTRYPOINT into the docker container - type: object - properties: - command: - description: allows user to pass custom command for the docker container entrypoint - type: array - items: - type: string - args: - description: allows user to pass custom arguments for the docker container entrypoint - type: array - items: - type: string - replicas: - description: number of replicas - type: integer - k8sProbes: - description: if enabled liveness probes will be collected from pod - type: boolean - default: false - envVariables: - description: allows us to pass specific environment variables that are going to be set into the pods. - type: object - x-kubernetes-preserve-unknown-fields: true - sharedMemory: - description: DESCRIPTION NEEDED - type: object - properties: - enabled: - description: DESCRIPTION NEEDED - type: boolean - externalBox: - description: using this section we can configure boxes that are going to be run outside of kubernetes cluster - properties: - address: - description: address to the machine on which external box is running - type: string - enabled: - description: represents the state of the external box - type: boolean - endpoints: - description: internal mapping for ports - items: - properties: - name: - description: name for the endpoint - type: string - targetPort: - description: value for exposed port - type: integer - required: - - name - - targetPort - type: object - type: array - type: object - hostAliases: - description: HostAliases is an optional list of hosts and IPs that will be injected into the pod's hosts file if specified. This is only valid for non-hostNetwork pods. - type: array - items: - properties: - ip: - description: IP address of the host file entry. - type: string - hostnames: - description: Hostnames for the above IP address. - items: - type: string - type: array - required: - - ip - - hostnames - type: object - hostNetwork: - description: if the flag is set to true, pod will run on node network and kubernetes will decide which node will be used for running the box. - type: boolean - nodeSelector: - additionalProperties: - type: string - description: | - NodeSelector is a selector which must be true for the pod to fit on a node. - Selector which must match a node's labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector - type: object - mounting: - description: allows to configure persistent volume mounting for pods - items: - properties: - path: - description: a directory in pod where you mount external folder - type: string - pvcName: - description: name for the pvc - type: string - type: object - type: array - resources: - description: here we can specify resource limitations and allowances for this specific component - properties: - limits: - properties: - cpu: - type: string - memory: - type: string - type: object - requests: - properties: - cpu: - type: string - memory: - type: string - type: object - type: object - service: - type: object - properties: - enabled: - description: if enabled allows pod to be exposed using ClusterIP - type: boolean - clusterIP: - description: endpoints for cluster IP service type - type: array - items: - type: object - properties: - name: - description: name of the endpoint - type: string - containerPort: - description: allows to specify Kubernetes port for the pod. - type: integer - required: - - name - nodePort: - description: endpoints for nodePort service type - type: array - items: - type: object - properties: - name: - description: name of the endpoint - type: string - exposedPort: - description: a port on which the service will be accessible. - type: integer - containerPort: - description: allows to specify Kubernetes port for the pod. - type: integer - required: - - name - - exposedPort - loadBalancer: - description: endpoints for loadBalancer service type - type: array - items: - type: object - properties: - name: - description: name of the endpoint - type: string - containerPort: - description: allows to specify Kubernetes port for the pod. - type: integer - required: - - name - ingress: - description: section for ingress - type: object - properties: - urlPaths: - description: url paths - type: array - items: - type: string - pattern: "^[-a-zA-Z0-9_/]+$" - disabled: - description: if set to true resource will act as if it is removed from schema without actually deleting the file. The default value is false. - type: boolean - imageName: - description: docker image repository URL - type: string - imageVersion: - description: docker image tag - type: string - versionRange: - description: when the appropriate pattern is provided, image-version will be updated with the latest tag from the image repository that satisfies that pattern. - type: string - params: - description: List of generic parameters - items: - properties: - name: - description: name of parameter - type: string - value: - description: value of parameter - type: string - required: - - name - - value - type: object - type: array - pins: - description: consists of grpc(server, client) and mq pin sections - type: object - properties: - grpc: - description: pin section for grpc - type: object - properties: - server: - description: grpc server subsection - type: array - items: - type: object - properties: - serviceClasses: - description: list of the service classes supported by the grpc server - items: - type: string - type: array - name: - description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters - type: string - pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" - maxLength: 70 - required: - - name - - serviceClasses - client: - description: grpc client subsection - type: array - items: - type: object - properties: - attributes: - description: attributes of grpc client - items: - type: string - type: array - serviceClass: - description: name of the service class used supported by the grpc client - type: string - strategy: - description: strategy to be used for grpc communication. default value is set to robin - type: string - default: robin - filters: - description: filters for grpc client - type: array - items: - type: object - properties: - properties: - description: DESCRIPTION NEEDED - type: array - items: - type: object - properties: - expectedValue: - description: DESCRIPTION NEEDED - type: string - fieldName: - description: DESCRIPTION NEEDED - type: string - operation: - description: DESCRIPTION NEEDED - enum: - - EQUAL - - NOT_EQUAL - - EMPTY - - NOT_EMPTY - - WILDCARD - type: string - required: - - fieldName - - expectedValue - - operation - linkTo: - description: DESCRIPTION NEEDED - type: array - items: - type: object - properties: - box: - description: name of the box - type: string - pin: - description: represents the pin of the box that we want to connect - type: string - required: - - box - - pin - name: - description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters - type: string - pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" - maxLength: 70 - required: - - name - - serviceClass - mq: - type: object - properties: - subscribers: - description: section for subscriber mq pins - type: array - items: - type: object - required: - - name - properties: - name: - description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters - type: string - pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" - maxLength: 70 - attributes: - description: attributes for mq - items: - type: string - type: array - filters: - description: filters for mq - type: array - items: - type: object - properties: - properties: - description: DESCRIPTION NEEDED - type: array - items: - type: object - properties: - expectedValue: - description: DESCRIPTION NEEDED - type: string - fieldName: - description: DESCRIPTION NEEDED - type: string - operation: - description: DESCRIPTION NEEDED - enum: - - EQUAL - - NOT_EQUAL - - EMPTY - - NOT_EMPTY - - WILDCARD - type: string - required: - - fieldName - - expectedValue - - operation - message: - description: DESCRIPTION NEEDED - type: array - items: - type: object - properties: - expectedValue: - description: DESCRIPTION NEEDED - type: string - fieldName: - description: DESCRIPTION NEEDED - type: string - operation: - description: DESCRIPTION NEEDED - enum: - - EQUAL - - NOT_EQUAL - - EMPTY - - NOT_EMPTY - - WILDCARD - type: string - required: - - fieldName - - expectedValue - - operation - metadata: - description: metadata for mq pin - type: array - items: - type: object - properties: - expectedValue: - description: DESCRIPTION NEEDED - type: string - fieldName: - description: DESCRIPTION NEEDED - type: string - operation: - description: DESCRIPTION NEEDED - enum: - - EQUAL - - NOT_EQUAL - - EMPTY - - NOT_EMPTY - - WILDCARD - type: string - required: - - fieldName - - expectedValue - - operation - linkTo: - description: DESCRIPTION NEEDED - type: array - items: - type: object - properties: - box: - description: name of the box - type: string - pin: - description: represents the pin of the box that we want to connect - type: string - required: - - box - - pin - settings: - description: settings for rabbitMq queue configuration - type: object - properties: - overloadStrategy: - description: configuration for rabbit mq queue. default is set to “drop-head”. - type: string - enum: - - "drop-head" - - "reject-publish" - default: "drop-head" - queueLength: - description: configuration for rabbit mq queue. default is set to 1000 msg. queueLength isn't used if storageOnDemand is set to true. - type: integer - default: 1000 - storageOnDemand: - description: configuration for rabbit mq queue. default value is set to false - type: boolean - default: false - publishers: - description: section for subscriber mq pins - type: array - items: - required: - - name - type: object - properties: - name: - description: name for the pin. must follow the regex pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$ and max length of 71 characters - type: string - pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9]*[_a-z0-9])+$" - maxLength: 70 - attributes: - description: attributes for mq - items: - type: string - type: array - filters: - description: filters for mq - type: array - items: - type: object - properties: - properties: - description: DESCRIPTION NEEDED - type: array - items: - type: object - properties: - expectedValue: - description: DESCRIPTION NEEDED - type: string - fieldName: - description: DESCRIPTION NEEDED - type: string - operation: - description: DESCRIPTION NEEDED - enum: - - EQUAL - - NOT_EQUAL - - EMPTY - - NOT_EMPTY - - WILDCARD - type: string - required: - - fieldName - - expectedValue - - operation - message: - description: DESCRIPTION NEEDED - type: array - items: - type: object - properties: - expectedValue: - description: DESCRIPTION NEEDED - type: string - fieldName: - description: DESCRIPTION NEEDED - type: string - operation: - description: DESCRIPTION NEEDED - enum: - - EQUAL - - NOT_EQUAL - - EMPTY - - NOT_EMPTY - - WILDCARD - type: string - required: - - fieldName - - expectedValue - - operation - metadata: - description: metadata for mq pin - type: array - items: - type: object - properties: - expectedValue: - description: DESCRIPTION NEEDED - type: string - fieldName: - description: DESCRIPTION NEEDED - type: string - operation: - description: DESCRIPTION NEEDED - enum: - - EQUAL - - NOT_EQUAL - - EMPTY - - NOT_EMPTY - - WILDCARD - type: string - required: - - fieldName - - expectedValue - - operation - prometheus: - description: custom configuration of prometheus for microservices - properties: - enabled: - description: By default this is set to true. - type: boolean - host: - description: host for prometheus - type: string - port: - description: port for prometheus - type: integer - type: object - type: - enum: - - th2-mstore - type: string - required: - - imageName - - imageVersion - type: object - status: - description: contains status information about this resource - properties: - conditions: - description: Conditions contains observations of the resource's state - items: - properties: - lastTransitionTime: - description: LastTransitionTime is the timestamp corresponding - to the last status change of this condition. - format: date-time - type: string - lastUpdateTime: - description: LastUpdateTime is the timestamp corresponding to - the last status update of this condition. - format: date-time - type: string - message: - description: Message is a human readable description of the - details of the last transition, complementing reason. - nullable: true - type: string - reason: - description: Reason is a brief machine readable explanation - for the condition's last transition. - type: string - status: - description: Status of the condition, one of ('True', 'False', - 'Unknown'). - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: Type of the condition - enum: - - Deployed - - Enqueued - type: string - required: - - status - - type - type: object - type: array - message: - description: Message describe current state of this resource - type: string - phase: - description: ComponentStatus is the status as given by Operator for - this resource - type: string - subResourceName: - description: SubResourceName is the name of produced helmrelease tied - with this resource - type: string - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - listKind: "" - plural: "" - singular: "" - conditions: [] - storedVersions: [] \ No newline at end of file From 5dc5513ea10cbda3f95ded27fd455fe39aeb4e40 Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Mon, 16 Sep 2024 13:14:43 +0400 Subject: [PATCH 37/42] [TH2-5226] added mq link test --- .../integration/IntegrationTest.kt | 218 +++++++++++++++--- .../integration/TestIntegrationUtils.kt | 45 ++++ .../integration/TestRabbitMQUtils.kt | 11 +- 3 files changed, 238 insertions(+), 36 deletions(-) create mode 100644 src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestIntegrationUtils.kt diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt index c307026e..da43588d 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt @@ -70,7 +70,6 @@ import com.exactpro.th2.infraoperator.spec.shared.status.RolloutPhase.DISABLED import com.exactpro.th2.infraoperator.spec.shared.status.RolloutPhase.SUCCEEDED import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.createEstoreQueue import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.createMstoreQueue -import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext.DIRECT import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext.TOPIC import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext.toExchangeName @@ -95,13 +94,11 @@ import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.Timeout import org.junit.jupiter.api.io.TempDir import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource import org.junit.jupiter.params.provider.ValueSource -import org.slf4j.LoggerFactory.getLogger import org.testcontainers.containers.RabbitMQContainer -import org.testcontainers.containers.output.Slf4jLogConsumer import org.testcontainers.k3s.K3sContainer -import org.testcontainers.lifecycle.Startable -import org.testcontainers.utility.DockerImageName import strikt.api.expectThat import strikt.assertions.getValue import strikt.assertions.hasSize @@ -149,17 +146,8 @@ class IntegrationTest { operatorConfig = configDir.resolve("infra-operator.yml") configDir.createDirectories() - k3sContainer = - K3sContainer(K3S_DOCKER_IMAGE) - .withLogConsumer(Slf4jLogConsumer(getLogger("K3S")).withSeparateOutputStreams()) - .also(Startable::start) - - rabbitMQContainer = - RabbitMQContainer(RABBITMQ_DOCKER_IMAGE) - .withLogConsumer(Slf4jLogConsumer(getLogger("RABBIT_MQ")).withSeparateOutputStreams()) - .also(Startable::start) - - K_LOGGER.info { "RabbitMQ URL: ${rabbitMQContainer.httpUrl}" } + k3sContainer = createK3sContainer() + rabbitMQContainer = createRabbitMQContainer() Files.writeString(kubeConfig, k3sContainer.kubeConfigYaml) YAML_MAPPER.writeValue(operatorConfig.toFile(), createOperatorConfig(rabbitMQContainer)) @@ -218,8 +206,7 @@ class IntegrationTest { kubeClient.deleteNamespace(TH2_NAMESPACE, 1, MINUTES) // FIXME: Secret not found "th2-test:Secret/rabbitMQ" - rabbitMQClient.assertNoQueue(createEstoreQueue(TH2_NAMESPACE)) - rabbitMQClient.assertNoQueue(createMstoreQueue(TH2_NAMESPACE)) + rabbitMQClient.assertNoQueues("link\\[.*\\]", RABBIT_MQ_V_HOST) rabbitMQClient.assertNoExchange(toExchangeName(TH2_NAMESPACE)) rabbitMQClient.assertNoUser(TH2_NAMESPACE) @@ -244,6 +231,13 @@ class IntegrationTest { abstract fun add(name: String) abstract fun disable(name: String) abstract fun enable(name: String) + abstract fun `mq link`( + subClass: Class, + subConstructor: () -> Th2CustomResource, + subSpecType: String, + subRunAsJob: Boolean + ) + abstract fun `grpc link`(name: String) protected fun addTest(name: String) { val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() @@ -346,6 +340,118 @@ class IntegrationTest { ) ) } + + protected fun mkLinkTest( + pubClass: Class, + pubConstructor: () -> Th2CustomResource, + pubSpecType: String, + pubRunAsJob: Boolean, + subClass: Class, + subConstructor: () -> Th2CustomResource, + subSpecType: String, + subRunAsJob: Boolean, + ) { + val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() + val pubName = "test-publisher" + val subName = "test-subscriber" + + val publisherSpec = """ + imageName: $IMAGE + imageVersion: $VERSION + type: $pubSpecType + pins: + mq: + publishers: + - name: $PUBLISH_PIN + attributes: [publish] + """.trimIndent() + + val spec = """ + imageName: $IMAGE + imageVersion: $VERSION + type: $subSpecType + pins: + mq: + subscribers: + - name: $SUBSCRIBE_PIN + attributes: [subscribe] + linkTo: + - box: $pubName + pin: $PUBLISH_PIN + """.trimIndent() + + kubeClient.createTh2CustomResource(TH2_NAMESPACE, pubName, gitHash, publisherSpec, pubConstructor) + kubeClient.createTh2CustomResource(TH2_NAMESPACE, subName, gitHash, spec, subConstructor) + + kubeClient.awaitPhase(TH2_NAMESPACE, pubName, SUCCEEDED, pubClass) + kubeClient.awaitPhase(TH2_NAMESPACE, subName, SUCCEEDED, subClass) + + val queueName = "link[$TH2_NAMESPACE:$subName:$SUBSCRIBE_PIN]" + val routingKey = "key[$TH2_NAMESPACE:$pubName:$PUBLISH_PIN]" + + kubeClient.awaitResource(TH2_NAMESPACE, pubName).assertMinCfg( + pubName, + pubRunAsJob, + queues = mapOf( + PUBLISH_PIN to mapOf( + "attributes" to listOf("publish"), + "exchange" to toExchangeName(TH2_NAMESPACE), + "filters" to emptyList(), + "name" to routingKey, + "queue" to "", + ) + ) + ) + kubeClient.awaitResource(TH2_NAMESPACE, hashNameIfNeeded(subName)).assertMinCfg( + subName, + subRunAsJob, + queues = mapOf( + SUBSCRIBE_PIN to mapOf( + "attributes" to listOf("subscribe"), + "exchange" to toExchangeName(TH2_NAMESPACE), + "filters" to emptyList(), + "name" to "", + "queue" to queueName, + ) + ) + ) + + rabbitMQClient.assertBindings( + queueName, + RABBIT_MQ_V_HOST, + setOf("link[$TH2_NAMESPACE:$subName:$SUBSCRIBE_PIN]", routingKey) + ) + rabbitMQClient.assertBindings( + createEstoreQueue(TH2_NAMESPACE), + RABBIT_MQ_V_HOST, + setOf( + "link[$TH2_NAMESPACE:$EVENT_STORAGE_BOX_ALIAS:$EVENT_STORAGE_PIN_ALIAS]", + "key[$TH2_NAMESPACE:$pubName:$EVENT_STORAGE_PIN_ALIAS]", + "key[$TH2_NAMESPACE:$subName:$EVENT_STORAGE_PIN_ALIAS]", + ) + ) + } + + protected fun grpcLinkTest(name: String) { + val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() + val spec = """ + imageName: $IMAGE + imageVersion: $VERSION + type: $specType + """.trimIndent() + + kubeClient.createTh2CustomResource(TH2_NAMESPACE, name, gitHash, spec, this::createResources) + kubeClient.awaitPhase(TH2_NAMESPACE, name, SUCCEEDED, resourceClass) + kubeClient.awaitResource(TH2_NAMESPACE, hashNameIfNeeded(name)).assertMinCfg(name, runAsJob) + rabbitMQClient.assertBindings( + createEstoreQueue(TH2_NAMESPACE), + RABBIT_MQ_V_HOST, + setOf( + "link[$TH2_NAMESPACE:$EVENT_STORAGE_BOX_ALIAS:$EVENT_STORAGE_PIN_ALIAS]", + "key[$TH2_NAMESPACE:$name:$EVENT_STORAGE_PIN_ALIAS]" + ) + ) + } } @Nested @@ -415,6 +521,21 @@ class IntegrationTest { @ParameterizedTest @ValueSource(strings = ["th2-core-component", "th2-core-component-more-than-$NAME_LENGTH_LIMIT-characters"]) override fun enable(name: String) = enableTest(name) + + @Timeout(30_000) + @ParameterizedTest + @MethodSource("com.exactpro.th2.infraoperator.integration.IntegrationTest#mqLinkArguments") + override fun `mq link`( + subClass: Class, + subConstructor: () -> Th2CustomResource, + subSpecType: String, + subRunAsJob: Boolean + ) = mkLinkTest(resourceClass, ::createResources, specType, runAsJob, subClass, subConstructor, subSpecType, subRunAsJob) + + @Timeout(30_000) + @ParameterizedTest + @ValueSource(strings = ["th2-core-component", "th2-core-component-more-than-$NAME_LENGTH_LIMIT-characters"]) + override fun `grpc link`(name: String) = grpcLinkTest(name) } @Nested @@ -442,6 +563,21 @@ class IntegrationTest { @ParameterizedTest @ValueSource(strings = ["th2-component", "th2-component-more-than-$NAME_LENGTH_LIMIT-characters"]) override fun enable(name: String) = enableTest(name) + + @Timeout(30_000) + @ParameterizedTest + @MethodSource("com.exactpro.th2.infraoperator.integration.IntegrationTest#mqLinkArguments") + override fun `mq link`( + subClass: Class, + subConstructor: () -> Th2CustomResource, + subSpecType: String, + subRunAsJob: Boolean + ) = mkLinkTest(resourceClass, ::createResources, specType, runAsJob, subClass, subConstructor, subSpecType, subRunAsJob) + + @Timeout(30_000) + @ParameterizedTest + @ValueSource(strings = ["th2-component", "th2-component-more-than-$NAME_LENGTH_LIMIT-characters"]) + override fun `grpc link`(name: String) = grpcLinkTest(name) } @Nested @@ -469,6 +605,21 @@ class IntegrationTest { @ParameterizedTest @ValueSource(strings = ["th2-job", "th2-job-more-than-$NAME_LENGTH_LIMIT-characters"]) override fun enable(name: String) = enableTest(name) + + @Timeout(30_000) + @ParameterizedTest + @MethodSource("com.exactpro.th2.infraoperator.integration.IntegrationTest#mqLinkArguments") + override fun `mq link`( + subClass: Class, + subConstructor: () -> Th2CustomResource, + subSpecType: String, + subRunAsJob: Boolean + ) = mkLinkTest(resourceClass, ::createResources, specType, runAsJob, subClass, subConstructor, subSpecType, subRunAsJob) + + @Timeout(30_000) + @ParameterizedTest + @ValueSource(strings = ["th2-job", "th2-job-more-than-$NAME_LENGTH_LIMIT-characters"]) + override fun `grpc link`(name: String) = grpcLinkTest(name) } @Nested @@ -509,9 +660,6 @@ class IntegrationTest { private val RESOURCE_GIT_HASH_COUNTER = AtomicLong(10_000_000) - private val RABBITMQ_DOCKER_IMAGE = DockerImageName.parse("rabbitmq:3.12.6-management") - private val K3S_DOCKER_IMAGE = DockerImageName.parse("rancher/k3s:v1.21.3-k3s1") - private val CRD_RESOURCE_NAMES = setOf( "helmreleases-crd.yaml", @@ -531,6 +679,8 @@ class IntegrationTest { private const val TH2_PREFIX = "th2-" private const val TH2_NAMESPACE = "${TH2_PREFIX}test" private const val TH2_BOOK = "test_book" + private const val PUBLISH_PIN = "test-publish-pin" + private const val SUBSCRIBE_PIN = "test-subscribe-pin" private const val CFG_FIELD = "test-cfg-field" private const val CFG_VALUE = "test-cfg-value" private const val IMAGE = "ghcr.io/th2-net/th2-estore" @@ -539,6 +689,13 @@ class IntegrationTest { private const val TEST_CONTENT = "test-content" + @JvmStatic + fun mqLinkArguments() = listOf( + Arguments.of(Th2Job::class.java, ::Th2Job, "th2-job", true), + Arguments.of(Th2Box::class.java, ::Th2Box, "th2-codec", false), + Arguments.of(Th2CoreBox::class.java, ::Th2CoreBox, "th2-rpt-data-provider", false), + ) + private fun createOperatorConfig(rabbitMQ: RabbitMQContainer) = OperatorConfig( chart = ChartSpec(), @@ -575,14 +732,6 @@ class IntegrationTest { "${'$'}{RABBITMQ_PASS}", ) - private fun createRabbitMQClient(rabbitMQ: RabbitMQContainer) = - RabbitMQContext.createClient( - rabbitMQ.host, - rabbitMQ.httpPort, - rabbitMQ.adminUsername, - rabbitMQ.adminPassword, - ) - private fun KubernetesClient.configureK3s() { CRD_RESOURCE_NAMES .asSequence() @@ -714,7 +863,11 @@ class IntegrationTest { deleteConfigMap(namespace, CRADLE_MANAGER_CM_NAME) } - private fun HelmRelease.assertMinCfg(name: String, runAsJob: Boolean) { + private fun HelmRelease.assertMinCfg( + name: String, + runAsJob: Boolean, + queues: Map> = emptyMap(), + ) { expectThat(componentValuesSection) { getValue(BOOK_CONFIG_ALIAS).isA>().and { hasSize(1) @@ -780,7 +933,7 @@ class IntegrationTest { getValue("exchange") isEqualTo RABBIT_MQ_TOPIC_EXCHANGE } getValue("queues").isA>().and { - hasSize(1) + hasSize(1 + queues.size) getValue(EVENT_STORAGE_PIN_ALIAS).isA>().and { hasSize(5) getValue("attributes").isA>() isEqualTo listOf("publish", "event") @@ -789,6 +942,9 @@ class IntegrationTest { getValue("name") isEqualTo "key[$TH2_NAMESPACE:$name:$EVENT_STORAGE_PIN_ALIAS]" getValue("queue").isA().isEmpty() } + queues.forEach { (key, value) -> + getValue(key).isA>() isEqualTo value + } } } } diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestIntegrationUtils.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestIntegrationUtils.kt new file mode 100644 index 00000000..818f8769 --- /dev/null +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestIntegrationUtils.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2024-2024 Exactpro (Exactpro Systems Limited) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.exactpro.th2.infraoperator.integration + +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext +import com.rabbitmq.http.client.Client +import org.slf4j.LoggerFactory +import org.testcontainers.containers.RabbitMQContainer +import org.testcontainers.containers.output.Slf4jLogConsumer +import org.testcontainers.k3s.K3sContainer +import org.testcontainers.lifecycle.Startable +import org.testcontainers.utility.DockerImageName + +private val K3S_DOCKER_IMAGE = DockerImageName.parse("rancher/k3s:v1.21.3-k3s1") +private val RABBITMQ_DOCKER_IMAGE = DockerImageName.parse("rabbitmq:3.12.6-management") + +fun createK3sContainer(): K3sContainer = K3sContainer(K3S_DOCKER_IMAGE) + .withLogConsumer(Slf4jLogConsumer(LoggerFactory.getLogger("K3S")).withSeparateOutputStreams()) + .also(Startable::start) + +fun createRabbitMQContainer(): RabbitMQContainer = RabbitMQContainer(RABBITMQ_DOCKER_IMAGE) + .withLogConsumer(Slf4jLogConsumer(LoggerFactory.getLogger("RABBIT_MQ")).withSeparateOutputStreams()) + .also(Startable::start) + +fun createRabbitMQClient(rabbitMQ: RabbitMQContainer): Client = + RabbitMQContext.createClient( + rabbitMQ.host, + rabbitMQ.httpPort, + rabbitMQ.adminUsername, + rabbitMQ.adminPassword, + ) \ No newline at end of file diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt index 7de1ed76..d10c4919 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt @@ -177,12 +177,13 @@ fun Client.assertBindings( ) } -fun Client.assertNoQueue( - queue: String, +fun Client.assertNoQueues( + queuePattern: String, + vHost: String, timeout: Long = 5_000, unit: TimeUnit = TimeUnit.MILLISECONDS, ) { - await("assertQueue('$queue')") + await("assertNoQueues('$queuePattern')") .timeout(timeout, unit) - .until { queues.firstOrNull { it.name == queue } == null } -} + .until { queues.map { it.name.matches(Regex(queuePattern)) && it.vhost == vHost }.isEmpty() } +} \ No newline at end of file From 0187cd05e14b34de28777655edf8ede520fa8e2d Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Mon, 16 Sep 2024 14:07:05 +0400 Subject: [PATCH 38/42] [TH2-5226] added grpc link test --- .../integration/IntegrationTest.kt | 163 +++++++++++------- 1 file changed, 104 insertions(+), 59 deletions(-) diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt index da43588d..0deef9b7 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt @@ -237,7 +237,12 @@ class IntegrationTest { subSpecType: String, subRunAsJob: Boolean ) - abstract fun `grpc link`(name: String) + abstract fun `grpc link`( + clientClass: Class, + clientConstructor: () -> Th2CustomResource, + clientSpecType: String, + clientRunAsJob: Boolean + ) protected fun addTest(name: String) { val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() @@ -342,10 +347,6 @@ class IntegrationTest { } protected fun mkLinkTest( - pubClass: Class, - pubConstructor: () -> Th2CustomResource, - pubSpecType: String, - pubRunAsJob: Boolean, subClass: Class, subConstructor: () -> Th2CustomResource, subSpecType: String, @@ -355,10 +356,10 @@ class IntegrationTest { val pubName = "test-publisher" val subName = "test-subscriber" - val publisherSpec = """ + val pubSpec = """ imageName: $IMAGE imageVersion: $VERSION - type: $pubSpecType + type: $specType pins: mq: publishers: @@ -366,7 +367,7 @@ class IntegrationTest { attributes: [publish] """.trimIndent() - val spec = """ + val subSpec = """ imageName: $IMAGE imageVersion: $VERSION type: $subSpecType @@ -380,10 +381,10 @@ class IntegrationTest { pin: $PUBLISH_PIN """.trimIndent() - kubeClient.createTh2CustomResource(TH2_NAMESPACE, pubName, gitHash, publisherSpec, pubConstructor) - kubeClient.createTh2CustomResource(TH2_NAMESPACE, subName, gitHash, spec, subConstructor) + kubeClient.createTh2CustomResource(TH2_NAMESPACE, pubName, gitHash, pubSpec, ::createResources) + kubeClient.createTh2CustomResource(TH2_NAMESPACE, subName, gitHash, subSpec, subConstructor) - kubeClient.awaitPhase(TH2_NAMESPACE, pubName, SUCCEEDED, pubClass) + kubeClient.awaitPhase(TH2_NAMESPACE, pubName, SUCCEEDED, resourceClass) kubeClient.awaitPhase(TH2_NAMESPACE, subName, SUCCEEDED, subClass) val queueName = "link[$TH2_NAMESPACE:$subName:$SUBSCRIBE_PIN]" @@ -391,7 +392,7 @@ class IntegrationTest { kubeClient.awaitResource(TH2_NAMESPACE, pubName).assertMinCfg( pubName, - pubRunAsJob, + runAsJob, queues = mapOf( PUBLISH_PIN to mapOf( "attributes" to listOf("publish"), @@ -402,7 +403,7 @@ class IntegrationTest { ) ) ) - kubeClient.awaitResource(TH2_NAMESPACE, hashNameIfNeeded(subName)).assertMinCfg( + kubeClient.awaitResource(TH2_NAMESPACE, subName).assertMinCfg( subName, subRunAsJob, queues = mapOf( @@ -432,23 +433,78 @@ class IntegrationTest { ) } - protected fun grpcLinkTest(name: String) { + protected fun grpcLinkTest( + clientClass: Class, + clientConstructor: () -> Th2CustomResource, + clientSpecType: String, + clientRunAsJob: Boolean, + ) { val gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() - val spec = """ + val serverName = "test-server" + val clientName = "test-client" + + val serverSpec = """ imageName: $IMAGE imageVersion: $VERSION type: $specType + pins: + grpc: + server: + - name: $SERVER_PIN + serviceClasses: [$GRPC_SERVICE] """.trimIndent() - kubeClient.createTh2CustomResource(TH2_NAMESPACE, name, gitHash, spec, this::createResources) - kubeClient.awaitPhase(TH2_NAMESPACE, name, SUCCEEDED, resourceClass) - kubeClient.awaitResource(TH2_NAMESPACE, hashNameIfNeeded(name)).assertMinCfg(name, runAsJob) + val clientSpec = """ + imageName: $IMAGE + imageVersion: $VERSION + type: $clientSpecType + pins: + grpc: + client: + - name: $CLIENT_PIN + serviceClass: $GRPC_SERVICE + strategy: robin + linkTo: + - box: $serverName + pin: $SERVER_PIN + """.trimIndent() + + kubeClient.createTh2CustomResource(TH2_NAMESPACE, serverName, gitHash, serverSpec, ::createResources) + kubeClient.createTh2CustomResource(TH2_NAMESPACE, clientName, gitHash, clientSpec, clientConstructor) + + kubeClient.awaitPhase(TH2_NAMESPACE, serverName, SUCCEEDED, resourceClass) + kubeClient.awaitPhase(TH2_NAMESPACE, clientName, SUCCEEDED, clientClass) + + kubeClient.awaitResource(TH2_NAMESPACE, serverName).assertMinCfg(serverName, runAsJob) + kubeClient.awaitResource(TH2_NAMESPACE, clientName).assertMinCfg( + clientName, + clientRunAsJob, + services = mapOf( + CLIENT_PIN to mapOf( + "endpoints" to mapOf( + "test-server-endpoint" to mapOf( + "attributes" to emptyList(), + "host" to serverName, + "port" to 8080 + ) + ), + "filters" to emptyList(), + "service-class" to GRPC_SERVICE, + "strategy" to mapOf( + "endpoints" to listOf("test-server-endpoint"), + "name" to "robin", + ) + ) + ) + ) + rabbitMQClient.assertBindings( createEstoreQueue(TH2_NAMESPACE), RABBIT_MQ_V_HOST, setOf( "link[$TH2_NAMESPACE:$EVENT_STORAGE_BOX_ALIAS:$EVENT_STORAGE_PIN_ALIAS]", - "key[$TH2_NAMESPACE:$name:$EVENT_STORAGE_PIN_ALIAS]" + "key[$TH2_NAMESPACE:$serverName:$EVENT_STORAGE_PIN_ALIAS]", + "key[$TH2_NAMESPACE:$clientName:$EVENT_STORAGE_PIN_ALIAS]", ) ) } @@ -530,12 +586,17 @@ class IntegrationTest { subConstructor: () -> Th2CustomResource, subSpecType: String, subRunAsJob: Boolean - ) = mkLinkTest(resourceClass, ::createResources, specType, runAsJob, subClass, subConstructor, subSpecType, subRunAsJob) + ) = mkLinkTest(subClass, subConstructor, subSpecType, subRunAsJob) @Timeout(30_000) @ParameterizedTest - @ValueSource(strings = ["th2-core-component", "th2-core-component-more-than-$NAME_LENGTH_LIMIT-characters"]) - override fun `grpc link`(name: String) = grpcLinkTest(name) + @MethodSource("com.exactpro.th2.infraoperator.integration.IntegrationTest#mqLinkArguments") + override fun `grpc link`( + clientClass: Class, + clientConstructor: () -> Th2CustomResource, + clientSpecType: String, + clientRunAsJob: Boolean + ) = grpcLinkTest(clientClass, clientConstructor, clientSpecType, clientRunAsJob) } @Nested @@ -572,12 +633,17 @@ class IntegrationTest { subConstructor: () -> Th2CustomResource, subSpecType: String, subRunAsJob: Boolean - ) = mkLinkTest(resourceClass, ::createResources, specType, runAsJob, subClass, subConstructor, subSpecType, subRunAsJob) + ) = mkLinkTest(subClass, subConstructor, subSpecType, subRunAsJob) @Timeout(30_000) @ParameterizedTest - @ValueSource(strings = ["th2-component", "th2-component-more-than-$NAME_LENGTH_LIMIT-characters"]) - override fun `grpc link`(name: String) = grpcLinkTest(name) + @MethodSource("com.exactpro.th2.infraoperator.integration.IntegrationTest#mqLinkArguments") + override fun `grpc link`( + clientClass: Class, + clientConstructor: () -> Th2CustomResource, + clientSpecType: String, + clientRunAsJob: Boolean + ) = grpcLinkTest(clientClass, clientConstructor, clientSpecType, clientRunAsJob) } @Nested @@ -614,12 +680,17 @@ class IntegrationTest { subConstructor: () -> Th2CustomResource, subSpecType: String, subRunAsJob: Boolean - ) = mkLinkTest(resourceClass, ::createResources, specType, runAsJob, subClass, subConstructor, subSpecType, subRunAsJob) + ) = mkLinkTest(subClass, subConstructor, subSpecType, subRunAsJob) @Timeout(30_000) @ParameterizedTest - @ValueSource(strings = ["th2-job", "th2-job-more-than-$NAME_LENGTH_LIMIT-characters"]) - override fun `grpc link`(name: String) = grpcLinkTest(name) + @MethodSource("com.exactpro.th2.infraoperator.integration.IntegrationTest#mqLinkArguments") + override fun `grpc link`( + clientClass: Class, + clientConstructor: () -> Th2CustomResource, + clientSpecType: String, + clientRunAsJob: Boolean + ) = grpcLinkTest(clientClass, clientConstructor, clientSpecType, clientRunAsJob) } @Nested @@ -681,8 +752,9 @@ class IntegrationTest { private const val TH2_BOOK = "test_book" private const val PUBLISH_PIN = "test-publish-pin" private const val SUBSCRIBE_PIN = "test-subscribe-pin" - private const val CFG_FIELD = "test-cfg-field" - private const val CFG_VALUE = "test-cfg-value" + private const val SERVER_PIN = "test-server-pin" + private const val CLIENT_PIN = "test-client-pin" + private const val GRPC_SERVICE = "com.exactpro.th2.test.grpc.TestService" private const val IMAGE = "ghcr.io/th2-net/th2-estore" private const val VERSION = "0.0.0" private const val DICTIONARY_CONTENT = "test-dictionary-content" @@ -760,10 +832,6 @@ class IntegrationTest { ) } - private fun KubernetesClient.deleteRabbitMQSecret(namespace: String) { - deleteSecret(namespace, "rabbitmq") - } - private fun KubernetesClient.createRabbitMQAppConfigCfgMap( namespace: String, gitHash: String, @@ -778,10 +846,6 @@ class IntegrationTest { ) } - private fun KubernetesClient.deleteRabbitMQAppConfigCfgMap(namespace: String) { - deleteConfigMap(namespace, DEFAULT_RABBITMQ_CONFIGMAP_NAME) - } - private fun KubernetesClient.createBookConfigCfgMap( namespace: String, gitHash: String, @@ -795,10 +859,6 @@ class IntegrationTest { ) } - private fun KubernetesClient.deleteBookConfigCfgMap(namespace: String) { - deleteConfigMap(namespace, BOOK_CONFIG_CM_NAME) - } - private fun KubernetesClient.createLoggingCfgMap( namespace: String, gitHash: String, @@ -811,10 +871,6 @@ class IntegrationTest { ) } - private fun KubernetesClient.deleteLoggingCfgMap(namespace: String) { - deleteConfigMap(namespace, LOGGING_CM_NAME) - } - private fun KubernetesClient.createMQRouterCfgMap( namespace: String, gitHash: String, @@ -827,10 +883,6 @@ class IntegrationTest { ) } - private fun KubernetesClient.deleteMQRouterCfgMap(namespace: String) { - deleteConfigMap(namespace, MQ_ROUTER_CM_NAME) - } - private fun KubernetesClient.createGrpcRouterCfgMap( namespace: String, gitHash: String, @@ -843,10 +895,6 @@ class IntegrationTest { ) } - private fun KubernetesClient.deleteGrpcRouterCfgMap(namespace: String) { - deleteConfigMap(namespace, GRPC_ROUTER_CM_NAME) - } - private fun KubernetesClient.createCradleManagerCfgMap( namespace: String, gitHash: String, @@ -859,14 +907,11 @@ class IntegrationTest { ) } - private fun KubernetesClient.deleteCradleManagerCfgMap(namespace: String) { - deleteConfigMap(namespace, CRADLE_MANAGER_CM_NAME) - } - private fun HelmRelease.assertMinCfg( name: String, runAsJob: Boolean, queues: Map> = emptyMap(), + services: Map> = emptyMap(), ) { expectThat(componentValuesSection) { getValue(BOOK_CONFIG_ALIAS).isA>().and { @@ -892,7 +937,7 @@ class IntegrationTest { getValue("port") isEqualTo 8080 getValue("workers") isEqualTo 5 } - getValue("services").isA>().isEmpty() + getValue("services").isA>() isEqualTo services } getValue(IS_JOB_ALIAS) isEqualTo runAsJob getValue(SECRET_PATHS_CONFIG_ALIAS).isA>().isEmpty() From 20ef110281229bfa975120421f553788a58c6e00 Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Mon, 16 Sep 2024 14:41:41 +0400 Subject: [PATCH 39/42] [TH2-5226] corrected after review --- .../util/CustomResourceUtils.java | 2 +- .../integration/IntegrationTest.kt | 41 ++++++------ .../integration/TestKubernetesUtils.kt | 65 +++++-------------- 3 files changed, 38 insertions(+), 70 deletions(-) diff --git a/src/main/java/com/exactpro/th2/infraoperator/util/CustomResourceUtils.java b/src/main/java/com/exactpro/th2/infraoperator/util/CustomResourceUtils.java index b458e2bd..7a0ad151 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/util/CustomResourceUtils.java +++ b/src/main/java/com/exactpro/th2/infraoperator/util/CustomResourceUtils.java @@ -62,7 +62,7 @@ public static String extractHashedName(Th2CustomResource customResource) { return hashNameIfNeeded(extractName(customResource)); } - public static String hashNameIfNeeded(String resName) { + private static String hashNameIfNeeded(String resName) { if (resName.length() >= HelmRelease.NAME_LENGTH_LIMIT) { String result = digest(resName); logger.debug("Resource '{}' name has been hashed to '{}'", resName, result); diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt index 0deef9b7..64bc34ce 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt @@ -73,7 +73,7 @@ import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.createMstoreQue import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext.DIRECT import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext.TOPIC import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext.toExchangeName -import com.exactpro.th2.infraoperator.util.CustomResourceUtils.hashNameIfNeeded +import com.exactpro.th2.infraoperator.util.CustomResourceUtils.extractHashedName import com.exactpro.th2.infraoperator.util.JsonUtils.JSON_MAPPER import com.exactpro.th2.infraoperator.util.JsonUtils.YAML_MAPPER import com.exactpro.th2.infraoperator.util.createKubernetesClient @@ -185,11 +185,11 @@ class IntegrationTest { kubeClient.createRabbitMQAppConfigCfgMap( TH2_NAMESPACE, gitHash, - createRabbitMQConfig(rabbitMQContainer, TH2_NAMESPACE) + createRabbitMQConfig(rabbitMQContainer) ) rabbitMQClient.assertUser(TH2_NAMESPACE, RABBIT_MQ_V_HOST, RABBIT_MQ_NAMESPACE_PERMISSIONS) - rabbitMQClient.assertExchange(toExchangeName(TH2_NAMESPACE), DIRECT, RABBIT_MQ_V_HOST) + rabbitMQClient.assertExchange(RABBIT_MQ_TH2_EXCHANGE, DIRECT, RABBIT_MQ_V_HOST) rabbitMQClient.assertQueue(createMstoreQueue(TH2_NAMESPACE), RABBIT_MQ_QUEUE_CLASSIC_TYPE, RABBIT_MQ_V_HOST) rabbitMQClient.assertQueue(createEstoreQueue(TH2_NAMESPACE), RABBIT_MQ_QUEUE_CLASSIC_TYPE, RABBIT_MQ_V_HOST) @@ -252,9 +252,9 @@ class IntegrationTest { type: $specType """.trimIndent() - kubeClient.createTh2CustomResource(TH2_NAMESPACE, name, gitHash, spec, this::createResources) + val resource = kubeClient.createTh2CustomResource(TH2_NAMESPACE, name, gitHash, spec, this::createResources) kubeClient.awaitPhase(TH2_NAMESPACE, name, SUCCEEDED, resourceClass) - kubeClient.awaitResource(TH2_NAMESPACE, hashNameIfNeeded(name)).assertMinCfg(name, runAsJob) + kubeClient.awaitResource(TH2_NAMESPACE, extractHashedName(resource)).assertMinCfg(name, runAsJob) rabbitMQClient.assertBindings( createEstoreQueue(TH2_NAMESPACE), RABBIT_MQ_V_HOST, @@ -274,9 +274,9 @@ class IntegrationTest { disabled: false """.trimIndent() - kubeClient.createTh2CustomResource(TH2_NAMESPACE, name, gitHash, spec, this::createResources) + val resource = kubeClient.createTh2CustomResource(TH2_NAMESPACE, name, gitHash, spec, this::createResources) kubeClient.awaitPhase(TH2_NAMESPACE, name, SUCCEEDED, resourceClass) - kubeClient.awaitResource(TH2_NAMESPACE, hashNameIfNeeded(name)).assertMinCfg(name, runAsJob) + kubeClient.awaitResource(TH2_NAMESPACE, extractHashedName(resource)).assertMinCfg(name, runAsJob) rabbitMQClient.assertBindings( createEstoreQueue(TH2_NAMESPACE), RABBIT_MQ_V_HOST, @@ -333,9 +333,9 @@ class IntegrationTest { disabled: false """.trimIndent() - kubeClient.modifyTh2CustomResource(TH2_NAMESPACE, name, gitHash, spec, resourceClass) + val resource = kubeClient.modifyTh2CustomResource(TH2_NAMESPACE, name, gitHash, spec, resourceClass) kubeClient.awaitPhase(TH2_NAMESPACE, name, SUCCEEDED, resourceClass) - kubeClient.awaitResource(TH2_NAMESPACE, hashNameIfNeeded(name)).assertMinCfg(name, runAsJob) + kubeClient.awaitResource(TH2_NAMESPACE, extractHashedName(resource)).assertMinCfg(name, runAsJob) rabbitMQClient.assertBindings( createEstoreQueue(TH2_NAMESPACE), RABBIT_MQ_V_HOST, @@ -396,7 +396,7 @@ class IntegrationTest { queues = mapOf( PUBLISH_PIN to mapOf( "attributes" to listOf("publish"), - "exchange" to toExchangeName(TH2_NAMESPACE), + "exchange" to RABBIT_MQ_TH2_EXCHANGE, "filters" to emptyList(), "name" to routingKey, "queue" to "", @@ -409,7 +409,7 @@ class IntegrationTest { queues = mapOf( SUBSCRIBE_PIN to mapOf( "attributes" to listOf("subscribe"), - "exchange" to toExchangeName(TH2_NAMESPACE), + "exchange" to RABBIT_MQ_TH2_EXCHANGE, "filters" to emptyList(), "name" to "", "queue" to queueName, @@ -742,23 +742,27 @@ class IntegrationTest { "th2-mstore-crd.yaml", ) + private const val TH2_PREFIX = "th2-" + private const val TH2_NAMESPACE = "${TH2_PREFIX}test" + private const val TH2_BOOK = "test_book" + private val RABBIT_MQ_NAMESPACE_PERMISSIONS = RabbitMQNamespacePermissions() private const val RABBIT_MQ_QUEUE_CLASSIC_TYPE = "classic" private const val RABBIT_MQ_V_HOST = "/" private const val RABBIT_MQ_TOPIC_EXCHANGE = "test-global-exchange" + private val RABBIT_MQ_TH2_EXCHANGE = toExchangeName(TH2_NAMESPACE) - private const val TH2_PREFIX = "th2-" - private const val TH2_NAMESPACE = "${TH2_PREFIX}test" - private const val TH2_BOOK = "test_book" private const val PUBLISH_PIN = "test-publish-pin" private const val SUBSCRIBE_PIN = "test-subscribe-pin" + private const val SERVER_PIN = "test-server-pin" private const val CLIENT_PIN = "test-client-pin" private const val GRPC_SERVICE = "com.exactpro.th2.test.grpc.TestService" + private const val IMAGE = "ghcr.io/th2-net/th2-estore" private const val VERSION = "0.0.0" - private const val DICTIONARY_CONTENT = "test-dictionary-content" + private const val DICTIONARY_CONTENT = "test-dictionary-content" private const val TEST_CONTENT = "test-content" @JvmStatic @@ -794,13 +798,12 @@ class IntegrationTest { private fun createRabbitMQConfig( rabbitMQ: RabbitMQContainer, - namespace: String, ) = RabbitMQConfig( rabbitMQ.amqpPort, rabbitMQ.host, RABBIT_MQ_V_HOST, - toExchangeName(namespace), - namespace, + RABBIT_MQ_TH2_EXCHANGE, + TH2_NAMESPACE, "${'$'}{RABBITMQ_PASS}", ) @@ -982,7 +985,7 @@ class IntegrationTest { getValue(EVENT_STORAGE_PIN_ALIAS).isA>().and { hasSize(5) getValue("attributes").isA>() isEqualTo listOf("publish", "event") - getValue("exchange") isEqualTo toExchangeName(TH2_NAMESPACE) + getValue("exchange") isEqualTo RABBIT_MQ_TH2_EXCHANGE getValue("filters").isA>().isEmpty() // FIXME getValue("name") isEqualTo "key[$TH2_NAMESPACE:$name:$EVENT_STORAGE_PIN_ALIAS]" getValue("queue").isA().isEmpty() diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt index 6a1ccb68..7b3f20e0 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt @@ -87,18 +87,6 @@ fun KubernetesClient.createSecret( ).create() } -fun KubernetesClient.deleteSecret( - namespace: String, - name: String, - timeout: Long = 200, - unit: TimeUnit = TimeUnit.MILLISECONDS, -) { - secrets() - .inNamespace(namespace) - ?.withName(name) - ?.awaitDeleteResource("deleteSecret('$namespace/$name')", timeout, unit) -} - fun KubernetesClient.createConfigMap( namespace: String, name: String, @@ -147,18 +135,6 @@ inline fun KubernetesClient.awaitNoResource( .until { resources(T::class.java).inNamespace(namespace).withName(name).get() == null } } -fun KubernetesClient.deleteConfigMap( - namespace: String, - name: String, - timeout: Long = 200, - unit: TimeUnit = TimeUnit.MILLISECONDS, -) { - configMaps() - .inNamespace(namespace) - ?.withName(name) - ?.awaitDeleteResource("deleteConfigMap('$namespace/$name')", timeout, unit) -} - inline fun KubernetesClient.awaitPhase( namespace: String, name: String, @@ -180,44 +156,33 @@ fun KubernetesClient.awaitPhase( .until { resources(resourceType)?.inNamespace(namespace)?.withName(name)?.get()?.status?.phase == phase } } -fun KubernetesClient.createTh2CustomResource( +fun KubernetesClient.createTh2CustomResource( namespace: String, name: String, gitHash: String, spec: String, - create: () -> Th2CustomResource, -) { - resource( - create().apply { - this.metadata = createMeta(name, namespace, createAnnotations(gitHash, spec.hashCode().toString())) - this.spec = YAML_MAPPER.readValue(spec, Th2Spec::class.java) - } - ).create() + create: () -> T, +): T = create().apply { + this.metadata = createMeta(name, namespace, createAnnotations(gitHash, spec.hashCode().toString())) + this.spec = YAML_MAPPER.readValue(spec, Th2Spec::class.java) +}.also { + resource(it).create() } -fun KubernetesClient.modifyTh2CustomResource( +fun KubernetesClient.modifyTh2CustomResource( namespace: String, name: String, gitHash: String, spec: String, - resourceType: Class, -) { - resource( - resources(resourceType).inNamespace(namespace).withName(name).get().apply { - this.metadata.annotations.putAll(createAnnotations(gitHash, spec.hashCode().toString())) - this.metadata.generation += 1 - this.spec = YAML_MAPPER.readValue(spec, Th2Spec::class.java) - } - ).update() + resourceType: Class, +): T = resources(resourceType).inNamespace(namespace).withName(name).get().apply { + this.metadata.annotations.putAll(createAnnotations(gitHash, spec.hashCode().toString())) + this.metadata.generation += 1 + this.spec = YAML_MAPPER.readValue(spec, Th2Spec::class.java) +}.also { + resource(it).update() } -inline fun KubernetesClient.modifyTh2CustomResource( - namespace: String, - name: String, - gitHash: String, - spec: String -) = modifyTh2CustomResource(namespace, name, gitHash, spec, T::class.java) - fun KubernetesClient.createTh2Dictionary( namespace: String, name: String, From 155541388d8dca8f745528403237f444863bd01e Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Mon, 16 Sep 2024 20:10:57 +0400 Subject: [PATCH 40/42] [TH2-5226] added DeleteRubbishOnStartTest --- build.gradle | 13 +- .../th2/infraoperator/Th2CrdController.java | 9 +- .../linkresolver/mq/RabbitMQContext.java | 5 +- .../th2/infraoperator/util/RabbitMQUtils.kt | 5 +- .../integration/DeleteRubbishOnStartTest.kt | 216 +++++++++++++++ .../integration/IntegrationTest.kt | 257 +++--------------- .../integration/TestIntegrationUtils.kt | 244 ++++++++++++++++- .../integration/TestKubernetesUtils.kt | 2 +- .../integration/TestRabbitMQUtils.kt | 27 +- .../infraoperator/util/RabbitMQUtilsTest.kt | 25 +- 10 files changed, 568 insertions(+), 235 deletions(-) create mode 100644 src/test/kotlin/com/exactpro/th2/infraoperator/integration/DeleteRubbishOnStartTest.kt diff --git a/build.gradle b/build.gradle index 57b8e862..4111371b 100644 --- a/build.gradle +++ b/build.gradle @@ -77,12 +77,6 @@ wrapper { distributionType Wrapper.DistributionType.BIN } -test { - useJUnitPlatform { - excludeTags("integration-test") - } -} - tasks.register("downloadCRDs", Download) { group = "verification" src([ @@ -96,6 +90,13 @@ tasks.register("downloadCRDs", Download) { dest layout.buildDirectory.dir('resources/test/crds').get() } +test { + dependsOn("downloadCRDs") + useJUnitPlatform { + excludeTags("integration-test") + } +} + tasks.register("integrationTest", Test) { group = "verification" dependsOn("downloadCRDs") diff --git a/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java b/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java index f97d8b66..0ebb95fe 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java +++ b/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java @@ -35,7 +35,7 @@ import java.nio.file.Files; import java.nio.file.Path; -public class Th2CrdController { +public class Th2CrdController implements AutoCloseable { private static final Logger LOGGER = LoggerFactory.getLogger(Th2CrdController.class); @@ -45,7 +45,7 @@ public void start() { OperatorMetrics.resetCacheErrors(); try { RabbitMQUtils.deleteRabbitMQRubbish(); - RabbitMQContext.declareTopicExchange(); + RabbitMQContext.declareTopicExchange(); // FIXME: topic exchange should be removed when all namespaces are removed / disabled watchManager.addTarget(MstoreHelmTh2Op::new); watchManager.addTarget(EstoreHelmTh2Op::new); @@ -82,4 +82,9 @@ private static void configureLogger(String filePath) { LOGGER.info("Logger configuration from {} file is applied", path); } } + + @Override + public void close() throws Exception { + + } } diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java index a5ec5bad..ca09307d 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java +++ b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java @@ -29,6 +29,7 @@ import com.exactpro.th2.infraoperator.spec.strategy.redeploy.tasks.RecreateQueuesAndBindings; import com.exactpro.th2.infraoperator.spec.strategy.redeploy.tasks.RetryRabbitSetup; import com.exactpro.th2.infraoperator.spec.strategy.redeploy.tasks.RetryTopicExchangeTask; +import com.rabbitmq.client.BuiltinExchangeType; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; @@ -70,8 +71,8 @@ public final class RabbitMQContext { private static final Logger logger = LoggerFactory.getLogger(RabbitMQContext.class); private static final int RETRY_DELAY = 120; - public static final String TOPIC = "topic"; - public static final String DIRECT = "direct"; + public static final String TOPIC = BuiltinExchangeType.TOPIC.getType(); + public static final String DIRECT = BuiltinExchangeType.DIRECT.getType(); private static volatile RabbitMQManagementConfig managementConfig; diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt index a14b9b1a..b2ab1b07 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt @@ -27,6 +27,8 @@ import com.exactpro.th2.infraoperator.model.box.mq.factory.MessageRouterConfigFa import com.exactpro.th2.infraoperator.spec.Th2CustomResource import com.exactpro.th2.infraoperator.spec.estore.Th2Estore import com.exactpro.th2.infraoperator.spec.mstore.Th2Mstore +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.createEstoreQueue +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.createMstoreQueue import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext import com.rabbitmq.client.Channel import com.rabbitmq.http.client.domain.ExchangeInfo @@ -92,10 +94,11 @@ internal fun ResourceHolder.filterRubbishResources( exchanges.remove(topicExchange) // FIXME: topic exchange should be declare after each namespace creation K_LOGGER.debug { "Search RabbitMQ resources in $namespaces namespaces" } -// exchanges.remove(topicExchange) FIXME: uncomment this line when topic exchange is declared after each namespace creation val factories: Map, MessageRouterConfigFactory> = createFactories() namespaces.forEach { namespace -> + queues.remove(createEstoreQueue(namespace)) + queues.remove(createMstoreQueue(namespace)) exchanges.remove(namespace) client.customResources(namespace).asSequence() diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/DeleteRubbishOnStartTest.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/DeleteRubbishOnStartTest.kt new file mode 100644 index 00000000..b782fae2 --- /dev/null +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/DeleteRubbishOnStartTest.kt @@ -0,0 +1,216 @@ +/* + * Copyright 2024-2024 Exactpro (Exactpro Systems Limited) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.exactpro.th2.infraoperator.integration + +import com.exactpro.th2.infraoperator.Th2CrdController +import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQNamespacePermissions +import com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op.MESSAGE_STORAGE_BOX_ALIAS +import com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op.MESSAGE_STORAGE_PIN_ALIAS +import com.exactpro.th2.infraoperator.spec.box.Th2Box +import com.exactpro.th2.infraoperator.spec.shared.status.RolloutPhase +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext.DIRECT +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext.toExchangeName +import com.exactpro.th2.infraoperator.util.createKubernetesClient +import com.rabbitmq.client.AMQP +import com.rabbitmq.client.Channel +import com.rabbitmq.client.Connection +import com.rabbitmq.http.client.Client +import com.rabbitmq.http.client.domain.QueueInfo +import io.fabric8.kubernetes.client.KubernetesClient +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.Timeout +import org.junit.jupiter.api.io.TempDir +import org.testcontainers.containers.RabbitMQContainer +import org.testcontainers.k3s.K3sContainer +import java.nio.file.Path +import kotlin.test.Test +import kotlin.test.assertEquals + +@Tag("integration-test") +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class DeleteRubbishOnStartTest { + + private lateinit var k3sContainer: K3sContainer + + private lateinit var rabbitMQContainer: RabbitMQContainer + + private lateinit var kubeClient: KubernetesClient + + private lateinit var rabbitMQClient: Client + + private lateinit var rabbitMQConnection: Connection + + @BeforeAll + @Timeout(30_000) + fun beforeAll(@TempDir tempDir: Path) { + k3sContainer = createK3sContainer() + rabbitMQContainer = createRabbitMQContainer() + + prepareTh2CfgDir( + k3sContainer.kubeConfigYaml, + createOperatorConfig( + rabbitMQContainer, + setOf(TH2_PREFIX), + RABBIT_MQ_V_HOST, + RABBIT_MQ_TOPIC_EXCHANGE, + RABBIT_MQ_NAMESPACE_PERMISSIONS, + ), + tempDir, + ) + + kubeClient = createKubernetesClient().apply { configureK3s() } + rabbitMQClient = createRabbitMQClient(rabbitMQContainer) + rabbitMQConnection = createRabbitMQConnection(rabbitMQContainer, RABBIT_MQ_V_HOST) + } + + @AfterAll + @Timeout(30_000) + fun afterAll() { + if (this::kubeClient.isInitialized) { + kubeClient.close() + } + if (this::k3sContainer.isInitialized) { + k3sContainer.stop() + } + if (this::rabbitMQContainer.isInitialized) { + rabbitMQContainer.stop() + } + if (this::rabbitMQConnection.isInitialized && rabbitMQConnection.isOpen) { + rabbitMQConnection.close() + } + } + + @Test + @Disabled("implement Th2CrdController.close") + fun deleteAllTest() { + TODO() + } + + @Test + fun deleteRubbishTest() { + var gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() + + val namespaceB = "${TH2_PREFIX}test-b" + val namespaceC = "${TH2_PREFIX}test-c" + + val exchangeB = toExchangeName(namespaceB) + val exchangeC = toExchangeName(namespaceC) + + val component = "test-component" + + prepareNamespace(gitHash, namespaceB) + + rabbitMQConnection.createChannel().use { channel -> + channel.confirmSelect() + /** queue of not existed component */ + val queue01 = channel.createQueue(namespaceB, "rubbish-component", PIN_NAME).assertQueue() + /** queue of not exited pin */ + val queue02 = channel.createQueue(namespaceB, component, "rubbish-pin").assertQueue() + /** mstore queue of not existed namespace */ + val queue03 = channel.createQueue(namespaceC, MESSAGE_STORAGE_BOX_ALIAS, MESSAGE_STORAGE_PIN_ALIAS).assertQueue() + + /** mstore queue of existed namespace */ + val queue11 = channel.createQueue(namespaceB, MESSAGE_STORAGE_BOX_ALIAS, MESSAGE_STORAGE_PIN_ALIAS).assertQueue() + .also { + channel.basicPublish("", it.queue, null, "test-content".toByteArray()) + } + /** queue of exited component and pin */ + val queue12 = channel.createQueue(namespaceB, component, PIN_NAME).assertQueue() + .also { + channel.basicPublish("", it.queue, null, "test-content".toByteArray()) + } + + /** exchange of not exited namespace */ + channel.createExchange(exchangeC, DIRECT) + rabbitMQClient.assertExchange(exchangeC, DIRECT, RABBIT_MQ_V_HOST) + + // TODO: add routing keys check: + // * existed key to exited queue + // * existed exchange A to existed queue B + // * not existed key to exited queue + + gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() + val spec = """ + imageName: "ghcr.io/th2-net/th2-component" + imageVersion: "0.0.0" + type: th2-codec + pins: + mq: + subscribers: + - name: $PIN_NAME + attributes: [subscribe] + """.trimIndent() + kubeClient.createTh2CustomResource(exchangeB, component, gitHash, spec, ::Th2Box) + + Th2CrdController().apply(Th2CrdController::start).use { + kubeClient.awaitPhase(exchangeB, component, RolloutPhase.SUCCEEDED, Th2Box::class.java) + + rabbitMQClient.assertNoQueue(queue01.queue, RABBIT_MQ_V_HOST) + rabbitMQClient.assertNoQueue(queue02.queue, RABBIT_MQ_V_HOST) + rabbitMQClient.assertNoQueue(queue03.queue, RABBIT_MQ_V_HOST) + + rabbitMQClient.assertQueue(queue11.queue, RABBIT_MQ_QUEUE_CLASSIC_TYPE, RABBIT_MQ_V_HOST) + .assertQueueSize(channel, 1) + rabbitMQClient.assertQueue(queue12.queue, RABBIT_MQ_QUEUE_CLASSIC_TYPE, RABBIT_MQ_V_HOST) + .assertQueueSize(channel, 1) + + rabbitMQClient.assertNoExchange(exchangeC) + } + } + } + + private fun prepareNamespace(gitHash: String, namespace: String) { + kubeClient.createNamespace(namespace) + kubeClient.createRabbitMQSecret(namespace, gitHash) + kubeClient.createRabbitMQAppConfigCfgMap( + namespace, + gitHash, + createRabbitMQConfig(rabbitMQContainer, RABBIT_MQ_V_HOST, toExchangeName(namespace), namespace) + ) + + kubeClient.createBookConfigCfgMap(namespace, gitHash, TH2_BOOK) + kubeClient.createLoggingCfgMap(namespace, gitHash) + kubeClient.createMQRouterCfgMap(namespace, gitHash) + kubeClient.createGrpcRouterCfgMap(namespace, gitHash) + kubeClient.createCradleManagerCfgMap(namespace, gitHash) + } + + private fun AMQP.Queue.DeclareOk.assertQueue(): AMQP.Queue.DeclareOk { + rabbitMQClient.assertQueue(queue, RABBIT_MQ_QUEUE_CLASSIC_TYPE, RABBIT_MQ_V_HOST) + return this + } + + private fun QueueInfo.assertQueueSize(channel: Channel, size: Int) { + val declareOk = channel.queueDeclare(name, isDurable, isExclusive, isAutoDelete, arguments) + assertEquals(size, declareOk.messageCount) + } + + companion object { + private const val TH2_PREFIX = "th2-" + private const val TH2_BOOK = "test_book" + + private val RABBIT_MQ_NAMESPACE_PERMISSIONS = RabbitMQNamespacePermissions() + private const val RABBIT_MQ_V_HOST = "/" + private const val RABBIT_MQ_TOPIC_EXCHANGE = "test-global-exchange" + + private const val PIN_NAME = "test-pin" + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt index 64bc34ce..1f20e6e0 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt @@ -16,14 +16,6 @@ package com.exactpro.th2.infraoperator.integration import com.exactpro.th2.infraoperator.Th2CrdController -import com.exactpro.th2.infraoperator.configuration.ConfigLoader.CONFIG_FILE_SYSTEM_PROPERTY -import com.exactpro.th2.infraoperator.configuration.OperatorConfig -import com.exactpro.th2.infraoperator.configuration.OperatorConfig.Companion.DEFAULT_RABBITMQ_CONFIGMAP_NAME -import com.exactpro.th2.infraoperator.configuration.OperatorConfig.Companion.RABBITMQ_SECRET_PASSWORD_KEY -import com.exactpro.th2.infraoperator.configuration.fields.ChartSpec -import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQConfig -import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQConfig.Companion.CONFIG_MAP_RABBITMQ_PROP_NAME -import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQManagementConfig import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQNamespacePermissions import com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.BOOK_CONFIG_ALIAS import com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.BOOK_NAME_ALIAS @@ -50,12 +42,6 @@ import com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op.SECRET_VALUES_CO import com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op.EVENT_STORAGE_BOX_ALIAS import com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op.EVENT_STORAGE_PIN_ALIAS import com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op.MESSAGE_STORAGE_BOX_ALIAS -import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.BOOK_CONFIG_CM_NAME -import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.CRADLE_MANAGER_CM_NAME -import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.DEFAULT_BOOK -import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.GRPC_ROUTER_CM_NAME -import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.LOGGING_CM_NAME -import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.MQ_ROUTER_CM_NAME import com.exactpro.th2.infraoperator.operator.manager.impl.Th2DictionaryEventHandler.DICTIONARY_SUFFIX import com.exactpro.th2.infraoperator.spec.Th2CustomResource import com.exactpro.th2.infraoperator.spec.box.Th2Box @@ -65,7 +51,6 @@ import com.exactpro.th2.infraoperator.spec.helmrelease.HelmRelease import com.exactpro.th2.infraoperator.spec.helmrelease.HelmRelease.NAME_LENGTH_LIMIT import com.exactpro.th2.infraoperator.spec.job.Th2Job import com.exactpro.th2.infraoperator.spec.mstore.Th2Mstore -import com.exactpro.th2.infraoperator.spec.shared.PrometheusConfiguration import com.exactpro.th2.infraoperator.spec.shared.status.RolloutPhase.DISABLED import com.exactpro.th2.infraoperator.spec.shared.status.RolloutPhase.SUCCEEDED import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.createEstoreQueue @@ -74,15 +59,10 @@ import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQCont import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext.TOPIC import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext.toExchangeName import com.exactpro.th2.infraoperator.util.CustomResourceUtils.extractHashedName -import com.exactpro.th2.infraoperator.util.JsonUtils.JSON_MAPPER -import com.exactpro.th2.infraoperator.util.JsonUtils.YAML_MAPPER import com.exactpro.th2.infraoperator.util.createKubernetesClient -import com.fasterxml.jackson.module.kotlin.readValue import com.rabbitmq.http.client.Client import io.fabric8.kubernetes.api.model.ConfigMap -import io.fabric8.kubernetes.client.Config import io.fabric8.kubernetes.client.KubernetesClient -import io.github.oshai.kotlinlogging.KotlinLogging import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeAll @@ -107,25 +87,12 @@ import strikt.assertions.isEmpty import strikt.assertions.isEqualTo import strikt.assertions.isNotNull import strikt.assertions.isNull -import java.nio.file.Files import java.nio.file.Path -import java.util.UUID import java.util.concurrent.TimeUnit.MINUTES -import java.util.concurrent.atomic.AtomicLong -import kotlin.io.path.absolutePathString -import kotlin.io.path.createDirectories -import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition as CRD @Tag("integration-test") @TestInstance(TestInstance.Lifecycle.PER_CLASS) class IntegrationTest { - private lateinit var tempDir: Path - - private lateinit var configDir: Path - - private lateinit var kubeConfig: Path - - private lateinit var operatorConfig: Path private lateinit var k3sContainer: K3sContainer @@ -140,20 +107,20 @@ class IntegrationTest { @BeforeAll @Timeout(30_000) fun beforeAll(@TempDir tempDir: Path) { - this.tempDir = tempDir - configDir = tempDir.resolve("cfg") - kubeConfig = configDir.resolve("kube-config.yaml") - operatorConfig = configDir.resolve("infra-operator.yml") - configDir.createDirectories() - k3sContainer = createK3sContainer() rabbitMQContainer = createRabbitMQContainer() - Files.writeString(kubeConfig, k3sContainer.kubeConfigYaml) - YAML_MAPPER.writeValue(operatorConfig.toFile(), createOperatorConfig(rabbitMQContainer)) - - System.setProperty(Config.KUBERNETES_KUBECONFIG_FILE, kubeConfig.absolutePathString()) - System.setProperty(CONFIG_FILE_SYSTEM_PROPERTY, operatorConfig.absolutePathString()) + prepareTh2CfgDir( + k3sContainer.kubeConfigYaml, + createOperatorConfig( + rabbitMQContainer, + setOf(TH2_PREFIX), + RABBIT_MQ_V_HOST, + RABBIT_MQ_TOPIC_EXCHANGE, + RABBIT_MQ_NAMESPACE_PERMISSIONS, + ), + tempDir, + ) kubeClient = createKubernetesClient().apply { configureK3s() } rabbitMQClient = createRabbitMQClient(rabbitMQContainer) @@ -185,7 +152,12 @@ class IntegrationTest { kubeClient.createRabbitMQAppConfigCfgMap( TH2_NAMESPACE, gitHash, - createRabbitMQConfig(rabbitMQContainer) + createRabbitMQConfig( + rabbitMQContainer, + RABBIT_MQ_V_HOST, + RABBIT_MQ_TH2_EXCHANGE, + TH2_NAMESPACE, + ) ) rabbitMQClient.assertUser(TH2_NAMESPACE, RABBIT_MQ_V_HOST, RABBIT_MQ_NAMESPACE_PERMISSIONS) @@ -259,8 +231,8 @@ class IntegrationTest { createEstoreQueue(TH2_NAMESPACE), RABBIT_MQ_V_HOST, setOf( - "link[$TH2_NAMESPACE:$EVENT_STORAGE_BOX_ALIAS:$EVENT_STORAGE_PIN_ALIAS]", - "key[$TH2_NAMESPACE:$name:$EVENT_STORAGE_PIN_ALIAS]" + formatQueue(TH2_NAMESPACE, EVENT_STORAGE_BOX_ALIAS, EVENT_STORAGE_PIN_ALIAS), + formatRoutingKey(TH2_NAMESPACE, name, EVENT_STORAGE_PIN_ALIAS) ) ) } @@ -281,8 +253,8 @@ class IntegrationTest { createEstoreQueue(TH2_NAMESPACE), RABBIT_MQ_V_HOST, setOf( - "link[$TH2_NAMESPACE:$EVENT_STORAGE_BOX_ALIAS:$EVENT_STORAGE_PIN_ALIAS]", - "key[$TH2_NAMESPACE:$name:$EVENT_STORAGE_PIN_ALIAS]" + formatQueue(TH2_NAMESPACE, EVENT_STORAGE_BOX_ALIAS, EVENT_STORAGE_PIN_ALIAS), + formatRoutingKey(TH2_NAMESPACE, name, EVENT_STORAGE_PIN_ALIAS), ) ) @@ -301,8 +273,8 @@ class IntegrationTest { createEstoreQueue(TH2_NAMESPACE), RABBIT_MQ_V_HOST, setOf( - "link[$TH2_NAMESPACE:$EVENT_STORAGE_BOX_ALIAS:$EVENT_STORAGE_PIN_ALIAS]", - "key[$TH2_NAMESPACE:$name:$EVENT_STORAGE_PIN_ALIAS]" + formatQueue(TH2_NAMESPACE, EVENT_STORAGE_BOX_ALIAS, EVENT_STORAGE_PIN_ALIAS), + formatRoutingKey(TH2_NAMESPACE, name, EVENT_STORAGE_PIN_ALIAS), ) ) } @@ -322,7 +294,7 @@ class IntegrationTest { rabbitMQClient.assertBindings( createEstoreQueue(TH2_NAMESPACE), RABBIT_MQ_V_HOST, - setOf("link[$TH2_NAMESPACE:$EVENT_STORAGE_BOX_ALIAS:$EVENT_STORAGE_PIN_ALIAS]") + setOf(formatQueue(TH2_NAMESPACE, EVENT_STORAGE_BOX_ALIAS, EVENT_STORAGE_PIN_ALIAS)) ) gitHash = RESOURCE_GIT_HASH_COUNTER.incrementAndGet().toString() @@ -340,8 +312,8 @@ class IntegrationTest { createEstoreQueue(TH2_NAMESPACE), RABBIT_MQ_V_HOST, setOf( - "link[$TH2_NAMESPACE:$EVENT_STORAGE_BOX_ALIAS:$EVENT_STORAGE_PIN_ALIAS]", - "key[$TH2_NAMESPACE:$name:$EVENT_STORAGE_PIN_ALIAS]" + formatQueue(TH2_NAMESPACE, EVENT_STORAGE_BOX_ALIAS, EVENT_STORAGE_PIN_ALIAS), + formatRoutingKey(TH2_NAMESPACE, name, EVENT_STORAGE_PIN_ALIAS) ) ) } @@ -387,8 +359,8 @@ class IntegrationTest { kubeClient.awaitPhase(TH2_NAMESPACE, pubName, SUCCEEDED, resourceClass) kubeClient.awaitPhase(TH2_NAMESPACE, subName, SUCCEEDED, subClass) - val queueName = "link[$TH2_NAMESPACE:$subName:$SUBSCRIBE_PIN]" - val routingKey = "key[$TH2_NAMESPACE:$pubName:$PUBLISH_PIN]" + val queueName = formatQueue(TH2_NAMESPACE, subName, SUBSCRIBE_PIN) + val routingKey = formatRoutingKey(TH2_NAMESPACE, pubName, PUBLISH_PIN) kubeClient.awaitResource(TH2_NAMESPACE, pubName).assertMinCfg( pubName, @@ -420,15 +392,15 @@ class IntegrationTest { rabbitMQClient.assertBindings( queueName, RABBIT_MQ_V_HOST, - setOf("link[$TH2_NAMESPACE:$subName:$SUBSCRIBE_PIN]", routingKey) + setOf(formatQueue(TH2_NAMESPACE, subName, SUBSCRIBE_PIN), routingKey) ) rabbitMQClient.assertBindings( createEstoreQueue(TH2_NAMESPACE), RABBIT_MQ_V_HOST, setOf( - "link[$TH2_NAMESPACE:$EVENT_STORAGE_BOX_ALIAS:$EVENT_STORAGE_PIN_ALIAS]", - "key[$TH2_NAMESPACE:$pubName:$EVENT_STORAGE_PIN_ALIAS]", - "key[$TH2_NAMESPACE:$subName:$EVENT_STORAGE_PIN_ALIAS]", + formatQueue(TH2_NAMESPACE, EVENT_STORAGE_BOX_ALIAS, EVENT_STORAGE_PIN_ALIAS), + formatRoutingKey(TH2_NAMESPACE, pubName, EVENT_STORAGE_PIN_ALIAS), + formatRoutingKey(TH2_NAMESPACE, subName, EVENT_STORAGE_PIN_ALIAS), ) ) } @@ -502,9 +474,9 @@ class IntegrationTest { createEstoreQueue(TH2_NAMESPACE), RABBIT_MQ_V_HOST, setOf( - "link[$TH2_NAMESPACE:$EVENT_STORAGE_BOX_ALIAS:$EVENT_STORAGE_PIN_ALIAS]", - "key[$TH2_NAMESPACE:$serverName:$EVENT_STORAGE_PIN_ALIAS]", - "key[$TH2_NAMESPACE:$clientName:$EVENT_STORAGE_PIN_ALIAS]", + formatQueue(TH2_NAMESPACE, EVENT_STORAGE_BOX_ALIAS, EVENT_STORAGE_PIN_ALIAS), + formatRoutingKey(TH2_NAMESPACE, serverName, EVENT_STORAGE_PIN_ALIAS), + formatRoutingKey(TH2_NAMESPACE, clientName, EVENT_STORAGE_PIN_ALIAS), ) ) } @@ -546,7 +518,7 @@ class IntegrationTest { createEstoreQueue(TH2_NAMESPACE), RABBIT_MQ_V_HOST, setOf( - "link[$TH2_NAMESPACE:$EVENT_STORAGE_BOX_ALIAS:$EVENT_STORAGE_PIN_ALIAS]", + formatQueue(TH2_NAMESPACE, EVENT_STORAGE_BOX_ALIAS, EVENT_STORAGE_PIN_ALIAS), ) ) } @@ -727,27 +699,11 @@ class IntegrationTest { } companion object { - private val K_LOGGER = KotlinLogging.logger {} - - private val RESOURCE_GIT_HASH_COUNTER = AtomicLong(10_000_000) - - private val CRD_RESOURCE_NAMES = - setOf( - "helmreleases-crd.yaml", - "th2-box-crd.yaml", - "th2-core-box-crd.yaml", - "th2-dictionary-crd.yaml", - "th2-estore-crd.yaml", - "th2-job-crd.yaml", - "th2-mstore-crd.yaml", - ) - private const val TH2_PREFIX = "th2-" private const val TH2_NAMESPACE = "${TH2_PREFIX}test" private const val TH2_BOOK = "test_book" private val RABBIT_MQ_NAMESPACE_PERMISSIONS = RabbitMQNamespacePermissions() - private const val RABBIT_MQ_QUEUE_CLASSIC_TYPE = "classic" private const val RABBIT_MQ_V_HOST = "/" private const val RABBIT_MQ_TOPIC_EXCHANGE = "test-global-exchange" private val RABBIT_MQ_TH2_EXCHANGE = toExchangeName(TH2_NAMESPACE) @@ -763,7 +719,6 @@ class IntegrationTest { private const val VERSION = "0.0.0" private const val DICTIONARY_CONTENT = "test-dictionary-content" - private const val TEST_CONTENT = "test-content" @JvmStatic fun mqLinkArguments() = listOf( @@ -772,144 +727,6 @@ class IntegrationTest { Arguments.of(Th2CoreBox::class.java, ::Th2CoreBox, "th2-rpt-data-provider", false), ) - private fun createOperatorConfig(rabbitMQ: RabbitMQContainer) = - OperatorConfig( - chart = ChartSpec(), - namespacePrefixes = setOf(TH2_PREFIX), - rabbitMQManagement = - RabbitMQManagementConfig( - host = rabbitMQ.host, - managementPort = rabbitMQ.httpPort, - applicationPort = rabbitMQ.amqpPort, - vhostName = RABBIT_MQ_V_HOST, - exchangeName = RABBIT_MQ_TOPIC_EXCHANGE, - username = rabbitMQ.adminUsername, - password = rabbitMQ.adminPassword, - persistence = true, - schemaPermissions = RABBIT_MQ_NAMESPACE_PERMISSIONS, - ), - prometheusConfiguration = - PrometheusConfiguration( - "0.0.0.0", - "9752", - false.toString(), - ), - ) - - private fun createRabbitMQConfig( - rabbitMQ: RabbitMQContainer, - ) = RabbitMQConfig( - rabbitMQ.amqpPort, - rabbitMQ.host, - RABBIT_MQ_V_HOST, - RABBIT_MQ_TH2_EXCHANGE, - TH2_NAMESPACE, - "${'$'}{RABBITMQ_PASS}", - ) - - private fun KubernetesClient.configureK3s() { - CRD_RESOURCE_NAMES - .asSequence() - .map(Companion::loadCrd) - .map(this::resource) - .forEach { crd -> - crd.create() - K_LOGGER.info { "Applied CRD: ${crd.get().metadata.name}" } - } - } - - private fun loadCrd(resourceName: String): CRD = - requireNotNull(IntegrationTest::class.java.classLoader.getResource("crds/$resourceName")) { - "Resource '$resourceName' isn't found" - }.let(YAML_MAPPER::readValue) - - private fun KubernetesClient.createRabbitMQSecret( - namespace: String, - gitHash: String, - ) { - createSecret( - namespace, - "rabbitmq", - createAnnotations(gitHash, TEST_CONTENT), - mapOf(RABBITMQ_SECRET_PASSWORD_KEY to UUID.randomUUID().toString()), - ) - } - - private fun KubernetesClient.createRabbitMQAppConfigCfgMap( - namespace: String, - gitHash: String, - data: RabbitMQConfig, - ) { - val content = JSON_MAPPER.writeValueAsString(data) - createConfigMap( - namespace, - DEFAULT_RABBITMQ_CONFIGMAP_NAME, - createAnnotations(gitHash, content), - mapOf(CONFIG_MAP_RABBITMQ_PROP_NAME to content), - ) - } - - private fun KubernetesClient.createBookConfigCfgMap( - namespace: String, - gitHash: String, - book: String, - ) { - createConfigMap( - namespace, - BOOK_CONFIG_CM_NAME, - createAnnotations(gitHash, book), - mapOf(DEFAULT_BOOK to book), - ) - } - - private fun KubernetesClient.createLoggingCfgMap( - namespace: String, - gitHash: String, - ) { - createConfigMap( - namespace, - LOGGING_CM_NAME, - createAnnotations(gitHash, TEST_CONTENT), - mapOf("log4j2.properties" to TEST_CONTENT), - ) - } - - private fun KubernetesClient.createMQRouterCfgMap( - namespace: String, - gitHash: String, - ) { - createConfigMap( - namespace, - MQ_ROUTER_CM_NAME, - createAnnotations(gitHash, TEST_CONTENT), - mapOf("mq_router.json" to TEST_CONTENT), - ) - } - - private fun KubernetesClient.createGrpcRouterCfgMap( - namespace: String, - gitHash: String, - ) { - createConfigMap( - namespace, - GRPC_ROUTER_CM_NAME, - createAnnotations(gitHash, TEST_CONTENT), - mapOf("grpc_router.json" to TEST_CONTENT), - ) - } - - private fun KubernetesClient.createCradleManagerCfgMap( - namespace: String, - gitHash: String, - ) { - createConfigMap( - namespace, - CRADLE_MANAGER_CM_NAME, - createAnnotations(gitHash, TEST_CONTENT), - mapOf("cradle_manager.json" to TEST_CONTENT), - ) - } - private fun HelmRelease.assertMinCfg( name: String, runAsJob: Boolean, @@ -987,7 +804,7 @@ class IntegrationTest { getValue("attributes").isA>() isEqualTo listOf("publish", "event") getValue("exchange") isEqualTo RABBIT_MQ_TH2_EXCHANGE getValue("filters").isA>().isEmpty() // FIXME - getValue("name") isEqualTo "key[$TH2_NAMESPACE:$name:$EVENT_STORAGE_PIN_ALIAS]" + getValue("name") isEqualTo formatRoutingKey(TH2_NAMESPACE, name, EVENT_STORAGE_PIN_ALIAS) getValue("queue").isA().isEmpty() } queues.forEach { (key, value) -> diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestIntegrationUtils.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestIntegrationUtils.kt index 818f8769..af77d423 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestIntegrationUtils.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestIntegrationUtils.kt @@ -16,18 +16,162 @@ package com.exactpro.th2.infraoperator.integration +import com.exactpro.th2.infraoperator.configuration.ConfigLoader +import com.exactpro.th2.infraoperator.configuration.OperatorConfig +import com.exactpro.th2.infraoperator.configuration.fields.ChartSpec +import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQConfig +import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQManagementConfig +import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQNamespacePermissions +import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler +import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.BOOK_CONFIG_CM_NAME +import com.exactpro.th2.infraoperator.operator.manager.impl.ConfigMapEventHandler.DEFAULT_BOOK +import com.exactpro.th2.infraoperator.spec.shared.PrometheusConfiguration import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext +import com.exactpro.th2.infraoperator.util.JsonUtils +import com.fasterxml.jackson.module.kotlin.readValue +import com.rabbitmq.client.AMQP +import com.rabbitmq.client.Channel +import com.rabbitmq.client.Connection +import com.rabbitmq.client.ConnectionFactory import com.rabbitmq.http.client.Client +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition +import io.fabric8.kubernetes.client.Config +import io.fabric8.kubernetes.client.KubernetesClient +import io.github.oshai.kotlinlogging.KotlinLogging import org.slf4j.LoggerFactory import org.testcontainers.containers.RabbitMQContainer import org.testcontainers.containers.output.Slf4jLogConsumer import org.testcontainers.k3s.K3sContainer import org.testcontainers.lifecycle.Startable import org.testcontainers.utility.DockerImageName +import java.nio.file.Files +import java.nio.file.Path +import java.util.UUID +import java.util.concurrent.atomic.AtomicLong +import kotlin.io.path.absolutePathString +import kotlin.io.path.createDirectories private val K3S_DOCKER_IMAGE = DockerImageName.parse("rancher/k3s:v1.21.3-k3s1") private val RABBITMQ_DOCKER_IMAGE = DockerImageName.parse("rabbitmq:3.12.6-management") +private val K_LOGGER = KotlinLogging.logger {} + +private val CRD_RESOURCE_NAMES = + setOf( + "helmreleases-crd.yaml", + "th2-box-crd.yaml", + "th2-core-box-crd.yaml", + "th2-dictionary-crd.yaml", + "th2-estore-crd.yaml", + "th2-job-crd.yaml", + "th2-mstore-crd.yaml", + ) + +val RESOURCE_GIT_HASH_COUNTER = AtomicLong(10_000_000) + +fun KubernetesClient.configureK3s() { + CRD_RESOURCE_NAMES + .asSequence() + .map(::loadCrd) + .map(this::resource) + .forEach { crd -> + crd.create() + K_LOGGER.info { "Applied CRD: ${crd.get().metadata.name}" } + } +} + +fun KubernetesClient.createRabbitMQSecret( + namespace: String, + gitHash: String, +) { + val data = mapOf(OperatorConfig.RABBITMQ_SECRET_PASSWORD_KEY to UUID.randomUUID().toString()) + createSecret( + namespace, + "rabbitmq", + createAnnotations(gitHash, data), + data, + ) +} + +fun KubernetesClient.createRabbitMQAppConfigCfgMap( + namespace: String, + gitHash: String, + data: RabbitMQConfig, +) { + val content = JsonUtils.JSON_MAPPER.writeValueAsString(data) + createConfigMap( + namespace, + OperatorConfig.DEFAULT_RABBITMQ_CONFIGMAP_NAME, + createAnnotations(gitHash, content), + mapOf(RabbitMQConfig.CONFIG_MAP_RABBITMQ_PROP_NAME to content), + ) +} + +fun KubernetesClient.createBookConfigCfgMap( + namespace: String, + gitHash: String, + book: String, +) { + createConfigMap( + namespace, + BOOK_CONFIG_CM_NAME, + createAnnotations(gitHash, book), + mapOf(DEFAULT_BOOK to book), + ) +} + +fun KubernetesClient.createLoggingCfgMap( + namespace: String, + gitHash: String, +) { + val data = mapOf("log4j2.properties" to "") + createConfigMap( + namespace, + ConfigMapEventHandler.LOGGING_CM_NAME, + createAnnotations(gitHash, data), + data, + ) +} + +fun KubernetesClient.createMQRouterCfgMap( + namespace: String, + gitHash: String, +) { + val data = mapOf("mq_router.json" to "{}") + createConfigMap( + namespace, + ConfigMapEventHandler.MQ_ROUTER_CM_NAME, + createAnnotations(gitHash, data), + data, + ) +} + +fun KubernetesClient.createGrpcRouterCfgMap( + namespace: String, + gitHash: String, +) { + val data = mapOf("grpc_router.json" to "{}") + createConfigMap( + namespace, + ConfigMapEventHandler.GRPC_ROUTER_CM_NAME, + createAnnotations(gitHash, data), + data, + ) +} + +fun KubernetesClient.createCradleManagerCfgMap( + namespace: String, + gitHash: String, +) { + val data = mapOf("cradle_manager.json" to "{}") + createConfigMap( + namespace, + ConfigMapEventHandler.CRADLE_MANAGER_CM_NAME, + createAnnotations(gitHash, data), + data, + ) +} + fun createK3sContainer(): K3sContainer = K3sContainer(K3S_DOCKER_IMAGE) .withLogConsumer(Slf4jLogConsumer(LoggerFactory.getLogger("K3S")).withSeparateOutputStreams()) .also(Startable::start) @@ -42,4 +186,102 @@ fun createRabbitMQClient(rabbitMQ: RabbitMQContainer): Client = rabbitMQ.httpPort, rabbitMQ.adminUsername, rabbitMQ.adminPassword, - ) \ No newline at end of file + ) + +fun createRabbitMQConnection(rabbitMQ: RabbitMQContainer, vHost: String): Connection = + ConnectionFactory().apply { + host = rabbitMQ.host + port = rabbitMQ.amqpPort + virtualHost = vHost + username = rabbitMQ.adminUsername + password = rabbitMQ.adminPassword + }.newConnection("integration-test") + +fun createRabbitMQConfig( + rabbitMQ: RabbitMQContainer, + vHost: String, + exchange: String, + user: String, +) = RabbitMQConfig( + rabbitMQ.amqpPort, + rabbitMQ.host, + vHost, + exchange, + user, + "${'$'}{RABBITMQ_PASS}", +) + +fun formatQueue(namespace: String, component: String, pin: String) = "link[$namespace:$component:$pin]" + +fun formatRoutingKey(namespace: String, component: String, pin: String) = "key[$namespace:$component:$pin]" + +fun Channel.createQueue( + namespace: String, + component: String, + pin: String, + durable: Boolean = true, + exclusive: Boolean = false, + autoDelete: Boolean = false, + arguments: Map = emptyMap(), +): AMQP.Queue.DeclareOk = queueDeclare(formatQueue(namespace, component, pin), durable, exclusive, autoDelete, arguments) + +fun Channel.createExchange( + exchange: String, + type: String, + durable: Boolean = true, + exclusive: Boolean = false, + autoDelete: Boolean = false, + arguments: Map = emptyMap(), +): AMQP.Exchange.DeclareOk = exchangeDeclare(exchange, type, durable, exclusive, autoDelete, arguments) + +fun prepareTh2CfgDir( + kubeConfigYaml: String, + operatorConfig: OperatorConfig, + baseDir: Path +) { + val configDir = baseDir.resolve("cfg") + val kubeCfgFile = configDir.resolve("kube-config.yaml") + val operatorCfgFile = configDir.resolve("infra-operator.yml") + configDir.createDirectories() + + Files.writeString(kubeCfgFile, kubeConfigYaml) + JsonUtils.YAML_MAPPER.writeValue(operatorCfgFile.toFile(), operatorConfig) + + System.setProperty(Config.KUBERNETES_KUBECONFIG_FILE, kubeCfgFile.absolutePathString()) + System.setProperty(ConfigLoader.CONFIG_FILE_SYSTEM_PROPERTY, operatorCfgFile.absolutePathString()) +} + +fun createOperatorConfig( + rabbitMQ: RabbitMQContainer, + namespacePrefixes: Set, + vHost: String, + topicExchange: String, + permissions: RabbitMQNamespacePermissions, +) = + OperatorConfig( + chart = ChartSpec(), + namespacePrefixes = namespacePrefixes, + rabbitMQManagement = + RabbitMQManagementConfig( + host = rabbitMQ.host, + managementPort = rabbitMQ.httpPort, + applicationPort = rabbitMQ.amqpPort, + vhostName = vHost, + exchangeName = topicExchange, + username = rabbitMQ.adminUsername, + password = rabbitMQ.adminPassword, + persistence = true, + schemaPermissions = permissions, + ), + prometheusConfiguration = + PrometheusConfiguration( + "0.0.0.0", + "9752", + false.toString(), + ), + ) + +private fun loadCrd(resourceName: String): CustomResourceDefinition = + requireNotNull(IntegrationTest::class.java.classLoader.getResource("crds/$resourceName")) { + "Resource '$resourceName' isn't found" + }.let(JsonUtils.YAML_MAPPER::readValue) diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt index 7b3f20e0..9aeb06e8 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt @@ -199,7 +199,7 @@ fun KubernetesClient.createTh2Dictionary( fun createAnnotations( gitHash: String, - sourceHash: String, + sourceHash: Any, ) = mapOf( KEY_DETECTION_TIME to System.currentTimeMillis().toString(), GIT_COMMIT_HASH to gitHash, diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt index d10c4919..7074340b 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt @@ -28,6 +28,8 @@ import kotlin.test.assertFalse import kotlin.test.assertNotNull import kotlin.test.assertTrue +const val RABBIT_MQ_QUEUE_CLASSIC_TYPE = "classic" + fun Client.assertUser( user: String, vHost: String, @@ -102,7 +104,7 @@ fun Client.assertQueue( timeout: Long = 5_000, unit: TimeUnit = TimeUnit.MILLISECONDS, ): QueueInfo { - await("assertQueue('$queue')") + await("assertQueue('$queue'") .timeout(timeout, unit) .until { getQueue(vHost, queue) != null } @@ -116,6 +118,18 @@ fun Client.assertQueue( } } +fun Client.awaitQueueSize( + queue: String, + vHost: String, + size: Long, + timeout: Long = 5_000, + unit: TimeUnit = TimeUnit.MILLISECONDS, +) { + await("awaitQueueSize('$queue', size: $size") + .timeout(timeout, unit) + .until { getQueue(vHost, queue)?.let { it.messagesReady == size } } +} + fun Client.assertBindings( queue: String, vHost: String, @@ -186,4 +200,15 @@ fun Client.assertNoQueues( await("assertNoQueues('$queuePattern')") .timeout(timeout, unit) .until { queues.map { it.name.matches(Regex(queuePattern)) && it.vhost == vHost }.isEmpty() } +} + +fun Client.assertNoQueue( + name: String, + vHost: String, + timeout: Long = 5_000, + unit: TimeUnit = TimeUnit.MILLISECONDS, +) { + await("assertNoQueue('$name')") + .timeout(timeout, unit) + .until { getQueue(vHost, name) == null } } \ No newline at end of file diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtilsTest.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtilsTest.kt index 8362fdb1..7c76e44e 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtilsTest.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtilsTest.kt @@ -16,6 +16,8 @@ package com.exactpro.th2.infraoperator.util +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.createEstoreQueue +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.createMstoreQueue import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext.toExchangeName import com.rabbitmq.client.Channel import io.fabric8.kubernetes.api.model.KubernetesResourceList @@ -76,7 +78,7 @@ class RabbitMQUtilsTest { } @Test - fun `no ns but rubbish queue`() { + fun `no ns and rubbish queue`() { val queueName = "test-link[th2-test-namespace:test-component:test-pin]" val client: KubernetesClient = mockKubernetesClient() val actual = @@ -144,6 +146,27 @@ class RabbitMQUtilsTest { assertEquals(expected, actual) } + @Test + fun `empty ns and store queues`() { + val namespaceName = "th2-test-active-namespace" + val client: KubernetesClient = + mockKubernetesClient( + setOf(namespaceName), + ) + val actual = + ResourceHolder( + queues = hashSetOf(createMstoreQueue(namespaceName), createEstoreQueue(namespaceName)), + exchanges = hashSetOf(toExchangeName(namespaceName), TOPIC_EXCHANGE_NAME), + ).filterRubbishResources( + client, + setOf("th2"), + TOPIC_EXCHANGE_NAME, + ) + val expected = ResourceHolder() + + assertEquals(expected, actual) + } + @Test fun `delete rubbish`() { val channel: Channel = mock {} From b409e5b84a7ba65e8d392f905429f89647755e78 Mon Sep 17 00:00:00 2001 From: Nikita Smirnov <46124551+Nikita-Smirnov-Exactpro@users.noreply.github.com> Date: Thu, 19 Sep 2024 13:47:50 +0400 Subject: [PATCH 41/42] [TH2-5226] Removed singletons (#124) --- .github/workflows/integration-tests.yml | 2 +- build.gradle | 2 + .../th2/infraoperator/Th2CrdController.java | 116 ++++++-- .../metrics/PrometheusServer.java | 47 ++-- .../client/DefaultResourceClient.java | 16 +- .../client/impl/DictionaryClient.java | 33 --- .../operator/GenericHelmTh2Op.java | 7 +- .../operator/HelmReleaseTh2Op.java | 27 +- .../operator/StoreHelmTh2Op.java | 5 +- .../operator/impl/BoxHelmTh2Op.java | 9 +- .../operator/impl/CoreBoxHelmTh2Op.java | 9 +- .../operator/impl/EstoreHelmTh2Op.java | 9 +- .../operator/impl/JobHelmTh2Op.java | 9 +- .../operator/impl/MstoreHelmTh2Op.java | 7 +- .../manager/impl/ConfigMapEventHandler.java | 27 +- .../manager/impl/DefaultWatchManager.java | 80 +++--- .../manager/impl/NamespaceEventHandler.java | 10 +- .../linkresolver/mq/DeclareQueueResolver.java | 41 +-- .../linkresolver/mq/RabbitMQContext.java | 257 +++++++++--------- .../redeploy/ContinuousTaskWorker.java | 22 +- .../tasks/RecreateQueuesAndBindings.java | 23 +- .../redeploy/tasks/RetryRabbitSetup.java | 9 +- .../tasks/RetryTopicExchangeTask.java | 9 +- .../linkresolver/mq/BindQueueLinkResolver.kt | 22 +- .../th2/infraoperator/util/RabbitMQUtils.kt | 27 +- .../exactpro/th2/infraoperator/util/Utils.kt | 32 +++ .../integration/DeleteRubbishOnStartTest.kt | 67 +++-- .../integration/IntegrationTest.kt | 7 +- .../integration/TestRabbitMQUtils.kt | 36 ++- 29 files changed, 574 insertions(+), 393 deletions(-) delete mode 100644 src/main/java/com/exactpro/th2/infraoperator/model/kubernetes/client/impl/DictionaryClient.java create mode 100644 src/main/kotlin/com/exactpro/th2/infraoperator/util/Utils.kt diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 0f113e34..23e327e0 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -1,4 +1,4 @@ -name: "Run integration tests for LDP" +name: "Run integration tests for infra-operator" on: push: diff --git a/build.gradle b/build.gradle index 4111371b..1880b6e2 100644 --- a/build.gradle +++ b/build.gradle @@ -55,6 +55,8 @@ dependencies { implementation "io.prometheus:simpleclient_httpserver" implementation "io.prometheus:simpleclient_hotspot" + implementation "com.google.guava:guava" + testImplementation(platform(libs.testcontainers.bom)) testImplementation 'org.testcontainers:rabbitmq' testImplementation 'org.testcontainers:k3s' diff --git a/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java b/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java index 0ebb95fe..520b6b58 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java +++ b/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java @@ -16,6 +16,8 @@ package com.exactpro.th2.infraoperator; +import com.exactpro.th2.infraoperator.configuration.ConfigLoader; +import com.exactpro.th2.infraoperator.configuration.OperatorConfig; import com.exactpro.th2.infraoperator.metrics.OperatorMetrics; import com.exactpro.th2.infraoperator.metrics.PrometheusServer; import com.exactpro.th2.infraoperator.operator.impl.BoxHelmTh2Op; @@ -28,49 +30,84 @@ import com.exactpro.th2.infraoperator.spec.strategy.redeploy.ContinuousTaskWorker; import com.exactpro.th2.infraoperator.spec.strategy.redeploy.tasks.CheckResourceCacheTask; import com.exactpro.th2.infraoperator.util.RabbitMQUtils; +import com.exactpro.th2.infraoperator.util.Utils; +import io.fabric8.kubernetes.client.KubernetesClient; import org.apache.logging.log4j.core.LoggerContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Deque; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import static com.exactpro.th2.infraoperator.util.KubernetesUtils.createKubernetesClient; public class Th2CrdController implements AutoCloseable { private static final Logger LOGGER = LoggerFactory.getLogger(Th2CrdController.class); - public void start() { - var watchManager = DefaultWatchManager.getInstance(); - PrometheusServer.start(); + private final PrometheusServer prometheusServer; + private final KubernetesClient kubClient; + private final DefaultWatchManager watchManager; + private final RabbitMQContext rabbitMQContext; + private final ContinuousTaskWorker continuousTaskWorker; + + public Th2CrdController() throws IOException, URISyntaxException { + OperatorConfig config = ConfigLoader.loadConfiguration(); + prometheusServer = new PrometheusServer(config.getPrometheusConfiguration()); + kubClient = createKubernetesClient(); + rabbitMQContext = new RabbitMQContext(config.getRabbitMQManagement()); + watchManager = new DefaultWatchManager(kubClient, rabbitMQContext); + continuousTaskWorker = new ContinuousTaskWorker(); + OperatorMetrics.resetCacheErrors(); - try { - RabbitMQUtils.deleteRabbitMQRubbish(); - RabbitMQContext.declareTopicExchange(); // FIXME: topic exchange should be removed when all namespaces are removed / disabled + RabbitMQUtils.deleteRabbitMQRubbish(kubClient, rabbitMQContext); + rabbitMQContext.declareTopicExchange(); // FIXME: topic exchange should be removed when all namespaces are removed / disabled - watchManager.addTarget(MstoreHelmTh2Op::new); - watchManager.addTarget(EstoreHelmTh2Op::new); - watchManager.addTarget(BoxHelmTh2Op::new); - watchManager.addTarget(CoreBoxHelmTh2Op::new); - watchManager.addTarget(JobHelmTh2Op::new); + watchManager.addTarget(MstoreHelmTh2Op::new); + watchManager.addTarget(EstoreHelmTh2Op::new); + watchManager.addTarget(BoxHelmTh2Op::new); + watchManager.addTarget(CoreBoxHelmTh2Op::new); + watchManager.addTarget(JobHelmTh2Op::new); - watchManager.startInformers(); + watchManager.startInformers(); + continuousTaskWorker.add(new CheckResourceCacheTask(300)); + } - ContinuousTaskWorker continuousTaskWorker = new ContinuousTaskWorker(); - continuousTaskWorker.add(new CheckResourceCacheTask(300)); + public static void main(String[] args) { + Deque resources = new ConcurrentLinkedDeque<>(); + Lock lock = new ReentrantLock(); + Condition condition = lock.newCondition(); + + configureShutdownHook(resources, lock, condition); + + try { + if (args.length > 0) { + configureLogger(args[0]); + } + Th2CrdController controller = new Th2CrdController(); + resources.add(controller); + + awaitShutdown(lock, condition); } catch (Exception e) { LOGGER.error("Exception in main thread", e); - watchManager.stopInformers(); - watchManager.close(); - throw e; + System.exit(1); } } - public static void main(String[] args) { - if (args.length > 0) { - configureLogger(args[0]); - } - Th2CrdController controller = new Th2CrdController(); - controller.start(); + @Override + public void close() { + Utils.close(prometheusServer, "Prometheus server"); + Utils.close(kubClient, "Kubernetes client"); + Utils.close(watchManager, "Watch manager"); + Utils.close(rabbitMQContext, "RabbitMQ context"); + Utils.close(continuousTaskWorker, "Continuous task worker"); } private static void configureLogger(String filePath) { @@ -83,8 +120,37 @@ private static void configureLogger(String filePath) { } } - @Override - public void close() throws Exception { + private static void configureShutdownHook(Deque resources, Lock lock, Condition condition) { + Runtime.getRuntime().addShutdownHook(new Thread( + () -> { + LOGGER.info("Shutdown start"); + lock.lock(); + try { + condition.signalAll(); + } finally { + lock.unlock(); + } + resources.descendingIterator().forEachRemaining((resource) -> { + try { + resource.close(); + } catch (Exception e) { + LOGGER.error("Cannot close resource {}", resource.getClass(), e); + } + }); + LOGGER.info("Shutdown end"); + }, + "Shutdown hook" + )); + } + private static void awaitShutdown(Lock lock, Condition condition) throws InterruptedException { + lock.lock(); + try { + LOGGER.info("Wait shutdown"); + condition.await(); + LOGGER.info("App shutdown"); + } finally { + lock.unlock(); + } } } diff --git a/src/main/java/com/exactpro/th2/infraoperator/metrics/PrometheusServer.java b/src/main/java/com/exactpro/th2/infraoperator/metrics/PrometheusServer.java index ab8e91e5..5e951335 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/metrics/PrometheusServer.java +++ b/src/main/java/com/exactpro/th2/infraoperator/metrics/PrometheusServer.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,40 +16,39 @@ package com.exactpro.th2.infraoperator.metrics; -import com.exactpro.th2.infraoperator.configuration.ConfigLoader; import com.exactpro.th2.infraoperator.spec.shared.PrometheusConfiguration; import io.prometheus.client.exporter.HTTPServer; import io.prometheus.client.hotspot.DefaultExports; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; -import java.util.concurrent.atomic.AtomicReference; -public class PrometheusServer { - private static final Logger logger = LoggerFactory.getLogger(PrometheusServer.class); +public class PrometheusServer implements AutoCloseable { + private static final Logger LOGGER = LoggerFactory.getLogger(PrometheusServer.class); + @Nullable + private final HTTPServer server; - private static final AtomicReference prometheusExporter = new AtomicReference<>(); - - public static void start() { + static { DefaultExports.initialize(); - PrometheusConfiguration prometheusConfiguration = ConfigLoader.getConfig().getPrometheusConfiguration(); + } - String host = prometheusConfiguration.getHost(); - int port = Integer.parseInt(prometheusConfiguration.getPort()); - boolean enabled = Boolean.parseBoolean(prometheusConfiguration.getEnabled()); + public PrometheusServer(PrometheusConfiguration configuration) throws IOException { + if (Boolean.parseBoolean(configuration.getEnabled())) { + String host = configuration.getHost(); + int port = Integer.parseInt(configuration.getPort()); + server = new HTTPServer(host, port); + LOGGER.info("Started prometheus server on: \"{}:{}\"", host, port); + } else { + server = null; + } + } - prometheusExporter.updateAndGet(server -> { - if (server == null && enabled) { - try { - server = new HTTPServer(host, port); - logger.info("Started prometheus server on: \"{}:{}\"", host, port); - return server; - } catch (IOException e) { - throw new RuntimeException("Failed to create Prometheus exporter", e); - } - } - return server; - }); + @Override + public void close() { + if (server != null) { + server.close(); + } } } diff --git a/src/main/java/com/exactpro/th2/infraoperator/model/kubernetes/client/DefaultResourceClient.java b/src/main/java/com/exactpro/th2/infraoperator/model/kubernetes/client/DefaultResourceClient.java index b3d8d979..4b961217 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/model/kubernetes/client/DefaultResourceClient.java +++ b/src/main/java/com/exactpro/th2/infraoperator/model/kubernetes/client/DefaultResourceClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,14 +21,10 @@ import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.dsl.MixedOperation; import io.fabric8.kubernetes.client.dsl.Resource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public abstract class DefaultResourceClient implements ResourceClient { - private static final Logger logger = LoggerFactory.getLogger(DefaultResourceClient.class); - - private final KubernetesClient client; + private final KubernetesClient kubClient; private final Class resourceType; @@ -37,15 +33,15 @@ public abstract class DefaultResourceClient implement private final String crdName; public DefaultResourceClient( - KubernetesClient client, + KubernetesClient kubClient, Class resourceType, String crdName ) { - this.client = client; + this.kubClient = kubClient; this.resourceType = resourceType; this.crdName = crdName; - instance = client.resources(resourceType); + instance = kubClient.resources(resourceType); } @Override @@ -54,7 +50,7 @@ public Class getResourceType() { } public KubernetesClient getClient() { - return this.client; + return this.kubClient; } public MixedOperation, ? extends Resource> getInstance() { diff --git a/src/main/java/com/exactpro/th2/infraoperator/model/kubernetes/client/impl/DictionaryClient.java b/src/main/java/com/exactpro/th2/infraoperator/model/kubernetes/client/impl/DictionaryClient.java deleted file mode 100644 index 399e13e2..00000000 --- a/src/main/java/com/exactpro/th2/infraoperator/model/kubernetes/client/impl/DictionaryClient.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.exactpro.th2.infraoperator.model.kubernetes.client.impl; - -import com.exactpro.th2.infraoperator.model.kubernetes.client.DefaultResourceClient; -import com.exactpro.th2.infraoperator.spec.dictionary.Th2Dictionary; -import io.fabric8.kubernetes.client.KubernetesClient; - -public class DictionaryClient extends DefaultResourceClient { - - public DictionaryClient(KubernetesClient client) { - super( - client, - Th2Dictionary.class, - "th2dictionaries.th2.exactpro.com" - ); - } - -} diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/GenericHelmTh2Op.java b/src/main/java/com/exactpro/th2/infraoperator/operator/GenericHelmTh2Op.java index 88e6b354..6be079d6 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/GenericHelmTh2Op.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/GenericHelmTh2Op.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +17,13 @@ package com.exactpro.th2.infraoperator.operator; import com.exactpro.th2.infraoperator.spec.Th2CustomResource; +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext; import io.fabric8.kubernetes.client.KubernetesClient; public abstract class GenericHelmTh2Op extends HelmReleaseTh2Op { - public GenericHelmTh2Op(KubernetesClient client) { - super(client); + public GenericHelmTh2Op(KubernetesClient kubClient, RabbitMQContext rabbitMQContext) { + super(kubClient, rabbitMQContext); } } diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/HelmReleaseTh2Op.java b/src/main/java/com/exactpro/th2/infraoperator/operator/HelmReleaseTh2Op.java index 3af2a828..ecfacc97 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/HelmReleaseTh2Op.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/HelmReleaseTh2Op.java @@ -30,6 +30,7 @@ import com.exactpro.th2.infraoperator.spec.shared.PrometheusConfiguration; import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.BindQueueLinkResolver; import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.DeclareQueueResolver; +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext; import com.exactpro.th2.infraoperator.util.CustomResourceUtils; import com.exactpro.th2.infraoperator.util.JsonUtils; @@ -126,13 +127,17 @@ public abstract class HelmReleaseTh2Op extends Abs protected final MixedOperation, Resource> helmReleaseClient; - public HelmReleaseTh2Op(KubernetesClient client) { + protected final DeclareQueueResolver declareQueueResolver; + protected final BindQueueLinkResolver bindQueueLinkResolver; - super(client); + public HelmReleaseTh2Op(KubernetesClient kubClient, RabbitMQContext rabbitMQContext) { - this.grpcConfigFactory = new GrpcRouterConfigFactory(); + super(kubClient); - helmReleaseClient = kubClient.resources(HelmRelease.class); + this.grpcConfigFactory = new GrpcRouterConfigFactory(); + this.helmReleaseClient = this.kubClient.resources(HelmRelease.class); + this.declareQueueResolver = new DeclareQueueResolver(rabbitMQContext); + this.bindQueueLinkResolver = new BindQueueLinkResolver(rabbitMQContext); } public abstract SharedIndexInformer generateInformerFromFactory(SharedInformerFactory factory); @@ -326,9 +331,9 @@ protected void addedEvent(CR resource) throws IOException { var lock = OperatorState.INSTANCE.getLock(namespace); lock.lock(); try { - DeclareQueueResolver.resolveAdd(resource); - BindQueueLinkResolver.resolveDeclaredLinks(resource); - BindQueueLinkResolver.resolveHiddenLinks(resource); + declareQueueResolver.resolveAdd(resource); + bindQueueLinkResolver.resolveDeclaredLinks(resource); + bindQueueLinkResolver.resolveHiddenLinks(resource); updateGrpcLinkedResourcesIfNeeded(resource); super.addedEvent(resource); } finally { @@ -344,9 +349,9 @@ protected void modifiedEvent(CR resource) throws IOException { var lock = OperatorState.INSTANCE.getLock(namespace); lock.lock(); try { - DeclareQueueResolver.resolveAdd(resource); - BindQueueLinkResolver.resolveDeclaredLinks(resource); - BindQueueLinkResolver.resolveHiddenLinks(resource); + declareQueueResolver.resolveAdd(resource); + bindQueueLinkResolver.resolveDeclaredLinks(resource); + bindQueueLinkResolver.resolveHiddenLinks(resource); updateGrpcLinkedResourcesIfNeeded(resource); super.modifiedEvent(resource); } finally { @@ -361,7 +366,7 @@ protected void deletedEvent(CR resource) { lock.lock(); try { super.deletedEvent(resource); - DeclareQueueResolver.resolveDelete(resource); + declareQueueResolver.resolveDelete(resource); } finally { lock.unlock(); } diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/StoreHelmTh2Op.java b/src/main/java/com/exactpro/th2/infraoperator/operator/StoreHelmTh2Op.java index b29432c8..abc1b916 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/StoreHelmTh2Op.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/StoreHelmTh2Op.java @@ -18,6 +18,7 @@ import com.exactpro.th2.infraoperator.OperatorState; import com.exactpro.th2.infraoperator.spec.Th2CustomResource; +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext; import io.fabric8.kubernetes.client.KubernetesClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,8 +39,8 @@ public abstract class StoreHelmTh2Op extends HelmR public static final String MESSAGE_STORAGE_BOX_ALIAS = "mstore"; - public StoreHelmTh2Op(KubernetesClient client) { - super(client); + public StoreHelmTh2Op(KubernetesClient kubClient, RabbitMQContext rabbitMQContext) { + super(kubClient, rabbitMQContext); } private void nameCheck(CR resource) throws IOException { diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/impl/BoxHelmTh2Op.java b/src/main/java/com/exactpro/th2/infraoperator/operator/impl/BoxHelmTh2Op.java index c1e188bb..9a35114d 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/impl/BoxHelmTh2Op.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/impl/BoxHelmTh2Op.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import com.exactpro.th2.infraoperator.model.kubernetes.client.impl.BoxClient; import com.exactpro.th2.infraoperator.operator.GenericHelmTh2Op; import com.exactpro.th2.infraoperator.spec.box.Th2Box; +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext; import com.exactpro.th2.infraoperator.util.CustomResourceUtils; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.informers.SharedIndexInformer; @@ -31,9 +32,9 @@ public class BoxHelmTh2Op extends GenericHelmTh2Op { private final BoxClient boxClient; - public BoxHelmTh2Op(KubernetesClient client) { - super(client); - this.boxClient = new BoxClient(client); + public BoxHelmTh2Op(KubernetesClient kubClient, RabbitMQContext rabbitMQContext) { + super(kubClient, rabbitMQContext); + this.boxClient = new BoxClient(kubClient); } @Override diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/impl/CoreBoxHelmTh2Op.java b/src/main/java/com/exactpro/th2/infraoperator/operator/impl/CoreBoxHelmTh2Op.java index 4e01f53f..19d47f8a 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/impl/CoreBoxHelmTh2Op.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/impl/CoreBoxHelmTh2Op.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import com.exactpro.th2.infraoperator.model.kubernetes.client.impl.CoreBoxClient; import com.exactpro.th2.infraoperator.operator.GenericHelmTh2Op; import com.exactpro.th2.infraoperator.spec.corebox.Th2CoreBox; +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext; import com.exactpro.th2.infraoperator.util.CustomResourceUtils; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.informers.SharedIndexInformer; @@ -31,9 +32,9 @@ public class CoreBoxHelmTh2Op extends GenericHelmTh2Op { private final CoreBoxClient coreBoxClient; - public CoreBoxHelmTh2Op(KubernetesClient client) { - super(client); - this.coreBoxClient = new CoreBoxClient(client); + public CoreBoxHelmTh2Op(KubernetesClient kubClient, RabbitMQContext rabbitMQContext) { + super(kubClient, rabbitMQContext); + this.coreBoxClient = new CoreBoxClient(kubClient); } @Override diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/impl/EstoreHelmTh2Op.java b/src/main/java/com/exactpro/th2/infraoperator/operator/impl/EstoreHelmTh2Op.java index fa863d54..99018cce 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/impl/EstoreHelmTh2Op.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/impl/EstoreHelmTh2Op.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import com.exactpro.th2.infraoperator.model.kubernetes.client.impl.EstoreClient; import com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op; import com.exactpro.th2.infraoperator.spec.estore.Th2Estore; +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext; import com.exactpro.th2.infraoperator.util.CustomResourceUtils; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.informers.SharedIndexInformer; @@ -31,9 +32,9 @@ public class EstoreHelmTh2Op extends StoreHelmTh2Op { private final EstoreClient estoreClient; - public EstoreHelmTh2Op(KubernetesClient client) { - super(client); - this.estoreClient = new EstoreClient(client); + public EstoreHelmTh2Op(KubernetesClient kubClient, RabbitMQContext rabbitMQContext) { + super(kubClient, rabbitMQContext); + this.estoreClient = new EstoreClient(kubClient); } @Override diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/impl/JobHelmTh2Op.java b/src/main/java/com/exactpro/th2/infraoperator/operator/impl/JobHelmTh2Op.java index a46e1603..41c29d62 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/impl/JobHelmTh2Op.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/impl/JobHelmTh2Op.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import com.exactpro.th2.infraoperator.model.kubernetes.client.impl.JobClient; import com.exactpro.th2.infraoperator.operator.GenericHelmTh2Op; import com.exactpro.th2.infraoperator.spec.job.Th2Job; +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext; import com.exactpro.th2.infraoperator.util.CustomResourceUtils; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.informers.SharedIndexInformer; @@ -31,9 +32,9 @@ public class JobHelmTh2Op extends GenericHelmTh2Op { private final JobClient jobClient; - public JobHelmTh2Op(KubernetesClient client) { - super(client); - this.jobClient = new JobClient(client); + public JobHelmTh2Op(KubernetesClient kubClient, RabbitMQContext rabbitMQContext) { + super(kubClient, rabbitMQContext); + this.jobClient = new JobClient(kubClient); } @Override diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/impl/MstoreHelmTh2Op.java b/src/main/java/com/exactpro/th2/infraoperator/operator/impl/MstoreHelmTh2Op.java index e0763f9f..19a80201 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/impl/MstoreHelmTh2Op.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/impl/MstoreHelmTh2Op.java @@ -22,6 +22,7 @@ import com.exactpro.th2.infraoperator.model.kubernetes.client.impl.MstoreClient; import com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op; import com.exactpro.th2.infraoperator.spec.mstore.Th2Mstore; +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext; import com.exactpro.th2.infraoperator.util.CustomResourceUtils; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.informers.SharedIndexInformer; @@ -31,9 +32,9 @@ public class MstoreHelmTh2Op extends StoreHelmTh2Op { private final MstoreClient mstoreClient; - public MstoreHelmTh2Op(KubernetesClient client) { - super(client); - this.mstoreClient = new MstoreClient(client); + public MstoreHelmTh2Op(KubernetesClient kubClient, RabbitMQContext rabbitMQContext) { + super(kubClient, rabbitMQContext); + this.mstoreClient = new MstoreClient(kubClient); } @Override diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/ConfigMapEventHandler.java b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/ConfigMapEventHandler.java index f5d14fca..005a0a26 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/ConfigMapEventHandler.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/ConfigMapEventHandler.java @@ -105,19 +105,24 @@ private ConfigMapMeta(String alias, String dataFileName) { private static final Logger LOGGER = LoggerFactory.getLogger(ConfigMapEventHandler.class); - private KubernetesClient client; + private final KubernetesClient kubClient; + + private final RabbitMQContext rabbitMQContext; + + private final DefaultWatchManager watchManager; private MixedOperation, Resource> helmReleaseClient; public KubernetesClient getClient() { - return client; + return kubClient; } public static ConfigMapEventHandler newInstance(SharedInformerFactory sharedInformerFactory, KubernetesClient client, + RabbitMQContext rabbitMQContext, + DefaultWatchManager watchManager, EventQueue eventQueue) { - var res = new ConfigMapEventHandler(client); - res.client = client; + var res = new ConfigMapEventHandler(client, rabbitMQContext, watchManager); res.helmReleaseClient = client.resources(HelmRelease.class); SharedIndexInformer configMapInformer = sharedInformerFactory.sharedIndexInformerFor( @@ -129,8 +134,10 @@ public static ConfigMapEventHandler newInstance(SharedInformerFactory sharedInfo return res; } - private ConfigMapEventHandler(KubernetesClient client) { - this.client = client; + private ConfigMapEventHandler(KubernetesClient kubClient, RabbitMQContext rabbitMQContext, DefaultWatchManager watchManager) { + this.kubClient = kubClient; + this.rabbitMQContext = rabbitMQContext; + this.watchManager = watchManager; } @Override @@ -165,10 +172,10 @@ public void eventReceived(Action action, ConfigMap resource) { if (!Objects.equals(rabbitMQConfig, newRabbitMQConfig)) { Histogram.Timer processTimer = OperatorMetrics.getConfigMapEventTimer(resource); configMaps.setRabbitMQConfig4Namespace(namespace, newRabbitMQConfig); - RabbitMQContext.setUpRabbitMqForNamespace(namespace); + rabbitMQContext.setUpRabbitMqForNamespace(namespace); LOGGER.info("RabbitMQ ConfigMap has been updated in namespace \"{}\". Updating all boxes", namespace); - DefaultWatchManager.getInstance().refreshBoxes(namespace); + watchManager.refreshBoxes(namespace); LOGGER.info("box-definition(s) have been updated"); processTimer.observeDuration(); } else { @@ -256,7 +263,7 @@ private void updateDefaultBookName(Action action, String namespace, ConfigMap re Histogram.Timer processTimer = OperatorMetrics.getConfigMapEventTimer(resource); OperatorState.INSTANCE.setBookName(namespace, newBookName); LOGGER.info("\"{}\" has been updated. Updating all boxes", resourceLabel); - DefaultWatchManager.getInstance().refreshBoxes(namespace); + watchManager.refreshBoxes(namespace); LOGGER.info("box-definition(s) have been updated"); processTimer.observeDuration(); } @@ -318,7 +325,7 @@ public void onClose(WatcherException cause) { private String readRabbitMQPasswordForSchema(String namespace, String secretName) throws Exception { - Secret secret = client.secrets().inNamespace(namespace).withName(secretName).get(); + Secret secret = kubClient.secrets().inNamespace(namespace).withName(secretName).get(); if (secret == null) { throw new Exception(String.format("Secret not found \"%s\"", annotationFor(namespace, "Secret", secretName))); diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/DefaultWatchManager.java b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/DefaultWatchManager.java index af18438a..72d61942 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/DefaultWatchManager.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/DefaultWatchManager.java @@ -20,6 +20,7 @@ import com.exactpro.th2.infraoperator.model.kubernetes.client.ResourceClient; import com.exactpro.th2.infraoperator.operator.HelmReleaseTh2Op; import com.exactpro.th2.infraoperator.spec.Th2CustomResource; +import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext; import com.exactpro.th2.infraoperator.util.CustomResourceUtils; import com.exactpro.th2.infraoperator.util.Strings; import com.fasterxml.uuid.Generators; @@ -38,18 +39,17 @@ import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Function; +import java.util.function.BiFunction; import java.util.function.Supplier; import java.util.stream.Collectors; import static com.exactpro.th2.infraoperator.operator.AbstractTh2Operator.REFRESH_TOKEN_ALIAS; import static com.exactpro.th2.infraoperator.util.CustomResourceUtils.annotationFor; -import static com.exactpro.th2.infraoperator.util.KubernetesUtils.createKubernetesClient; import static com.exactpro.th2.infraoperator.util.WatcherUtils.createExceptionHandler; -public class DefaultWatchManager { +public class DefaultWatchManager implements AutoCloseable { - private static final Logger logger = LoggerFactory.getLogger(DefaultWatchManager.class); + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultWatchManager.class); private boolean isWatching = false; @@ -59,29 +59,29 @@ public class DefaultWatchManager { private final SharedInformerFactory sharedInformerFactory; - private static DefaultWatchManager instance; - private final EventDispatcher eventDispatcher; - private final KubernetesClient client; + private final KubernetesClient kubClient; + + private final RabbitMQContext rabbitMQContext; - private synchronized SharedInformerFactory getInformerFactory() { + private SharedInformerFactory getInformerFactory() { return sharedInformerFactory; } - private DefaultWatchManager(KubernetesClient client) { - this.sharedInformerFactory = client.informers(); + public DefaultWatchManager(KubernetesClient kubClient, RabbitMQContext rabbitMQContext) { + this.sharedInformerFactory = kubClient.informers(); this.eventDispatcher = new EventDispatcher(); - this.client = client; + this.kubClient = kubClient; + this.rabbitMQContext = rabbitMQContext; sharedInformerFactory.addSharedInformerEventListener(exception -> { - logger.error("Exception in InformerFactory : {}", exception.getMessage()); + LOGGER.error("Exception in InformerFactory : {}", exception.getMessage()); }); - instance = this; } - public void startInformers() { - logger.info("Starting all informers..."); + public synchronized void startInformers() { + LOGGER.info("Starting all informers..."); SharedInformerFactory sharedInformerFactory = getInformerFactory(); @@ -92,11 +92,11 @@ public void startInformers() { isWatching = true; sharedInformerFactory.startAllRegisteredInformers(); - logger.info("All informers has been started"); + LOGGER.info("All informers has been started"); } - public void stopInformers() { - logger.info("Shutting down informers"); + private void stopInformers() { + LOGGER.info("Shutting down informers"); getInformerFactory().stopAllRegisteredInformers(); } @@ -107,10 +107,10 @@ private void loadResources(EventHandlerContext context) { private void loadConfigMaps(EventHandlerContext context) { var configMapEventHandler = (ConfigMapEventHandler) context.getHandler(ConfigMapEventHandler.class); - List configMaps = client.configMaps().inAnyNamespace().list().getItems(); + List configMaps = kubClient.configMaps().inAnyNamespace().list().getItems(); configMaps = filterByNamespace(configMaps); for (var configMap : configMaps) { - logger.info("Loading \"{}\"", annotationFor(configMap)); + LOGGER.info("Loading \"{}\"", annotationFor(configMap)); configMapEventHandler.eventReceived(Watcher.Action.ADDED, configMap); } } @@ -138,11 +138,11 @@ private EventHandlerContext registerInformers(SharedInformerFactory sharedInform EventHandlerContext context = new EventHandlerContext(); - context.addHandler(NamespaceEventHandler.newInstance(sharedInformerFactory, eventDispatcher.getEventQueue())); - context.addHandler(Th2DictionaryEventHandler.newInstance(sharedInformerFactory, client, - eventDispatcher.getEventQueue())); - context.addHandler(ConfigMapEventHandler.newInstance(sharedInformerFactory, client, + context.addHandler(NamespaceEventHandler.newInstance(sharedInformerFactory, rabbitMQContext, eventDispatcher.getEventQueue())); + context.addHandler(Th2DictionaryEventHandler.newInstance(sharedInformerFactory, kubClient, eventDispatcher.getEventQueue())); + context.addHandler(ConfigMapEventHandler.newInstance(sharedInformerFactory, kubClient, rabbitMQContext, + this, eventDispatcher.getEventQueue())); /* resourceClients initialization should be done first @@ -173,25 +173,25 @@ private EventHandlerContext registerInformers(SharedInformerFactory sharedInform return context; } - public boolean isWatching() { + public synchronized boolean isWatching() { return isWatching; } - public void addTarget( - Function> operator) { + public synchronized void addTarget( + BiFunction> operator) { helmWatchersCommands.add(() -> { // T extends Th2CustomResource -> T is a Th2CustomResource @SuppressWarnings("unchecked") - var th2ResOp = (HelmReleaseTh2Op) operator.apply(client); + var th2ResOp = (HelmReleaseTh2Op) operator.apply(kubClient, rabbitMQContext); return th2ResOp; }); } - void refreshBoxes(String namespace) { + synchronized void refreshBoxes(String namespace) { if (!isWatching()) { - logger.warn("Not watching for resources yet"); + LOGGER.warn("Not watching for resources yet"); return; } @@ -204,7 +204,7 @@ void refreshBoxes(String namespace) { } } - logger.info("{} boxes updated", refreshedBoxes); + LOGGER.info("{} boxes updated", refreshedBoxes); } private void createResource(String linkNamespace, Th2CustomResource resource, @@ -215,20 +215,16 @@ private void createResource(String linkNamespace, Th2CustomResource resource, resMeta.setAnnotations(Objects.nonNull(resMeta.getAnnotations()) ? resMeta.getAnnotations() : new HashMap<>()); resMeta.getAnnotations().put(REFRESH_TOKEN_ALIAS, refreshToken); resClient.getInstance().inNamespace(linkNamespace).resource(resource).createOrReplace(); - logger.debug("refreshed \"{}\" with refresh-token={}", + LOGGER.debug("refreshed \"{}\" with refresh-token={}", CustomResourceUtils.annotationFor(resource), refreshToken); } - public static synchronized DefaultWatchManager getInstance() { - if (instance == null) { - instance = new DefaultWatchManager(createKubernetesClient()); - } - - return instance; - } - - public void close() { + @Override + public synchronized void close() throws InterruptedException { + stopInformers(); eventDispatcher.interrupt(); - client.close(); + eventDispatcher.join(5_000); + resourceClients.clear(); + helmWatchersCommands.clear(); } } diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/NamespaceEventHandler.java b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/NamespaceEventHandler.java index 886328d1..53da675b 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/NamespaceEventHandler.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/NamespaceEventHandler.java @@ -37,23 +37,27 @@ public class NamespaceEventHandler implements ResourceEventHandler, Watcher { private static final Logger LOGGER = LoggerFactory.getLogger(NamespaceEventHandler.class); + private final RabbitMQContext rabbitMQContext; + private final EventQueue eventQueue; private final OperatorConfig config = ConfigLoader.getConfig(); public static NamespaceEventHandler newInstance(SharedInformerFactory sharedInformerFactory, + RabbitMQContext rabbitMQContext, EventQueue eventQueue) { SharedIndexInformer namespaceInformer = sharedInformerFactory.sharedIndexInformerFor( Namespace.class, RESYNC_TIME); - var res = new NamespaceEventHandler(eventQueue); + var res = new NamespaceEventHandler(rabbitMQContext, eventQueue); namespaceInformer.exceptionHandler(createExceptionHandler(Namespace.class)); namespaceInformer.addEventHandler(res); return res; } - public NamespaceEventHandler(EventQueue eventQueue) { + public NamespaceEventHandler(RabbitMQContext rabbitMQContext, EventQueue eventQueue) { + this.rabbitMQContext = rabbitMQContext; this.eventQueue = eventQueue; } @@ -114,7 +118,7 @@ public void eventReceived(Action action, Namespace resource) { lock.lock(); try { LOGGER.info("Processing {} event for namespace: \"{}\"", action, namespaceName); - RabbitMQContext.cleanupRabbit(namespaceName); + rabbitMQContext.cleanupRabbit(namespaceName); LOGGER.info("Deleted namespace {}", namespaceName); } catch (Exception e) { LOGGER.error("Exception processing event for \"{}\"", resourceLabel, e); diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/DeclareQueueResolver.java b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/DeclareQueueResolver.java index 578326ec..cfc566d7 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/DeclareQueueResolver.java +++ b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/DeclareQueueResolver.java @@ -39,41 +39,46 @@ import static com.exactpro.th2.infraoperator.spec.strategy.linkresolver.Util.createEstoreQueue; import static com.exactpro.th2.infraoperator.spec.strategy.linkresolver.Util.createMstoreQueue; -import static com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext.getChannel; import static com.exactpro.th2.infraoperator.util.ExtractUtils.extractName; public class DeclareQueueResolver { - private static final Logger logger = LoggerFactory.getLogger(DeclareQueueResolver.class); + private static final Logger LOGGER = LoggerFactory.getLogger(DeclareQueueResolver.class); - public static void resolveAdd(Th2CustomResource resource) { + private final RabbitMQContext rabbitMQContext; + + public DeclareQueueResolver(RabbitMQContext rabbitMQContext) { + this.rabbitMQContext = rabbitMQContext; + } + + public void resolveAdd(Th2CustomResource resource) { String namespace = ExtractUtils.extractNamespace(resource); try { declareQueueBunch(namespace, resource); } catch (Exception e) { String message = "Exception while working with rabbitMq"; - logger.error(message, e); + LOGGER.error(message, e); throw new NonTerminalException(message, e); } } - public static void resolveDelete(Th2CustomResource resource) { + public void resolveDelete(Th2CustomResource resource) { String namespace = ExtractUtils.extractNamespace(resource); try { - Channel channel = getChannel(); + Channel channel = rabbitMQContext.getChannel(); //get queues that are associated with current box. Set boxQueueNames = generateBoxQueues(namespace, resource); removeExtinctQueues(channel, boxQueueNames, CustomResourceUtils.annotationFor(resource), namespace); } catch (Exception e) { String message = "Exception while working with rabbitMq"; - logger.error(message, e); + LOGGER.error(message, e); throw new NonTerminalException(message, e); } } - private static void declareQueueBunch(String namespace, Th2CustomResource resource) throws IOException { + private void declareQueueBunch(String namespace, Th2CustomResource resource) throws IOException { - Channel channel = getChannel(); + Channel channel = rabbitMQContext.getChannel(); boolean persistence = ConfigLoader.getConfig().getRabbitMQManagement().getPersistence(); //get queues that are associated with current box and are not linked through Th2Link resources @@ -86,9 +91,9 @@ private static void declareQueueBunch(String namespace, Th2CustomResource resour //remove from set if pin for queue still exists. boxQueues.remove(queueName); var newQueueArguments = RabbitMQContext.generateQueueArguments(pin.getSettings()); - var currentQueue = RabbitMQContext.getQueue(queueName); + var currentQueue = rabbitMQContext.getQueue(queueName); if (currentQueue != null && !currentQueue.getArguments().equals(newQueueArguments)) { - logger.warn("Arguments for queue '{}' were modified. Recreating with new arguments", queueName); + LOGGER.warn("Arguments for queue '{}' were modified. Recreating with new arguments", queueName); channel.queueDelete(queueName); } var declareResult = channel.queueDeclare(queueName @@ -96,14 +101,14 @@ private static void declareQueueBunch(String namespace, Th2CustomResource resour , false , false , newQueueArguments); - logger.info("Queue '{}' of resource {} was successfully declared", + LOGGER.info("Queue '{}' of resource {} was successfully declared", declareResult.getQueue(), extractName(resource)); } //remove from rabbit queues that are left i.e. inactive removeExtinctQueues(channel, boxQueues, CustomResourceUtils.annotationFor(resource), namespace); } - private static Set getBoxPreviousQueues(String namespace, String boxName) { + private Set getBoxPreviousQueues(String namespace, String boxName) { HelmRelease hr = OperatorState.INSTANCE.getHelmReleaseFromCache(boxName, namespace); if (hr == null) { return getBoxQueuesFromRabbit(namespace, boxName); @@ -115,9 +120,9 @@ private static Set getBoxPreviousQueues(String namespace, String boxName * Collect all queues related to the {@code namespace} {@code boxName} component in RabbitMQ * @return mutable set of queues */ - private static @NotNull Set getBoxQueuesFromRabbit(String namespace, String boxName) { + private @NotNull Set getBoxQueuesFromRabbit(String namespace, String boxName) { - List queueInfoList = RabbitMQContext.getQueues(); + List queueInfoList = rabbitMQContext.getQueues(); Set queueNames = new HashSet<>(); queueInfoList.forEach(q -> { @@ -148,16 +153,16 @@ private static void removeExtinctQueues( String mstoreQueue = createMstoreQueue(namespace); if (!extinctQueueNames.isEmpty()) { - logger.info("Trying to delete queues associated with \"{}\"", resourceLabel); + LOGGER.info("Trying to delete queues associated with \"{}\"", resourceLabel); extinctQueueNames .stream() .filter(name -> !name.equals(estoreQueue) && !name.equals(mstoreQueue)) .forEach(queueName -> { try { channel.queueDelete(queueName); - logger.info("Deleted queue: [{}]", queueName); + LOGGER.info("Deleted queue: [{}]", queueName); } catch (IOException e) { - logger.error("Exception deleting queue: [{}]", queueName, e); + LOGGER.error("Exception deleting queue: [{}]", queueName, e); } }); } diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java index ca09307d..d4443b2e 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java +++ b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java @@ -29,6 +29,7 @@ import com.exactpro.th2.infraoperator.spec.strategy.redeploy.tasks.RecreateQueuesAndBindings; import com.exactpro.th2.infraoperator.spec.strategy.redeploy.tasks.RetryRabbitSetup; import com.exactpro.th2.infraoperator.spec.strategy.redeploy.tasks.RetryTopicExchangeTask; +import com.exactpro.th2.infraoperator.util.Utils; import com.rabbitmq.client.BuiltinExchangeType; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; @@ -55,6 +56,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; import static com.exactpro.th2.infraoperator.spec.strategy.linkresolver.Util.createEstoreQueue; @@ -66,161 +70,151 @@ import static java.util.Objects.requireNonNullElse; import static org.apache.commons.lang3.StringUtils.isNoneBlank; -public final class RabbitMQContext { +public final class RabbitMQContext implements AutoCloseable { - private static final Logger logger = LoggerFactory.getLogger(RabbitMQContext.class); + private static final Logger LOGGER = LoggerFactory.getLogger(RabbitMQContext.class); private static final int RETRY_DELAY = 120; public static final String TOPIC = BuiltinExchangeType.TOPIC.getType(); public static final String DIRECT = BuiltinExchangeType.DIRECT.getType(); + private final RabbitMQManagementConfig managementConfig; + private final ChannelContext channelContext; - private static volatile RabbitMQManagementConfig managementConfig; - - private static volatile ChannelContext channelContext; - - private static volatile Client rmqClient; + private final Client rmqClient; private static final RetryableTaskQueue retryableTaskQueue = new RetryableTaskQueue(); - private RabbitMQContext() { + public RabbitMQContext(RabbitMQManagementConfig managementConfig) throws MalformedURLException, URISyntaxException { + this.managementConfig = managementConfig; + this.rmqClient = createClient(managementConfig); + this.channelContext = new ChannelContext(this, managementConfig); } - public static String getTopicExchangeName() { - return getManagementConfig().getExchangeName(); + public String getTopicExchangeName() { + return managementConfig.getExchangeName(); } - public static void declareTopicExchange() { + public void declareTopicExchange() { String exchangeName = getTopicExchangeName(); - RabbitMQManagementConfig rabbitMQManagementConfig = getManagementConfig(); try { - getChannel().exchangeDeclare(exchangeName, TOPIC, rabbitMQManagementConfig.getPersistence()); + getChannel().exchangeDeclare(exchangeName, TOPIC, managementConfig.getPersistence()); } catch (Exception e) { - logger.error("Exception setting up exchange: \"{}\"", exchangeName, e); - RetryTopicExchangeTask retryTopicExchangeTask = new RetryTopicExchangeTask(exchangeName, RETRY_DELAY); + LOGGER.error("Exception setting up exchange: \"{}\"", exchangeName, e); + RetryTopicExchangeTask retryTopicExchangeTask = new RetryTopicExchangeTask(this, exchangeName, RETRY_DELAY); retryableTaskQueue.add(retryTopicExchangeTask, true); - logger.info("Task \"{}\" added to scheduler, with delay \"{}\" seconds", + LOGGER.info("Task \"{}\" added to scheduler, with delay \"{}\" seconds", retryTopicExchangeTask.getName(), RETRY_DELAY); } } - public static void setUpRabbitMqForNamespace(String namespace) { + public void setUpRabbitMqForNamespace(String namespace) { try { createUser(namespace); declareTopicExchange(); declareExchange(toExchangeName(namespace)); createStoreQueues(namespace); } catch (Exception e) { - logger.error("Exception setting up rabbitMq for namespace: \"{}\"", namespace, e); - RetryRabbitSetup retryRabbitSetup = new RetryRabbitSetup(namespace, RETRY_DELAY); + LOGGER.error("Exception setting up rabbitMq for namespace: \"{}\"", namespace, e); + RetryRabbitSetup retryRabbitSetup = new RetryRabbitSetup(this, namespace, RETRY_DELAY); retryableTaskQueue.add(retryRabbitSetup, true); - logger.info("Task \"{}\" added to scheduler, with delay \"{}\" seconds", + LOGGER.info("Task \"{}\" added to scheduler, with delay \"{}\" seconds", retryRabbitSetup.getName(), RETRY_DELAY); } } - private static void createUser(String namespace) throws Exception { + private void createUser(String namespace) { - RabbitMQManagementConfig rabbitMQManagementConfig = getManagementConfig(); RabbitMQConfig rabbitMQConfig = getRabbitMQConfig(namespace); String password = rabbitMQConfig.getPassword(); - String vHostName = rabbitMQManagementConfig.getVhostName(); + String vHostName = managementConfig.getVhostName(); if (StringUtils.isBlank(namespace)) { return; } try { - Client rmqClient = getClient(); - if (rmqClient.getVhost(vHostName) == null) { - logger.error("vHost: \"{}\" is not present", vHostName); + LOGGER.error("vHost: \"{}\" is not present", vHostName); return; } rmqClient.createUser(namespace, password.toCharArray(), new ArrayList<>()); - logger.info("Created user \"{}\" on vHost \"{}\"", namespace, vHostName); + LOGGER.info("Created user \"{}\" on vHost \"{}\"", namespace, vHostName); // set permissions - RabbitMQNamespacePermissions rabbitMQNamespacePermissions = - rabbitMQManagementConfig.getSchemaPermissions(); + RabbitMQNamespacePermissions rabbitMQNamespacePermissions = managementConfig.getSchemaPermissions(); UserPermissions permissions = new UserPermissions(); permissions.setConfigure(rabbitMQNamespacePermissions.getConfigure()); permissions.setRead(rabbitMQNamespacePermissions.getRead()); permissions.setWrite(rabbitMQNamespacePermissions.getWrite()); rmqClient.updatePermissions(vHostName, namespace, permissions); - logger.info("User \"{}\" permissions set in RabbitMQ", namespace); + LOGGER.info("User \"{}\" permissions set in RabbitMQ", namespace); } catch (Exception e) { - logger.error("Exception setting up user: \"{}\" for vHost: \"{}\"", namespace, vHostName, e); + LOGGER.error("Exception setting up user: \"{}\" for vHost: \"{}\"", namespace, vHostName, e); throw e; } } - private static void declareExchange(String exchangeName) throws Exception { - RabbitMQManagementConfig rabbitMQManagementConfig = getManagementConfig(); + private void declareExchange(String exchangeName) throws Exception { try { - getChannel().exchangeDeclare(exchangeName, DIRECT, rabbitMQManagementConfig.getPersistence()); + getChannel().exchangeDeclare(exchangeName, DIRECT, managementConfig.getPersistence()); } catch (Exception e) { - logger.error("Exception setting up exchange: \"{}\"", exchangeName, e); + LOGGER.error("Exception setting up exchange: \"{}\"", exchangeName, e); throw e; } } - private static void createStoreQueues(String namespace) throws Exception { + private void createStoreQueues(String namespace) throws Exception { var channel = getChannel(); - RabbitMQManagementConfig rabbitMQManagementConfig = getManagementConfig(); var declareResult = channel.queueDeclare( createEstoreQueue(namespace), - rabbitMQManagementConfig.getPersistence(), + managementConfig.getPersistence(), false, false, null ); - logger.info("Queue \"{}\" was successfully declared", declareResult.getQueue()); + LOGGER.info("Queue \"{}\" was successfully declared", declareResult.getQueue()); declareResult = channel.queueDeclare( createMstoreQueue(namespace), - rabbitMQManagementConfig.getPersistence(), + managementConfig.getPersistence(), false, false, null ); - logger.info("Queue \"{}\" was successfully declared", declareResult.getQueue()); + LOGGER.info("Queue \"{}\" was successfully declared", declareResult.getQueue()); } - public static void cleanupRabbit(String namespace) throws Exception { + public void cleanupRabbit(String namespace) { removeSchemaExchange(toExchangeName(namespace)); removeSchemaQueues(namespace); removeSchemaUser(namespace); } - private static void removeSchemaUser(String namespace) throws Exception { - RabbitMQManagementConfig rabbitMQManagementConfig = getManagementConfig(); - - String vHostName = rabbitMQManagementConfig.getVhostName(); - - Client rmqClient = getClient(); + private void removeSchemaUser(String namespace) { + String vHostName = managementConfig.getVhostName(); if (rmqClient.getVhost(vHostName) == null) { - logger.error("vHost: \"{}\" is not present", vHostName); + LOGGER.error("vHost: \"{}\" is not present", vHostName); return; } rmqClient.deleteUser(namespace); - logger.info("Deleted user \"{}\" from vHost \"{}\"", namespace, vHostName); + LOGGER.info("Deleted user \"{}\" from vHost \"{}\"", namespace, vHostName); } - private static void removeSchemaExchange(String exchangeName) { + private void removeSchemaExchange(String exchangeName) { try { getChannel().exchangeDelete(exchangeName); } catch (Exception e) { - logger.error("Exception deleting exchange: \"{}\"", exchangeName, e); + LOGGER.error("Exception deleting exchange: \"{}\"", exchangeName, e); } } - private static void removeSchemaQueues(String namespace) { + private void removeSchemaQueues(String namespace) { try { Channel channel = getChannel(); @@ -231,14 +225,14 @@ private static void removeSchemaQueues(String namespace) { if (queue != null && queue.getNamespace().equals(namespace)) { try { channel.queueDelete(queueName); - logger.info("Deleted queue: [{}]", queueName); + LOGGER.info("Deleted queue: [{}]", queueName); } catch (IOException e) { - logger.error("Exception deleting queue: [{}]", queueName, e); + LOGGER.error("Exception deleting queue: [{}]", queueName, e); } } }); } catch (Exception e) { - logger.error("Exception cleaning up queues for: \"{}\"", namespace, e); + LOGGER.error("Exception cleaning up queues for: \"{}\"", namespace, e); } } @@ -246,15 +240,8 @@ public static String toExchangeName(String namespace) { return namespace; } - public static Channel getChannel() { - Channel channel = getChannelContext().channel; - if (!channel.isOpen()) { - logger.warn("RabbitMQ connection is broken, trying to reconnect..."); - getChannelContext().close(); - channel = getChannelContext().channel; - logger.info("RabbitMQ connection has been restored"); - } - return channel; + public Channel getChannel() { + return channelContext.getChannel(); } public static Map generateQueueArguments(PinSettings pinSettings) throws NumberFormatException { @@ -272,49 +259,46 @@ public static Map generateQueueArguments(PinSettings pinSettings } } - public static @NotNull List getQueues() { - String vHostName = getManagementConfig().getVhostName(); + public @NotNull List getQueues() { + String vHostName = managementConfig.getVhostName(); try { - Client rmqClient = getClient(); return requireNonNullElse(rmqClient.getQueues(vHostName), emptyList()); } catch (Exception e) { String message = "Exception while fetching queues"; - logger.error(message, e); + LOGGER.error(message, e); throw new NonTerminalException(message, e); } } - public static @NotNull List getTh2Queues() { + public @NotNull List getTh2Queues() { return getQueues().stream() .filter(queueInfo -> queueInfo.getName() != null && queueInfo.getName().matches(QUEUE_NAME_REGEXP)) .collect(Collectors.toList()); } - public static List getQueueBindings(String queue) { - String vHostName = getManagementConfig().getVhostName(); + public List getQueueBindings(String queue) { + String vHostName = managementConfig.getVhostName(); try { - Client rmqClient = getClient(); return rmqClient.getQueueBindings(vHostName, queue); } catch (Exception e) { String message = "Exception while fetching bindings"; - logger.error(message, e); + LOGGER.error(message, e); throw new NonTerminalException(message, e); } } - public static @NotNull List getExchanges() { + public @NotNull List getExchanges() { try { - Client rmqClient = getClient(); return rmqClient.getExchanges(); } catch (Exception e) { String message = "Exception while fetching exchanges"; - logger.error(message, e); + LOGGER.error(message, e); throw new NonTerminalException(message, e); } } - public static @NotNull List getTh2Exchanges() { + public @NotNull List getTh2Exchanges() { Collection namespacePrefixes = ConfigLoader.getConfig().getNamespacePrefixes(); String topicExchange = getTopicExchangeName(); return getExchanges().stream() @@ -326,15 +310,14 @@ public static List getQueueBindings(String queue) { }).collect(Collectors.toList()); } - public static QueueInfo getQueue(String queueName) { + public QueueInfo getQueue(String queueName) { - String vHostName = getManagementConfig().getVhostName(); + String vHostName = managementConfig.getVhostName(); try { - Client rmqClient = getClient(); return rmqClient.getQueue(vHostName, queueName); } catch (Exception e) { String message = "Exception while fetching queue"; - logger.error(message, e); + LOGGER.error(message, e); throw new NonTerminalException(message, e); } } @@ -347,31 +330,11 @@ public static Client createClient(String host, int port, String username, String ); } - private static RabbitMQManagementConfig getManagementConfig() { - // we do not need to synchronize as we are assigning immutable object from singleton - if (managementConfig == null) { - managementConfig = ConfigLoader.getConfig().getRabbitMQManagement(); - } - return managementConfig; - } - - private static Client getClient() throws MalformedURLException, URISyntaxException { - if (rmqClient == null) { - RabbitMQManagementConfig rabbitMQMngConfig = getManagementConfig(); - rmqClient = createClient(rabbitMQMngConfig.getHost(), - rabbitMQMngConfig.getManagementPort(), - rabbitMQMngConfig.getUsername(), - rabbitMQMngConfig.getPassword()); - } - return rmqClient; - } - - private static ChannelContext getChannelContext() { - // we do not need to synchronize as we are assigning immutable object from singleton - if (channelContext == null) { - channelContext = new ChannelContext(); - } - return channelContext; + private static Client createClient(RabbitMQManagementConfig rabbitMQMngConfig) throws MalformedURLException, URISyntaxException { + return createClient(rabbitMQMngConfig.getHost(), + rabbitMQMngConfig.getManagementPort(), + rabbitMQMngConfig.getUsername(), + rabbitMQMngConfig.getPassword()); } private static RabbitMQConfig getRabbitMQConfig(String namespace) { @@ -384,49 +347,77 @@ private static RabbitMQConfig getRabbitMQConfig(String namespace) { return rabbitMQConfig; } - static class ChannelContext { + @Override + public void close() { + Utils.close(channelContext, "AMQP channel context"); + } + + static class ChannelContext implements AutoCloseable { + + private final Lock lock = new ReentrantLock(); + + private final RabbitMQContext rabbitMQContext; + + private final RabbitMQManagementConfig rabbitMQManagementConfig; private Connection connection; private Channel channel; - ChannelContext() { - ConnectionFactory connectionFactory = createConnectionFactory(); + ChannelContext(RabbitMQContext rabbitMQContext, RabbitMQManagementConfig rabbitMQManagementConfig) { + this.rabbitMQContext = rabbitMQContext; + this.rabbitMQManagementConfig = rabbitMQManagementConfig; + getChannel(); + } + + public Channel getChannel() { + lock.lock(); try { - this.connection = connectionFactory.newConnection(); - this.connection.addShutdownListener(new RmqClientShutdownEventListener()); - this.channel = connection.createChannel(); + if (connection == null || !connection.isOpen()) { + close(); + LOGGER.warn("RabbitMQ connection is broken, trying to reconnect..."); + connection = createConnection(); + } + if (channel == null || !channel.isOpen()) { + channel = connection.createChannel(); + } + return channel; } catch (Exception e) { close(); String message = "Exception while creating rabbitMq channel"; - logger.error(message, e); + LOGGER.error(message, e); throw new NonTerminalException(message, e); + } finally { + lock.unlock(); } } - synchronized void close() { + @Override + public void close() { + lock.lock(); try { if (channel != null && channel.isOpen()) { - channel.close(); + Utils.close(channel, "AMQP channel"); } - } catch (Exception e) { - logger.error("Exception closing RabbitMQ channel", e); - } - try { if (connection != null && connection.isOpen()) { - connection.close(); + Utils.close(connection, "AMQP connection"); } - } catch (Exception e) { - logger.error("Exception closing RabbitMQ connection for", e); + connection = null; + channel = null; + } finally { + lock.unlock(); } - channel = null; - connection = null; - channelContext = null; + } + + private Connection createConnection() throws IOException, TimeoutException { + ConnectionFactory connectionFactory = createConnectionFactory(); + Connection connection = connectionFactory.newConnection(); + connection.addShutdownListener(new RmqClientShutdownEventListener(rabbitMQContext)); + return connection; } @NotNull - private static ConnectionFactory createConnectionFactory() { - RabbitMQManagementConfig rabbitMQManagementConfig = getManagementConfig(); + private ConnectionFactory createConnectionFactory() { ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost(rabbitMQManagementConfig.getHost()); connectionFactory.setPort(rabbitMQManagementConfig.getApplicationPort()); @@ -438,16 +429,22 @@ private static ConnectionFactory createConnectionFactory() { } private static class RmqClientShutdownEventListener implements ShutdownListener { + private final RabbitMQContext rabbitMQContext; + + private RmqClientShutdownEventListener(RabbitMQContext rabbitMQContext) { + this.rabbitMQContext = rabbitMQContext; + } @Override public void shutdownCompleted(ShutdownSignalException cause) { - logger.error("Detected Rabbit mq connection lose", cause); + LOGGER.error("Detected Rabbit mq connection lose", cause); RecreateQueuesAndBindings recreateQueuesAndBindingsTask = new RecreateQueuesAndBindings( + rabbitMQContext, OperatorState.INSTANCE.getAllBoxResources(), RETRY_DELAY ); retryableTaskQueue.add(recreateQueuesAndBindingsTask, true); - logger.info("Task \"{}\" added to scheduler, with delay \"{}\" seconds", + LOGGER.info("Task \"{}\" added to scheduler, with delay \"{}\" seconds", recreateQueuesAndBindingsTask.getName(), RETRY_DELAY); } } diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/redeploy/ContinuousTaskWorker.java b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/redeploy/ContinuousTaskWorker.java index bda96929..37f4b137 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/redeploy/ContinuousTaskWorker.java +++ b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/redeploy/ContinuousTaskWorker.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,35 +17,43 @@ package com.exactpro.th2.infraoperator.spec.strategy.redeploy; import com.exactpro.th2.infraoperator.spec.strategy.redeploy.tasks.Task; +import com.google.common.util.concurrent.ThreadFactoryBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; -public class ContinuousTaskWorker { - private static final Logger logger = LoggerFactory.getLogger(ContinuousTaskWorker.class); +public class ContinuousTaskWorker implements AutoCloseable { + private static final Logger LOGGER = LoggerFactory.getLogger(ContinuousTaskWorker.class); private static final int THREAD_POOL_SIZE = 2; private final Map taskMap = new HashMap<>(); - private final ScheduledExecutorService taskScheduler = new ScheduledThreadPoolExecutor(THREAD_POOL_SIZE); + private final ScheduledExecutorService taskScheduler = new ScheduledThreadPoolExecutor(THREAD_POOL_SIZE, + new ThreadFactoryBuilder().setNameFormat("worker-%d").build()); public synchronized void add(Task task) { if (!taskMap.containsKey(task.getName())) { taskMap.put(task.getName(), task); taskScheduler.scheduleWithFixedDelay(task, task.getRetryDelay(), task.getRetryDelay(), TimeUnit.SECONDS); - logger.info("Added task '{}' to scheduler", task.getName()); + LOGGER.info("Added task '{}' to scheduler", task.getName()); } else { - logger.info("Task '{}' is already present in scheduler. Will not be added again", task.getName()); + LOGGER.info("Task '{}' is already present in scheduler. Will not be added again", task.getName()); } } - public void shutdown() { + @Override + public void close() throws Exception { taskScheduler.shutdown(); + if (!taskScheduler.awaitTermination(5, TimeUnit.SECONDS)) { + List tasks = taskScheduler.shutdownNow(); + LOGGER.error("The {} tasks in {} are not completed", tasks.size(), ContinuousTaskWorker.class); + } } } diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/RecreateQueuesAndBindings.java b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/RecreateQueuesAndBindings.java index 1206a9db..fc7b9dfa 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/RecreateQueuesAndBindings.java +++ b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/RecreateQueuesAndBindings.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,11 +24,22 @@ import java.util.Collection; public class RecreateQueuesAndBindings implements Task { + private final RabbitMQContext rabbitMQContext; + + private final DeclareQueueResolver declareQueueResolver; + + private final BindQueueLinkResolver bindQueueLinkResolver; + private final long retryDelay; private final Collection resources; - public RecreateQueuesAndBindings(Collection resources, long retryDelay) { + public RecreateQueuesAndBindings(RabbitMQContext rabbitMQContext, + Collection resources, + long retryDelay) { + this.rabbitMQContext = rabbitMQContext; + this.declareQueueResolver = new DeclareQueueResolver(rabbitMQContext); + this.bindQueueLinkResolver = new BindQueueLinkResolver(rabbitMQContext); this.resources = resources; this.retryDelay = retryDelay; } @@ -45,11 +56,11 @@ public long getRetryDelay() { @Override public void run() { - RabbitMQContext.getChannel(); + rabbitMQContext.getChannel(); resources.forEach(resource -> { - DeclareQueueResolver.resolveAdd(resource); - BindQueueLinkResolver.resolveDeclaredLinks(resource); - BindQueueLinkResolver.resolveHiddenLinks(resource); + declareQueueResolver.resolveAdd(resource); + bindQueueLinkResolver.resolveDeclaredLinks(resource); + bindQueueLinkResolver.resolveHiddenLinks(resource); }); } } diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/RetryRabbitSetup.java b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/RetryRabbitSetup.java index 3f5e558e..610cfd77 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/RetryRabbitSetup.java +++ b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/RetryRabbitSetup.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,11 +20,14 @@ public class RetryRabbitSetup implements Task { + private final RabbitMQContext rabbitMQContext; + private final long retryDelay; private final String namespace; - public RetryRabbitSetup(String namespace, long retryDelay) { + public RetryRabbitSetup(RabbitMQContext rabbitMQContext, String namespace, long retryDelay) { + this.rabbitMQContext = rabbitMQContext; this.retryDelay = retryDelay; this.namespace = namespace; } @@ -41,6 +44,6 @@ public long getRetryDelay() { @Override public void run() { - RabbitMQContext.setUpRabbitMqForNamespace(namespace); + rabbitMQContext.setUpRabbitMqForNamespace(namespace); } } diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/RetryTopicExchangeTask.java b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/RetryTopicExchangeTask.java index f67cd9ab..b47d2583 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/RetryTopicExchangeTask.java +++ b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/redeploy/tasks/RetryTopicExchangeTask.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,11 +20,14 @@ public class RetryTopicExchangeTask implements Task { + private final RabbitMQContext rabbitMQContext; + private final long retryDelay; private final String exchangeName; - public RetryTopicExchangeTask(String exchangeName, long retryDelay) { + public RetryTopicExchangeTask(RabbitMQContext rabbitMQContext, String exchangeName, long retryDelay) { + this.rabbitMQContext = rabbitMQContext; this.retryDelay = retryDelay; this.exchangeName = exchangeName; } @@ -41,6 +44,6 @@ public long getRetryDelay() { @Override public void run() { - RabbitMQContext.declareTopicExchange(); + rabbitMQContext.declareTopicExchange(); } } diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt index 14868e5c..ecc02f40 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt @@ -33,10 +33,9 @@ import com.exactpro.th2.infraoperator.util.CustomResourceUtils import com.exactpro.th2.infraoperator.util.CustomResourceUtils.annotationFor import io.github.oshai.kotlinlogging.KotlinLogging -object BindQueueLinkResolver { - private val K_LOGGER = KotlinLogging.logger { } - - @JvmStatic +class BindQueueLinkResolver( + private val rabbitMQContext: RabbitMQContext, +) { fun resolveDeclaredLinks(resource: Th2CustomResource) { val namespace = resource.metadata.namespace val resourceName = resource.metadata.name @@ -55,7 +54,6 @@ object BindQueueLinkResolver { } } - @JvmStatic fun resolveHiddenLinks(resource: Th2CustomResource) { val namespace = resource.metadata.namespace val resourceName = resource.metadata.name @@ -110,9 +108,9 @@ object BindQueueLinkResolver { private fun bindQueues(queue: LinkDescription, commitHash: String) { try { - val channel = RabbitMQContext.getChannel() + val channel = rabbitMQContext.channel val queueName = queue.queueName.toString() - val currentQueue = RabbitMQContext.getQueue(queueName) + val currentQueue = rabbitMQContext.getQueue(queueName) if (currentQueue == null) { K_LOGGER.info {"Queue '$queueName' does not yet exist. skipping binding" } return @@ -135,7 +133,7 @@ object BindQueueLinkResolver { resName: String? = null ) { val queueName = queue.toString() - val bindingOnRabbit = RabbitMQContext.getQueueBindings(queueName) + val bindingOnRabbit = rabbitMQContext.getQueueBindings(queueName) ?.map { it.routingKey } ?.filter { it.matches(ROUTING_KEY_REGEXP.toRegex()) && @@ -145,10 +143,10 @@ object BindQueueLinkResolver { RoutingKeyName(queue.namespace, it.box, it.pin).toString() } try { - val channel = RabbitMQContext.getChannel() + val channel = rabbitMQContext.channel bindingOnRabbit?.forEach { if (!currentBindings.contains(it)) { - val currentQueue = RabbitMQContext.getQueue(queueName) + val currentQueue = rabbitMQContext.getQueue(queueName) if (currentQueue == null) { K_LOGGER.info { "Queue '$queueName' already removed. skipping unbinding" } return @@ -163,4 +161,8 @@ object BindQueueLinkResolver { throw NonTerminalException(message, e) } } + + companion object { + private val K_LOGGER = KotlinLogging.logger { } + } } diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt index b2ab1b07..64f3c6f5 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt @@ -39,11 +39,14 @@ import java.io.IOException private val K_LOGGER = KotlinLogging.logger { } -fun deleteRabbitMQRubbish() { +fun deleteRabbitMQRubbish( + kubernetesClient: KubernetesClient, + rabbitMQContext: RabbitMQContext, +) { try { val resourceHolder = collectRabbitMQResources( - RabbitMQContext.getTh2Queues(), - RabbitMQContext.getTh2Exchanges(), + rabbitMQContext.th2Queues, + rabbitMQContext.th2Exchanges, ) if (resourceHolder.isHolderEmpty()) { @@ -51,17 +54,15 @@ fun deleteRabbitMQRubbish() { } val namespacePrefixes = ConfigLoader.loadConfiguration().namespacePrefixes - val topicExchange = RabbitMQContext.getTopicExchangeName() - - createKubernetesClient().use { kuClient -> - resourceHolder.filterRubbishResources( - kuClient, - namespacePrefixes, - topicExchange, - ) - } + val topicExchange = rabbitMQContext.topicExchangeName + + resourceHolder.filterRubbishResources( + kubernetesClient, + namespacePrefixes, + topicExchange, + ) K_LOGGER.info { "RabbitMQ rubbish: $resourceHolder" } - deleteRabbitMQRubbish(resourceHolder, RabbitMQContext::getChannel) + deleteRabbitMQRubbish(resourceHolder, rabbitMQContext::getChannel) } catch (e: Exception) { K_LOGGER.error(e) { "Delete RabbitMQ rubbish failure" } } diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/util/Utils.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/util/Utils.kt new file mode 100644 index 00000000..ad16c5a6 --- /dev/null +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/util/Utils.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2024-2024 Exactpro (Exactpro Systems Limited) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:JvmName("Utils") + +package com.exactpro.th2.infraoperator.util + +import io.github.oshai.kotlinlogging.KotlinLogging + +private val K_LOGGER = KotlinLogging.logger {} + +fun close(closeable: AutoCloseable, name: String) { + try { + closeable.close() + K_LOGGER.info { "$name closed" } + } catch (e: Exception) { + K_LOGGER.error(e) { "$name close failure" } + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/DeleteRubbishOnStartTest.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/DeleteRubbishOnStartTest.kt index b782fae2..5c840987 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/DeleteRubbishOnStartTest.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/DeleteRubbishOnStartTest.kt @@ -18,6 +18,8 @@ package com.exactpro.th2.infraoperator.integration import com.exactpro.th2.infraoperator.Th2CrdController import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQNamespacePermissions +import com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op.EVENT_STORAGE_BOX_ALIAS +import com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op.EVENT_STORAGE_PIN_ALIAS import com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op.MESSAGE_STORAGE_BOX_ALIAS import com.exactpro.th2.infraoperator.operator.StoreHelmTh2Op.MESSAGE_STORAGE_PIN_ALIAS import com.exactpro.th2.infraoperator.spec.box.Th2Box @@ -26,23 +28,20 @@ import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQCont import com.exactpro.th2.infraoperator.spec.strategy.linkresolver.mq.RabbitMQContext.toExchangeName import com.exactpro.th2.infraoperator.util.createKubernetesClient import com.rabbitmq.client.AMQP -import com.rabbitmq.client.Channel import com.rabbitmq.client.Connection import com.rabbitmq.http.client.Client -import com.rabbitmq.http.client.domain.QueueInfo import io.fabric8.kubernetes.client.KubernetesClient import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.BeforeAll -import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Tag import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.Timeout +import org.junit.jupiter.api.assertAll import org.junit.jupiter.api.io.TempDir import org.testcontainers.containers.RabbitMQContainer import org.testcontainers.k3s.K3sContainer import java.nio.file.Path import kotlin.test.Test -import kotlin.test.assertEquals @Tag("integration-test") @TestInstance(TestInstance.Lifecycle.PER_CLASS) @@ -99,9 +98,50 @@ class DeleteRubbishOnStartTest { } @Test - @Disabled("implement Th2CrdController.close") + @Timeout(30_000) fun deleteAllTest() { - TODO() + val namespaces = listOf( + "${TH2_PREFIX}test-b", + "${TH2_PREFIX}test-c" + ) + val component = "test-component" + + rabbitMQConnection.createChannel().use { channel -> + val queues = mutableListOf() + val exchanges = mutableListOf() + + namespaces.forEach { namespace -> + channel.createQueue(namespace, "rubbish-component", PIN_NAME) + .assertQueue().queue.also(queues::add) + channel.createQueue(namespace, component, "rubbish-pin") + .assertQueue().queue.also(queues::add) + channel.createQueue(namespace, component, PIN_NAME) + .assertQueue().queue.also(queues::add) + channel.createQueue(namespace, MESSAGE_STORAGE_BOX_ALIAS, MESSAGE_STORAGE_PIN_ALIAS) + .assertQueue().queue.also(queues::add) + channel.createQueue(namespace, EVENT_STORAGE_BOX_ALIAS, EVENT_STORAGE_PIN_ALIAS) + .assertQueue().queue.also(queues::add) + + toExchangeName(namespace).apply { + channel.createExchange(this, DIRECT) + rabbitMQClient.assertExchange(this, DIRECT, RABBIT_MQ_V_HOST) + exchanges.add(this) + } + } + + Th2CrdController().use { + assertAll( + queues.map { queue -> + { rabbitMQClient.assertNoQueue(queue, RABBIT_MQ_V_HOST) } + } + exchanges.map { exchange -> + { rabbitMQClient.assertNoExchange(exchange, RABBIT_MQ_V_HOST) } + } + listOf( + { rabbitMQClient.assertNoQueues("link\\[.*\\]", RABBIT_MQ_V_HOST) }, + { rabbitMQClient.assertNoExchanges("${TH2_PREFIX}.*", RABBIT_MQ_V_HOST) } + ) + ) + } + } } @Test @@ -160,19 +200,19 @@ class DeleteRubbishOnStartTest { """.trimIndent() kubeClient.createTh2CustomResource(exchangeB, component, gitHash, spec, ::Th2Box) - Th2CrdController().apply(Th2CrdController::start).use { + Th2CrdController().use { kubeClient.awaitPhase(exchangeB, component, RolloutPhase.SUCCEEDED, Th2Box::class.java) rabbitMQClient.assertNoQueue(queue01.queue, RABBIT_MQ_V_HOST) rabbitMQClient.assertNoQueue(queue02.queue, RABBIT_MQ_V_HOST) rabbitMQClient.assertNoQueue(queue03.queue, RABBIT_MQ_V_HOST) - rabbitMQClient.assertQueue(queue11.queue, RABBIT_MQ_QUEUE_CLASSIC_TYPE, RABBIT_MQ_V_HOST) - .assertQueueSize(channel, 1) + rabbitMQClient.assertQueue(queue11.queue, RABBIT_MQ_QUEUE_CLASSIC_TYPE, RABBIT_MQ_V_HOST) + rabbitMQClient.awaitQueueSize(queue11.queue, RABBIT_MQ_V_HOST, 1) rabbitMQClient.assertQueue(queue12.queue, RABBIT_MQ_QUEUE_CLASSIC_TYPE, RABBIT_MQ_V_HOST) - .assertQueueSize(channel, 1) + rabbitMQClient.awaitQueueSize(queue12.queue, RABBIT_MQ_V_HOST, 1) - rabbitMQClient.assertNoExchange(exchangeC) + rabbitMQClient.assertNoExchange(exchangeC, RABBIT_MQ_V_HOST) } } } @@ -198,11 +238,6 @@ class DeleteRubbishOnStartTest { return this } - private fun QueueInfo.assertQueueSize(channel: Channel, size: Int) { - val declareOk = channel.queueDeclare(name, isDurable, isExclusive, isAutoDelete, arguments) - assertEquals(size, declareOk.messageCount) - } - companion object { private const val TH2_PREFIX = "th2-" private const val TH2_BOOK = "test_book" diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt index 1f20e6e0..39630f37 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt @@ -124,7 +124,7 @@ class IntegrationTest { kubeClient = createKubernetesClient().apply { configureK3s() } rabbitMQClient = createRabbitMQClient(rabbitMQContainer) - controller = Th2CrdController().apply(Th2CrdController::start) + controller = Th2CrdController() rabbitMQClient.assertExchange(RABBIT_MQ_TOPIC_EXCHANGE, TOPIC, RABBIT_MQ_V_HOST) } @@ -141,6 +141,9 @@ class IntegrationTest { if (this::rabbitMQContainer.isInitialized) { rabbitMQContainer.stop() } + if (this::controller.isInitialized) { + controller.close() + } } @BeforeEach @@ -179,7 +182,7 @@ class IntegrationTest { // FIXME: Secret not found "th2-test:Secret/rabbitMQ" rabbitMQClient.assertNoQueues("link\\[.*\\]", RABBIT_MQ_V_HOST) - rabbitMQClient.assertNoExchange(toExchangeName(TH2_NAMESPACE)) + rabbitMQClient.assertNoExchange(toExchangeName(TH2_NAMESPACE), RABBIT_MQ_V_HOST) rabbitMQClient.assertNoUser(TH2_NAMESPACE) kubeClient.awaitNoResources(TH2_NAMESPACE) diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt index 7074340b..4b805ef9 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt @@ -19,7 +19,9 @@ package com.exactpro.th2.infraoperator.integration import com.exactpro.th2.infraoperator.configuration.fields.RabbitMQNamespacePermissions import com.rabbitmq.http.client.Client import com.rabbitmq.http.client.domain.DestinationType +import com.rabbitmq.http.client.domain.ExchangeInfo import com.rabbitmq.http.client.domain.QueueInfo +import io.github.oshai.kotlinlogging.KotlinLogging import org.junit.jupiter.api.assertAll import org.testcontainers.shaded.org.awaitility.Awaitility.await import java.util.concurrent.TimeUnit @@ -30,6 +32,8 @@ import kotlin.test.assertTrue const val RABBIT_MQ_QUEUE_CLASSIC_TYPE = "classic" +private val K_LOGGER = KotlinLogging.logger {} + fun Client.assertUser( user: String, vHost: String, @@ -87,14 +91,34 @@ fun Client.assertExchange( assertFalse(exchangeInfo.isAutoDelete, "Exchange '$exchange' is auto delete") } +fun Client.assertNoExchanges( + exchangePattern: String, + vHost: String, + timeout: Long = 5_000, + unit: TimeUnit = TimeUnit.MILLISECONDS, +) { + val filter: (ExchangeInfo) -> Boolean = { + exchangeInfo -> exchangeInfo.name.matches(Regex(exchangePattern)) && exchangeInfo.vhost == vHost + } + await("assertNoExchanges('$exchangePattern')") + .timeout(timeout, unit) + .conditionEvaluationListener { _ -> + K_LOGGER.debug { + "Remaining exchanges by '$exchangePattern': ${exchanges.filter(filter).map(ExchangeInfo::getName)}" + } + } + .until { exchanges.none(filter) } +} + fun Client.assertNoExchange( exchange: String, + vHost: String, timeout: Long = 5_000, unit: TimeUnit = TimeUnit.MILLISECONDS, ) { await("assertNoExchange('$exchange')") .timeout(timeout, unit) - .until { exchanges.firstOrNull { it.name == exchange } == null } + .until { exchanges.firstOrNull { it.name == exchange && it.vhost == vHost } == null } } fun Client.assertQueue( @@ -197,9 +221,17 @@ fun Client.assertNoQueues( timeout: Long = 5_000, unit: TimeUnit = TimeUnit.MILLISECONDS, ) { + val filter: (QueueInfo) -> Boolean = { + queueInfo -> queueInfo.name.matches(Regex(queuePattern)) && queueInfo.vhost == vHost + } await("assertNoQueues('$queuePattern')") .timeout(timeout, unit) - .until { queues.map { it.name.matches(Regex(queuePattern)) && it.vhost == vHost }.isEmpty() } + .conditionEvaluationListener { _ -> + K_LOGGER.debug { + "Remaining queues by '$queuePattern': ${queues.filter(filter).map(QueueInfo::getName)}" + } + } + .until { queues.none(filter) } } fun Client.assertNoQueue( From a77dd93c8e595cef5be6af782f78f3fe19652457 Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Fri, 20 Sep 2024 15:49:09 +0400 Subject: [PATCH 42/42] [TH2-5226] Enabled style check --- build.gradle | 24 ++- config/detekt/detekt.yml | 2 +- .../th2/infraoperator/Th2CrdController.java | 7 +- .../metrics/PrometheusServer.java | 1 + .../operator/AbstractTh2Operator.java | 3 +- .../operator/HelmReleaseTh2Op.java | 1 + .../manager/impl/ConfigMapEventHandler.java | 4 +- .../manager/impl/DefaultWatchManager.java | 4 +- .../linkresolver/mq/RabbitMQContext.java | 15 +- .../linkresolver/mq/BindQueueLinkResolver.kt | 8 +- .../exactpro/th2/infraoperator/util/Utils.kt | 2 +- .../integration/DeleteRubbishOnStartTest.kt | 29 ++- .../integration/IntegrationTest.kt | 189 +++++++++--------- .../integration/TestIntegrationUtils.kt | 8 +- .../integration/TestKubernetesUtils.kt | 20 +- .../integration/TestRabbitMQUtils.kt | 11 +- 16 files changed, 194 insertions(+), 134 deletions(-) diff --git a/build.gradle b/build.gradle index 1880b6e2..9e1f2008 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ plugins { id 'application' -// id 'checkstyle' -// alias(libs.plugins.detekt) + id 'checkstyle' + alias(libs.plugins.detekt) alias(libs.plugins.kotlin) alias(libs.plugins.th2.component) alias(libs.plugins.download) @@ -18,15 +18,15 @@ repositories { mavenCentral() } -//checkstyle { -// toolVersion = "10.12.4" -//} +checkstyle { + toolVersion = "10.12.4" +} -//detekt { -// buildUponDefaultConfig = true -// autoCorrect = true -// config.setFrom("$rootDir/config/detekt/detekt.yml") -//} +detekt { + buildUponDefaultConfig = true + autoCorrect = true + config.setFrom("$rootDir/config/detekt/detekt.yml") +} dependencies { implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml" @@ -71,7 +71,7 @@ dependencies { testImplementation libs.awaitility testImplementation libs.strikt.core -// detektPlugins libs.detekt.formatting + detektPlugins libs.detekt.formatting } wrapper { @@ -92,6 +92,8 @@ tasks.register("downloadCRDs", Download) { dest layout.buildDirectory.dir('resources/test/crds').get() } +checkstyleTest.dependsOn("downloadCRDs") + test { dependsOn("downloadCRDs") useJUnitPlatform { diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml index c6447f59..70661f98 100644 --- a/config/detekt/detekt.yml +++ b/config/detekt/detekt.yml @@ -119,7 +119,7 @@ complexity: active: true functionThreshold: 6 constructorThreshold: 15 - ignoreDefaultParameters: false + ignoreDefaultParameters: true ignoreDataClasses: false ignoreAnnotatedParameter: [] MethodOverloading: diff --git a/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java b/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java index 520b6b58..82a1d92e 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java +++ b/src/main/java/com/exactpro/th2/infraoperator/Th2CrdController.java @@ -53,9 +53,13 @@ public class Th2CrdController implements AutoCloseable { private static final Logger LOGGER = LoggerFactory.getLogger(Th2CrdController.class); private final PrometheusServer prometheusServer; + private final KubernetesClient kubClient; + private final DefaultWatchManager watchManager; + private final RabbitMQContext rabbitMQContext; + private final ContinuousTaskWorker continuousTaskWorker; public Th2CrdController() throws IOException, URISyntaxException { @@ -68,7 +72,8 @@ public Th2CrdController() throws IOException, URISyntaxException { OperatorMetrics.resetCacheErrors(); RabbitMQUtils.deleteRabbitMQRubbish(kubClient, rabbitMQContext); - rabbitMQContext.declareTopicExchange(); // FIXME: topic exchange should be removed when all namespaces are removed / disabled + // TODO: topic exchange should be removed when all namespaces are removed / disabled + rabbitMQContext.declareTopicExchange(); watchManager.addTarget(MstoreHelmTh2Op::new); watchManager.addTarget(EstoreHelmTh2Op::new); diff --git a/src/main/java/com/exactpro/th2/infraoperator/metrics/PrometheusServer.java b/src/main/java/com/exactpro/th2/infraoperator/metrics/PrometheusServer.java index 5e951335..8cecc6a7 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/metrics/PrometheusServer.java +++ b/src/main/java/com/exactpro/th2/infraoperator/metrics/PrometheusServer.java @@ -27,6 +27,7 @@ public class PrometheusServer implements AutoCloseable { private static final Logger LOGGER = LoggerFactory.getLogger(PrometheusServer.class); + @Nullable private final HTTPServer server; diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/AbstractTh2Operator.java b/src/main/java/com/exactpro/th2/infraoperator/operator/AbstractTh2Operator.java index bd0aa89b..d62d04f5 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/AbstractTh2Operator.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/AbstractTh2Operator.java @@ -167,7 +167,8 @@ protected void processEvent(Action action, CR resource) throws IOException { .withName(helmReleaseName); HelmRelease helmRelease = helmReleaseResource.get(); if (helmRelease == null) { - LOGGER.info("Resource \"{}\" hasn't been deleted because it already doesn't exist", annotationFor(namespace, helmReleaseName, HelmRelease.class.getSimpleName())); + LOGGER.info("Resource \"{}\" hasn't been deleted because it already doesn't exist", + annotationFor(namespace, helmReleaseName, HelmRelease.class.getSimpleName())); } else { String helmReleaseLabel = annotationFor(helmRelease); helmReleaseResource.delete(); diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/HelmReleaseTh2Op.java b/src/main/java/com/exactpro/th2/infraoperator/operator/HelmReleaseTh2Op.java index ecfacc97..bd0e44bb 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/HelmReleaseTh2Op.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/HelmReleaseTh2Op.java @@ -128,6 +128,7 @@ public abstract class HelmReleaseTh2Op extends Abs helmReleaseClient; protected final DeclareQueueResolver declareQueueResolver; + protected final BindQueueLinkResolver bindQueueLinkResolver; public HelmReleaseTh2Op(KubernetesClient kubClient, RabbitMQContext rabbitMQContext) { diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/ConfigMapEventHandler.java b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/ConfigMapEventHandler.java index 005a0a26..cdecc816 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/ConfigMapEventHandler.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/ConfigMapEventHandler.java @@ -134,7 +134,9 @@ public static ConfigMapEventHandler newInstance(SharedInformerFactory sharedInfo return res; } - private ConfigMapEventHandler(KubernetesClient kubClient, RabbitMQContext rabbitMQContext, DefaultWatchManager watchManager) { + private ConfigMapEventHandler(KubernetesClient kubClient, + RabbitMQContext rabbitMQContext, + DefaultWatchManager watchManager) { this.kubClient = kubClient; this.rabbitMQContext = rabbitMQContext; this.watchManager = watchManager; diff --git a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/DefaultWatchManager.java b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/DefaultWatchManager.java index 72d61942..ba49de7a 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/DefaultWatchManager.java +++ b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/DefaultWatchManager.java @@ -138,7 +138,9 @@ private EventHandlerContext registerInformers(SharedInformerFactory sharedInform EventHandlerContext context = new EventHandlerContext(); - context.addHandler(NamespaceEventHandler.newInstance(sharedInformerFactory, rabbitMQContext, eventDispatcher.getEventQueue())); + context.addHandler(NamespaceEventHandler.newInstance(sharedInformerFactory, + rabbitMQContext, + eventDispatcher.getEventQueue())); context.addHandler(Th2DictionaryEventHandler.newInstance(sharedInformerFactory, kubClient, eventDispatcher.getEventQueue())); context.addHandler(ConfigMapEventHandler.newInstance(sharedInformerFactory, kubClient, rabbitMQContext, diff --git a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java index d4443b2e..7710d02a 100644 --- a/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java +++ b/src/main/java/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/RabbitMQContext.java @@ -75,9 +75,13 @@ public final class RabbitMQContext implements AutoCloseable { private static final Logger LOGGER = LoggerFactory.getLogger(RabbitMQContext.class); private static final int RETRY_DELAY = 120; + public static final String TOPIC = BuiltinExchangeType.TOPIC.getType(); + public static final String DIRECT = BuiltinExchangeType.DIRECT.getType(); + private final RabbitMQManagementConfig managementConfig; + private final ChannelContext channelContext; private final Client rmqClient; @@ -322,7 +326,12 @@ public QueueInfo getQueue(String queueName) { } } - public static Client createClient(String host, int port, String username, String password) throws MalformedURLException, URISyntaxException { + public static Client createClient( + String host, + int port, + String username, + String password + ) throws MalformedURLException, URISyntaxException { return new Client(new ClientParameters() .url(format("http://%s:%s/api", host, port)) .username(username) @@ -330,7 +339,9 @@ public static Client createClient(String host, int port, String username, String ); } - private static Client createClient(RabbitMQManagementConfig rabbitMQMngConfig) throws MalformedURLException, URISyntaxException { + private static Client createClient( + RabbitMQManagementConfig rabbitMQMngConfig + ) throws MalformedURLException, URISyntaxException { return createClient(rabbitMQMngConfig.getHost(), rabbitMQMngConfig.getManagementPort(), rabbitMQMngConfig.getUsername(), diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt index ecc02f40..0c2c3177 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/mq/BindQueueLinkResolver.kt @@ -93,13 +93,15 @@ class BindQueueLinkResolver( } if (attributes.contains(PinAttribute.parsed.name)) { K_LOGGER.warn { - "Detected a pin: $resourceLabel:$pinName with incorrect store configuration. attribute 'parsed' not allowed" + "Detected a pin: $resourceLabel:$pinName with incorrect store configuration. " + + "attribute 'parsed' not allowed" } return false } if (!attributes.contains(PinAttribute.raw.name)) { K_LOGGER.warn { - "Detected a pin: $resourceLabel:$pinName with incorrect store configuration. attribute 'raw' is missing" + "Detected a pin: $resourceLabel:$pinName with incorrect store configuration. " + + "attribute 'raw' is missing" } return false } @@ -112,7 +114,7 @@ class BindQueueLinkResolver( val queueName = queue.queueName.toString() val currentQueue = rabbitMQContext.getQueue(queueName) if (currentQueue == null) { - K_LOGGER.info {"Queue '$queueName' does not yet exist. skipping binding" } + K_LOGGER.info { "Queue '$queueName' does not yet exist. skipping binding" } return } channel.queueBind(queue.queueName.toString(), queue.exchange, queue.routingKey.toString()) diff --git a/src/main/kotlin/com/exactpro/th2/infraoperator/util/Utils.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/util/Utils.kt index ad16c5a6..de37964c 100644 --- a/src/main/kotlin/com/exactpro/th2/infraoperator/util/Utils.kt +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/util/Utils.kt @@ -29,4 +29,4 @@ fun close(closeable: AutoCloseable, name: String) { } catch (e: Exception) { K_LOGGER.error(e) { "$name close failure" } } -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/DeleteRubbishOnStartTest.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/DeleteRubbishOnStartTest.kt index 5c840987..305e5de7 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/DeleteRubbishOnStartTest.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/DeleteRubbishOnStartTest.kt @@ -132,9 +132,13 @@ class DeleteRubbishOnStartTest { Th2CrdController().use { assertAll( queues.map { queue -> - { rabbitMQClient.assertNoQueue(queue, RABBIT_MQ_V_HOST) } + { + rabbitMQClient.assertNoQueue(queue, RABBIT_MQ_V_HOST) + } } + exchanges.map { exchange -> - { rabbitMQClient.assertNoExchange(exchange, RABBIT_MQ_V_HOST) } + { + rabbitMQClient.assertNoExchange(exchange, RABBIT_MQ_V_HOST) + } } + listOf( { rabbitMQClient.assertNoQueues("link\\[.*\\]", RABBIT_MQ_V_HOST) }, { rabbitMQClient.assertNoExchanges("${TH2_PREFIX}.*", RABBIT_MQ_V_HOST) } @@ -162,16 +166,27 @@ class DeleteRubbishOnStartTest { channel.confirmSelect() /** queue of not existed component */ val queue01 = channel.createQueue(namespaceB, "rubbish-component", PIN_NAME).assertQueue() + /** queue of not exited pin */ val queue02 = channel.createQueue(namespaceB, component, "rubbish-pin").assertQueue() + /** mstore queue of not existed namespace */ - val queue03 = channel.createQueue(namespaceC, MESSAGE_STORAGE_BOX_ALIAS, MESSAGE_STORAGE_PIN_ALIAS).assertQueue() + val queue03 = channel.createQueue( + namespaceC, + MESSAGE_STORAGE_BOX_ALIAS, + MESSAGE_STORAGE_PIN_ALIAS + ).assertQueue() /** mstore queue of existed namespace */ - val queue11 = channel.createQueue(namespaceB, MESSAGE_STORAGE_BOX_ALIAS, MESSAGE_STORAGE_PIN_ALIAS).assertQueue() + val queue11 = channel.createQueue( + namespaceB, + MESSAGE_STORAGE_BOX_ALIAS, + MESSAGE_STORAGE_PIN_ALIAS + ).assertQueue() .also { channel.basicPublish("", it.queue, null, "test-content".toByteArray()) } + /** queue of exited component and pin */ val queue12 = channel.createQueue(namespaceB, component, PIN_NAME).assertQueue() .also { @@ -209,7 +224,7 @@ class DeleteRubbishOnStartTest { rabbitMQClient.assertQueue(queue11.queue, RABBIT_MQ_QUEUE_CLASSIC_TYPE, RABBIT_MQ_V_HOST) rabbitMQClient.awaitQueueSize(queue11.queue, RABBIT_MQ_V_HOST, 1) - rabbitMQClient.assertQueue(queue12.queue, RABBIT_MQ_QUEUE_CLASSIC_TYPE, RABBIT_MQ_V_HOST) + rabbitMQClient.assertQueue(queue12.queue, RABBIT_MQ_QUEUE_CLASSIC_TYPE, RABBIT_MQ_V_HOST) rabbitMQClient.awaitQueueSize(queue12.queue, RABBIT_MQ_V_HOST, 1) rabbitMQClient.assertNoExchange(exchangeC, RABBIT_MQ_V_HOST) @@ -223,7 +238,7 @@ class DeleteRubbishOnStartTest { kubeClient.createRabbitMQAppConfigCfgMap( namespace, gitHash, - createRabbitMQConfig(rabbitMQContainer, RABBIT_MQ_V_HOST, toExchangeName(namespace), namespace) + createRabbitMQConfig(rabbitMQContainer, RABBIT_MQ_V_HOST, toExchangeName(namespace), namespace) ) kubeClient.createBookConfigCfgMap(namespace, gitHash, TH2_BOOK) @@ -248,4 +263,4 @@ class DeleteRubbishOnStartTest { private const val PIN_NAME = "test-pin" } -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt index 39630f37..92ce56e1 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/IntegrationTest.kt @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.exactpro.th2.infraoperator.integration import com.exactpro.th2.infraoperator.Th2CrdController @@ -79,6 +80,7 @@ import org.junit.jupiter.params.provider.MethodSource import org.junit.jupiter.params.provider.ValueSource import org.testcontainers.containers.RabbitMQContainer import org.testcontainers.k3s.K3sContainer +import strikt.api.Assertion import strikt.api.expectThat import strikt.assertions.getValue import strikt.assertions.hasSize @@ -197,7 +199,7 @@ class IntegrationTest { fun `add component`() } - abstract inner class ComponentTest { + abstract inner class ComponentTest { abstract val resourceClass: Class abstract val specType: String abstract val runAsJob: Boolean @@ -229,7 +231,10 @@ class IntegrationTest { val resource = kubeClient.createTh2CustomResource(TH2_NAMESPACE, name, gitHash, spec, this::createResources) kubeClient.awaitPhase(TH2_NAMESPACE, name, SUCCEEDED, resourceClass) - kubeClient.awaitResource(TH2_NAMESPACE, extractHashedName(resource)).assertMinCfg(name, runAsJob) + kubeClient.awaitResource( + TH2_NAMESPACE, + extractHashedName(resource) + ).assertMinCfg(name, runAsJob) rabbitMQClient.assertBindings( createEstoreQueue(TH2_NAMESPACE), RABBIT_MQ_V_HOST, @@ -251,7 +256,10 @@ class IntegrationTest { val resource = kubeClient.createTh2CustomResource(TH2_NAMESPACE, name, gitHash, spec, this::createResources) kubeClient.awaitPhase(TH2_NAMESPACE, name, SUCCEEDED, resourceClass) - kubeClient.awaitResource(TH2_NAMESPACE, extractHashedName(resource)).assertMinCfg(name, runAsJob) + kubeClient.awaitResource( + TH2_NAMESPACE, + extractHashedName(resource) + ).assertMinCfg(name, runAsJob) rabbitMQClient.assertBindings( createEstoreQueue(TH2_NAMESPACE), RABBIT_MQ_V_HOST, @@ -310,7 +318,10 @@ class IntegrationTest { val resource = kubeClient.modifyTh2CustomResource(TH2_NAMESPACE, name, gitHash, spec, resourceClass) kubeClient.awaitPhase(TH2_NAMESPACE, name, SUCCEEDED, resourceClass) - kubeClient.awaitResource(TH2_NAMESPACE, extractHashedName(resource)).assertMinCfg(name, runAsJob) + kubeClient.awaitResource( + TH2_NAMESPACE, + extractHashedName(resource) + ).assertMinCfg(name, runAsJob) rabbitMQClient.assertBindings( createEstoreQueue(TH2_NAMESPACE), RABBIT_MQ_V_HOST, @@ -368,28 +379,12 @@ class IntegrationTest { kubeClient.awaitResource(TH2_NAMESPACE, pubName).assertMinCfg( pubName, runAsJob, - queues = mapOf( - PUBLISH_PIN to mapOf( - "attributes" to listOf("publish"), - "exchange" to RABBIT_MQ_TH2_EXCHANGE, - "filters" to emptyList(), - "name" to routingKey, - "queue" to "", - ) - ) + queues = createQueueCfg(PUBLISH_PIN, listOf("publish"), routingKey = routingKey, queueName = "") ) kubeClient.awaitResource(TH2_NAMESPACE, subName).assertMinCfg( subName, subRunAsJob, - queues = mapOf( - SUBSCRIBE_PIN to mapOf( - "attributes" to listOf("subscribe"), - "exchange" to RABBIT_MQ_TH2_EXCHANGE, - "filters" to emptyList(), - "name" to "", - "queue" to queueName, - ) - ) + queues = createQueueCfg(SUBSCRIBE_PIN, listOf("subscribe"), routingKey = "", queueName = queueName) ) rabbitMQClient.assertBindings( @@ -454,23 +449,7 @@ class IntegrationTest { kubeClient.awaitResource(TH2_NAMESPACE, clientName).assertMinCfg( clientName, clientRunAsJob, - services = mapOf( - CLIENT_PIN to mapOf( - "endpoints" to mapOf( - "test-server-endpoint" to mapOf( - "attributes" to emptyList(), - "host" to serverName, - "port" to 8080 - ) - ), - "filters" to emptyList(), - "service-class" to GRPC_SERVICE, - "strategy" to mapOf( - "endpoints" to listOf("test-server-endpoint"), - "name" to "robin", - ) - ) - ) + services = createGrpcCfg(serverName) ) rabbitMQClient.assertBindings( @@ -486,7 +465,7 @@ class IntegrationTest { } @Nested - inner class Mstore: StoreComponentTest { + inner class Mstore : StoreComponentTest { @Test @Timeout(30_000) @@ -505,7 +484,7 @@ class IntegrationTest { } @Nested - inner class Estore: StoreComponentTest { + inner class Estore : StoreComponentTest { @Test @Timeout(30_000) override fun `add component`() { @@ -528,7 +507,7 @@ class IntegrationTest { } @Nested - inner class CoreComponent: ComponentTest() { + inner class CoreComponent : ComponentTest() { override val resourceClass: Class get() = Th2CoreBox::class.java override val specType: String @@ -575,7 +554,7 @@ class IntegrationTest { } @Nested - inner class Component: ComponentTest() { + inner class Component : ComponentTest() { override val resourceClass: Class get() = Th2Box::class.java override val specType: String @@ -622,7 +601,7 @@ class IntegrationTest { } @Nested - inner class Job: ComponentTest() { + inner class Job : ComponentTest() { override val resourceClass: Class get() = Th2Job::class.java override val specType: String @@ -678,7 +657,7 @@ class IntegrationTest { val name = "th2-dictionary" val spec = """ data: $DICTIONARY_CONTENT - """.trimIndent() + """.trimIndent() val annotations = createAnnotations(gitHash, spec.hashCode().toString()) kubeClient.createTh2Dictionary( @@ -730,6 +709,39 @@ class IntegrationTest { Arguments.of(Th2CoreBox::class.java, ::Th2CoreBox, "th2-rpt-data-provider", false), ) + private fun createQueueCfg( + pinName: String, + attributes: List, + routingKey: String, + queueName: String, + ) = mapOf( + pinName to mapOf( + "attributes" to attributes, + "exchange" to RABBIT_MQ_TH2_EXCHANGE, + "filters" to emptyList(), + "name" to routingKey, + "queue" to queueName, + ) + ) + + private fun createGrpcCfg(serverName: String) = mapOf( + CLIENT_PIN to mapOf( + "endpoints" to mapOf( + "$serverName-endpoint" to mapOf( + "attributes" to emptyList(), + "host" to serverName, + "port" to 8080 + ) + ), + "filters" to emptyList(), + "service-class" to GRPC_SERVICE, + "strategy" to mapOf( + "endpoints" to listOf("$serverName-endpoint"), + "name" to "robin", + ) + ) + ) + private fun HelmRelease.assertMinCfg( name: String, runAsJob: Boolean, @@ -737,31 +749,18 @@ class IntegrationTest { services: Map> = emptyMap(), ) { expectThat(componentValuesSection) { - getValue(BOOK_CONFIG_ALIAS).isA>().and { - hasSize(1) + getValue(BOOK_CONFIG_ALIAS).isA>().hasSize(1).and { getValue(BOOK_NAME_ALIAS) isEqualTo TH2_BOOK } - getValue(CRADLE_MGR_ALIAS).isA>().and { - hasSize(2) + getValue(CRADLE_MGR_ALIAS).isA>().hasSize(2).and { getValue(CHECKSUM_ALIAS).isNotNull() getValue(CONFIG_ALIAS).isNull() // FIXME: shouldn't be null } getValue(CUSTOM_CONFIG_ALIAS).isA>().isEmpty() getValue(DICTIONARIES_ALIAS).isA>().isEmpty() // FIXME getValue(PULL_SECRETS_ALIAS).isA>().isEmpty() // FIXME - getValue(EXTENDED_SETTINGS_ALIAS).isA>().and { - isEmpty() // FIXME: add extendedSettings - } - getValue(GRPC_P2P_CONFIG_ALIAS).isA>().and { - hasSize(2) - getValue("server").isA>().and { - getValue("attributes").isNull() // FIXME: add attributes - getValue("host").isNull() // FIXME: add host - getValue("port") isEqualTo 8080 - getValue("workers") isEqualTo 5 - } - getValue("services").isA>() isEqualTo services - } + getValue(EXTENDED_SETTINGS_ALIAS).isA>().isEmpty() + verifyGrpcCfg(services) getValue(IS_JOB_ALIAS) isEqualTo runAsJob getValue(SECRET_PATHS_CONFIG_ALIAS).isA>().isEmpty() getValue(SECRET_VALUES_CONFIG_ALIAS).isA>().isEmpty() @@ -769,50 +768,60 @@ class IntegrationTest { getValue("cassandra") isEqualTo "cassandra" getValue("rabbitMQ") isEqualTo "rabbitMQ" } - getValue(GRPC_ROUTER_ALIAS).isA>().and { - hasSize(2) + getValue(GRPC_ROUTER_ALIAS).isA>().hasSize(2).and { getValue(CHECKSUM_ALIAS).isNotNull() getValue(CONFIG_ALIAS).isNull() // FIXME } getValue(DOCKER_IMAGE_ALIAS) isEqualTo "$IMAGE:$VERSION" getValue(COMPONENT_NAME_ALIAS) isEqualTo name - getValue(ROOTLESS_ALIAS).isA>().and { - hasSize(1) + getValue(ROOTLESS_ALIAS).isA>().hasSize(1).and { getValue("enabled") isEqualTo false // FIXME } - getValue(PROMETHEUS_CONFIG_ALIAS).isA>().and { - hasSize(1) + getValue(PROMETHEUS_CONFIG_ALIAS).isA>().hasSize(1).and { getValue("enabled") isEqualTo true // FIXME } - getValue(LOGGING_ALIAS).isA>().and { - hasSize(2) + getValue(LOGGING_ALIAS).isA>().hasSize(2).and { getValue(CHECKSUM_ALIAS).isNotNull() getValue(CONFIG_ALIAS).isNull() // FIXME } - getValue(MQ_ROUTER_ALIAS).isA>().and { - hasSize(2) + getValue(MQ_ROUTER_ALIAS).isA>().hasSize(2).and { getValue(CHECKSUM_ALIAS).isNotNull() getValue(CONFIG_ALIAS).isNull() // FIXME } - getValue(MQ_QUEUE_CONFIG_ALIAS).isA>().and { - hasSize(2) - getValue("globalNotification").isA>().and { - hasSize(1) - getValue("exchange") isEqualTo RABBIT_MQ_TOPIC_EXCHANGE + verifyMqCfg(name, queues) + } + } + + private fun Assertion.Builder>.verifyGrpcCfg(services: Map>) { + getValue(GRPC_P2P_CONFIG_ALIAS).isA>().hasSize(2).and { + getValue("server").isA>().hasSize(4) and { + getValue("attributes").isNull() // FIXME: add attributes + getValue("host").isNull() // FIXME: add host + getValue("port") isEqualTo 8080 + getValue("workers") isEqualTo 5 + } + getValue("services").isA>() isEqualTo services + } + } + + private fun Assertion.Builder>.verifyMqCfg( + name: String, + queues: Map> + ) { + getValue(MQ_QUEUE_CONFIG_ALIAS).isA>().hasSize(2).and { + getValue("globalNotification").isA>().hasSize(1).and { + getValue("exchange") isEqualTo RABBIT_MQ_TOPIC_EXCHANGE + } + getValue("queues").isA>().hasSize(1 + queues.size).and { + getValue(EVENT_STORAGE_PIN_ALIAS).isA>().hasSize(5).and { + getValue("attributes").isA>() isEqualTo listOf("publish", "event") + getValue("exchange") isEqualTo RABBIT_MQ_TH2_EXCHANGE + getValue("filters").isA>().isEmpty() // FIXME + getValue("name") isEqualTo formatRoutingKey(TH2_NAMESPACE, name, EVENT_STORAGE_PIN_ALIAS) + getValue("queue").isA().isEmpty() } - getValue("queues").isA>().and { - hasSize(1 + queues.size) - getValue(EVENT_STORAGE_PIN_ALIAS).isA>().and { - hasSize(5) - getValue("attributes").isA>() isEqualTo listOf("publish", "event") - getValue("exchange") isEqualTo RABBIT_MQ_TH2_EXCHANGE - getValue("filters").isA>().isEmpty() // FIXME - getValue("name") isEqualTo formatRoutingKey(TH2_NAMESPACE, name, EVENT_STORAGE_PIN_ALIAS) - getValue("queue").isA().isEmpty() - } - queues.forEach { (key, value) -> - getValue(key).isA>() isEqualTo value - } + queues.forEach { (key, value) -> + getValue(key).isA>() isEqualTo value } } } diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestIntegrationUtils.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestIntegrationUtils.kt index af77d423..968c8723 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestIntegrationUtils.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestIntegrationUtils.kt @@ -223,7 +223,13 @@ fun Channel.createQueue( exclusive: Boolean = false, autoDelete: Boolean = false, arguments: Map = emptyMap(), -): AMQP.Queue.DeclareOk = queueDeclare(formatQueue(namespace, component, pin), durable, exclusive, autoDelete, arguments) +): AMQP.Queue.DeclareOk = queueDeclare( + formatQueue(namespace, component, pin), + durable, + exclusive, + autoDelete, + arguments +) fun Channel.createExchange( exchange: String, diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt index 9aeb06e8..d60dbdce 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestKubernetesUtils.kt @@ -101,7 +101,7 @@ fun KubernetesClient.createConfigMap( ).create() } -inline fun KubernetesClient.awaitResource( +inline fun KubernetesClient.awaitResource( namespace: String, name: String, timeout: Long = 5_000, @@ -114,7 +114,7 @@ inline fun KubernetesClient.awaitResource( return resources(T::class.java).inNamespace(namespace).withName(name).get() } -inline fun KubernetesClient.awaitNoResources( +inline fun KubernetesClient.awaitNoResources( namespace: String, timeout: Long = 5_000, unit: TimeUnit = TimeUnit.MILLISECONDS, @@ -124,7 +124,7 @@ inline fun KubernetesClient.awaitNoResources( .until { resources(T::class.java).inNamespace(namespace).list().items.isEmpty() } } -inline fun KubernetesClient.awaitNoResource( +inline fun KubernetesClient.awaitNoResource( namespace: String, name: String, timeout: Long = 5_000, @@ -135,7 +135,7 @@ inline fun KubernetesClient.awaitNoResource( .until { resources(T::class.java).inNamespace(namespace).withName(name).get() == null } } -inline fun KubernetesClient.awaitPhase( +inline fun KubernetesClient.awaitPhase( namespace: String, name: String, phase: RolloutPhase, @@ -153,10 +153,10 @@ fun KubernetesClient.awaitPhase( ) { await("awaitStatus ($name $resourceType $phase)") .timeout(timeout, unit) - .until { resources(resourceType)?.inNamespace(namespace)?.withName(name)?.get()?.status?.phase == phase } + .until { resources(resourceType)?.inNamespace(namespace)?.withName(name)?.get()?.status?.phase == phase } } -fun KubernetesClient.createTh2CustomResource( +fun KubernetesClient.createTh2CustomResource( namespace: String, name: String, gitHash: String, @@ -169,7 +169,7 @@ fun KubernetesClient.createTh2CustomResource( resource(it).create() } -fun KubernetesClient.modifyTh2CustomResource( +fun KubernetesClient.modifyTh2CustomResource( namespace: String, name: String, gitHash: String, @@ -191,9 +191,9 @@ fun KubernetesClient.createTh2Dictionary( ) { resource( Th2Dictionary().apply { - this.metadata = createMeta(name, namespace, annotations) - this.spec = YAML_MAPPER.readValue(spec, Th2DictionarySpec::class.java) - } + this.metadata = createMeta(name, namespace, annotations) + this.spec = YAML_MAPPER.readValue(spec, Th2DictionarySpec::class.java) + } ).create() } diff --git a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt index 4b805ef9..be5461ab 100644 --- a/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/integration/TestRabbitMQUtils.kt @@ -98,7 +98,8 @@ fun Client.assertNoExchanges( unit: TimeUnit = TimeUnit.MILLISECONDS, ) { val filter: (ExchangeInfo) -> Boolean = { - exchangeInfo -> exchangeInfo.name.matches(Regex(exchangePattern)) && exchangeInfo.vhost == vHost + exchangeInfo -> + exchangeInfo.name.matches(Regex(exchangePattern)) && exchangeInfo.vhost == vHost } await("assertNoExchanges('$exchangePattern')") .timeout(timeout, unit) @@ -166,7 +167,8 @@ fun Client.assertBindings( .until { getQueueBindings(vHost, queue).size == routingKeys.size } val queueBindings = getQueueBindings(vHost, queue) - assertAll(routingKeys.map { routingKey -> + assertAll( + routingKeys.map { routingKey -> { val queueBinding = assertNotNull( @@ -222,7 +224,8 @@ fun Client.assertNoQueues( unit: TimeUnit = TimeUnit.MILLISECONDS, ) { val filter: (QueueInfo) -> Boolean = { - queueInfo -> queueInfo.name.matches(Regex(queuePattern)) && queueInfo.vhost == vHost + queueInfo -> + queueInfo.name.matches(Regex(queuePattern)) && queueInfo.vhost == vHost } await("assertNoQueues('$queuePattern')") .timeout(timeout, unit) @@ -243,4 +246,4 @@ fun Client.assertNoQueue( await("assertNoQueue('$name')") .timeout(timeout, unit) .until { getQueue(vHost, name) == null } -} \ No newline at end of file +}