diff --git a/.github/workflows/java-publish-docker.yml b/.github/workflows/build-dev-release.yml similarity index 57% rename from .github/workflows/java-publish-docker.yml rename to .github/workflows/build-dev-release.yml index d40f3ed2..b438d11f 100644 --- a/.github/workflows/java-publish-docker.yml +++ b/.github/workflows/build-dev-release.yml @@ -1,20 +1,14 @@ -name: Build and publish Docker images to github registry +name: Build and publish dev release Docker image to Github Container Registry ghcr.io -on: - push: - branches: - - master - - version-* - paths: - - gradle.properties +on: workflow_dispatch jobs: build: uses: th2-net/.github/.github/workflows/compound-java.yml@main with: build-target: 'Docker' - runsOn: ubuntu-latest - gradleVersion: '7' + devRelease: true + createTag: true docker-username: ${{ github.actor }} secrets: docker-password: ${{ secrets.GITHUB_TOKEN }} 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 76% rename from .github/workflows/dev-java-publish-docker.yml rename to .github/workflows/build-sanpshot.yml index 7f3c716f..9454329f 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,6 @@ 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 }} # FIXME: strict scanner was disabled for 4.6.4 hotfix publishing and must be removed after that strict-scanner: false diff --git a/.github/workflows/ci-unwelcome-words.yml b/.github/workflows/ci-unwelcome-words.yml index cd7adcf3..add8e7fd 100644 --- a/.github/workflows/ci-unwelcome-words.yml +++ b/.github/workflows/ci-unwelcome-words.yml @@ -5,19 +5,19 @@ on: jobs: test: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest 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/Dockerfile b/Dockerfile index d7a0206c..666e6cd2 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", "/var/th2/config/log4j2.properties"] \ No newline at end of file diff --git a/README.md b/README.md index c9ccba76..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: 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 # see RabbitMQ documentation to find out how permissions are described @@ -147,5 +144,19 @@ openshift: ## Release notes +### 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` + ++ 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` + ### 4.6.4 + Added `rabbitMQManagement.cleanUpOnStart` option \ No newline at end of file diff --git a/build.gradle b/build.gradle index a4d1eafd..fd1389f5 100644 --- a/build.gradle +++ b/build.gradle @@ -1,141 +1,76 @@ -/* - * 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}" - - detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:${detekt_version}") -} - -dependencyCheck { - formats=['SARIF', 'JSON', 'HTML'] - failBuildOnCVSS=5 + testImplementation libs.mockito.core + testImplementation libs.mockito.kotlin + testImplementation libs.junit.jupiter.api + testRuntimeOnly libs.junit.jupiter.engine - analyzers { - assemblyEnabled = false - nugetconfEnabled = false - nodeEnabled = false - } + detektPlugins libs.detekt.formatting } -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) } - } +wrapper { + version '8.7' + distributionType Wrapper.DistributionType.BIN } 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 6d703e7e..72df96a7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1 @@ -release_version = 4.6.4 - -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..1207fe44 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,27 @@ +[versions] +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" } +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" } + +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.ref = "detekt" } + +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..b82aa23a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,22 +1,7 @@ -# -# 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 +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-7.6-all.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 diff --git a/src/main/java/com/exactpro/th2/infraoperator/OperatorState.java b/src/main/java/com/exactpro/th2/infraoperator/OperatorState.java index c0e2155b..4c7de721 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. @@ -22,6 +22,8 @@ import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import static java.util.Collections.unmodifiableSet; + public enum OperatorState { INSTANCE; @@ -104,6 +106,10 @@ public Collection getAllBoxResources() { return resources; } + public Collection getNamespaces() { + return 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..936493ff 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. @@ -27,21 +27,28 @@ 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; +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) { - + if (args.length > 0) { + configureLogger(args[0]); + } var watchManager = DefaultWatchManager.getInstance(); PrometheusServer.start(); OperatorMetrics.resetCacheErrors(); try { RabbitMQContext.declareTopicExchange(); - RabbitMQContext.cleanUpRabbitBeforeStart(); + RabbitMQUtils.deleteRabbitMQRubbish(); watchManager.addTarget(MstoreHelmTh2Op::new); watchManager.addTarget(EstoreHelmTh2Op::new); @@ -51,12 +58,23 @@ public static void main(String[] args) { watchManager.startInformers(); - new ContinuousTaskWorker().add(new CheckResourceCacheTask(300)); + ContinuousTaskWorker continuousTaskWorker = new ContinuousTaskWorker(); + continuousTaskWorker.add(new CheckResourceCacheTask(300)); } 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(String filePath) { + Path path = Path.of(filePath); + if (Files.exists(path)) { + LoggerContext loggerContext = LoggerContext.getContext(false); + loggerContext.setConfigLocation(path.toUri()); + loggerContext.reconfigure(); + LOGGER.info("Logger configuration from {} file is applied", path); + } + } } 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..8526ed0b 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.isNotActive; 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 (isNotActive(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 (isNotActive(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/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..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 @@ -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,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; @@ -57,5 +60,4 @@ protected MessageRouterConfigFactory getMqConfigFactory() { protected String getStorageName() { return MESSAGE_STORAGE_BOX_ALIAS; } - } 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..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 @@ -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,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,15 +44,25 @@ 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; -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; } @@ -142,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; @@ -288,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/DefaultWatchManager.java b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/DefaultWatchManager.java index bf5ccf39..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 @@ -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,13 +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; @@ -40,7 +44,8 @@ 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 { @@ -157,7 +162,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); } @@ -181,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; @@ -204,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++; } } @@ -228,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/NamespaceEventHandler.java b/src/main/java/com/exactpro/th2/infraoperator/operator/manager/impl/NamespaceEventHandler.java index 9a6289fb..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 @@ -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,11 +32,12 @@ 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); - private EventQueue eventQueue; + private final EventQueue eventQueue; private final OperatorConfig config = ConfigLoader.getConfig(); @@ -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..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 @@ -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. @@ -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,20 @@ 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.isNotActive; +import static com.exactpro.th2.infraoperator.util.WatcherUtils.createExceptionHandler; public class Th2DictionaryEventHandler implements Watcher { @@ -72,6 +76,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; } @@ -191,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 (isNotActive(kubClient, namespace)) { return; } for (var linkedResourceName : linkedResources) { 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..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 @@ -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,10 +37,8 @@ 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.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; @@ -112,7 +111,11 @@ 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(); @@ -141,8 +144,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 = 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 0f45c658..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 @@ -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; @@ -48,11 +48,22 @@ 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.Collection; +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.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 { @@ -71,8 +82,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()); @@ -88,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); @@ -107,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; } @@ -152,7 +167,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(), + createEstoreQueue(namespace), rabbitMQManagementConfig.getPersistence(), false, false, @@ -160,7 +175,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(), + createMstoreQueue(namespace), rabbitMQManagementConfig.getPersistence(), false, false, @@ -170,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); @@ -223,47 +238,8 @@ 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 String toExchangeName(String namespace) { + return namespace; } public static Channel getChannel() { @@ -292,13 +268,12 @@ public static Map generateQueueArguments(PinSettings pinSettings } } - public static 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); @@ -306,6 +281,12 @@ public static List getQueues() { } } + public static @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(); try { @@ -318,8 +299,7 @@ public static List getQueueBindings(String queue) { } } - public static List getExchanges() { - + public static @NotNull List getExchanges() { try { Client rmqClient = getClient(); return rmqClient.getExchanges(); @@ -330,6 +310,18 @@ public static List getExchanges() { } } + public static @NotNull List getTh2Exchanges() { + Collection namespacePrefixes = ConfigLoader.getConfig().getNamespacePrefixes(); + String topicExchange = getTopicExchangeName(); + return getExchanges().stream() + .filter(exchangeInfo -> { + String name = exchangeInfo.getName(); + return isNoneBlank(name) + && (name.equals(topicExchange) || anyPrefixMatch(name, namespacePrefixes)); + + }).collect(Collectors.toList()); + } + public static QueueInfo getQueue(String queueName) { String vHostName = getManagementConfig().getVhostName(); 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/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/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/configuration/fields/RabbitMQManagementConfig.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/configuration/fields/RabbitMQManagementConfig.kt index 059cd646..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 = false, 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..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 @@ -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.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 @@ -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), + 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/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..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 @@ -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.createEstoreQueueName 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), + 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 c0e3f162..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 @@ -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.createMstoreQueueName 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), + 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 new file mode 100644 index 00000000..b004174b --- /dev/null +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/spec/strategy/linkresolver/Util.kt @@ -0,0 +1,66 @@ +/* + * 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("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 createEstoreQueueName(namespace: String): QueueName = QueueName( + namespace, + EVENT_STORAGE_BOX_ALIAS, + EVENT_STORAGE_PIN_ALIAS +) + +fun createEstoreQueue(namespace: String): String = createEstoreQueueName(namespace).toString() + +fun createEstoreQueueName(namespace: String, component: String): QueueName = QueueName( + namespace, + component, + EVENT_STORAGE_PIN_ALIAS +) + +fun createEstoreQueue(namespace: String, component: String): String = + createEstoreQueueName(namespace, component).toString() + +fun createEstoreRoutingKeyName(namespace: String, component: String) = RoutingKeyName( + namespace, + component, + EVENT_STORAGE_PIN_ALIAS +) + +fun createMstoreQueueName(namespace: String) = QueueName( + namespace, + MESSAGE_STORAGE_BOX_ALIAS, + MESSAGE_STORAGE_PIN_ALIAS +) + +fun createMstoreQueue(namespace: String): String = createMstoreQueueName(namespace).toString() + +fun createMstoreQueueName(namespace: String, component: String) = QueueName( + namespace, + component, + MESSAGE_STORAGE_PIN_ALIAS +) + +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 3ec3e759..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 @@ -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.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 @@ -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), + createEstoreQueueName(namespace), + createEstoreRoutingKeyName(namespace, resourceName), namespace ) bindQueues(estoreLinkDescription, commitHash) val currentLinks: MutableList = ArrayList() - val queueName = QueueName(namespace, MESSAGE_STORAGE_BOX_ALIAS, MESSAGE_STORAGE_PIN_ALIAS) + 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/util/KubernetesUtils.kt b/src/main/kotlin/com/exactpro/th2/infraoperator/util/KubernetesUtils.kt new file mode 100644 index 00000000..7dc1d040 --- /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: Set> = + 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.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 +} 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..0b4ba6cf --- /dev/null +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtils.kt @@ -0,0 +1,165 @@ +/* + * 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.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 +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.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 java.io.IOException + +private val K_LOGGER = KotlinLogging.logger { } + +fun deleteRabbitMQRubbish() { + try { + val resourceHolder = collectRabbitMQResources( + RabbitMQContext.getTh2Queues(), + RabbitMQContext.getTh2Exchanges(), + ) + + if (resourceHolder.isHolderEmpty()) { + return + } + + val namespacePrefixes = ConfigLoader.loadConfiguration().namespacePrefixes + val topicExchange = RabbitMQContext.getTopicExchangeName() + + createKubernetesClient().use { kuClient -> + resourceHolder.filterRubbishResources( + kuClient, + namespacePrefixes, + topicExchange, + ) + } + K_LOGGER.info { "RabbitMQ rubbish: $resourceHolder" } + deleteRabbitMQRubbish(resourceHolder, RabbitMQContext::getChannel) + } catch (e: Exception) { + K_LOGGER.error(e) { "Delete RabbitMQ rubbish failure" } + } +} + +internal fun collectRabbitMQResources( + th2Queues: Collection, + th2Exchanges: Collection, +): ResourceHolder = ResourceHolder().apply { + th2Queues.asSequence() + .map(QueueInfo::getName) + .forEach(queues::add) + + th2Exchanges.asSequence() + .map(ExchangeInfo::getName) + .forEach(exchanges::add) + + K_LOGGER.debug { "Actual set in RabbitMQ, queues: $queues, exchanges: $exchanges" } +} + +internal fun ResourceHolder.filterRubbishResources( + client: KubernetesClient, + namespacePrefixes: Set, + topicExchange: String, +): ResourceHolder = apply { + val namespaces: Set = client.namespaces(namespacePrefixes) + if (namespaces.isEmpty()) { + return@apply + } + + K_LOGGER.debug { "Search RabbitMQ resources in $namespaces namespaces" } + exchanges.remove(topicExchange) + + val factories: Map, MessageRouterConfigFactory> = createFactories() + namespaces.forEach { namespace -> + exchanges.remove(namespace) + + client.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, " + + "queues: $queues, exchanges: $exchanges" + } + } +} + +internal fun deleteRabbitMQRubbish( + resourceHolder: ResourceHolder, + getChannel: () -> Channel +) { + if (resourceHolder.isHolderEmpty()) { + return + } + + val channel: Channel = 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() +} + +internal 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 new file mode 100644 index 00000000..76dc7a11 --- /dev/null +++ b/src/main/kotlin/com/exactpro/th2/infraoperator/util/WatcherUtils.kt @@ -0,0 +1,35 @@ +/* + * 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" } + // 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 92776a39..ac52ed19 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 { @@ -63,14 +65,13 @@ void testFullConfig() { "username", "password", true, - false, new RabbitMQNamespacePermissions( "configure", "read", "write" ) ) ); 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 +81,7 @@ void testFullConfig() { void testNsPrefixes() { beforeEach("nsPrefixesConfig.yml"); - expected.setNamespacePrefixes(Arrays.asList("string1", "string2")); + expected.setNamespacePrefixes(Set.of("string1", "string2")); assertEquals(expected, loadConfiguration()); } @@ -99,7 +100,6 @@ void testRabbitMQManagementConfig() { "username", "password", true, - false, new RabbitMQNamespacePermissions( "configure", "read", "write" ) @@ -121,7 +121,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 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..8362fdb1 --- /dev/null +++ b/src/test/kotlin/com/exactpro/th2/infraoperator/util/RabbitMQUtilsTest.kt @@ -0,0 +1,199 @@ +/* + * 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 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 = + ResourceHolder( + exchanges = hashSetOf(TOPIC_EXCHANGE_NAME), + ).filterRubbishResources( + client, + setOf("th2"), + 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 = + ResourceHolder( + exchanges = hashSetOf(exchangeName), + ).filterRubbishResources( + client, + setOf("th2"), + TOPIC_EXCHANGE_NAME, + ) + 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 = + ResourceHolder( + queues = hashSetOf(queueName), + ).filterRubbishResources( + client, + setOf("th2"), + TOPIC_EXCHANGE_NAME, + ) + 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 = + ResourceHolder( + exchanges = hashSetOf(exchangeName, toExchangeName(namespaceName), TOPIC_EXCHANGE_NAME), + ).filterRubbishResources( + client, + setOf("th2"), + 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 = + ResourceHolder( + queues = hashSetOf(queueName), + exchanges = hashSetOf(toExchangeName(namespaceName), TOPIC_EXCHANGE_NAME), + ).filterRubbishResources( + client, + setOf("th2"), + 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