From f29587e876f6dda4be8cb9413d7c9bd7caab84a4 Mon Sep 17 00:00:00 2001 From: Radovan Date: Mon, 30 Sep 2024 16:18:27 +0300 Subject: [PATCH 1/8] K8OP-262 Bump Reaper images in the e2e test that uses HTTP auth (#1418) --- test/testdata/fixtures/reaper/reaper.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/testdata/fixtures/reaper/reaper.yaml b/test/testdata/fixtures/reaper/reaper.yaml index 1eea845ee..f20d0ffec 100644 --- a/test/testdata/fixtures/reaper/reaper.yaml +++ b/test/testdata/fixtures/reaper/reaper.yaml @@ -13,11 +13,9 @@ spec: containerImage: repository: thelastpickle name: cassandra-reaper - tag: "3.0.0" pullPolicy: Always initContainerImage: repository: thelastpickle name: cassandra-reaper - tag: "3.0.0" pullPolicy: IfNotPresent heapSize: 256Mi From c911554a80b166fa66534e27d5998c682469659b Mon Sep 17 00:00:00 2001 From: Radovan Date: Tue, 1 Oct 2024 12:30:03 +0300 Subject: [PATCH 2/8] Add docs for Reaper Control Plane mode (#1398) * Add docs for Reaper Control Plane mode * Do not mention Postgres * Reword storageClassName being explicit * Recommend enabling httpManagement Co-authored-by: Alexander Dejanovski * Update docs/content/en/components/reaper/_index.md Co-authored-by: Alexander Dejanovski * Add reaper namespace --------- Co-authored-by: Alexander Dejanovski --- CHANGELOG/CHANGELOG-1.20.md | 4 ++ docs/content/en/components/reaper/_index.md | 68 +++++++++++++++++++-- 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/CHANGELOG/CHANGELOG-1.20.md b/CHANGELOG/CHANGELOG-1.20.md index 2c494452b..3555126a1 100644 --- a/CHANGELOG/CHANGELOG-1.20.md +++ b/CHANGELOG/CHANGELOG-1.20.md @@ -13,6 +13,10 @@ Changelog for the K8ssandra Operator, new PRs should update the `unreleased` sec When cutting a new release, update the `unreleased` heading to the tag being generated and date, like `## vX.Y.Z - YYYY-MM-DD` and create a new placeholder section for `unreleased` entries. +## unreleased + +* [DOCS] [#1469](https://github.com/riptano/mission-control/issues/1469) Add docs for Reaper's Control Plane deployment mode + ## v1.20.1 - 2024-09-19 * [BUGFIX] Upgrade cass-operator to v1.22.4 to fix security context overwrites diff --git a/docs/content/en/components/reaper/_index.md b/docs/content/en/components/reaper/_index.md index e5b634ba4..997ee22d0 100644 --- a/docs/content/en/components/reaper/_index.md +++ b/docs/content/en/components/reaper/_index.md @@ -119,15 +119,23 @@ Reaper also has the ability to listen and display live Cassandra’s emitted Dia In Cassandra 4.0, internal system “diagnostic events” have become available, via the work done in CASSANDRA-12944. These allow operators to observe internal Cassandra events, for example in unit tests and with external tools. These diagnostic events provide operational monitoring and troubleshooting beyond logs and metrics. -Reaper can use Postgres and Cassandra itself as a storage backend for its data, and is capable of repairing all Cassandra versions since 1.2 up to the latest 4.0. +Reaper does not need an external database to store its data. It can run with a _local_ storage, which means Reaper will persist its in-memory data to the local file system. Reaper can also use Cassandra itself as a storage backend for its data, and is capable of repairing all Cassandra versions since 1.2 up to the latest 4.0. In order to make Reaper more efficient, segment orchestration was recently revamped and modernized. It opened for a long awaited feature: fully concurrent repairs for different keyspaces and tables. These changes also introduced a long awaited feature by allowing fully concurrent repairs for different keyspaces/tables. ## Deploying Reaper in k8ssandra-operator -Reaper is not deployed by default by k8ssandra-operator. -In order to deploy it with the default settings, you'll need to add the following section to your `K8ssandraCluster` manifest: +Reaper is not deployed by default by k8ssandra-operator. It can be deployed in two modes: + +* **_bundled_** : This is the conventional way of deploying one (or several co-operating instances of) Reaper per k8ssandra cluster. +* **_control plane_** : This is a more recent way of deploying just one instance of Reaper that takes care of repairing several k8ssandra clusters. + +### Deploying a bundled Reaper + +Bundled Reaper is the default way of deploying Reaper. + +In order to deploy Reaper with the default settings, you'll need to add the following section to your `K8ssandraCluster` manifest: ```yaml spec: @@ -143,7 +151,59 @@ spec: deploymentMode: SINGLE ``` -The list of available configuration options can be found in the [Reaper CRD]({{< relref "/reference/crd/k8ssandra-operator-crds-latest/#k8ssandraclusterspecreaper" >}}). +### Deploying a control plane Reaper + +In order to deploy a control plane Reaper, you'll need to create the Reaper object directly: + +```yaml +kind: Reaper +metadata: + name: cp-reaper +spec: + storageType: local + storageConfig: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 256Mi + httpManagement: + enabled: true +``` + +The _control plane_ Reaper must use a _local_ storage because there's no Cassandra cluster yet. +The local storage requires its own config, mostly to ensure enough disk space is allocated to Reaper. You can skip the `storageClassName` field to use the default storage class, or set it to specify one explicitly. +We recommend enabling the `httpManagement` feature. This will make Reaper use HTTP instead of JMX when interacting with Cassandra. + +In order to enroll a k8ssandra cluster in the control plane Reaper, you'll need to add a `reaperRef` to the `reaper` section of the `K8ssandraCluster` manifest: + +```yaml +spec: + reaper: + reaperRef: + name: cp-reaper + namespace: reaper-namespace +``` + +The k8ssandra-operator will then take care of enrolling the k8ssandra cluster in the control plane Reaper instead of deploying a bundled Reaper. + +By default, Reaper will not require authentication when connecting to it. If you wish to use authentication when connecting to Reaper, you first need to create a secret yourself. Then, you can reference this secret in Reaper's manifest: + +```yaml +kind: Reaper +metadata: + name: cp-reaper +spec: + # other fields + uiUserSecretRef: + name: reaper-ui-secret +``` + +This same secret then needs to be referenced in the `K8ssandraCluster` manifest at `spec.reaper.uiUserSecretRef`. The k8ssandra-operator will use these credentials when enrolling the k8ssandra cluster in the control plane Reaper. + +### Configuring Reaper + +The list of available configuration options can be found in the [Reaper CRD]({{< relref "/reference/crd/k8ssandra-operator-crds-latest/#k8ssandraclusterspecreaper" >}}). It's applicable for both bundled and control plane Reapers. ## Next steps From a217aa07d0d0b535fb163c64642eaa0716b42990 Mon Sep 17 00:00:00 2001 From: Radovan Date: Wed, 2 Oct 2024 17:40:52 +0300 Subject: [PATCH 3/8] Bump medusa version to 0.22.3 (#1423) --- CHANGELOG/CHANGELOG-1.20.md | 1 + pkg/medusa/reconcile.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG/CHANGELOG-1.20.md b/CHANGELOG/CHANGELOG-1.20.md index 3555126a1..9319146da 100644 --- a/CHANGELOG/CHANGELOG-1.20.md +++ b/CHANGELOG/CHANGELOG-1.20.md @@ -16,6 +16,7 @@ When cutting a new release, update the `unreleased` heading to the tag being gen ## unreleased * [DOCS] [#1469](https://github.com/riptano/mission-control/issues/1469) Add docs for Reaper's Control Plane deployment mode +* [CHANGE] Bump default Medusa version to 0.22.3 ## v1.20.1 - 2024-09-19 diff --git a/pkg/medusa/reconcile.go b/pkg/medusa/reconcile.go index 4eb9891ee..929c1459b 100644 --- a/pkg/medusa/reconcile.go +++ b/pkg/medusa/reconcile.go @@ -25,7 +25,7 @@ import ( const ( DefaultMedusaImageRepository = "k8ssandra" DefaultMedusaImageName = "medusa" - DefaultMedusaVersion = "0.22.2" + DefaultMedusaVersion = "0.22.3" DefaultMedusaPort = 50051 DefaultProbeInitialDelay = 10 DefaultProbeTimeout = 1 From b34b80d964428d84967aa824a7a86652e3439d0f Mon Sep 17 00:00:00 2001 From: Michael Burman Date: Fri, 4 Oct 2024 18:16:37 +0300 Subject: [PATCH 4/8] =?UTF-8?q?Fix=20empty=20log=20lines=20parsing=20in=20?= =?UTF-8?q?parse=5Fcassandra=5Flog,=20add=20automated=20tes=E2=80=A6=20(#1?= =?UTF-8?q?424)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix empty log lines parsing in parse_cassandra_log, add automated tests for Vector * Fix kustomize paths in tests, remove manual old kustomize install to prevent old versions from being in the path, change vector-test back to Makefile entirely for GHA --- .github/workflows/kind_e2e_tests.yaml | 6 -- .../kind_multicluster_e2e_tests.yaml | 6 -- ...ster_isolated_control_plane_e2e_tests.yaml | 6 -- .github/workflows/kuttl_tests.yaml | 6 -- .github/workflows/test_and_build_image.yaml | 3 + .github/workflows/version_tests.yaml | 6 -- CHANGELOG/CHANGELOG-1.20.md | 3 + Makefile | 20 +++- pkg/telemetry/vector.go | 6 +- pkg/telemetry/vector_test.go | 95 +++++++++++++++++++ scripts/prepare-helm-release.sh | 8 +- scripts/run-vector-tests.sh | 7 ++ test/kustomize/kustomize.go | 32 +++---- 13 files changed, 147 insertions(+), 57 deletions(-) create mode 100755 scripts/run-vector-tests.sh diff --git a/.github/workflows/kind_e2e_tests.yaml b/.github/workflows/kind_e2e_tests.yaml index 27d5d4c99..4dd06fc2c 100644 --- a/.github/workflows/kind_e2e_tests.yaml +++ b/.github/workflows/kind_e2e_tests.yaml @@ -134,12 +134,6 @@ jobs: with: go-version-file: 'go.mod' cache: true - - name: Install kustomize - uses: imranismail/setup-kustomize@v2 - with: - kustomize-version: 4.x - - name: Log kustomize version - run: kustomize version - name: Install Kind run: go get sigs.k8s.io/kind - name: Set up Docker Buildx diff --git a/.github/workflows/kind_multicluster_e2e_tests.yaml b/.github/workflows/kind_multicluster_e2e_tests.yaml index 9d6e426d1..c50e41f85 100644 --- a/.github/workflows/kind_multicluster_e2e_tests.yaml +++ b/.github/workflows/kind_multicluster_e2e_tests.yaml @@ -102,12 +102,6 @@ jobs: with: go-version-file: 'go.mod' cache: true - - name: Install kustomize - uses: imranismail/setup-kustomize@v2 - with: - kustomize-version: 4.x - - name: Log kustomize version - run: kustomize version - name: Install Kind run: go get sigs.k8s.io/kind - name: Set up Docker Buildx diff --git a/.github/workflows/kind_multicluster_isolated_control_plane_e2e_tests.yaml b/.github/workflows/kind_multicluster_isolated_control_plane_e2e_tests.yaml index 9b923e68b..2e04a545f 100644 --- a/.github/workflows/kind_multicluster_isolated_control_plane_e2e_tests.yaml +++ b/.github/workflows/kind_multicluster_isolated_control_plane_e2e_tests.yaml @@ -80,12 +80,6 @@ jobs: with: go-version-file: 'go.mod' cache: true - - name: Install kustomize - uses: imranismail/setup-kustomize@v2 - with: - kustomize-version: 4.x - - name: Log kustomize version - run: kustomize version - name: Install Kind run: go get sigs.k8s.io/kind - name: Set up Docker Buildx diff --git a/.github/workflows/kuttl_tests.yaml b/.github/workflows/kuttl_tests.yaml index 2838a3d11..a8b7d083c 100644 --- a/.github/workflows/kuttl_tests.yaml +++ b/.github/workflows/kuttl_tests.yaml @@ -86,12 +86,6 @@ jobs: - name: Load Docker images run: | docker load --input /tmp/k8ssandra-k8ssandra-operator.tar - - name: Install kustomize - uses: imranismail/setup-kustomize@v2 - with: - kustomize-version: 4.x - - name: Log kustomize version - run: kustomize version - name: Install kuttl run: | make install-kuttl diff --git a/.github/workflows/test_and_build_image.yaml b/.github/workflows/test_and_build_image.yaml index 558d060ad..c091778bd 100644 --- a/.github/workflows/test_and_build_image.yaml +++ b/.github/workflows/test_and_build_image.yaml @@ -48,6 +48,9 @@ jobs: git status exit 1 fi + - name: Run Vector tests + run: | + make vector-install vector-test - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 build_image: diff --git a/.github/workflows/version_tests.yaml b/.github/workflows/version_tests.yaml index 218d4afa7..ea26bc387 100644 --- a/.github/workflows/version_tests.yaml +++ b/.github/workflows/version_tests.yaml @@ -90,12 +90,6 @@ jobs: - name: Load Docker images run: | docker load --input /tmp/k8ssandra-k8ssandra-operator.tar - - name: Install kustomize - uses: imranismail/setup-kustomize@v2 - with: - kustomize-version: 4.x - - name: Log kustomize version - run: kustomize version - name: Install kuttl run: | make install-kuttl diff --git a/CHANGELOG/CHANGELOG-1.20.md b/CHANGELOG/CHANGELOG-1.20.md index 9319146da..f232c12d5 100644 --- a/CHANGELOG/CHANGELOG-1.20.md +++ b/CHANGELOG/CHANGELOG-1.20.md @@ -17,6 +17,9 @@ When cutting a new release, update the `unreleased` heading to the tag being gen * [DOCS] [#1469](https://github.com/riptano/mission-control/issues/1469) Add docs for Reaper's Control Plane deployment mode * [CHANGE] Bump default Medusa version to 0.22.3 +* [BUGFIX] [#1409](https://github.com/k8ssandra/k8ssandra-operator/issues/1409) Vector would crash in the Cassandra log parsing if empty lines were present. Add automated tests for Vector parsing rules. +* [BUGFIX] [#1425](https://github.com/k8ssandra/k8ssandra-operator/issues/1425) prepare-helm-release.sh requires kustomize to be in the path and that makes make manifests fail. + ## v1.20.1 - 2024-09-19 diff --git a/Makefile b/Makefile index 049ec4330..ef15da416 100644 --- a/Makefile +++ b/Makefile @@ -130,9 +130,9 @@ help: ## Display this help. ##@ Development .PHONY: manifests -manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. +manifests: controller-gen kustomize ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=k8ssandra-operator webhook paths="./..." output:crd:artifacts:config=config/crd/bases - ./scripts/prepare-helm-release.sh + KUSTOMIZE=$(KUSTOMIZE) ./scripts/prepare-helm-release.sh .PHONY: generate generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. @@ -151,7 +151,7 @@ lint: golangci-lint ## Run golangci-lint against code. $(GOLANGCI_LINT) run ./... ENVTEST_ASSETS_DIR=$(shell pwd)/testbin -test: manifests generate fmt vet lint envtest kustomize ## Run tests. +test: manifests generate fmt vet lint envtest ## Run tests. ifdef TEST @echo Running test $(TEST) KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $(GO_FLAGS) ./apis/... ./pkg/... ./test/yq/... ./controllers/... -run="$(TEST)" -covermode=atomic -coverprofile coverage.out @@ -159,6 +159,15 @@ else KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $(GO_FLAGS) ./apis/... ./pkg/... ./test/yq/... ./controllers/... -covermode=atomic -coverprofile coverage.out endif +.PHONY: vector-test +vector-test: ## Run vector tests + @echo Generating test files for Vector tests + $(eval TMP := $(shell mktemp -d)) + VECTOR_TEST_FILES=true OUTPUT_PATH=$(TMP) go test -v ./pkg/telemetry -run=TestGenerateTomlTestFiles + @echo Running vector test files + OUTPUT_PATH=$(TMP) VECTOR=$(VECTOR) scripts/run-vector-tests.sh + rm -rf $(TMP) + E2E_TEST_TIMEOUT ?= 3600s PHONY: e2e-test @@ -326,6 +335,7 @@ KUSTOMIZE ?= $(LOCALBIN)/kustomize CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen ENVTEST ?= $(LOCALBIN)/setup-envtest GOLANGCI_LINT ?= $(LOCALBIN)/golangci-lint # TODO Add linting to the GHA also +VECTOR ?= $(LOCALBIN)/bin/vector ## Tool Versions CERT_MANAGER_VERSION ?= v1.12.2 @@ -346,6 +356,10 @@ cert-manager-multi: ## Install cert-manager to the clusters make cert-manager; \ done +.PHONY: vector-install +vector-install: + curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev | bash -s -- --prefix $(LOCALBIN) -y + # Install NGINX in the current Kind cluster using Helm and a values file that is suitable for # running e2e tests locally with a cluster created with setup-kind-multicluster.sh. Helm must be # pre-installed on the system. diff --git a/pkg/telemetry/vector.go b/pkg/telemetry/vector.go index 42e536ae6..4dccef017 100644 --- a/pkg/telemetry/vector.go +++ b/pkg/telemetry/vector.go @@ -140,10 +140,14 @@ timeout_ms = 10000 Inputs: []string{"systemlog"}, Config: `source = ''' del(.source_type) +.message = string!(.message) +.message = strip_whitespace(.message) . |= parse_groks!(.message, patterns: [ - "%{LOGLEVEL:loglevel}\\s+\\[(?((.+)))\\]\\s+%{TIMESTAMP_ISO8601:timestamp}\\s+%{JAVACLASS:class}:%{NUMBER:line}\\s+-\\s+(?(.+\\n?)+)", + "%{LOGLEVEL:loglevel}\\s+\\[(?((.+)))\\]\\s+%{TIMESTAMP_ISO8601:timestamp_raw}\\s+%{JAVACLASS:class}:%{NUMBER:line}\\s+-\\s+(?(.+\\n?)+)", ] ) +.timestamp = parse_timestamp!(.timestamp_raw, format: "%Y-%m-%d %T,%3f") +del(.timestamp_raw) pod_name, err = get_env_var("POD_NAME") if err == null { .pod_name = pod_name diff --git a/pkg/telemetry/vector_test.go b/pkg/telemetry/vector_test.go index d80fa2688..f590d3370 100644 --- a/pkg/telemetry/vector_test.go +++ b/pkg/telemetry/vector_test.go @@ -1,6 +1,9 @@ package telemetry import ( + "fmt" + "os" + "strings" "testing" "github.com/go-logr/logr/testr" @@ -366,3 +369,95 @@ func TestOverrideSourcePossible(t *testing.T) { assert.Equal("stdin", sources[0].Type) } + +func TestGenerateTomlTestFiles(t *testing.T) { + if os.Getenv("VECTOR_TEST_FILES") == "" { + t.Skip("Set VECTOR_TEST_FILES to generate vector test files") + } + outputDir := os.Getenv("OUTPUT_PATH") + if outputDir == "" { + fmt.Printf("No OUTPUT_PATH env variable set") + t.FailNow() + } + assert := assert.New(t) + sources, transformers, sinks := BuildDefaultVectorComponents(vector.VectorConfig{}) + assert.Equal(2, len(sources)) + assert.Equal(2, len(transformers)) + assert.Equal(1, len(sinks)) + + telemetrySpec := &telemetry.TelemetrySpec{ + Vector: &telemetry.VectorSpec{ + Enabled: ptr.To[bool](true), + Components: &telemetry.VectorComponentsSpec{ + Sources: sources, + Sinks: sinks, + Transforms: transformers, + }, + }, + } + + // Vector components are provided in the Telemetry spec, build the Vector sink config from them + vectorConfigToml := BuildCustomVectorToml(telemetrySpec) + + b := strings.Builder{} + fmt.Fprint(&b, vectorConfigToml) + + // Append tests + + fmt.Fprint(&b, ` +[[tests]] +name = "Test parsing normal Cassandra logs" + +[[tests.inputs]] +insert_at = "parse_cassandra_log" +value = "WARN [ScheduledTasks:1] 2024-10-01 12:31:17,694 LeaksDetectorImpl.java:306 - LEAK: RandomAccessReader/RandomAccessReader was not released before it was garbage-collected. This resource is a debugging aid, no negative consequences follow from this leak. However, please report this nonetheless even if auto-cleaning succeeded. Auto cleaning result: not attempted, no cleaner." + +[[tests.outputs]] +extract_from = "parse_cassandra_log" + +[[tests.outputs.conditions]] +type = "vrl" +source = ''' +assert_eq!(.loglevel, "WARN") +assert_eq!(.thread, "ScheduledTasks:1") +assert!(is_timestamp(.timestamp)) +assert!(is_string(.message)) +assert!(is_string(.line)) +assert!(exists(.class)) +''' +`) + + assert.NoError(os.WriteFile(fmt.Sprintf("%s/vector-simple.toml", outputDir), []byte(b.String()), 0644)) + + b.Reset() + fmt.Fprint(&b, vectorConfigToml) + + fmt.Fprint(&b, ` +[[tests]] +name = "Test parsing normal Cassandra logs" + +[[tests.inputs]] +insert_at = "parse_cassandra_log" +value = ''' +WARN [ScheduledTasks:1] 2024-10-01 12:31:17,694 LeaksDetectorImpl.java:306 - LEAK: RandomAccessReader/RandomAccessReader was not released before it was garbage-collected. This resource is a debugging aid, no negative consequences follow from this leak. However, please report this nonetheless even if auto-cleaning succeeded. Auto cleaning result: not attempted, no cleaner." + + +''' + +[[tests.outputs]] +extract_from = "parse_cassandra_log" + +[[tests.outputs.conditions]] +type = "vrl" +source = ''' +assert_eq!(.loglevel, "WARN") +assert_eq!(.thread, "ScheduledTasks:1") +assert!(is_timestamp(.timestamp)) +assert!(is_string(.message)) +assert!(is_string(.line)) +assert!(exists(.class)) +''' +`) + + assert.NoError(os.WriteFile(fmt.Sprintf("%s/vector-emptyline.toml", outputDir), []byte(b.String()), 0644)) +} diff --git a/scripts/prepare-helm-release.sh b/scripts/prepare-helm-release.sh index 17d94ce34..37042115b 100755 --- a/scripts/prepare-helm-release.sh +++ b/scripts/prepare-helm-release.sh @@ -8,11 +8,15 @@ set -e +if [ -z "$KUSTOMIZE" ]; then + KUSTOMIZE=$(command -v kustomize) +fi + mkdir -p build/helm # Generate the CRDs -kustomize build config/crd > charts/k8ssandra-operator/crds/k8ssandra-operator-crds.yaml +$KUSTOMIZE build config/crd > charts/k8ssandra-operator/crds/k8ssandra-operator-crds.yaml # Generate the role.yaml and clusterrole.yaml files using the RBAC generated manifests -kustomize build config/rbac > build/helm/k8ssandra-operator-rbac.yaml +$KUSTOMIZE build config/rbac > build/helm/k8ssandra-operator-rbac.yaml cat charts/templates/role.tmpl.yaml | tee build/helm/role.yaml > /dev/null cat build/helm/k8ssandra-operator-rbac.yaml | yq 'select(di == 1).rules' | tee -a build/helm/role.yaml > /dev/null echo "{{- end }}" >> build/helm/role.yaml diff --git a/scripts/run-vector-tests.sh b/scripts/run-vector-tests.sh new file mode 100755 index 000000000..e533f0ca5 --- /dev/null +++ b/scripts/run-vector-tests.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +for i in $OUTPUT_PATH/*; do + $VECTOR test $i + if [ "$?" -ne 0 ]; then + exit 1 + fi +done diff --git a/test/kustomize/kustomize.go b/test/kustomize/kustomize.go index 436f354dc..dfcedf8ce 100644 --- a/test/kustomize/kustomize.go +++ b/test/kustomize/kustomize.go @@ -13,20 +13,18 @@ func LogOutput(enabled bool) { logOutput = enabled } -func BuildDir(dir string) (*bytes.Buffer, error) { +func kustomizePath() string { binDir := os.Getenv("LOCALBIN") - kustomizeLocation := "" if binDir == "" { fmt.Println("warning: LOCALBIN environment variable not set, attempting to use system kustomize") - kustomizeLocation = "kustomize" - } else { - fmt.Println("LOCALBIN: " + binDir) - kustomizeLocation = binDir + "/kustomize" + return "kustomize" } - cmd := exec.Command(kustomizeLocation, "build") - cmd.Dir = dir + return binDir + "/kustomize" +} - fmt.Println(cmd) +func BuildDir(dir string) (*bytes.Buffer, error) { + cmd := exec.Command(kustomizePath(), "build") + cmd.Dir = dir output, err := cmd.CombinedOutput() buffer := bytes.NewBuffer(output) @@ -39,9 +37,7 @@ func BuildDir(dir string) (*bytes.Buffer, error) { } func BuildUrl(url string) (*bytes.Buffer, error) { - cmd := exec.Command("kustomize", "build", url) - - fmt.Println(cmd) + cmd := exec.Command(kustomizePath(), "build", url) output, err := cmd.CombinedOutput() buffer := bytes.NewBuffer(output) @@ -54,11 +50,9 @@ func BuildUrl(url string) (*bytes.Buffer, error) { } func SetNamespace(dir, namespace string) error { - cmd := exec.Command("kustomize", "edit", "set", "namespace", namespace) + cmd := exec.Command(kustomizePath(), "edit", "set", "namespace", namespace) cmd.Dir = dir - fmt.Println(cmd) - output, err := cmd.CombinedOutput() if logOutput { @@ -69,11 +63,9 @@ func SetNamespace(dir, namespace string) error { } func AddResource(path string) error { - cmd := exec.Command("kustomize", "edit", "add", "resource", path) + cmd := exec.Command(kustomizePath(), "edit", "add", "resource", path) cmd.Dir = "../testdata/k8ssandra-operator" - fmt.Println(cmd) - output, err := cmd.CombinedOutput() if logOutput { @@ -84,11 +76,9 @@ func AddResource(path string) error { } func RemoveResource(path string) error { - cmd := exec.Command("kustomize", "edit", "remove", "resource", path) + cmd := exec.Command(kustomizePath(), "edit", "remove", "resource", path) cmd.Dir = "../testdata/k8ssandra-operator" - fmt.Println(cmd) - output, err := cmd.CombinedOutput() if logOutput { From 7ddb75571c0aaf58fc059e53e8074f8546a89e8d Mon Sep 17 00:00:00 2001 From: Miles Garnsey <11435896+Miles-Garnsey@users.noreply.github.com> Date: Mon, 7 Oct 2024 10:44:01 +0400 Subject: [PATCH 5/8] Prevent changes of DC name or both adding and removing a DC at the same time. K8OP-263 (#1426) * Prevent changes of DC name or both adding and removing a DC at the same time. --- .../v1alpha1/k8ssandracluster_types.go | 31 ++++++++++++++++ .../v1alpha1/k8ssandracluster_types_test.go | 34 +++++++++++++++++ .../v1alpha1/k8ssandracluster_webhook.go | 7 +++- .../v1alpha1/k8ssandracluster_webhook_test.go | 12 +++++- .../k8ssandra/k8ssandracluster_controller.go | 4 ++ .../k8ssandracluster_controller_test.go | 16 +++++++- .../k8ssandra/validate_cluster_update.go | 37 +++++++++++++++++++ 7 files changed, 136 insertions(+), 5 deletions(-) create mode 100644 controllers/k8ssandra/validate_cluster_update.go diff --git a/apis/k8ssandra/v1alpha1/k8ssandracluster_types.go b/apis/k8ssandra/v1alpha1/k8ssandracluster_types.go index b4d3203c3..1c52b9056 100644 --- a/apis/k8ssandra/v1alpha1/k8ssandracluster_types.go +++ b/apis/k8ssandra/v1alpha1/k8ssandracluster_types.go @@ -573,3 +573,34 @@ func (kc *K8ssandraCluster) GetClusterIdHash() string { func (k *K8ssandraCluster) GenerationChanged() bool { return k.Status.ObservedGeneration < k.Generation } + +func DcAdded(oldSpec K8ssandraClusterSpec, newSpec K8ssandraClusterSpec) bool { + oldDcs := make([]string, 0) + for _, dc := range oldSpec.Cassandra.Datacenters { + oldDcs = append(oldDcs, dc.Meta.Name) + } + wasAdded := false + for _, dc := range newSpec.Cassandra.Datacenters { + if !utils.SliceContains(oldDcs, dc.Meta.Name) { + wasAdded = true + break + } + } + return wasAdded + +} + +func DcRemoved(oldSpec K8ssandraClusterSpec, newSpec K8ssandraClusterSpec) bool { + newDcs := make([]string, 0) + for _, dc := range newSpec.Cassandra.Datacenters { + newDcs = append(newDcs, dc.Meta.Name) + } + wasRemoved := false + for _, dc := range oldSpec.Cassandra.Datacenters { + if !utils.SliceContains(newDcs, dc.Meta.Name) { + wasRemoved = true + break + } + } + return wasRemoved +} diff --git a/apis/k8ssandra/v1alpha1/k8ssandracluster_types_test.go b/apis/k8ssandra/v1alpha1/k8ssandracluster_types_test.go index 154620021..e588cb487 100644 --- a/apis/k8ssandra/v1alpha1/k8ssandracluster_types_test.go +++ b/apis/k8ssandra/v1alpha1/k8ssandracluster_types_test.go @@ -10,6 +10,7 @@ import ( cassdcapi "github.com/k8ssandra/cass-operator/apis/cassandra/v1beta1" stargateapi "github.com/k8ssandra/k8ssandra-operator/apis/stargate/v1alpha1" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestK8ssandraCluster(t *testing.T) { @@ -215,3 +216,36 @@ func TestGenerationChanged(t *testing.T) { kc.ObjectMeta.Generation = 3 assert.True(kc.GenerationChanged()) } + +func TestDcRemoved(t *testing.T) { + kcOld := createClusterObjWithCassandraConfig("testcluster", "testns") + kcNew := kcOld.DeepCopy() + require.False(t, DcRemoved(kcOld.Spec, kcNew.Spec)) + kcOld.Spec.Cassandra.Datacenters = append(kcOld.Spec.Cassandra.Datacenters, CassandraDatacenterTemplate{ + Meta: EmbeddedObjectMeta{ + Name: "dc2", + }, + }) + require.True(t, DcRemoved(kcOld.Spec, kcNew.Spec)) + kcOld = createClusterObjWithCassandraConfig("testcluster", "testns") + kcNew = kcOld.DeepCopy() + kcNew.Spec.Cassandra.Datacenters[0].Meta.Name = "newName" + require.True(t, DcRemoved(kcOld.Spec, kcNew.Spec)) +} + +func TestDcAdded(t *testing.T) { + kcOld := createClusterObjWithCassandraConfig("testcluster", "testns") + kcNew := kcOld.DeepCopy() + require.False(t, DcAdded(kcOld.Spec, kcNew.Spec)) + kcNew.Spec.Cassandra.Datacenters = append(kcOld.Spec.Cassandra.Datacenters, CassandraDatacenterTemplate{ + Meta: EmbeddedObjectMeta{ + Name: "dc2", + }, + }) + require.True(t, DcAdded(kcOld.Spec, kcNew.Spec)) + + kcOld = createClusterObjWithCassandraConfig("testcluster", "testns") + kcNew = kcOld.DeepCopy() + kcNew.Spec.Cassandra.Datacenters[0].Meta.Name = "newName" + require.True(t, DcAdded(kcOld.Spec, kcNew.Spec)) +} diff --git a/apis/k8ssandra/v1alpha1/k8ssandracluster_webhook.go b/apis/k8ssandra/v1alpha1/k8ssandracluster_webhook.go index 4501ebae2..92043e3c7 100644 --- a/apis/k8ssandra/v1alpha1/k8ssandracluster_webhook.go +++ b/apis/k8ssandra/v1alpha1/k8ssandracluster_webhook.go @@ -18,9 +18,10 @@ package v1alpha1 import ( "fmt" + "strings" + "github.com/Masterminds/semver/v3" reaperapi "github.com/k8ssandra/k8ssandra-operator/apis/reaper/v1alpha1" - "strings" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/validation" @@ -172,7 +173,9 @@ func (r *K8ssandraCluster) ValidateUpdate(old runtime.Object) (admission.Warning if err := validateUpdateNumTokens(oldCluster.Spec.Cassandra, r.Spec.Cassandra); err != nil { return nil, err } - + if DcRemoved(oldCluster.Spec, r.Spec) && DcAdded(oldCluster.Spec, r.Spec) { + return nil, fmt.Errorf("renaming, as well as adding and removing DCs at the same time is prohibited as it can cause data loss") + } // Verify that the cluster name override was not changed if r.Spec.Cassandra.ClusterName != oldCluster.Spec.Cassandra.ClusterName { return nil, ErrClusterName diff --git a/apis/k8ssandra/v1alpha1/k8ssandracluster_webhook_test.go b/apis/k8ssandra/v1alpha1/k8ssandracluster_webhook_test.go index f39ad758f..570c7e772 100644 --- a/apis/k8ssandra/v1alpha1/k8ssandracluster_webhook_test.go +++ b/apis/k8ssandra/v1alpha1/k8ssandracluster_webhook_test.go @@ -20,12 +20,13 @@ import ( "context" "crypto/tls" "fmt" - "k8s.io/apimachinery/pkg/api/resource" "net" "path/filepath" "testing" "time" + "k8s.io/apimachinery/pkg/api/resource" + logrusr "github.com/bombsimon/logrusr/v2" "github.com/k8ssandra/cass-operator/apis/cassandra/v1beta1" "github.com/k8ssandra/k8ssandra-operator/pkg/clientcache" @@ -179,6 +180,7 @@ func TestWebhook(t *testing.T) { t.Run("MedusaConfigNonLocalNamespace", testMedusaNonLocalNamespace) t.Run("AutomatedUpdateAnnotation", testAutomatedUpdateAnnotation) t.Run("ReaperStorage", testReaperStorage) + t.Run("NoDCRename", testNoDCRename) } func testContextValidation(t *testing.T) { @@ -650,3 +652,11 @@ func testAutomatedUpdateAnnotation(t *testing.T) { cluster.Annotations[AutomatedUpdateAnnotation] = string("true") require.Error(cluster.validateK8ssandraCluster()) } + +func testNoDCRename(t *testing.T) { + kcOld := createClusterObjWithCassandraConfig("testcluster", "testns") + kcNew := kcOld.DeepCopy() + kcNew.Spec.Cassandra.Datacenters[0].Meta.Name = "newdc1name" + _, err := kcNew.ValidateUpdate(kcOld) + require.Error(t, err) +} diff --git a/controllers/k8ssandra/k8ssandracluster_controller.go b/controllers/k8ssandra/k8ssandracluster_controller.go index 36035e8f3..84cf7cc3f 100644 --- a/controllers/k8ssandra/k8ssandracluster_controller.go +++ b/controllers/k8ssandra/k8ssandracluster_controller.go @@ -119,6 +119,10 @@ func (r *K8ssandraClusterReconciler) reconcile(ctx context.Context, kc *api.K8ss return recResult.Output() } + if err := validateK8ssandraCluster(*kc); err != nil { + return reconcile.Result{}, err + } + if kc.Spec.Cassandra == nil { // TODO handle the scenario of CassandraClusterTemplate being set to nil after having a non-nil value return ctrl.Result{}, nil diff --git a/controllers/k8ssandra/k8ssandracluster_controller_test.go b/controllers/k8ssandra/k8ssandracluster_controller_test.go index 96f7cb39b..cad05d642 100644 --- a/controllers/k8ssandra/k8ssandracluster_controller_test.go +++ b/controllers/k8ssandra/k8ssandracluster_controller_test.go @@ -116,7 +116,7 @@ func TestK8ssandraCluster(t *testing.T) { t.Run("ApplyClusterWithEncryptionOptionsExternalSecrets", testEnv.ControllerTest(ctx, applyClusterWithEncryptionOptionsExternalSecrets)) t.Run("StopDatacenter", testEnv.ControllerTest(ctx, stopDc)) t.Run("ConvertSystemReplicationAnnotation", testEnv.ControllerTest(ctx, convertSystemReplicationAnnotation)) - t.Run("ChangeClusterNameFails", testEnv.ControllerTest(ctx, changeClusterNameFails)) + t.Run("ChangeClusterDcNameFails", testEnv.ControllerTest(ctx, changeClusterDcNameFails)) t.Run("InjectContainersAndVolumes", testEnv.ControllerTest(ctx, injectContainersAndVolumes)) t.Run("CreateMultiDcDseCluster", testEnv.ControllerTest(ctx, createMultiDcDseCluster)) t.Run("PerNodeConfiguration", testEnv.ControllerTest(ctx, perNodeConfiguration)) @@ -2334,7 +2334,7 @@ func convertSystemReplicationAnnotation(t *testing.T, ctx context.Context, f *fr // Create a cluster with server and client encryption but client encryption stores missing. // Verify that dc1 never gets created. -func changeClusterNameFails(t *testing.T, ctx context.Context, f *framework.Framework, namespace string) { +func changeClusterDcNameFails(t *testing.T, ctx context.Context, f *framework.Framework, namespace string) { require := require.New(t) clusterName := "cluster-with-encryption" @@ -2366,6 +2366,13 @@ func changeClusterNameFails(t *testing.T, ctx context.Context, f *framework.Fram K8sContext: f.DataPlaneContexts[0], Size: dc1Size, }, + { + Meta: api.EmbeddedObjectMeta{ + Name: "dc2", + }, + K8sContext: f.DataPlaneContexts[0], + Size: dc1Size, + }, }, ServerEncryptionStores: &encryption.Stores{ KeystoreSecretRef: &encryption.SecretKeySelector{LocalObjectReference: corev1.LocalObjectReference{ @@ -2398,7 +2405,12 @@ func changeClusterNameFails(t *testing.T, ctx context.Context, f *framework.Fram err = f.Client.Get(ctx, client.ObjectKey{Namespace: namespace, Name: clusterName}, k8c) require.NoError(err, "failed to get K8ssandraCluster") + k8c.Spec.Cassandra.Datacenters[0].Meta.Name = "newDC1" + err = f.Client.Update(ctx, k8c) + require.Error(err, "failed to update K8ssandraCluster") + // Change the cluster name + k8c.Spec.Cassandra.Datacenters[0].Meta.Name = "dc1" k8c.Spec.Cassandra.ClusterName = newClusterName err = f.Client.Update(ctx, k8c) require.Error(err, "failed to update K8ssandraCluster") diff --git a/controllers/k8ssandra/validate_cluster_update.go b/controllers/k8ssandra/validate_cluster_update.go new file mode 100644 index 000000000..a349e25a7 --- /dev/null +++ b/controllers/k8ssandra/validate_cluster_update.go @@ -0,0 +1,37 @@ +package k8ssandra + +import ( + "fmt" + + api "github.com/k8ssandra/k8ssandra-operator/apis/k8ssandra/v1alpha1" + "github.com/k8ssandra/k8ssandra-operator/pkg/utils" +) + +func validateK8ssandraCluster(kc api.K8ssandraCluster) error { + oldDCs := make([]string, 0) + for dcName := range kc.Status.Datacenters { + oldDCs = append(oldDCs, dcName) + } + newDcs := make([]string, 0) + for _, dc := range kc.Spec.Cassandra.Datacenters { + newDcs = append(newDcs, dc.Meta.Name) + } + wasAdded := false + wasRemoved := false + for _, dc := range kc.Spec.Cassandra.Datacenters { + if !utils.SliceContains(oldDCs, dc.Meta.Name) { + wasAdded = true + break + } + } + for dcName := range kc.Status.Datacenters { + if !utils.SliceContains(newDcs, dcName) { + wasRemoved = true + break + } + } + if wasAdded && wasRemoved { + return fmt.Errorf("cannot add and remove datacenters at the same time") + } + return nil +} From bf5c40c684f92400d8ab9e3ba4b9f2e29e5ea6d4 Mon Sep 17 00:00:00 2001 From: Alexander Dejanovski Date: Mon, 7 Oct 2024 15:29:20 +0200 Subject: [PATCH 6/8] Update changelog for v1.20.2 --- CHANGELOG/CHANGELOG-1.20.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG/CHANGELOG-1.20.md b/CHANGELOG/CHANGELOG-1.20.md index f232c12d5..9b4ebf00e 100644 --- a/CHANGELOG/CHANGELOG-1.20.md +++ b/CHANGELOG/CHANGELOG-1.20.md @@ -13,13 +13,13 @@ Changelog for the K8ssandra Operator, new PRs should update the `unreleased` sec When cutting a new release, update the `unreleased` heading to the tag being generated and date, like `## vX.Y.Z - YYYY-MM-DD` and create a new placeholder section for `unreleased` entries. -## unreleased +## v1.20.2 - 2024-10-07 * [DOCS] [#1469](https://github.com/riptano/mission-control/issues/1469) Add docs for Reaper's Control Plane deployment mode * [CHANGE] Bump default Medusa version to 0.22.3 * [BUGFIX] [#1409](https://github.com/k8ssandra/k8ssandra-operator/issues/1409) Vector would crash in the Cassandra log parsing if empty lines were present. Add automated tests for Vector parsing rules. * [BUGFIX] [#1425](https://github.com/k8ssandra/k8ssandra-operator/issues/1425) prepare-helm-release.sh requires kustomize to be in the path and that makes make manifests fail. - +* [BUGFIX] [#1422](https://github.com/k8ssandra/k8ssandra-operator/issues/1422) Changing a DC name should be forbidden ## v1.20.1 - 2024-09-19 From 8ca22ab1e62cd1af679da9cc5ecd348febd3baea Mon Sep 17 00:00:00 2001 From: Alexander Dejanovski Date: Mon, 7 Oct 2024 15:29:48 +0200 Subject: [PATCH 7/8] Release v1.20.2 --- charts/k8ssandra-operator/Chart.yaml | 2 +- config/deployments/default/kustomization.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/k8ssandra-operator/Chart.yaml b/charts/k8ssandra-operator/Chart.yaml index c09d7e485..fcfc857cb 100644 --- a/charts/k8ssandra-operator/Chart.yaml +++ b/charts/k8ssandra-operator/Chart.yaml @@ -3,7 +3,7 @@ name: k8ssandra-operator description: | Kubernetes operator which handles the provisioning and management of K8ssandra clusters. type: application -version: 1.20.2-SNAPSHOT +version: 1.20.2 dependencies: - name: k8ssandra-common version: 0.29.0 diff --git a/config/deployments/default/kustomization.yaml b/config/deployments/default/kustomization.yaml index bec55fd12..535211c0b 100644 --- a/config/deployments/default/kustomization.yaml +++ b/config/deployments/default/kustomization.yaml @@ -5,4 +5,4 @@ resources: - ../../default images: - name: cr.k8ssandra.io/k8ssandra/k8ssandra-operator - newTag: 1.20-latest + newTag: v1.20.2 From 6b4347d8666d7b983d7e237360cec4f2e4b250ac Mon Sep 17 00:00:00 2001 From: Alexander Dejanovski Date: Mon, 7 Oct 2024 17:42:52 +0200 Subject: [PATCH 8/8] Prepare next patch release --- charts/k8ssandra-operator/Chart.yaml | 2 +- config/deployments/default/kustomization.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/k8ssandra-operator/Chart.yaml b/charts/k8ssandra-operator/Chart.yaml index fcfc857cb..509d2d43d 100644 --- a/charts/k8ssandra-operator/Chart.yaml +++ b/charts/k8ssandra-operator/Chart.yaml @@ -3,7 +3,7 @@ name: k8ssandra-operator description: | Kubernetes operator which handles the provisioning and management of K8ssandra clusters. type: application -version: 1.20.2 +version: 1.20.3-SNAPSHOT dependencies: - name: k8ssandra-common version: 0.29.0 diff --git a/config/deployments/default/kustomization.yaml b/config/deployments/default/kustomization.yaml index 535211c0b..bec55fd12 100644 --- a/config/deployments/default/kustomization.yaml +++ b/config/deployments/default/kustomization.yaml @@ -5,4 +5,4 @@ resources: - ../../default images: - name: cr.k8ssandra.io/k8ssandra/k8ssandra-operator - newTag: v1.20.2 + newTag: 1.20-latest