diff --git a/.dockerignore b/.dockerignore index 7c504700d..da2dbb715 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,7 @@ # Temporary Build Files build/_output build/_test +catalog/ # Created by https://www.gitignore.io/api/go,vim,emacs,visualstudiocode ### Emacs ### # -*- mode: gitignore; -*- diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 6c31783de..70bac6e19 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -113,7 +113,6 @@ jobs: make manifests make api-gen make docs - make olm # check for uncommited changes to crds, docs or API git diff --exit-code make test diff --git a/.github/workflows/operatorhub.yaml b/.github/workflows/operatorhub.yaml index cba21058c..c87bce619 100644 --- a/.github/workflows/operatorhub.yaml +++ b/.github/workflows/operatorhub.yaml @@ -38,7 +38,7 @@ jobs: repository: ${{ matrix.repo.upstream }} ref: main token: ${{ secrets.VM_BOT_GH_TOKEN }} - path: __k8s-operatorhub-repo + path: __operatorhub-repo - name: Import GPG key uses: crazy-max/ghaction-import-gpg@v6 @@ -48,16 +48,24 @@ jobs: passphrase: ${{ secrets.VM_BOT_PASSPHRASE }} git_user_signingkey: true git_commit_gpgsign: true - workdir: __k8s-operatorhub-repo + workdir: __operatorhub-repo - uses: dawidd6/action-download-artifact@v11 with: name: olm - workflow: main.yaml + workflow: release.yaml github_token: ${{ secrets.VM_BOT_GH_TOKEN }} run_id: ${{ github.event.workflow_run.id }} path: bundle + - name: Install opm + run: | + OPM_VERSION=v1.65.0 + curl -fsSLO https://github.com/operator-framework/operator-registry/releases/download/${OPM_VERSION}/linux-amd64-opm + curl -fsSLO https://github.com/operator-framework/operator-registry/releases/download/${OPM_VERSION}/checksums.txt + grep ' linux-amd64-opm$' checksums.txt | sha256sum -c - + install -m 0755 linux-amd64-opm /usr/local/bin/opm + - name: Add operatorhub bundle id: update run: | @@ -65,41 +73,47 @@ jobs: echo "No bundle directory found" exit 1; fi - OPERATOR_DIR=__k8s-operatorhub-repo/operators/victoriametrics-operator - CATALOGS_DIR=__k8s-operatorhub-repo/catalogs + CATALOGS_DIR=__operatorhub-repo/catalogs + NEW_VERSION=$(ls bundle | head -1) + CATALOG_FILE=${CATALOGS_DIR}/latest/victoriametrics-operator/catalog.yaml + mkdir -p ${CATALOGS_DIR}/latest/victoriametrics-operator - mkdir -p ${CATALOGS_DIR} - mkdir -p ${OPERATOR_DIR} + # Save existing catalog as PREV_CATALOG before overwriting + if [ -f "${CATALOG_FILE}" ]; then + cp "${CATALOG_FILE}" /tmp/vm-prev-catalog.yaml + PREV_CATALOG=/tmp/vm-prev-catalog.yaml + fi - export OLD_VERSION=$(find ${OPERATOR_DIR}/* ! -path "*/catalog-templates" -maxdepth 0 -type d -exec basename {} \; | sort -V -r | head -1) - export OLD_ENTRY="victoriametrics-operator.v${OLD_VERSION}" + PREVIOUS_VERSION=$(yq 'select(.schema == "olm.channel") | .entries[0].name' \ + ${PREV_CATALOG} 2>/dev/null || true) - export NEW_VERSION=$(ls bundle | head -1) + opm render bundle/${NEW_VERSION} --output=yaml > /tmp/vm-bundle-rendered.yaml + yq 'select(.schema == "olm.package")' "${PREV_CATALOG}" > /tmp/vm-catalog-header.yaml + echo "---" >> /tmp/vm-catalog-header.yaml + yq '.entries[] | select(.schema == "olm.channel")' \ + bundle/${NEW_VERSION}/catalog-templates/latest.yaml >> /tmp/vm-catalog-header.yaml - if [ ! -z $OLD_VERSION ]; then - export MANIFEST_PATH=bundle/${NEW_VERSION}/manifests/victoriametrics-operator.clusterserviceversion.yaml - yq -i '.spec.replaces = "victoriametrics-operator.v" + strenv(OLD_VERSION)' $MANIFEST_PATH - fi + # Include previous bundles from PREV_CATALOG (excludes current version to avoid duplicates) + yq "select(.schema == \"olm.bundle\" and .name != \"victoriametrics-operator.v${NEW_VERSION}\")" \ + "${PREV_CATALOG}" > /tmp/vm-prev-bundles.yaml + yq eval-all '.' /tmp/vm-catalog-header.yaml /tmp/vm-bundle-rendered.yaml /tmp/vm-prev-bundles.yaml > ${CATALOG_FILE} - mv bundle/* ${OPERATOR_DIR}/ - if [ -f ${OPERATOR_DIR}/Makefile ]; then - if [ ! -z $OLD_VERSION ]; then - yq -i -I2 '.catalog_templates.[].replaces = strenv(OLD_ENTRY)' ${OPERATOR_DIR}/${NEW_VERSION}/release-config.yaml - fi - else - rm -f ${OPERATOR_DIR}/${NEW_VERSION}/release-config.yaml + if [ -n "${PREVIOUS_VERSION}" ]; then + yq -i '(select(.schema == "olm.channel") | .entries[0]).replaces = strenv(PREVIOUS_VERSION)' ${CATALOG_FILE} fi + opm validate ${CATALOGS_DIR}/latest/victoriametrics-operator + echo "VERSION=$NEW_VERSION" >> $GITHUB_OUTPUT - name: Create Pull Request if: ${{ steps.update.outputs.VERSION != '' }} uses: peter-evans/create-pull-request@v7 with: - add-paths: operators/victoriametrics-operator,catalogs + add-paths: catalogs commit-message: 'victoriametrics-operator: ${{ steps.update.outputs.VERSION }}' signoff: true committer: "Github Actions <${{ steps.import-gpg.outputs.email }}>" - path: __k8s-operatorhub-repo + path: __operatorhub-repo push-to-fork: ${{ matrix.repo.fork }} branch: vm-operator-release-${{ steps.update.outputs.VERSION }} token: ${{ secrets.VM_BOT_GH_TOKEN }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 5160ee9d4..7f27e0853 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -37,7 +37,7 @@ jobs: make lint test build build-installer echo ${{secrets.REPO_KEY}} | docker login --username ${{secrets.REPO_USER}} --password-stdin echo ${{secrets.QUAY_ACCESSKEY}} | docker login quay.io --username '${{secrets.QUAY_USER}}' --password-stdin - make publish + TAG=${TAG} make publish TAG=${TAG} REGISTRY=quay.io make olm gh release upload ${{github.event.release.tag_name}} ./dist/install-no-webhook.yaml#install-no-webhook.yaml --clobber || echo "fix me NOT enough security permissions" gh release upload ${{github.event.release.tag_name}} ./dist/install-with-webhook.yaml#install-with-webhook.yaml --clobber || echo "fix me NOT enough security permissions" diff --git a/.gitignore b/.gitignore index 9b9e1e387..0496def64 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ report.xml /bin/ /build* /bundle* +/catalog* release operator.zip coverage.txt diff --git a/Makefile b/Makefile index 4a8139e4e..9f03b021f 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,7 @@ DATEINFO_TAG ?= $(shell date -u +'%Y%m%d-%H%M%S') NAMESPACE ?= vm OVERLAY ?= config/manager E2E_TESTS_CONCURRENCY ?= $(shell getconf _NPROCESSORS_ONLN) +E2E_TARGET ?= ./test/e2e/... FIPS_VERSION=v1.0.0 BASEIMAGE ?=scratch @@ -160,26 +161,18 @@ test: manifests generate fmt vet envtest ## Run tests. # Utilize Kind or modify the e2e tests to load the image locally, enabling compatibility with other vendors. .PHONY: test-e2e # Run the e2e tests against a Kind k8s instance that is spun up. -BASE_REF ?= origin/master -SKIP_UPGRADE_TESTS ?= $(shell if git diff --quiet $(BASE_REF)...HEAD -- test/e2e/upgrade 2>/dev/null; then echo "--skip-package=upgrade"; fi) - test-e2e: load-kind ginkgo crust-gather mirrord env CGO_ENABLED=1 OPERATOR_IMAGE=$(OPERATOR_IMAGE) REPORTS_DIR=$(shell pwd) CRUST_GATHER_BIN=$(CRUST_GATHER_BIN) $(MIRRORD_BIN) exec -f ./mirrord.json -- $(GINKGO_BIN) \ -ldflags="-linkmode=external" \ - $(SKIP_UPGRADE_TESTS) \ + --output-interceptor-mode=none \ -procs=$(E2E_TESTS_CONCURRENCY) \ -randomize-all \ -timeout=60m \ - -junit-report=report.xml ./test/e2e/... + -junit-report=report.xml $(E2E_TARGET) .PHONY: test-e2e-upgrade # Run only the e2e upgrade tests against a Kind k8s instance that is spun up. -test-e2e-upgrade: load-kind ginkgo crust-gather mirrord - env CGO_ENABLED=1 OPERATOR_IMAGE=$(OPERATOR_IMAGE) REPORTS_DIR=$(shell pwd) CRUST_GATHER_BIN=$(CRUST_GATHER_BIN) $(MIRRORD_BIN) exec -f ./mirrord.json -- $(GINKGO_BIN) \ - -ldflags="-linkmode=external" \ - -procs=$(E2E_TESTS_CONCURRENCY) \ - -randomize-all \ - -timeout=60m \ - -junit-report=report.xml ./test/e2e/upgrade/... +test-e2e-upgrade: E2E_TARGET=./test/e2e/upgrade/... +test-e2e-upgrade: test-e2e .PHONY: lint lint: golangci-lint ## Run golangci-lint linter @@ -281,7 +274,7 @@ build-installer: manifests generate kustomize ## Generate a consolidated YAML wi olm: operator-sdk opm yq docs $(eval DIGEST = $(shell $(CONTAINER_TOOL) buildx imagetools inspect $(REGISTRY)/$(ORG)/$(REPO):$(TAG)-ubi --format "{{print .Manifest.Digest}}")) - rm -rf bundle* + rm -rf bundle* catalog $(OPERATOR_SDK) generate kustomize manifests -q cd config/manifests && \ $(KUSTOMIZE) edit set image manager=$(REGISTRY)/$(ORG)/$(REPO)@$(DIGEST) @@ -289,17 +282,29 @@ olm: operator-sdk opm yq docs -q --overwrite --version $(VERSION) \ --channels=beta --default-channel=beta --output-dir=bundle/$(VERSION) $(OPERATOR_SDK) bundle validate ./bundle/$(VERSION) - cp config/manifests/release-config.yaml bundle/$(VERSION)/ - $(YQ) -i '.metadata.annotations.containerImage = "$(REGISTRY)/$(ORG)/$(REPO)@$(DIGEST)"' \ - bundle/$(VERSION)/manifests/victoriametrics-operator.clusterserviceversion.yaml - $(YQ) -i '.spec.install.spec.deployments[0].spec.template.containers[0].image = "$(REGISTRY)/$(ORG)/$(REPO)@$(DIGEST)"' \ - bundle/$(VERSION)/manifests/victoriametrics-operator.clusterserviceversion.yaml - $(YQ) -i '.spec.install.spec.deployments[0].spec.template.spec.containers[0].image = "$(REGISTRY)/$(ORG)/$(REPO)@$(DIGEST)"' \ - bundle/$(VERSION)/manifests/victoriametrics-operator.clusterserviceversion.yaml - $(YQ) -i '.spec.relatedImages = [{"name": "victoriametrics-operator", "image": "$(REGISTRY)/$(ORG)/$(REPO)@$(DIGEST)"}]' \ + $(YQ) -i '.metadata.annotations.containerImage = "$(REGISTRY)/$(ORG)/$(REPO)@$(DIGEST)" | .spec.install.spec.deployments[0].spec.template.containers[0].image = "$(REGISTRY)/$(ORG)/$(REPO)@$(DIGEST)" | .spec.install.spec.deployments[0].spec.template.spec.containers[0].image = "$(REGISTRY)/$(ORG)/$(REPO)@$(DIGEST)"' \ bundle/$(VERSION)/manifests/victoriametrics-operator.clusterserviceversion.yaml $(YQ) -i '.annotations."com.redhat.openshift.versions" = "v4.12-v4.21"' \ bundle/$(VERSION)/metadata/annotations.yaml + mkdir -p bundle/$(VERSION)/catalog-templates catalog/latest + $(YQ) '.entries[] | select(.schema == "olm.channel") | .entries[] | select(.name != "victoriametrics-operator.v$(VERSION)") | .name' config/manifests/catalog-templates/latest.yaml > /tmp/vm-prev-names.txt + $(YQ) '.entries[] | select(.schema == "olm.channel") | .entries[] | select(.name != "victoriametrics-operator.v$(VERSION)") | .replaces | select(.)' config/manifests/catalog-templates/latest.yaml > /tmp/vm-prev-replaces.txt + PREV_HEAD=$$(grep -Fxvf /tmp/vm-prev-replaces.txt /tmp/vm-prev-names.txt | head -1); \ + test -n "$$PREV_HEAD" || { echo "Error: could not determine previous channel head from catalog template"; exit 1; }; \ + PREV_HEAD="$$PREV_HEAD" $(YQ) -i '(.entries[] | select(.schema == "olm.channel")).entries = [{"name": "victoriametrics-operator.v$(VERSION)", "replaces": strenv(PREV_HEAD)}] + (.entries[] | select(.schema == "olm.channel") | .entries | map(select(.name != "victoriametrics-operator.v$(VERSION)")))' \ + config/manifests/catalog-templates/latest.yaml; \ + PREV_HEAD="$$PREV_HEAD" $(YQ) -i '.spec.relatedImages = [{"name": "victoriametrics-operator", "image": "$(REGISTRY)/$(ORG)/$(REPO)@$(DIGEST)"}] | .spec.replaces = strenv(PREV_HEAD)' \ + bundle/$(VERSION)/manifests/victoriametrics-operator.clusterserviceversion.yaml + cp config/manifests/catalog-templates/latest.yaml bundle/$(VERSION)/catalog-templates/latest.yaml + { $(YQ) '.entries[] | select(.schema == "olm.package")' \ + bundle/$(VERSION)/catalog-templates/latest.yaml; \ + echo "---"; \ + $(YQ) '(.entries[] | select(.schema == "olm.channel")) | .entries = [.entries[0]]' \ + bundle/$(VERSION)/catalog-templates/latest.yaml; \ + $(OPM) render bundle/$(VERSION) --output=yaml | \ + $(YQ) '(select(.schema == "olm.bundle") | .image) = "$(REGISTRY)/$(ORG)/$(REPO)@$(DIGEST)" | (select(.schema == "olm.bundle") | .relatedImages) = [{"name": "victoriametrics-operator", "image": "$(REGISTRY)/$(ORG)/$(REPO)@$(DIGEST)"}]'; \ + } > catalog/latest/catalog.yaml + $(OPM) validate catalog/latest ##@ Deployment diff --git a/api/operator/v1/cluster_types_test.go b/api/operator/v1/cluster_types_test.go new file mode 100644 index 000000000..0b3e53871 --- /dev/null +++ b/api/operator/v1/cluster_types_test.go @@ -0,0 +1,78 @@ +package v1 + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "k8s.io/utils/ptr" + + vmv1beta1 "github.com/VictoriaMetrics/operator/api/operator/v1beta1" +) + +func TestVTCluster_AvailableStorageNodeIDs(t *testing.T) { + f := func(cr *VTCluster, requestsType string, want []int32) { + t.Helper() + assert.Equal(t, want, cr.AvailableStorageNodeIDs(requestsType)) + } + + cr := &VTCluster{ + Spec: VTClusterSpec{ + Storage: &VTStorage{ + CommonAppsParams: vmv1beta1.CommonAppsParams{ + ReplicaCount: ptr.To(int32(5)), + }, + MaintenanceSelectNodeIDs: []int32{1, 3}, + MaintenanceInsertNodeIDs: []int32{0, 4}, + }, + }, + } + + // select excludes maintenance nodes + f(cr, "select", []int32{0, 2, 4}) + + // insert excludes maintenance nodes + f(cr, "insert", []int32{1, 2, 3}) + + // no maintenance nodes + f(&VTCluster{ + Spec: VTClusterSpec{ + Storage: &VTStorage{ + CommonAppsParams: vmv1beta1.CommonAppsParams{ReplicaCount: ptr.To(int32(3))}, + }, + }, + }, "select", []int32{0, 1, 2}) +} + +func TestVLCluster_AvailableStorageNodeIDs(t *testing.T) { + f := func(cr *VLCluster, requestsType string, want []int32) { + t.Helper() + assert.Equal(t, want, cr.AvailableStorageNodeIDs(requestsType)) + } + + cr := &VLCluster{ + Spec: VLClusterSpec{ + VLStorage: &VLStorage{ + CommonAppsParams: vmv1beta1.CommonAppsParams{ + ReplicaCount: ptr.To(int32(5)), + }, + MaintenanceSelectNodeIDs: []int32{1, 3}, + MaintenanceInsertNodeIDs: []int32{0, 4}, + }, + }, + } + + // select excludes maintenance nodes + f(cr, "select", []int32{0, 2, 4}) + + // insert excludes maintenance nodes + f(cr, "insert", []int32{1, 2, 3}) + + // no maintenance nodes + f(&VLCluster{ + Spec: VLClusterSpec{ + VLStorage: &VLStorage{ + CommonAppsParams: vmv1beta1.CommonAppsParams{ReplicaCount: ptr.To(int32(3))}, + }, + }, + }, "select", []int32{0, 1, 2}) +} diff --git a/api/operator/v1/vlcluster_types.go b/api/operator/v1/vlcluster_types.go index 903903c69..02f2db222 100644 --- a/api/operator/v1/vlcluster_types.go +++ b/api/operator/v1/vlcluster_types.go @@ -781,21 +781,17 @@ func (cr *VLCluster) AvailableStorageNodeIDs(requestsType string) []int32 { if cr.Spec.VLStorage == nil || cr.Spec.VLStorage.ReplicaCount == nil { return result } - maintenanceNodes := make(map[int32]struct{}) + maintenanceNodes := sets.New[int32]() switch requestsType { case "select": - for _, i := range cr.Spec.VLStorage.MaintenanceSelectNodeIDs { - maintenanceNodes[i] = struct{}{} - } + maintenanceNodes.Insert(cr.Spec.VLStorage.MaintenanceSelectNodeIDs...) case "insert": - for _, i := range cr.Spec.VLStorage.MaintenanceInsertNodeIDs { - maintenanceNodes[i] = struct{}{} - } + maintenanceNodes.Insert(cr.Spec.VLStorage.MaintenanceInsertNodeIDs...) default: panic("BUG unsupported requestsType: " + requestsType) } for i := int32(0); i < *cr.Spec.VLStorage.ReplicaCount; i++ { - if _, ok := maintenanceNodes[i]; ok { + if maintenanceNodes.Has(i) { continue } result = append(result, i) diff --git a/api/operator/v1/vtcluster_types.go b/api/operator/v1/vtcluster_types.go index 8a8c9121f..2a3323d6b 100644 --- a/api/operator/v1/vtcluster_types.go +++ b/api/operator/v1/vtcluster_types.go @@ -692,21 +692,17 @@ func (cr *VTCluster) AvailableStorageNodeIDs(requestsType string) []int32 { if cr.Spec.Storage == nil || cr.Spec.Storage.ReplicaCount == nil { return result } - maintenanceNodes := make(map[int32]struct{}) + maintenanceNodes := sets.New[int32]() switch requestsType { case "select": - for _, i := range cr.Spec.Storage.MaintenanceSelectNodeIDs { - maintenanceNodes[i] = struct{}{} - } + maintenanceNodes.Insert(cr.Spec.Storage.MaintenanceSelectNodeIDs...) case "insert": - for _, i := range cr.Spec.Storage.MaintenanceInsertNodeIDs { - maintenanceNodes[i] = struct{}{} - } + maintenanceNodes.Insert(cr.Spec.Storage.MaintenanceInsertNodeIDs...) default: panic("BUG unsupported requestsType: " + requestsType) } for i := int32(0); i < *cr.Spec.Storage.ReplicaCount; i++ { - if _, ok := maintenanceNodes[i]; ok { + if maintenanceNodes.Has(i) { continue } result = append(result, i) diff --git a/api/operator/v1alpha1/vmdistributed_types.go b/api/operator/v1alpha1/vmdistributed_types.go index 213dd17e2..2e27b964f 100644 --- a/api/operator/v1alpha1/vmdistributed_types.go +++ b/api/operator/v1alpha1/vmdistributed_types.go @@ -24,6 +24,7 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" @@ -433,9 +434,9 @@ func (cr *VMDistributedSpec) UnmarshalJSON(src []byte) error { // Validate validates the VMDistributed resource func (cr *VMDistributed) Validate() error { - zones := make(map[string]struct{}) - clusters := make(map[string]struct{}) - agents := make(map[string]struct{}) + zones := sets.New[string]() + clusters := sets.New[string]() + agents := sets.New[string]() spec := cr.Spec hasCommonVMInsert := cr.Spec.ZoneCommon.VMCluster.Spec.VMInsert != nil hasCommonVMSelect := cr.Spec.ZoneCommon.VMCluster.Spec.VMSelect != nil @@ -444,23 +445,23 @@ func (cr *VMDistributed) Validate() error { if len(zone.Name) == 0 { return fmt.Errorf("spec.zones[%d].name is required", i) } - if _, ok := zones[zone.Name]; ok { + if zones.Has(zone.Name) { return fmt.Errorf("spec.zones[%d].name=%s is duplicated, zone names must be unique", i, zone.Name) } - zones[zone.Name] = struct{}{} + zones.Insert(zone.Name) clusterName := zone.VMClusterName(cr) agentName := zone.VMAgentName(cr) if len(clusterName) > 0 { - if _, ok := clusters[clusterName]; ok { + if clusters.Has(clusterName) { return fmt.Errorf("spec.zones[%d].vmcluster.name=%s is already added in a different zone", i, clusterName) } - clusters[clusterName] = struct{}{} + clusters.Insert(clusterName) } if len(agentName) > 0 { - if _, ok := agents[agentName]; ok { + if agents.Has(agentName) { return fmt.Errorf("spec.zones[%d].vmagent.name=%s is already added in a different zone", i, agentName) } - agents[agentName] = struct{}{} + agents.Insert(agentName) } if zone.VMAgent.Spec.StatefulMode { if zone.VMAgent.Spec.StatefulRollingUpdateStrategyBehavior != nil { diff --git a/api/operator/v1beta1/vmagent_types.go b/api/operator/v1beta1/vmagent_types.go index 519aec153..4c7cd5890 100644 --- a/api/operator/v1beta1/vmagent_types.go +++ b/api/operator/v1beta1/vmagent_types.go @@ -10,6 +10,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -177,10 +178,10 @@ func (cr *VMAgent) Validate() error { return fmt.Errorf("enableKubernetesAPISelectors cannot be used with daemonSetMode") } } - scrapeClassNames := make(map[string]struct{}) + scrapeClassNames := sets.New[string]() defaultScrapeClass := false for _, sc := range cr.Spec.ScrapeClasses { - if _, ok := scrapeClassNames[sc.Name]; ok { + if scrapeClassNames.Has(sc.Name) { return fmt.Errorf("duplicated scrapeClass=%q", sc.Name) } if ptr.Deref(sc.Default, false) { @@ -203,6 +204,7 @@ func (cr *VMAgent) Validate() error { if err := sc.validate(); err != nil { return fmt.Errorf("incorrect relabeling for scrapeClass=%q: %w", sc.Name, err) } + scrapeClassNames.Insert(sc.Name) } return nil } diff --git a/api/operator/v1beta1/vmagent_types_test.go b/api/operator/v1beta1/vmagent_types_test.go index 48455a5db..08d5a101d 100644 --- a/api/operator/v1beta1/vmagent_types_test.go +++ b/api/operator/v1beta1/vmagent_types_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "k8s.io/utils/ptr" ) func TestVMAgent_Validate(t *testing.T) { @@ -54,6 +55,28 @@ func TestVMAgent_Validate(t *testing.T) { }, }, false) + // duplicate scrape class names + f(VMAgentSpec{ + RemoteWrite: []VMAgentRemoteWriteSpec{{URL: "http://some-rw"}}, + CommonScrapeParams: CommonScrapeParams{ + ScrapeClasses: []ScrapeClass{ + {Name: "class-a"}, + {Name: "class-a"}, + }, + }, + }, true) + + // multiple default scrape classes + f(VMAgentSpec{ + RemoteWrite: []VMAgentRemoteWriteSpec{{URL: "http://some-rw"}}, + CommonScrapeParams: CommonScrapeParams{ + ScrapeClasses: []ScrapeClass{ + {Name: "class-a", Default: ptr.To(true)}, + {Name: "class-b", Default: ptr.To(true)}, + }, + }, + }, true) + // relabeling with if array f(VMAgentSpec{ RemoteWrite: []VMAgentRemoteWriteSpec{{URL: "http://some-rw"}}, diff --git a/api/operator/v1beta1/vmalertmanagerconfig_types.go b/api/operator/v1beta1/vmalertmanagerconfig_types.go index 09fb05744..7d4adf35e 100644 --- a/api/operator/v1beta1/vmalertmanagerconfig_types.go +++ b/api/operator/v1beta1/vmalertmanagerconfig_types.go @@ -36,6 +36,7 @@ import ( corev1 "k8s.io/api/core/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" ) // VMAlertmanagerConfigSpec defines configuration for VMAlertmanagerConfig @@ -121,12 +122,12 @@ func (r *VMAlertmanagerConfig) Validate() error { if MustSkipCRValidation(r) { return nil } - receivers := make(map[string]struct{}) + receivers := sets.New[string]() for idx, recv := range r.Spec.Receivers { - if _, ok := receivers[recv.Name]; ok { + if receivers.Has(recv.Name) { return fmt.Errorf("notification config name %q is not unique", recv.Name) } - receivers[recv.Name] = struct{}{} + receivers.Insert(recv.Name) if err := validateReceiver(recv); err != nil { return fmt.Errorf("receiver at idx=%d is invalid: %w", idx, err) } @@ -1506,17 +1507,17 @@ func parseTime(in string) (mins int, err error) { return mins, nil } -func validateTimeIntervals(timeIntervals []TimeIntervals) (map[string]struct{}, error) { - timeIntervalNames := make(map[string]struct{}, len(timeIntervals)) +func validateTimeIntervals(timeIntervals []TimeIntervals) (sets.Set[string], error) { + timeIntervalNames := sets.New[string]() for idx, ti := range timeIntervals { if err := validateTimeIntervalsEntry(&ti); err != nil { return nil, fmt.Errorf("time interval at idx=%d is invalid: %w", idx, err) } - if _, ok := timeIntervalNames[ti.Name]; ok { + if timeIntervalNames.Has(ti.Name) { return nil, fmt.Errorf("time interval at idx=%d is not unique with name=%q", idx, ti.Name) } - timeIntervalNames[ti.Name] = struct{}{} + timeIntervalNames.Insert(ti.Name) } return timeIntervalNames, nil } @@ -1527,21 +1528,21 @@ var opsgenieTypeMatcher = regexp.MustCompile(opsgenieValidTypesRe) // checkRouteReceiver returns an error if a node in the routing tree // references a receiver not in the given map. -func checkRouteReceiver(r *SubRoute, receivers map[string]struct{}, tiNames map[string]struct{}) error { +func checkRouteReceiver(r *SubRoute, receivers sets.Set[string], tiNames sets.Set[string]) error { for _, ti := range r.ActiveTimeIntervals { - if _, ok := tiNames[ti]; !ok { + if !tiNames.Has(ti) { return fmt.Errorf("undefined time interval %q used in route", ti) } } for _, ti := range r.MuteTimeIntervals { - if _, ok := tiNames[ti]; !ok { + if !tiNames.Has(ti) { return fmt.Errorf("undefined time interval %q used in route", ti) } } if r.Receiver == "" { return nil } - if _, ok := receivers[r.Receiver]; !ok { + if !receivers.Has(r.Receiver) { return fmt.Errorf("undefined receiver %q used in route", r.Receiver) } for idx, sr := range r.Routes { diff --git a/api/operator/v1beta1/vmcluster_types.go b/api/operator/v1beta1/vmcluster_types.go index 3752a2bdf..0f4f72c98 100644 --- a/api/operator/v1beta1/vmcluster_types.go +++ b/api/operator/v1beta1/vmcluster_types.go @@ -9,6 +9,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -719,21 +720,17 @@ func (cr *VMCluster) AvailableStorageNodeIDs(requestsType string) []int32 { if cr.Spec.VMStorage == nil || cr.Spec.VMStorage.ReplicaCount == nil { return result } - maintenanceNodes := make(map[int32]struct{}) + maintenanceNodes := sets.New[int32]() switch requestsType { case "select": - for _, i := range cr.Spec.VMStorage.MaintenanceSelectNodeIDs { - maintenanceNodes[i] = struct{}{} - } + maintenanceNodes.Insert(cr.Spec.VMStorage.MaintenanceSelectNodeIDs...) case "insert": - for _, i := range cr.Spec.VMStorage.MaintenanceInsertNodeIDs { - maintenanceNodes[i] = struct{}{} - } + maintenanceNodes.Insert(cr.Spec.VMStorage.MaintenanceInsertNodeIDs...) default: panic("BUG unsupported requestsType: " + requestsType) } for i := int32(0); i < *cr.Spec.VMStorage.ReplicaCount; i++ { - if _, ok := maintenanceNodes[i]; ok { + if maintenanceNodes.Has(i) { continue } result = append(result, i) diff --git a/api/operator/v1beta1/vmcluster_types_test.go b/api/operator/v1beta1/vmcluster_types_test.go index af34f69d4..032f8f591 100644 --- a/api/operator/v1beta1/vmcluster_types_test.go +++ b/api/operator/v1beta1/vmcluster_types_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "k8s.io/utils/ptr" ) func TestVMBackup_SnapshotDeletePathWithFlags(t *testing.T) { @@ -86,3 +87,37 @@ func TestVMBackup_SnapshotCreatePathWithFlags(t *testing.T) { want: "http://localhost:8429/prefix/custom/snapshot/create?authKey=some-auth-key", }) } + +func TestVMCluster_AvailableStorageNodeIDs(t *testing.T) { + f := func(cr *VMCluster, requestsType string, want []int32) { + t.Helper() + assert.Equal(t, want, cr.AvailableStorageNodeIDs(requestsType)) + } + + cr := &VMCluster{ + Spec: VMClusterSpec{ + VMStorage: &VMStorage{ + CommonAppsParams: CommonAppsParams{ + ReplicaCount: ptr.To(int32(5)), + }, + MaintenanceSelectNodeIDs: []int32{1, 3}, + MaintenanceInsertNodeIDs: []int32{0, 4}, + }, + }, + } + + // select excludes maintenance nodes + f(cr, "select", []int32{0, 2, 4}) + + // insert excludes maintenance nodes + f(cr, "insert", []int32{1, 2, 3}) + + // no maintenance nodes + f(&VMCluster{ + Spec: VMClusterSpec{ + VMStorage: &VMStorage{ + CommonAppsParams: CommonAppsParams{ReplicaCount: ptr.To(int32(3))}, + }, + }, + }, "select", []int32{0, 1, 2}) +} diff --git a/api/operator/v1beta1/vmextra_types.go b/api/operator/v1beta1/vmextra_types.go index 2dae14b9e..c3e49f297 100644 --- a/api/operator/v1beta1/vmextra_types.go +++ b/api/operator/v1beta1/vmextra_types.go @@ -188,23 +188,39 @@ type StorageSpec struct { // IntoSTSVolume converts storageSpec into proper volume for statefulsetSpec // by default, it adds emptyDir volume. -func (ss *StorageSpec) IntoSTSVolume(name string, sts *appsv1.StatefulSetSpec) { +func (ss *StorageSpec) IntoSTSVolume(name string, sts *appsv1.StatefulSetSpec) error { + podSpec := &sts.Template.Spec + foundVolume := false + for _, volume := range podSpec.Volumes { + if volume.Name == name { + foundVolume = true + } + } switch { case ss == nil: - sts.Template.Spec.Volumes = append(sts.Template.Spec.Volumes, corev1.Volume{ + if foundVolume { + return nil + } + podSpec.Volumes = append(podSpec.Volumes, corev1.Volume{ Name: name, VolumeSource: corev1.VolumeSource{ EmptyDir: &corev1.EmptyDirVolumeSource{}, }, }) case ss.EmptyDir != nil: - sts.Template.Spec.Volumes = append(sts.Template.Spec.Volumes, corev1.Volume{ + if foundVolume { + return fmt.Errorf("either unset storage.emptyDir or remove volume=%q from spec", name) + } + podSpec.Volumes = append(podSpec.Volumes, corev1.Volume{ Name: name, VolumeSource: corev1.VolumeSource{ EmptyDir: ss.EmptyDir, }, }) default: + if foundVolume { + return fmt.Errorf("either unset storage.volumeClaimTemplate or remove volume=%q from spec", name) + } claimTemplate := ss.VolumeClaimTemplate stsClaim := corev1.PersistentVolumeClaim{ TypeMeta: metav1.TypeMeta{ @@ -224,6 +240,7 @@ func (ss *StorageSpec) IntoSTSVolume(name string, sts *appsv1.StatefulSetSpec) { } sts.VolumeClaimTemplates = append(sts.VolumeClaimTemplates, stsClaim) } + return nil } // EmbeddedPersistentVolumeClaim is an embedded version of k8s.io/api/core/v1.PersistentVolumeClaim. diff --git a/api/operator/v1beta1/vmrule_types.go b/api/operator/v1beta1/vmrule_types.go index 4f70f4423..e708905fb 100644 --- a/api/operator/v1beta1/vmrule_types.go +++ b/api/operator/v1beta1/vmrule_types.go @@ -14,6 +14,7 @@ import ( "gopkg.in/yaml.v2" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" ) // MaxConfigMapDataSize is a maximum `Data` field size of a ConfigMap. @@ -165,7 +166,7 @@ func (cr *VMRule) Validate() error { panic(fmt.Sprintf("cannot init vmalert templates for validation: %s", err)) } }) - uniqNames := make(map[string]struct{}) + uniqNames := sets.New[string]() var totalSize int for i := range cr.Spec.Groups { // make a copy @@ -179,10 +180,10 @@ func (cr *VMRule) Validate() error { group.Tenant = "" } errContext := fmt.Sprintf("VMRule: %s/%s group: %s", cr.Namespace, cr.Name, group.Name) - if _, ok := uniqNames[group.Name]; ok { + if uniqNames.Has(group.Name) { return fmt.Errorf("duplicate group name: %s", errContext) } - uniqNames[group.Name] = struct{}{} + uniqNames.Insert(group.Name) groupBytes, err := yaml.Marshal(group) if err != nil { return fmt.Errorf("cannot marshal %s, err: %w", errContext, err) diff --git a/cmd/config-reloader/file_watch.go b/cmd/config-reloader/file_watch.go index 0e56b8b87..915ae1436 100644 --- a/cmd/config-reloader/file_watch.go +++ b/cmd/config-reloader/file_watch.go @@ -14,6 +14,7 @@ import ( "github.com/VictoriaMetrics/VictoriaMetrics/lib/logger" "github.com/fsnotify/fsnotify" + "k8s.io/apimachinery/pkg/util/sets" ) type fileWatcher struct { @@ -114,13 +115,13 @@ func readFileContent(src string) ([]byte, error) { } type dirWatcher struct { - dirs map[string]struct{} + dirs sets.Set[string] wg sync.WaitGroup w *fsnotify.Watcher } func newDirWatchers(dirs []string) (*dirWatcher, error) { - dws := map[string]struct{}{} + dws := sets.New[string]() w, err := fsnotify.NewWatcher() if err != nil { return nil, fmt.Errorf("cannot create new dir watcher: %w", err) @@ -130,7 +131,7 @@ func newDirWatchers(dirs []string) (*dirWatcher, error) { if err := w.Add(dir); err != nil { return nil, fmt.Errorf("cannot dir: %s to watcher: %w", dir, err) } - dws[dir] = struct{}{} + dws.Insert(dir) } return &dirWatcher{ w: w, diff --git a/config/manifests/catalog-templates/latest.yaml b/config/manifests/catalog-templates/latest.yaml new file mode 100644 index 000000000..4e6cbcf64 --- /dev/null +++ b/config/manifests/catalog-templates/latest.yaml @@ -0,0 +1,69 @@ +schema: olm.template.basic +entries: + - schema: olm.package + name: victoriametrics-operator + defaultChannel: beta + icon: + base64data: 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 + mediatype: image/png + - schema: olm.channel + package: victoriametrics-operator + name: beta + entries: + - name: victoriametrics-operator.v0.68.4 + replaces: victoriametrics-operator.v0.68.3 + - name: victoriametrics-operator.v0.68.3 + replaces: victoriametrics-operator.v0.68.2 + - name: victoriametrics-operator.v0.68.2 + replaces: victoriametrics-operator.v0.68.1 + - name: victoriametrics-operator.v0.68.1 + replaces: victoriametrics-operator.v0.67.0 + - name: victoriametrics-operator.v0.67.0 + replaces: victoriametrics-operator.v0.66.1 + - name: victoriametrics-operator.v0.66.1 + replaces: victoriametrics-operator.v0.66.0 + - name: victoriametrics-operator.v0.66.0 + replaces: victoriametrics-operator.v0.65.0 + - name: victoriametrics-operator.v0.65.0 + replaces: victoriametrics-operator.v0.64.1 + - name: victoriametrics-operator.v0.64.1 + replaces: victoriametrics-operator.v0.64.0 + - name: victoriametrics-operator.v0.64.0 + replaces: victoriametrics-operator.v0.63.0 + - name: victoriametrics-operator.v0.63.0 + replaces: victoriametrics-operator.v0.62.0 + - name: victoriametrics-operator.v0.62.0 + replaces: victoriametrics-operator.v0.61.2 + - name: victoriametrics-operator.v0.61.2 + replaces: victoriametrics-operator.v0.61.1 + - name: victoriametrics-operator.v0.61.1 + replaces: victoriametrics-operator.v0.61.0 + - name: victoriametrics-operator.v0.61.0 + replaces: victoriametrics-operator.v0.60.2 + - name: victoriametrics-operator.v0.60.2 + replaces: victoriametrics-operator.v0.60.1 + - name: victoriametrics-operator.v0.60.1 + replaces: victoriametrics-operator.v0.60.0 + - name: victoriametrics-operator.v0.60.0 + replaces: victoriametrics-operator.v0.59.2 + - name: victoriametrics-operator.v0.59.2 + replaces: victoriametrics-operator.v0.59.1 + - name: victoriametrics-operator.v0.59.1 + replaces: victoriametrics-operator.v0.59.0 + - name: victoriametrics-operator.v0.59.0 + replaces: victoriametrics-operator.v0.58.1 + - name: victoriametrics-operator.v0.58.1 + replaces: victoriametrics-operator.v0.58.0 + - name: victoriametrics-operator.v0.58.0 + replaces: victoriametrics-operator.v0.57.0 + - name: victoriametrics-operator.v0.57.0 + replaces: victoriametrics-operator.v0.56.0 + - name: victoriametrics-operator.v0.56.0 + replaces: victoriametrics-operator.v0.55.0 + - name: victoriametrics-operator.v0.55.0 + replaces: victoriametrics-operator.v0.54.1 + - name: victoriametrics-operator.v0.54.1 + replaces: victoriametrics-operator.v0.53.0 + - name: victoriametrics-operator.v0.53.0 + replaces: victoriametrics-operator.v0.52.0 + - name: victoriametrics-operator.v0.52.0 diff --git a/config/manifests/release-config.yaml b/config/manifests/release-config.yaml deleted file mode 100644 index 52df9476f..000000000 --- a/config/manifests/release-config.yaml +++ /dev/null @@ -1,5 +0,0 @@ -catalog_templates: - - template_name: v4.12-v4.16.yaml - channels: [beta] - - template_name: latest.yaml - channels: [beta] diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 7e7745084..89aed9512 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -13,6 +13,14 @@ aliases: ## tip +**Update note 1**: `-eula` flag is not set by default anymore for VMBackup and VMRestore. To avoid VMCluster/VMSingle rollouts set `spec.vmstorage.vmBackup.acceptEula: true` for VMCluster and `spec.vmBackup.acceptEula: true` for VMSingle and replace it with `spec.license` during VMSingle/VMCluster upgrade. + +* BUGFIX: [vmagent](https://docs.victoriametrics.com/operator/resources/vmagent/): use volume from spec.volumes as persistent queue volume if its name is `persistent-queue-data`, previously emptyDir was mounted. See [#1677](https://github.com/VictoriaMetrics/operator/issues/1677). +* BUGFIX: [vmcluster](https://docs.victoriametrics.com/operator/resources/vmcluster/): use volume from spec.vmstorage.volumes and spec.vmselect.volumes as data and cache volumes if its name is `vmstorage-db` and `vmselect-cachedir` respectively. See [#784](https://github.com/VictoriaMetrics/operator/issues/784). +* BUGFIX: [vmoperator](https://docs.victoriametrics.com/operator/): Improve reconcile error handling for Prometheus and VictoriaMetrics controllers. +* BUGFIX: [vmoperator](https://docs.victoriametrics.com/operator/): Add acceptEula support for VMBackup/VMRestore. +* BUGFIX: [vmdistributed](https://docs.victoriametrics.com/operator/resources/vmdistributed/): change default load balancing policy for write requests from `first_available` to `least_loaded`. This should allow to evenly distribute write load across all VMAgents. + ## [v0.68.4](https://github.com/VictoriaMetrics/operator/releases/tag/v0.68.4) **Release date:** 09 April 2026 diff --git a/internal/controller/operator/controllers.go b/internal/controller/operator/controllers.go index 4cf1c925a..54e0e03ec 100644 --- a/internal/controller/operator/controllers.go +++ b/internal/controller/operator/controllers.go @@ -98,6 +98,11 @@ func (pe *parsingError) Error() string { pe.controller, pe.origin) } +func isParsingError(err error) bool { + var pe *parsingError + return errors.As(err, &pe) +} + // getError could usually occur at following cases: // - not enough k8s permissions // - object was deleted and due to race condition queue by operator cache @@ -116,18 +121,41 @@ func (ge *getError) Error() string { return fmt.Sprintf("get_object error for controller=%q object_name=%q at namespace=%q, origin=%q", ge.controller, ge.requestObject.Name, ge.requestObject.Namespace, ge.origin) } -func handleReconcileErr[T client.Object, ST reconcile.StatusWithMetadata[STC], STC any]( +func handleReconcileErrWithStatus[T client.Object, ST reconcile.StatusWithMetadata[STC], STC any]( ctx context.Context, rclient client.Client, object reconcile.ObjectWithDeepCopyAndStatus[T, ST, STC], originResult ctrl.Result, err error, ) (ctrl.Result, error) { + result, err := handleReconcileErr(ctx, rclient, object, originResult, err) + if isParsingError(err) { + if err := reconcile.UpdateObjectStatus(ctx, rclient, object, vmv1beta1.UpdateStatusFailed, err); err != nil { + logger.WithContext(ctx).Error(err, "failed to update status with parsing error") + } + } + return result, err +} + +func handleReconcileErr(ctx context.Context, rclient client.Client, object client.Object, originResult ctrl.Result, err error) (ctrl.Result, error) { if err == nil { return originResult, nil } - var ge *getError - var pe *parsingError + + switch e := err.(type) { + case *getError: + deregisterObjectByCollector(e.requestObject.Name, e.requestObject.Namespace, e.controller) + getObjectsErrorsTotal.WithLabelValues(e.controller, e.requestObject.String()).Inc() + if k8serrors.IsNotFound(err) { + return originResult, nil + } + case *parsingError: + if object != nil && !reflect.ValueOf(object).IsNil() { + namespacedName := fmt.Sprintf("%s/%s", object.GetNamespace(), object.GetName()) + parseObjectErrorsTotal.WithLabelValues(e.controller, namespacedName).Inc() + } + } + switch { case errors.Is(err, context.Canceled): contextCancelErrorsTotal.Inc() @@ -135,29 +163,12 @@ func handleReconcileErr[T client.Object, ST reconcile.StatusWithMetadata[STC], S originResult.RequeueAfter = time.Second * 5 } return originResult, nil - case errors.As(err, &pe): - namespacedName := "unknown" - if object != nil && !reflect.ValueOf(object).IsNil() { - namespacedName = fmt.Sprintf("%s/%s", object.GetNamespace(), object.GetName()) - if err := reconcile.UpdateObjectStatus(ctx, rclient, object, vmv1beta1.UpdateStatusFailed, err); err != nil { - logger.WithContext(ctx).Error(err, "failed to update status with parsing error") - } - } - parseObjectErrorsTotal.WithLabelValues(pe.controller, namespacedName).Inc() - case errors.As(err, &ge): - deregisterObjectByCollector(ge.requestObject.Name, ge.requestObject.Namespace, ge.controller) - getObjectsErrorsTotal.WithLabelValues(ge.controller, ge.requestObject.String()).Inc() - if k8serrors.IsNotFound(err) { - return originResult, nil - } case k8serrors.IsConflict(err): - controller := "unknown" - namespacedName := "unknown" if object != nil && !reflect.ValueOf(object).IsNil() && object.GetNamespace() != "" { - controller = object.GetObjectKind().GroupVersionKind().GroupKind().Kind - namespacedName = fmt.Sprintf("%s/%s", object.GetNamespace(), object.GetName()) + controller := object.GetObjectKind().GroupVersionKind().GroupKind().Kind + namespacedName := fmt.Sprintf("%s/%s", object.GetNamespace(), object.GetName()) + conflictErrorsTotal.WithLabelValues(controller, namespacedName).Inc() } - conflictErrorsTotal.WithLabelValues(controller, namespacedName).Inc() return ctrl.Result{RequeueAfter: time.Second * 5}, nil } if object != nil && !reflect.ValueOf(object).IsNil() && object.GetNamespace() != "" { diff --git a/internal/controller/operator/controllers_test.go b/internal/controller/operator/controllers_test.go index ecfa434ef..a0ded524e 100644 --- a/internal/controller/operator/controllers_test.go +++ b/internal/controller/operator/controllers_test.go @@ -319,6 +319,80 @@ func TestIsSelectorsMatchesTargetCRD(t *testing.T) { }) } +func TestHandleReconcileErrWithStatus(t *testing.T) { + type opts struct { + ctx context.Context + err error + origin ctrl.Result + object *vmv1beta1.VMCluster + wantResult ctrl.Result + wantErr error + wantStatus vmv1beta1.UpdateStatus + } + + f := func(o opts) { + t.Helper() + if o.ctx == nil { + o.ctx = context.Background() + } + var predefined []runtime.Object + if o.object != nil { + predefined = append(predefined, o.object) + } + fclient := k8stools.GetTestClientWithObjects(predefined) + got, err := handleReconcileErrWithStatus(o.ctx, fclient, o.object, o.origin, o.err) + assert.Equal(t, o.wantErr, err) + assert.Equal(t, o.wantResult, got) + if o.wantStatus != "" && o.object != nil { + updated := &vmv1beta1.VMCluster{} + assert.NoError(t, fclient.Get(o.ctx, client.ObjectKeyFromObject(o.object), updated)) + assert.Equal(t, o.wantStatus, updated.Status.UpdateStatus) + } + } + + // nil error + f(opts{ + err: nil, + object: &vmv1beta1.VMCluster{}, + origin: ctrl.Result{RequeueAfter: 10}, + wantResult: ctrl.Result{RequeueAfter: 10}, + wantErr: nil, + }) + + // parsingError + f(opts{ + err: &parsingError{origin: "bad field value", controller: "vmcluster"}, + object: &vmv1beta1.VMCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + Namespace: "default", + }, + }, + origin: ctrl.Result{}, + wantResult: ctrl.Result{}, + wantErr: &parsingError{origin: "bad field value", controller: "vmcluster"}, + wantStatus: vmv1beta1.UpdateStatusFailed, + }) + + // context.Canceled sets RequeueAfter, no status update + f(opts{ + err: context.Canceled, + object: &vmv1beta1.VMCluster{}, + origin: ctrl.Result{}, + wantResult: ctrl.Result{RequeueAfter: time.Second * 5}, + wantErr: nil, + }) + + // transient error + f(opts{ + err: fmt.Errorf("some transient error"), + object: &vmv1beta1.VMCluster{}, + origin: ctrl.Result{}, + wantResult: ctrl.Result{}, + wantErr: fmt.Errorf("some transient error"), + }) +} + func TestHandleReconcileErr(t *testing.T) { type opts struct { ctx context.Context diff --git a/internal/controller/operator/factory/build/backup.go b/internal/controller/operator/factory/build/backup.go index 872b51f88..29e9d57ad 100644 --- a/internal/controller/operator/factory/build/backup.go +++ b/internal/controller/operator/factory/build/backup.go @@ -54,17 +54,22 @@ func VMBackupManager( fmt.Sprintf("-dst=%s", backupDst), fmt.Sprintf("-snapshot.createURL=%s", snapshotCreateURL), fmt.Sprintf("-snapshot.deleteURL=%s", snapshotDeleteURL), - "-eula", } - + if cr.AcceptEULA { + args = append(args, "-eula") + } if cr.LogLevel != nil { args = append(args, fmt.Sprintf("-loggerLevel=%s", *cr.LogLevel)) } if cr.LogFormat != nil { args = append(args, fmt.Sprintf("-loggerFormat=%s", *cr.LogFormat)) } - for arg, value := range cr.ExtraArgs { - args = append(args, fmt.Sprintf("-%s=%s", arg, value)) + for key, value := range cr.ExtraArgs { + arg := fmt.Sprintf("-%s", key) + if len(value) != 0 { + arg = fmt.Sprintf("%s=%s", arg, value) + } + args = append(args, arg) } if cr.Concurrency != nil { args = append(args, fmt.Sprintf("-concurrency=%d", *cr.Concurrency)) @@ -171,15 +176,21 @@ func VMRestore( fmt.Sprintf("-storageDataPath=%s", storagePath), "-eula", } - + if cr.AcceptEULA { + args = append(args, "-eula") + } if cr.LogLevel != nil { args = append(args, fmt.Sprintf("-loggerLevel=%s", *cr.LogLevel)) } if cr.LogFormat != nil { args = append(args, fmt.Sprintf("-loggerFormat=%s", *cr.LogFormat)) } - for arg, value := range cr.ExtraArgs { - args = append(args, fmt.Sprintf("-%s=%s", arg, value)) + for key, value := range cr.ExtraArgs { + arg := fmt.Sprintf("-%s", key) + if len(value) != 0 { + arg = fmt.Sprintf("%s=%s", arg, value) + } + args = append(args, arg) } if cr.Concurrency != nil { args = append(args, fmt.Sprintf("-concurrency=%d", *cr.Concurrency)) diff --git a/internal/controller/operator/factory/build/container.go b/internal/controller/operator/factory/build/container.go index 2f4afb8bf..582e53145 100644 --- a/internal/controller/operator/factory/build/container.go +++ b/internal/controller/operator/factory/build/container.go @@ -607,7 +607,7 @@ func AddSyslogTLSConfigToVolumes(dstVolumes []corev1.Volume, dstMounts []corev1. return dstVolumes, dstMounts } -func StorageVolumeMountsTo(volumes []corev1.Volume, mounts []corev1.VolumeMount, pvcSrc *corev1.PersistentVolumeClaimVolumeSource, storagePath, dataVolumeName string) ([]corev1.Volume, []corev1.VolumeMount, error) { +func StorageVolumeMountsTo(volumes []corev1.Volume, mounts []corev1.VolumeMount, pvcSrc *corev1.PersistentVolumeClaimVolumeSource, storagePath, dataVolumeName string, isStatefulSet bool) ([]corev1.Volume, []corev1.VolumeMount, error) { foundMount := false for _, volumeMount := range mounts { rel, err := filepath.Rel(volumeMount.MountPath, storagePath) @@ -643,6 +643,9 @@ func StorageVolumeMountsTo(volumes []corev1.Volume, mounts []corev1.VolumeMount, return volumes, mounts, nil } } + if isStatefulSet { + return volumes, mounts, nil + } var source corev1.VolumeSource if pvcSrc != nil { source.PersistentVolumeClaim = pvcSrc diff --git a/internal/controller/operator/factory/build/container_test.go b/internal/controller/operator/factory/build/container_test.go index 630cdabe0..9a1a9cfa4 100644 --- a/internal/controller/operator/factory/build/container_test.go +++ b/internal/controller/operator/factory/build/container_test.go @@ -356,10 +356,11 @@ func TestStorageVolumeMountsTo(t *testing.T) { mounts []corev1.VolumeMount expectedMounts []corev1.VolumeMount wantErr bool + isStatefulSet bool } f := func(o opts) { t.Helper() - gotVolumes, gotMounts, err := StorageVolumeMountsTo(o.volumes, o.mounts, o.pvcSrc, o.storagePath, DataVolumeName) + gotVolumes, gotMounts, err := StorageVolumeMountsTo(o.volumes, o.mounts, o.pvcSrc, o.storagePath, DataVolumeName, o.isStatefulSet) assert.Equal(t, o.expectedMounts, gotMounts) assert.Equal(t, o.expectedVolumes, gotVolumes) if o.wantErr { @@ -542,6 +543,61 @@ func TestStorageVolumeMountsTo(t *testing.T) { }, wantErr: true, }) + + // isStatefulSet, add data volume + f(opts{ + isStatefulSet: true, + storagePath: "/test", + expectedVolumes: nil, + expectedMounts: []corev1.VolumeMount{{ + Name: DataVolumeName, + MountPath: "/test", + }}, + }) + + // isStatefulSet, mount PVC + f(opts{ + isStatefulSet: true, + volumes: []corev1.Volume{{ + Name: DataVolumeName, + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: "test-claim", + }, + }, + }}, + storagePath: "/test", + expectedVolumes: []corev1.Volume{{ + Name: DataVolumeName, + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: "test-claim", + }, + }, + }}, + expectedMounts: []corev1.VolumeMount{{ + Name: DataVolumeName, + MountPath: "/test", + }}, + }) + + // isStatefulSet, existing volume + pvcSrc — error + f(opts{ + isStatefulSet: true, + volumes: []corev1.Volume{{ + Name: DataVolumeName, + VolumeSource: corev1.VolumeSource{ + AWSElasticBlockStore: &corev1.AWSElasticBlockStoreVolumeSource{ + VolumeID: "aws-volume", + }, + }, + }}, + storagePath: "/test", + pvcSrc: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: "test-claim", + }, + wantErr: true, + }) } func TestBuildConfigReloaderContainer(t *testing.T) { diff --git a/internal/controller/operator/factory/reconcile/status.go b/internal/controller/operator/factory/reconcile/status.go index df2670ea7..d269b96f8 100644 --- a/internal/controller/operator/factory/reconcile/status.go +++ b/internal/controller/operator/factory/reconcile/status.go @@ -13,6 +13,7 @@ import ( "k8s.io/apimachinery/pkg/api/equality" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + utilerrors "k8s.io/apimachinery/pkg/util/errors" "sigs.k8s.io/controller-runtime/pkg/client" vmv1beta1 "github.com/VictoriaMetrics/operator/api/operator/v1beta1" @@ -39,7 +40,7 @@ func StatusForChildObjects[T any, PT interface { *T objectWithStatus }](ctx context.Context, rclient client.Client, parentObjectName string, childObjects []PT) error { - var errors []string + var errs []error n := strings.Split(parentObjectName, ".") if len(n) != 3 { @@ -63,14 +64,14 @@ func StatusForChildObjects[T any, PT interface { } else { currCound.Status = "False" currCound.Message = st.CurrentSyncError - errors = append(errors, fmt.Sprintf("parent=%s config=namespace/name=%s/%s error text: %s", parentObjectName, childObject.GetNamespace(), childObject.GetName(), st.CurrentSyncError)) + errs = append(errs, fmt.Errorf("parent=%s config=namespace/name=%s/%s error text: %s", parentObjectName, childObject.GetNamespace(), childObject.GetName(), st.CurrentSyncError)) } if err := updateChildStatusConditions[T](ctx, rclient, childObject, currCound); err != nil { return err } } - if len(errors) > 0 { - logger.WithContext(ctx).Error(fmt.Errorf("%s have errors", parentObjectName), fmt.Sprintf("skip config generation for resources: %s", strings.Join(errors, ","))) + if aggErr := utilerrors.NewAggregate(errs); aggErr != nil { + logger.WithContext(ctx).Error(aggErr, fmt.Sprintf("%s skip config generation for resources", parentObjectName)) } return nil } diff --git a/internal/controller/operator/factory/vlagent/vlagent.go b/internal/controller/operator/factory/vlagent/vlagent.go index 95207f5c5..5f712f910 100644 --- a/internal/controller/operator/factory/vlagent/vlagent.go +++ b/internal/controller/operator/factory/vlagent/vlagent.go @@ -238,7 +238,9 @@ func newK8sApp(cr *vmv1.VLAgent) (client.Object, error) { build.StatefulSetAddCommonParams(stsSpec, &cr.Spec.CommonAppsParams) if cr.Spec.TmpDataPath == nil { - cr.Spec.Storage.IntoSTSVolume(tmpDataVolumeName, &stsSpec.Spec) + if err := cr.Spec.Storage.IntoSTSVolume(tmpDataVolumeName, &stsSpec.Spec); err != nil { + return nil, err + } } stsSpec.Spec.VolumeClaimTemplates = append(stsSpec.Spec.VolumeClaimTemplates, cr.Spec.ClaimTemplates...) return stsSpec, nil @@ -268,7 +270,7 @@ func newPodSpec(cr *vmv1.VLAgent) (*corev1.PodSpec, error) { args = append(args, "-envflag.enable=true") } - var agentVolumeMounts []corev1.VolumeMount + var vmMounts []corev1.VolumeMount var volumes []corev1.Volume tmpDataPath := defaultTmpDataPath if cr.Spec.K8sCollector.Enabled { @@ -330,7 +332,7 @@ func newPodSpec(cr *vmv1.VLAgent) (*corev1.PodSpec, error) { }, }, }) - agentVolumeMounts = append(agentVolumeMounts, corev1.VolumeMount{ + vmMounts = append(vmMounts, corev1.VolumeMount{ Name: logVolumeName, MountPath: logVolumePath, ReadOnly: true, @@ -359,7 +361,7 @@ func newPodSpec(cr *vmv1.VLAgent) (*corev1.PodSpec, error) { } if cr.Spec.TmpDataPath == nil { - agentVolumeMounts = append(agentVolumeMounts, + vmMounts = append(vmMounts, corev1.VolumeMount{ Name: tmpDataVolumeName, MountPath: tmpDataPath, @@ -377,14 +379,14 @@ func newPodSpec(cr *vmv1.VLAgent) (*corev1.PodSpec, error) { if cr.Spec.SyslogSpec != nil { args = build.AddSyslogArgsTo(args, cr.Spec.SyslogSpec, tlsServerConfigMountPath) - volumes, agentVolumeMounts = build.AddSyslogTLSConfigToVolumes(volumes, agentVolumeMounts, cr.Spec.SyslogSpec, tlsServerConfigMountPath) + volumes, vmMounts = build.AddSyslogTLSConfigToVolumes(volumes, vmMounts, cr.Spec.SyslogSpec, tlsServerConfigMountPath) ports = build.AddSyslogPortsTo(ports, cr.Spec.SyslogSpec) } - volumes, agentVolumeMounts = build.LicenseVolumeTo(volumes, agentVolumeMounts, cr.Spec.License, vmv1beta1.SecretsDir) + volumes, vmMounts = build.LicenseVolumeTo(volumes, vmMounts, cr.Spec.License, vmv1beta1.SecretsDir) args = build.LicenseArgsTo(args, cr.Spec.License, vmv1beta1.SecretsDir) - agentVolumeMounts = append(agentVolumeMounts, cr.Spec.VolumeMounts...) + vmMounts = append(vmMounts, cr.Spec.VolumeMounts...) volumes = append(volumes, cr.Spec.Volumes...) for _, s := range cr.Spec.Secrets { @@ -396,7 +398,7 @@ func newPodSpec(cr *vmv1.VLAgent) (*corev1.PodSpec, error) { }, }, }) - agentVolumeMounts = append(agentVolumeMounts, corev1.VolumeMount{ + vmMounts = append(vmMounts, corev1.VolumeMount{ Name: k8stools.SanitizeVolumeName("secret-" + s), ReadOnly: true, MountPath: path.Join(vmv1beta1.SecretsDir, s), @@ -419,9 +421,9 @@ func newPodSpec(cr *vmv1.VLAgent) (*corev1.PodSpec, error) { ReadOnly: true, MountPath: path.Join(vmv1beta1.ConfigMapsDir, c), } - agentVolumeMounts = append(agentVolumeMounts, cvm) + vmMounts = append(vmMounts, cvm) } - volumes, agentVolumeMounts = addRemoteWriteAssetsToVolumes(volumes, agentVolumeMounts, cr) + volumes, vmMounts = addRemoteWriteAssetsToVolumes(volumes, vmMounts, cr) args = build.AddExtraArgsOverrideDefaults(args, cr.Spec.ExtraArgs, "-") sort.Strings(args) @@ -433,7 +435,7 @@ func newPodSpec(cr *vmv1.VLAgent) (*corev1.PodSpec, error) { Args: args, Env: envs, EnvFrom: cr.Spec.ExtraEnvsFrom, - VolumeMounts: agentVolumeMounts, + VolumeMounts: vmMounts, Resources: cr.Spec.Resources, TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, } diff --git a/internal/controller/operator/factory/vlcluster/vlstorage.go b/internal/controller/operator/factory/vlcluster/vlstorage.go index 01742e7ca..2a2b491b4 100644 --- a/internal/controller/operator/factory/vlcluster/vlstorage.go +++ b/internal/controller/operator/factory/vlcluster/vlstorage.go @@ -210,7 +210,9 @@ func buildVLStorageSTSSpec(cr *vmv1.VLCluster) (*appsv1.StatefulSet, error) { } build.StatefulSetAddCommonParams(stsSpec, &cr.Spec.VLStorage.CommonAppsParams) storageSpec := cr.Spec.VLStorage.Storage - storageSpec.IntoSTSVolume(cr.Spec.VLStorage.GetStorageVolumeName(), &stsSpec.Spec) + if err := storageSpec.IntoSTSVolume(cr.Spec.VLStorage.GetStorageVolumeName(), &stsSpec.Spec); err != nil { + return nil, err + } stsSpec.Spec.VolumeClaimTemplates = append(stsSpec.Spec.VolumeClaimTemplates, cr.Spec.VLStorage.ClaimTemplates...) return stsSpec, nil diff --git a/internal/controller/operator/factory/vlsingle/vlsingle.go b/internal/controller/operator/factory/vlsingle/vlsingle.go index 387ed92c1..d6dc4c74a 100644 --- a/internal/controller/operator/factory/vlsingle/vlsingle.go +++ b/internal/controller/operator/factory/vlsingle/vlsingle.go @@ -192,7 +192,7 @@ func makePodSpec(r *vmv1.VLSingle) (*corev1.PodTemplateSpec, error) { ClaimName: r.PrefixedName(), } } - volumes, vmMounts, err := build.StorageVolumeMountsTo(r.Spec.Volumes, r.Spec.VolumeMounts, pvcSrc, storagePath, build.DataVolumeName) + volumes, vmMounts, err := build.StorageVolumeMountsTo(r.Spec.Volumes, r.Spec.VolumeMounts, pvcSrc, storagePath, build.DataVolumeName, false) if err != nil { return nil, err } diff --git a/internal/controller/operator/factory/vmagent/vmagent.go b/internal/controller/operator/factory/vmagent/vmagent.go index 9d51bc4ee..be9206dff 100644 --- a/internal/controller/operator/factory/vmagent/vmagent.go +++ b/internal/controller/operator/factory/vmagent/vmagent.go @@ -426,7 +426,9 @@ func newK8sApp(cr *vmv1beta1.VMAgent, ac *build.AssetsCache) (client.Object, err } build.StatefulSetAddCommonParams(stsSpec, &cr.Spec.CommonAppsParams) stsSpec.Spec.Template.Spec.Volumes = build.AddServiceAccountTokenVolume(stsSpec.Spec.Template.Spec.Volumes, &cr.Spec.CommonAppsParams) - cr.Spec.StatefulStorage.IntoSTSVolume(persistentQueueMountName, &stsSpec.Spec) + if err := cr.Spec.StatefulStorage.IntoSTSVolume(persistentQueueMountName, &stsSpec.Spec); err != nil { + return nil, err + } stsSpec.Spec.VolumeClaimTemplates = append(stsSpec.Spec.VolumeClaimTemplates, cr.Spec.ClaimTemplates...) return stsSpec, nil } @@ -523,7 +525,6 @@ func newPodSpec(cr *vmv1beta1.VMAgent, ac *build.AssetsCache) (*corev1.PodSpec, ports = append(ports, corev1.ContainerPort{Name: "http", Protocol: "TCP", ContainerPort: intstr.Parse(cr.Spec.Port).IntVal}) ports = build.AppendInsertPorts(ports, cr.Spec.InsertPorts) - var agentVolumeMounts []corev1.VolumeMount var crMounts []corev1.VolumeMount // mount data path any way, even if user changes its value // we cannot rely on value of remoteWriteSettings. @@ -531,27 +532,12 @@ func newPodSpec(cr *vmv1beta1.VMAgent, ac *build.AssetsCache) (*corev1.PodSpec, if cr.Spec.StatefulMode { pqMountPath = persistentQueueSTSDir } - agentVolumeMounts = append(agentVolumeMounts, - corev1.VolumeMount{ - Name: persistentQueueMountName, - MountPath: pqMountPath, - }, - ) - agentVolumeMounts = append(agentVolumeMounts, cr.Spec.VolumeMounts...) - var volumes []corev1.Volume - // in case for sts, we have to use persistentVolumeClaimTemplate instead - if !cr.Spec.StatefulMode { - volumes = append(volumes, corev1.Volume{ - Name: persistentQueueMountName, - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{}, - }, - }) + volumes, vmMounts, err := build.StorageVolumeMountsTo(cr.Spec.Volumes, cr.Spec.VolumeMounts, nil, pqMountPath, persistentQueueMountName, cr.Spec.StatefulMode) + if err != nil { + return nil, fmt.Errorf("cannot configure persistent queue volume: %w", err) } - volumes = append(volumes, cr.Spec.Volumes...) - if !ptr.Deref(cr.Spec.IngestOnlyMode, false) { args = append(args, fmt.Sprintf("-promscrape.config=%s", path.Join(confOutDir, configFilename))) @@ -588,23 +574,23 @@ func newPodSpec(cr *vmv1beta1.VMAgent, ac *build.AssetsCache) (*corev1.PodSpec, } crMounts = append(crMounts, m) m.ReadOnly = true - agentVolumeMounts = append(agentVolumeMounts, m) - agentVolumeMounts = append(agentVolumeMounts, corev1.VolumeMount{ + vmMounts = append(vmMounts, m) + vmMounts = append(vmMounts, corev1.VolumeMount{ Name: string(build.TLSAssetsResourceKind), MountPath: tlsAssetsDir, ReadOnly: true, }) - agentVolumeMounts = append(agentVolumeMounts, corev1.VolumeMount{ + vmMounts = append(vmMounts, corev1.VolumeMount{ Name: string(build.SecretConfigResourceKind), MountPath: confDir, ReadOnly: true, }) } - mountsLen := len(agentVolumeMounts) - volumes, agentVolumeMounts = build.StreamAggrVolumeTo(volumes, agentVolumeMounts, cr) - volumes, agentVolumeMounts = build.RelabelVolumeTo(volumes, agentVolumeMounts, cr) - crMounts = append(crMounts, agentVolumeMounts[mountsLen:]...) + mountsLen := len(vmMounts) + volumes, vmMounts = build.StreamAggrVolumeTo(volumes, vmMounts, cr) + volumes, vmMounts = build.RelabelVolumeTo(volumes, vmMounts, cr) + crMounts = append(crMounts, vmMounts[mountsLen:]...) for _, s := range cr.Spec.Secrets { volumes = append(volumes, corev1.Volume{ Name: k8stools.SanitizeVolumeName("secret-" + s), @@ -614,7 +600,7 @@ func newPodSpec(cr *vmv1beta1.VMAgent, ac *build.AssetsCache) (*corev1.PodSpec, }, }, }) - agentVolumeMounts = append(agentVolumeMounts, corev1.VolumeMount{ + vmMounts = append(vmMounts, corev1.VolumeMount{ Name: k8stools.SanitizeVolumeName("secret-" + s), ReadOnly: true, MountPath: path.Join(vmv1beta1.SecretsDir, s), @@ -637,11 +623,11 @@ func newPodSpec(cr *vmv1beta1.VMAgent, ac *build.AssetsCache) (*corev1.PodSpec, ReadOnly: true, MountPath: path.Join(vmv1beta1.ConfigMapsDir, c), } - agentVolumeMounts = append(agentVolumeMounts, cvm) + vmMounts = append(vmMounts, cvm) crMounts = append(crMounts, cvm) } - volumes, agentVolumeMounts = build.LicenseVolumeTo(volumes, agentVolumeMounts, cr.Spec.License, vmv1beta1.SecretsDir) + volumes, vmMounts = build.LicenseVolumeTo(volumes, vmMounts, cr.Spec.License, vmv1beta1.SecretsDir) args = build.LicenseArgsTo(args, cr.Spec.License, vmv1beta1.SecretsDir) relabelKeys := []string{globalRelabelingName} @@ -664,7 +650,7 @@ func newPodSpec(cr *vmv1beta1.VMAgent, ac *build.AssetsCache) (*corev1.PodSpec, Args: args, Env: envs, EnvFrom: cr.Spec.ExtraEnvsFrom, - VolumeMounts: agentVolumeMounts, + VolumeMounts: vmMounts, Resources: cr.Spec.Resources, TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, } @@ -691,7 +677,6 @@ func newPodSpec(cr *vmv1beta1.VMAgent, ac *build.AssetsCache) (*corev1.PodSpec, configReloader := build.ConfigReloaderContainer(false, cr, crMounts, ss) operatorContainers = append(operatorContainers, configReloader) } - var err error ic, err = k8stools.MergePatchContainers(ic, cr.Spec.InitContainers) if err != nil { return nil, fmt.Errorf("cannot apply patch for initContainers: %w", err) diff --git a/internal/controller/operator/factory/vmagent/vmagent_test.go b/internal/controller/operator/factory/vmagent/vmagent_test.go index 66f4aa534..c4e9b4dd3 100644 --- a/internal/controller/operator/factory/vmagent/vmagent_test.go +++ b/internal/controller/operator/factory/vmagent/vmagent_test.go @@ -457,6 +457,72 @@ func TestCreateOrUpdate(t *testing.T) { }, }) + // generate vmagent daemonset with predefined volume for persistent queue data + f(opts{ + cr: &vmv1beta1.VMAgent{ + ObjectMeta: metav1.ObjectMeta{ + Name: "example-agent-with-existing-volume", + Namespace: "default", + }, + Spec: vmv1beta1.VMAgentSpec{ + RemoteWrite: []vmv1beta1.VMAgentRemoteWriteSpec{ + {URL: "http://remote-write"}, + }, + CommonAppsParams: vmv1beta1.CommonAppsParams{ + Volumes: []corev1.Volume{{ + Name: "persistent-queue-data", + VolumeSource: corev1.VolumeSource{ + HostPath: &corev1.HostPathVolumeSource{ + Path: "/host/path/cache", + }, + }, + }}, + }, + DaemonSetMode: true, + }, + }, + validate: func(ctx context.Context, fclient client.Client, cr *vmv1beta1.VMAgent) { + var ds appsv1.DaemonSet + assert.NoError(t, fclient.Get(ctx, types.NamespacedName{Namespace: cr.Namespace, Name: cr.PrefixedName()}, &ds)) + expected := []corev1.Volume{ + { + Name: "persistent-queue-data", + VolumeSource: corev1.VolumeSource{ + HostPath: &corev1.HostPathVolumeSource{ + Path: "/host/path/cache", + }, + }, + }, + { + Name: "tls-assets", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: "tls-assets-vmagent-example-agent-with-existing-volume", + }, + }, + }, + { + Name: "config-out", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + { + Name: "config", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: "vmagent-example-agent-with-existing-volume", + }, + }, + }, + } + assert.Equal(t, ds.Spec.Template.Spec.Volumes, expected) + }, + predefinedObjects: []runtime.Object{ + k8stools.NewReadyDeployment("vmagent-example-agent", "default"), + }, + }) + // generate vmagent sharded statefulset with prevSpec f(opts{ cr: &vmv1beta1.VMAgent{ diff --git a/internal/controller/operator/factory/vmalert/rules.go b/internal/controller/operator/factory/vmalert/rules.go index 67630364a..44b915e5e 100644 --- a/internal/controller/operator/factory/vmalert/rules.go +++ b/internal/controller/operator/factory/vmalert/rules.go @@ -10,6 +10,7 @@ import ( "gopkg.in/yaml.v2" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/controller-runtime/pkg/client" vmv1beta1 "github.com/VictoriaMetrics/operator/api/operator/v1beta1" @@ -228,14 +229,14 @@ func deduplicateRules(ctx context.Context, origin []*vmv1beta1.VMRule) []*vmv1be // deduplicate rules across groups. for _, vmRule := range origin { for i, grp := range vmRule.Spec.Groups { - uniqRules := make(map[uint64]struct{}) + uniqRules := sets.New[uint64]() rules := make([]vmv1beta1.Rule, 0, len(grp.Rules)) for _, rule := range grp.Rules { ruleID := calculateRuleID(rule) - if _, ok := uniqRules[ruleID]; ok { + if uniqRules.Has(ruleID) { logger.WithContext(ctx).Info(fmt.Sprintf("duplicate rule=%q found at group=%q for vmrule=%q", rule.Expr, grp.Name, vmRule.Name)) } else { - uniqRules[ruleID] = struct{}{} + uniqRules.Insert(ruleID) rules = append(rules, rule) } } diff --git a/internal/controller/operator/factory/vmalertmanager/config.go b/internal/controller/operator/factory/vmalertmanager/config.go index b424316df..2abea3ea9 100644 --- a/internal/controller/operator/factory/vmalertmanager/config.go +++ b/internal/controller/operator/factory/vmalertmanager/config.go @@ -8,6 +8,7 @@ import ( "strings" "gopkg.in/yaml.v2" + "k8s.io/apimachinery/pkg/util/sets" vmv1beta1 "github.com/VictoriaMetrics/operator/api/operator/v1beta1" "github.com/VictoriaMetrics/operator/internal/controller/operator/factory/build" @@ -157,13 +158,13 @@ func addConfigTemplates(baseCfg []byte, templates []string) ([]byte, error) { func buildGlobalTimeIntervals(cr *vmv1beta1.VMAlertmanagerConfig) ([]yaml.MapSlice, error) { var r []yaml.MapSlice - timeIntervalNameList := map[string]struct{}{} + timeIntervalNameList := sets.New[string]() tis := cr.Spec.TimeIntervals for _, mti := range tis { - if _, ok := timeIntervalNameList[mti.Name]; ok { + if timeIntervalNameList.Has(mti.Name) { return r, fmt.Errorf("got duplicate timeInterval name %s", mti.Name) } - timeIntervalNameList[mti.Name] = struct{}{} + timeIntervalNameList.Insert(mti.Name) if len(mti.TimeIntervals) == 0 { continue } diff --git a/internal/controller/operator/factory/vmalertmanager/statefulset.go b/internal/controller/operator/factory/vmalertmanager/statefulset.go index f2118aaad..ec16fa39c 100644 --- a/internal/controller/operator/factory/vmalertmanager/statefulset.go +++ b/internal/controller/operator/factory/vmalertmanager/statefulset.go @@ -16,6 +16,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" @@ -74,7 +75,9 @@ func newStsForAlertManager(cr *vmv1beta1.VMAlertmanager) (*appsv1.StatefulSet, e statefulset.Spec.PersistentVolumeClaimRetentionPolicy = cr.Spec.PersistentVolumeClaimRetentionPolicy } build.StatefulSetAddCommonParams(statefulset, &cr.Spec.CommonAppsParams) - cr.Spec.Storage.IntoSTSVolume(cr.GetVolumeName(), &statefulset.Spec) + if err := cr.Spec.Storage.IntoSTSVolume(cr.GetVolumeName(), &statefulset.Spec); err != nil { + return nil, err + } statefulset.Spec.Template.Spec.Volumes = append(statefulset.Spec.Template.Spec.Volumes, cr.Spec.Volumes...) return statefulset, nil @@ -331,13 +334,13 @@ func makeStatefulSetSpec(cr *vmv1beta1.VMAlertmanager) (*appsv1.StatefulSetSpec, crMounts = append(crMounts, cmVolumeMount) } - volumeByName := make(map[string]struct{}) + volumeByName := sets.New[string]() for _, t := range cr.Spec.Templates { // Deduplicate configmaps by name - if _, ok := volumeByName[t.Name]; ok { + if volumeByName.Has(t.Name) { continue } - volumeByName[t.Name] = struct{}{} + volumeByName.Insert(t.Name) volumes = append(volumes, corev1.Volume{ Name: k8stools.SanitizeVolumeName("templates-" + t.Name), VolumeSource: corev1.VolumeSource{ diff --git a/internal/controller/operator/factory/vmanomaly/statefulset.go b/internal/controller/operator/factory/vmanomaly/statefulset.go index 63f6acac1..577192106 100644 --- a/internal/controller/operator/factory/vmanomaly/statefulset.go +++ b/internal/controller/operator/factory/vmanomaly/statefulset.go @@ -2,7 +2,6 @@ package vmanomaly import ( "context" - "errors" "fmt" "maps" "sync" @@ -12,6 +11,7 @@ import ( policyv1 "k8s.io/api/policy/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" @@ -158,7 +158,9 @@ func newK8sApp(cr *vmv1.VMAnomaly, configHash string, ac *build.AssetsCache) (*a } build.StatefulSetAddCommonParams(app, &cr.Spec.CommonAppsParams) app.Spec.Template.Spec.Volumes = append(app.Spec.Template.Spec.Volumes, cr.Spec.Volumes...) - cr.Spec.Storage.IntoSTSVolume(cr.GetVolumeName(), &app.Spec) + if err := cr.Spec.Storage.IntoSTSVolume(cr.GetVolumeName(), &app.Spec); err != nil { + return nil, err + } app.Spec.VolumeClaimTemplates = append(app.Spec.VolumeClaimTemplates, cr.Spec.ClaimTemplates...) return app, nil } @@ -263,8 +265,8 @@ func createOrUpdateApp(ctx context.Context, rclient client.Client, cr, prevCR *v } } } - if len(errs) > 0 { - return errors.Join(errs...) + if err := utilerrors.NewAggregate(errs); err != nil { + return err } if err := finalize.RemoveOrphanedPDBs(ctx, rclient, cr, pdbToKeep, true); err != nil { return err diff --git a/internal/controller/operator/factory/vmcluster/vmcluster.go b/internal/controller/operator/factory/vmcluster/vmcluster.go index 82bd12a53..ffaaa2d5f 100644 --- a/internal/controller/operator/factory/vmcluster/vmcluster.go +++ b/internal/controller/operator/factory/vmcluster/vmcluster.go @@ -533,7 +533,9 @@ func genVMSelectSpec(cr *vmv1beta1.VMCluster) (*appsv1.StatefulSet, error) { } build.StatefulSetAddCommonParams(stsSpec, &cr.Spec.VMSelect.CommonAppsParams) if cr.Spec.VMSelect.CacheMountPath != "" { - cr.Spec.VMSelect.StorageSpec.IntoSTSVolume(cr.Spec.VMSelect.GetCacheMountVolumeName(), &stsSpec.Spec) + if err := cr.Spec.VMSelect.StorageSpec.IntoSTSVolume(cr.Spec.VMSelect.GetCacheMountVolumeName(), &stsSpec.Spec); err != nil { + return nil, err + } } stsSpec.Spec.VolumeClaimTemplates = append(stsSpec.Spec.VolumeClaimTemplates, cr.Spec.VMSelect.ClaimTemplates...) return stsSpec, nil @@ -959,7 +961,9 @@ func buildVMStorageSpec(ctx context.Context, cr *vmv1beta1.VMCluster) (*appsv1.S } build.StatefulSetAddCommonParams(stsSpec, &cr.Spec.VMStorage.CommonAppsParams) storageSpec := cr.Spec.VMStorage.Storage - storageSpec.IntoSTSVolume(cr.Spec.VMStorage.GetStorageVolumeName(), &stsSpec.Spec) + if err := storageSpec.IntoSTSVolume(cr.Spec.VMStorage.GetStorageVolumeName(), &stsSpec.Spec); err != nil { + return nil, err + } stsSpec.Spec.VolumeClaimTemplates = append(stsSpec.Spec.VolumeClaimTemplates, cr.Spec.VMStorage.ClaimTemplates...) return stsSpec, nil diff --git a/internal/controller/operator/factory/vmcluster/vmcluster_test.go b/internal/controller/operator/factory/vmcluster/vmcluster_test.go index a7f84d6c5..fac870ff1 100644 --- a/internal/controller/operator/factory/vmcluster/vmcluster_test.go +++ b/internal/controller/operator/factory/vmcluster/vmcluster_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/assert" "gopkg.in/yaml.v2" + appsv1 "k8s.io/api/apps/v1" autoscalingv1 "k8s.io/api/autoscaling/v1" autoscalingv2 "k8s.io/api/autoscaling/v2" corev1 "k8s.io/api/core/v1" @@ -387,7 +388,16 @@ func TestCreateOrUpdate(t *testing.T) { VMSelect: &vmv1beta1.VMSelect{ CommonAppsParams: vmv1beta1.CommonAppsParams{ ReplicaCount: ptr.To(int32(0)), + Volumes: []corev1.Volume{{ + Name: "vmselect-cachedir", + VolumeSource: corev1.VolumeSource{ + HostPath: &corev1.HostPathVolumeSource{ + Path: "/host/path/cache", + }, + }, + }}, }, + CacheMountPath: "/cache", VPA: &vmv1beta1.EmbeddedVPA{ UpdatePolicy: &vpav1.PodUpdatePolicy{ UpdateMode: ptr.To(vpav1.UpdateModeRecreate), @@ -411,13 +421,17 @@ func TestCreateOrUpdate(t *testing.T) { c.VPAAPIEnabled = true }, validate: func(ctx context.Context, rclient client.Client, cr *vmv1beta1.VMCluster) { - var got vpav1.VerticalPodAutoscaler + var vpaGot vpav1.VerticalPodAutoscaler component := vmv1beta1.ClusterComponentSelect - vpaName := cr.PrefixedName(component) - assert.NoError(t, rclient.Get(ctx, types.NamespacedName{Namespace: cr.Namespace, Name: vpaName}, &got)) - expected := vpav1.VerticalPodAutoscaler{ + selectName := cr.PrefixedName(component) + nsn := types.NamespacedName{ + Namespace: cr.Namespace, + Name: selectName, + } + assert.NoError(t, rclient.Get(ctx, nsn, &vpaGot)) + vpaExpected := vpav1.VerticalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{ - Name: vpaName, + Name: selectName, Namespace: cr.Namespace, Labels: cr.FinalLabels(component), ResourceVersion: "1", @@ -425,7 +439,7 @@ func TestCreateOrUpdate(t *testing.T) { }, Spec: vpav1.VerticalPodAutoscalerSpec{ TargetRef: &autoscalingv1.CrossVersionObjectReference{ - Name: vpaName, + Name: selectName, Kind: "StatefulSet", APIVersion: "apps/v1", }, @@ -443,7 +457,18 @@ func TestCreateOrUpdate(t *testing.T) { }, }, } - assert.Equal(t, got, expected) + assert.Equal(t, vpaGot, vpaExpected) + var stsSelectGot appsv1.StatefulSet + assert.NoError(t, rclient.Get(ctx, nsn, &stsSelectGot)) + volumesSelectExpected := []corev1.Volume{{ + Name: "vmselect-cachedir", + VolumeSource: corev1.VolumeSource{ + HostPath: &corev1.HostPathVolumeSource{ + Path: "/host/path/cache", + }, + }, + }} + assert.Equal(t, stsSelectGot.Spec.Template.Spec.Volumes, volumesSelectExpected) }, }) @@ -455,6 +480,14 @@ func TestCreateOrUpdate(t *testing.T) { VMStorage: &vmv1beta1.VMStorage{ CommonAppsParams: vmv1beta1.CommonAppsParams{ ReplicaCount: ptr.To(int32(0)), + Volumes: []corev1.Volume{{ + Name: "vmstorage-db", + VolumeSource: corev1.VolumeSource{ + HostPath: &corev1.HostPathVolumeSource{ + Path: "/host/path/storage", + }, + }, + }}, }, VPA: &vmv1beta1.EmbeddedVPA{ UpdatePolicy: &vpav1.PodUpdatePolicy{ @@ -475,11 +508,15 @@ func TestCreateOrUpdate(t *testing.T) { validate: func(ctx context.Context, rclient client.Client, cr *vmv1beta1.VMCluster) { component := vmv1beta1.ClusterComponentStorage var got vpav1.VerticalPodAutoscaler - vpaName := cr.PrefixedName(component) - assert.NoError(t, rclient.Get(ctx, types.NamespacedName{Namespace: cr.Namespace, Name: vpaName}, &got)) + storageName := cr.PrefixedName(component) + nsn := types.NamespacedName{ + Namespace: cr.Namespace, + Name: storageName, + } + assert.NoError(t, rclient.Get(ctx, nsn, &got)) expected := vpav1.VerticalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{ - Name: vpaName, + Name: storageName, Namespace: cr.Namespace, Labels: cr.FinalLabels(component), ResourceVersion: "1", @@ -487,7 +524,7 @@ func TestCreateOrUpdate(t *testing.T) { }, Spec: vpav1.VerticalPodAutoscalerSpec{ TargetRef: &autoscalingv1.CrossVersionObjectReference{ - Name: vpaName, + Name: storageName, Kind: "StatefulSet", APIVersion: "apps/v1", }, @@ -502,6 +539,18 @@ func TestCreateOrUpdate(t *testing.T) { }, } assert.Equal(t, got, expected) + + var stsStorageGot appsv1.StatefulSet + assert.NoError(t, rclient.Get(ctx, nsn, &stsStorageGot)) + volumesStorageExpected := []corev1.Volume{{ + Name: "vmstorage-db", + VolumeSource: corev1.VolumeSource{ + HostPath: &corev1.HostPathVolumeSource{ + Path: "/host/path/storage", + }, + }, + }} + assert.Equal(t, stsStorageGot.Spec.Template.Spec.Volumes, volumesStorageExpected) }, }) diff --git a/internal/controller/operator/factory/vmdistributed/vmauth.go b/internal/controller/operator/factory/vmdistributed/vmauth.go index 85c3efeee..2c36dd296 100644 --- a/internal/controller/operator/factory/vmdistributed/vmauth.go +++ b/internal/controller/operator/factory/vmdistributed/vmauth.go @@ -66,7 +66,7 @@ func vmAgentTargetRef(vmAgents []*vmv1beta1.VMAgent, owner *metav1.OwnerReferenc } return vmv1beta1.TargetRef{ URLMapCommon: vmv1beta1.URLMapCommon{ - LoadBalancingPolicy: ptr.To("first_available"), + LoadBalancingPolicy: ptr.To("least_loaded"), RetryStatusCodes: []int{500, 502, 503}, }, Paths: []string{"/insert/.+", "/api/v1/write"}, diff --git a/internal/controller/operator/factory/vmdistributed/vmdistributed_test.go b/internal/controller/operator/factory/vmdistributed/vmdistributed_test.go index 961249b00..8abe1e161 100644 --- a/internal/controller/operator/factory/vmdistributed/vmdistributed_test.go +++ b/internal/controller/operator/factory/vmdistributed/vmdistributed_test.go @@ -404,17 +404,22 @@ func TestCreateOrUpdate(t *testing.T) { Static: &vmv1beta1.StaticRef{ URLs: vmAgentURLs, }, + URLMapCommon: vmv1beta1.URLMapCommon{ + LoadBalancingPolicy: ptr.To("least_loaded"), + }, }, { Paths: []string{"/select/.+", "/admin/tenants"}, Static: &vmv1beta1.StaticRef{ URLs: vmClusterURLs, }, + URLMapCommon: vmv1beta1.URLMapCommon{ + LoadBalancingPolicy: ptr.To("first_available"), + }, }, } for i := range targetRefs { targetRef := &targetRefs[i] - targetRef.LoadBalancingPolicy = ptr.To("first_available") targetRef.RetryStatusCodes = []int{500, 502, 503} } var got vmv1beta1.VMAuth @@ -527,17 +532,22 @@ func TestCreateOrUpdate(t *testing.T) { Static: &vmv1beta1.StaticRef{ URLs: vmAgentURLs, }, + URLMapCommon: vmv1beta1.URLMapCommon{ + LoadBalancingPolicy: ptr.To("least_loaded"), + }, }, { Paths: []string{"/select/.+", "/admin/tenants"}, Static: &vmv1beta1.StaticRef{ URLs: vmClusterURLs, }, + URLMapCommon: vmv1beta1.URLMapCommon{ + LoadBalancingPolicy: ptr.To("first_available"), + }, }, } for i := range targetRefs { targetRef := &targetRefs[i] - targetRef.LoadBalancingPolicy = ptr.To("first_available") targetRef.RetryStatusCodes = []int{500, 502, 503} } var got vmv1beta1.VMAuth diff --git a/internal/controller/operator/factory/vmdistributed/zone.go b/internal/controller/operator/factory/vmdistributed/zone.go index bd72e55c9..46406cee5 100644 --- a/internal/controller/operator/factory/vmdistributed/zone.go +++ b/internal/controller/operator/factory/vmdistributed/zone.go @@ -18,6 +18,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" @@ -244,7 +245,7 @@ func (zs *zones) updateLB(ctx context.Context, rclient client.Client, cr *vmv1al return reconcile.VMAuth(ctx, rclient, vmAuth, nil, &owner) } -func getMetricsAddrs(ctx context.Context, rclient client.Client, vmAgent *vmv1beta1.VMAgent) map[string]struct{} { +func getMetricsAddrs(ctx context.Context, rclient client.Client, vmAgent *vmv1beta1.VMAgent) sets.Set[string] { var esl discoveryv1.EndpointSliceList o := client.ListOptions{ LabelSelector: labels.SelectorFromSet(labels.Set{discoveryv1.LabelServiceName: vmAgent.PrefixedName()}), @@ -257,7 +258,7 @@ func getMetricsAddrs(ctx context.Context, rclient client.Client, vmAgent *vmv1be if len(esl.Items) == 0 { return nil } - addrs := make(map[string]struct{}) + addrs := sets.New[string]() for i := range esl.Items { es := &esl.Items[i] var port int32 @@ -286,7 +287,7 @@ func getMetricsAddrs(ctx context.Context, rclient client.Client, vmAgent *vmv1be Scheme: strings.ToLower(vmAgent.ProbeScheme()), Path: vmAgent.GetMetricsPath(), } - addrs[u.String()] = struct{}{} + addrs.Insert(u.String()) } } } diff --git a/internal/controller/operator/factory/vmsingle/vmsingle.go b/internal/controller/operator/factory/vmsingle/vmsingle.go index 16e059118..ebf5c4e2a 100644 --- a/internal/controller/operator/factory/vmsingle/vmsingle.go +++ b/internal/controller/operator/factory/vmsingle/vmsingle.go @@ -186,7 +186,7 @@ func makeSpec(ctx context.Context, cr *vmv1beta1.VMSingle) (*corev1.PodTemplateS } } - volumes, vmMounts, err := build.StorageVolumeMountsTo(cr.Spec.Volumes, cr.Spec.VolumeMounts, pvcSrc, storagePath, build.DataVolumeName) + volumes, vmMounts, err := build.StorageVolumeMountsTo(cr.Spec.Volumes, cr.Spec.VolumeMounts, pvcSrc, storagePath, build.DataVolumeName, false) if err != nil { return nil, err } diff --git a/internal/controller/operator/factory/vtcluster/storage.go b/internal/controller/operator/factory/vtcluster/storage.go index e6ec80418..9222d7a3e 100644 --- a/internal/controller/operator/factory/vtcluster/storage.go +++ b/internal/controller/operator/factory/vtcluster/storage.go @@ -207,7 +207,9 @@ func buildVTStorageSTSSpec(cr *vmv1.VTCluster) (*appsv1.StatefulSet, error) { } build.StatefulSetAddCommonParams(stsSpec, &cr.Spec.Storage.CommonAppsParams) storageSpec := cr.Spec.Storage.Storage - storageSpec.IntoSTSVolume(cr.Spec.Storage.GetStorageVolumeName(), &stsSpec.Spec) + if err := storageSpec.IntoSTSVolume(cr.Spec.Storage.GetStorageVolumeName(), &stsSpec.Spec); err != nil { + return nil, err + } stsSpec.Spec.VolumeClaimTemplates = append(stsSpec.Spec.VolumeClaimTemplates, cr.Spec.Storage.ClaimTemplates...) return stsSpec, nil diff --git a/internal/controller/operator/factory/vtsingle/vtsingle.go b/internal/controller/operator/factory/vtsingle/vtsingle.go index 061ef7d76..5d3edbb1b 100644 --- a/internal/controller/operator/factory/vtsingle/vtsingle.go +++ b/internal/controller/operator/factory/vtsingle/vtsingle.go @@ -191,7 +191,7 @@ func makePodSpec(r *vmv1.VTSingle) (*corev1.PodTemplateSpec, error) { ClaimName: r.PrefixedName(), } } - volumes, vmMounts, err := build.StorageVolumeMountsTo(r.Spec.Volumes, r.Spec.VolumeMounts, pvcSrc, storagePath, build.DataVolumeName) + volumes, vmMounts, err := build.StorageVolumeMountsTo(r.Spec.Volumes, r.Spec.VolumeMounts, pvcSrc, storagePath, build.DataVolumeName, false) if err != nil { return nil, err } diff --git a/internal/controller/operator/objects_stat.go b/internal/controller/operator/objects_stat.go index 7483f794a..adc0cddf2 100644 --- a/internal/controller/operator/objects_stat.go +++ b/internal/controller/operator/objects_stat.go @@ -5,6 +5,7 @@ import ( "sync" "github.com/prometheus/client_golang/prometheus" + "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/metrics" ) @@ -16,19 +17,19 @@ var ( type objectCollector struct { mu sync.Mutex - objectsByController map[string]map[string]struct{} + objectsByController map[string]sets.Set[string] } func (oc *objectCollector) register(name, ns, controller string) { oc.mu.Lock() defer oc.mu.Unlock() - oc.objectsByController[controller][ns+"/"+name] = struct{}{} + oc.objectsByController[controller].Insert(ns + "/" + name) } func (oc *objectCollector) deRegister(name, ns, controller string) { oc.mu.Lock() defer oc.mu.Unlock() - delete(oc.objectsByController[controller], ns+"/"+name) + oc.objectsByController[controller].Delete(ns + "/" + name) } func (oc *objectCollector) countByController(controller string) float64 { @@ -38,12 +39,12 @@ func (oc *objectCollector) countByController(controller string) float64 { if !ok { panic(fmt.Sprintf("BUG, controller: %s is not registered", controller)) } - return float64(len(objects)) + return float64(objects.Len()) } func newCollector() *objectCollector { oc := &objectCollector{ - objectsByController: map[string]map[string]struct{}{}, + objectsByController: map[string]sets.Set[string]{}, } registeredObjects := []string{ "vmagent", "vmalert", "vmsingle", "vmcluster", "vmalertmanager", "vmauth", "vlogs", "vlsingle", @@ -52,7 +53,7 @@ func newCollector() *objectCollector { "vtsingle", "vtcluster", "vmdistributed", } for _, controller := range registeredObjects { - oc.objectsByController[controller] = map[string]struct{}{} + oc.objectsByController[controller] = sets.New[string]() } registry := metrics.Registry instrumentMetric := func(controller string) prometheus.GaugeFunc { diff --git a/internal/controller/operator/vlagent_controller.go b/internal/controller/operator/vlagent_controller.go index 12ec3ab1b..ee26a0dc6 100644 --- a/internal/controller/operator/vlagent_controller.go +++ b/internal/controller/operator/vlagent_controller.go @@ -66,7 +66,7 @@ func (r *VLAgentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (re var instance vmv1.VLAgent defer func() { - result, err = handleReconcileErr(ctx, r.Client, &instance, result, err) + result, err = handleReconcileErrWithStatus(ctx, r.Client, &instance, result, err) }() // Fetch the VLAgent instance if err = r.Get(ctx, req.NamespacedName, &instance); err != nil { diff --git a/internal/controller/operator/vlcluster_controller.go b/internal/controller/operator/vlcluster_controller.go index 502838077..45c0ed462 100644 --- a/internal/controller/operator/vlcluster_controller.go +++ b/internal/controller/operator/vlcluster_controller.go @@ -61,7 +61,7 @@ func (r *VLClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( var instance vmv1.VLCluster defer func() { - result, err = handleReconcileErr(ctx, r.Client, &instance, result, err) + result, err = handleReconcileErrWithStatus(ctx, r.Client, &instance, result, err) }() if err = r.Get(ctx, req.NamespacedName, &instance); err != nil { diff --git a/internal/controller/operator/vlogs_controller.go b/internal/controller/operator/vlogs_controller.go index 6be8408a1..530e82449 100644 --- a/internal/controller/operator/vlogs_controller.go +++ b/internal/controller/operator/vlogs_controller.go @@ -67,7 +67,7 @@ func (r *VLogsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (resu var instance vmv1beta1.VLogs defer func() { - result, err = handleReconcileErr(ctx, r.Client, &instance, result, err) + result, err = handleReconcileErrWithStatus(ctx, r.Client, &instance, result, err) }() if err = r.Get(ctx, req.NamespacedName, &instance); err != nil { diff --git a/internal/controller/operator/vlsingle_controller.go b/internal/controller/operator/vlsingle_controller.go index b2dc2841c..04e782d4b 100644 --- a/internal/controller/operator/vlsingle_controller.go +++ b/internal/controller/operator/vlsingle_controller.go @@ -62,7 +62,7 @@ func (r *VLSingleReconciler) Reconcile(ctx context.Context, req ctrl.Request) (r var instance vmv1.VLSingle defer func() { - result, err = handleReconcileErr(ctx, r.Client, &instance, result, err) + result, err = handleReconcileErrWithStatus(ctx, r.Client, &instance, result, err) }() if err = r.Get(ctx, req.NamespacedName, &instance); err != nil { diff --git a/internal/controller/operator/vmagent_controller.go b/internal/controller/operator/vmagent_controller.go index 508c4e308..001cff4c5 100644 --- a/internal/controller/operator/vmagent_controller.go +++ b/internal/controller/operator/vmagent_controller.go @@ -85,7 +85,7 @@ func (r *VMAgentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (re ctx = logger.AddToContext(ctx, l) var instance vmv1beta1.VMAgent defer func() { - result, err = handleReconcileErr(ctx, r.Client, &instance, result, err) + result, err = handleReconcileErrWithStatus(ctx, r.Client, &instance, result, err) }() // Fetch the VMAgent instance diff --git a/internal/controller/operator/vmalert_controller.go b/internal/controller/operator/vmalert_controller.go index 1b05f3c7f..4be02c361 100644 --- a/internal/controller/operator/vmalert_controller.go +++ b/internal/controller/operator/vmalert_controller.go @@ -72,7 +72,7 @@ func (r *VMAlertReconciler) Reconcile(ctx context.Context, req ctrl.Request) (re var instance vmv1beta1.VMAlert defer func() { - result, err = handleReconcileErr(ctx, r.Client, &instance, result, err) + result, err = handleReconcileErrWithStatus(ctx, r.Client, &instance, result, err) }() if err = r.Get(ctx, req.NamespacedName, &instance); err != nil { diff --git a/internal/controller/operator/vmalertmanager_controller.go b/internal/controller/operator/vmalertmanager_controller.go index 4b8f418cc..2d18f9853 100644 --- a/internal/controller/operator/vmalertmanager_controller.go +++ b/internal/controller/operator/vmalertmanager_controller.go @@ -75,7 +75,7 @@ func (r *VMAlertmanagerReconciler) Reconcile(ctx context.Context, req ctrl.Reque var instance vmv1beta1.VMAlertmanager defer func() { - result, err = handleReconcileErr(ctx, r.Client, &instance, result, err) + result, err = handleReconcileErrWithStatus(ctx, r.Client, &instance, result, err) }() if err = r.Get(ctx, req.NamespacedName, &instance); err != nil { err = &getError{err, "vmalertmanager", req} diff --git a/internal/controller/operator/vmalertmanagerconfig_controller.go b/internal/controller/operator/vmalertmanagerconfig_controller.go index 215d600af..cd466919b 100644 --- a/internal/controller/operator/vmalertmanagerconfig_controller.go +++ b/internal/controller/operator/vmalertmanagerconfig_controller.go @@ -62,7 +62,7 @@ func (r *VMAlertmanagerConfigReconciler) Reconcile(ctx context.Context, req ctrl l := r.Log.WithValues("vmalertmanagerconfig", req.Name, "namespace", req.Namespace) var instance vmv1beta1.VMAlertmanagerConfig defer func() { - result, err = handleReconcileErr(ctx, r.Client, &instance, result, err) + result, err = handleReconcileErrWithStatus(ctx, r.Client, &instance, result, err) }() if err = r.Get(ctx, req.NamespacedName, &instance); err != nil { diff --git a/internal/controller/operator/vmanomaly_controller.go b/internal/controller/operator/vmanomaly_controller.go index 7732c59b6..cb8df5a4e 100644 --- a/internal/controller/operator/vmanomaly_controller.go +++ b/internal/controller/operator/vmanomaly_controller.go @@ -66,7 +66,7 @@ func (r *VMAnomalyReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( var instance vmv1.VMAnomaly defer func() { - result, err = handleReconcileErr(ctx, r.Client, &instance, result, err) + result, err = handleReconcileErrWithStatus(ctx, r.Client, &instance, result, err) }() // Fetch the VMAnomaly instance if err = r.Get(ctx, req.NamespacedName, &instance); err != nil { diff --git a/internal/controller/operator/vmauth_controller.go b/internal/controller/operator/vmauth_controller.go index 0157a77ca..23c3037a3 100644 --- a/internal/controller/operator/vmauth_controller.go +++ b/internal/controller/operator/vmauth_controller.go @@ -72,7 +72,7 @@ func (r *VMAuthReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res var instance vmv1beta1.VMAuth defer func() { - result, err = handleReconcileErr(ctx, r.Client, &instance, result, err) + result, err = handleReconcileErrWithStatus(ctx, r.Client, &instance, result, err) }() if err := r.Get(ctx, req.NamespacedName, &instance); err != nil { diff --git a/internal/controller/operator/vmcluster_controller.go b/internal/controller/operator/vmcluster_controller.go index 2369c7414..4d9fcb01e 100644 --- a/internal/controller/operator/vmcluster_controller.go +++ b/internal/controller/operator/vmcluster_controller.go @@ -50,7 +50,7 @@ func (r *VMClusterReconciler) Reconcile(ctx context.Context, request ctrl.Reques var instance vmv1beta1.VMCluster defer func() { - result, err = handleReconcileErr(ctx, r.Client, &instance, result, err) + result, err = handleReconcileErrWithStatus(ctx, r.Client, &instance, result, err) }() if err = r.Client.Get(ctx, request.NamespacedName, &instance); err != nil { diff --git a/internal/controller/operator/vmdistributed_controller.go b/internal/controller/operator/vmdistributed_controller.go index e3095fd10..5462234d9 100644 --- a/internal/controller/operator/vmdistributed_controller.go +++ b/internal/controller/operator/vmdistributed_controller.go @@ -59,7 +59,7 @@ func (r *VMDistributedReconciler) Reconcile(ctx context.Context, req ctrl.Reques // Handle reconcile errors defer func() { - result, err = handleReconcileErr(ctx, r.Client, &instance, result, err) + result, err = handleReconcileErrWithStatus(ctx, r.Client, &instance, result, err) }() // Fetch VMDistributed instance diff --git a/internal/controller/operator/vmnodescrape_controller.go b/internal/controller/operator/vmnodescrape_controller.go index 3798f691b..2e77204b1 100644 --- a/internal/controller/operator/vmnodescrape_controller.go +++ b/internal/controller/operator/vmnodescrape_controller.go @@ -61,7 +61,7 @@ func (r *VMNodeScrapeReconciler) Reconcile(ctx context.Context, req ctrl.Request var instance vmv1beta1.VMNodeScrape ctx = logger.AddToContext(ctx, l) defer func() { - result, err = handleReconcileErr(ctx, r.Client, &instance, result, err) + result, err = handleReconcileErrWithStatus(ctx, r.Client, &instance, result, err) }() // Fetch the VMNodeScrape instance diff --git a/internal/controller/operator/vmpodscrape_controller.go b/internal/controller/operator/vmpodscrape_controller.go index 7167e4373..6bae3cb4e 100644 --- a/internal/controller/operator/vmpodscrape_controller.go +++ b/internal/controller/operator/vmpodscrape_controller.go @@ -61,7 +61,7 @@ func (r *VMPodScrapeReconciler) Reconcile(ctx context.Context, req ctrl.Request) ctx = logger.AddToContext(ctx, l) defer func() { - result, err = handleReconcileErr(ctx, r.Client, &instance, result, err) + result, err = handleReconcileErrWithStatus(ctx, r.Client, &instance, result, err) }() // Fetch the VMPodScrape instance if err = r.Get(ctx, req.NamespacedName, &instance); err != nil { diff --git a/internal/controller/operator/vmprobe_controller.go b/internal/controller/operator/vmprobe_controller.go index c5c65b165..75dea6076 100644 --- a/internal/controller/operator/vmprobe_controller.go +++ b/internal/controller/operator/vmprobe_controller.go @@ -60,7 +60,7 @@ func (r *VMProbeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (re var instance vmv1beta1.VMProbe ctx = logger.AddToContext(ctx, l) defer func() { - result, err = handleReconcileErr(ctx, r.Client, &instance, result, err) + result, err = handleReconcileErrWithStatus(ctx, r.Client, &instance, result, err) }() // Fetch the VMPodScrape instance diff --git a/internal/controller/operator/vmrule_controller.go b/internal/controller/operator/vmrule_controller.go index 7ab942896..da22a1861 100644 --- a/internal/controller/operator/vmrule_controller.go +++ b/internal/controller/operator/vmrule_controller.go @@ -64,7 +64,7 @@ func (r *VMRuleReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res ctx = logger.AddToContext(ctx, l) defer func() { - result, err = handleReconcileErr(ctx, r.Client, &instance, result, err) + result, err = handleReconcileErrWithStatus(ctx, r.Client, &instance, result, err) }() // Fetch the VMRule instance diff --git a/internal/controller/operator/vmscrapeconfig_controller.go b/internal/controller/operator/vmscrapeconfig_controller.go index 290b0e0ed..40ae3f5d8 100644 --- a/internal/controller/operator/vmscrapeconfig_controller.go +++ b/internal/controller/operator/vmscrapeconfig_controller.go @@ -60,7 +60,7 @@ func (r *VMScrapeConfigReconciler) Reconcile(ctx context.Context, req ctrl.Reque var instance vmv1beta1.VMScrapeConfig ctx = logger.AddToContext(ctx, l) defer func() { - result, err = handleReconcileErr(ctx, r.Client, &instance, result, err) + result, err = handleReconcileErrWithStatus(ctx, r.Client, &instance, result, err) }() // Fetch the VMScrapeConfig instance diff --git a/internal/controller/operator/vmservicescrape_controller.go b/internal/controller/operator/vmservicescrape_controller.go index a8021066d..06aeee231 100644 --- a/internal/controller/operator/vmservicescrape_controller.go +++ b/internal/controller/operator/vmservicescrape_controller.go @@ -60,7 +60,7 @@ func (r *VMServiceScrapeReconciler) Reconcile(ctx context.Context, req ctrl.Requ ctx = logger.AddToContext(ctx, l) var instance vmv1beta1.VMServiceScrape defer func() { - result, err = handleReconcileErr(ctx, r.Client, &instance, result, err) + result, err = handleReconcileErrWithStatus(ctx, r.Client, &instance, result, err) }() // Fetch the VMServiceScrape instance diff --git a/internal/controller/operator/vmsingle_controller.go b/internal/controller/operator/vmsingle_controller.go index 84a1b7b75..cd5fb3b86 100644 --- a/internal/controller/operator/vmsingle_controller.go +++ b/internal/controller/operator/vmsingle_controller.go @@ -69,7 +69,7 @@ func (r *VMSingleReconciler) Reconcile(ctx context.Context, req ctrl.Request) (r var instance vmv1beta1.VMSingle defer func() { - result, err = handleReconcileErr(ctx, r.Client, &instance, result, err) + result, err = handleReconcileErrWithStatus(ctx, r.Client, &instance, result, err) }() if err = r.Get(ctx, req.NamespacedName, &instance); err != nil { diff --git a/internal/controller/operator/vmstaticscrape_controller.go b/internal/controller/operator/vmstaticscrape_controller.go index c79ba7d03..6c0dc11b3 100644 --- a/internal/controller/operator/vmstaticscrape_controller.go +++ b/internal/controller/operator/vmstaticscrape_controller.go @@ -42,7 +42,7 @@ func (r *VMStaticScrapeReconciler) Reconcile(ctx context.Context, req ctrl.Reque l := r.Log.WithValues("vmstaticscrape", req.Name, "namespace", req.Namespace) var instance vmv1beta1.VMStaticScrape defer func() { - result, err = handleReconcileErr(ctx, r.Client, &instance, result, err) + result, err = handleReconcileErrWithStatus(ctx, r.Client, &instance, result, err) }() if err = r.Get(ctx, req.NamespacedName, &instance); err != nil { err = &getError{err, "vmstaticscrape", req} diff --git a/internal/controller/operator/vmuser_controller.go b/internal/controller/operator/vmuser_controller.go index e54b7b2f4..aa54f1555 100644 --- a/internal/controller/operator/vmuser_controller.go +++ b/internal/controller/operator/vmuser_controller.go @@ -65,7 +65,7 @@ func (r *VMUserReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res l := r.Log.WithValues("vmuser", req.Name, "namespace", req.Namespace) var instance vmv1beta1.VMUser defer func() { - result, err = handleReconcileErr(ctx, r.Client, &instance, result, err) + result, err = handleReconcileErrWithStatus(ctx, r.Client, &instance, result, err) }() if err = r.Get(ctx, req.NamespacedName, &instance); err != nil { diff --git a/internal/controller/operator/vtcluster_controller.go b/internal/controller/operator/vtcluster_controller.go index 5163971a0..fa7f0c6af 100644 --- a/internal/controller/operator/vtcluster_controller.go +++ b/internal/controller/operator/vtcluster_controller.go @@ -61,7 +61,7 @@ func (r *VTClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( var instance vmv1.VTCluster defer func() { - result, err = handleReconcileErr(ctx, r.Client, &instance, result, err) + result, err = handleReconcileErrWithStatus(ctx, r.Client, &instance, result, err) }() if err = r.Get(ctx, req.NamespacedName, &instance); err != nil { diff --git a/internal/controller/operator/vtsingle_controller.go b/internal/controller/operator/vtsingle_controller.go index 0f328e65f..0ccb4d7a9 100644 --- a/internal/controller/operator/vtsingle_controller.go +++ b/internal/controller/operator/vtsingle_controller.go @@ -62,7 +62,7 @@ func (r *VTSingleReconciler) Reconcile(ctx context.Context, req ctrl.Request) (r var instance vmv1.VTSingle defer func() { - result, err = handleReconcileErr(ctx, r.Client, &instance, result, err) + result, err = handleReconcileErrWithStatus(ctx, r.Client, &instance, result, err) }() if err = r.Get(ctx, req.NamespacedName, &instance); err != nil { diff --git a/internal/manager/manager.go b/internal/manager/manager.go index 3cc576624..b0fc53828 100644 --- a/internal/manager/manager.go +++ b/internal/manager/manager.go @@ -601,14 +601,14 @@ func getMetricsServerMTLSOpts() ([]func(*tls.Config), error) { } func flagsAsMetrics(registry metrics.RegistererGatherer, flagSet *flag.FlagSet) { - isSetMap := make(map[string]struct{}) + isSetMap := sets.New[string]() flagSet.Visit(func(f *flag.Flag) { - isSetMap[f.Name] = struct{}{} + isSetMap.Insert(f.Name) }) m := prometheus.NewGaugeVec(prometheus.GaugeOpts{Name: "flag", Help: "defines provided flags for the operator"}, []string{"name", "value", "is_set"}) flagSet.VisitAll(func(f *flag.Flag) { isSetStr := "false" - if _, isSet := isSetMap[f.Name]; isSet { + if isSetMap.Has(f.Name) { isSetStr = "true" } m.WithLabelValues(f.Name, f.Value.String(), isSetStr).Set(1) diff --git a/test/e2e/suite/allure/result.go b/test/e2e/suite/allure/result.go index 08ebf8751..77732150c 100644 --- a/test/e2e/suite/allure/result.go +++ b/test/e2e/suite/allure/result.go @@ -8,7 +8,6 @@ package allure import ( "encoding/json" "fmt" - "maps" "os" "reflect" "runtime" @@ -18,6 +17,7 @@ import ( "github.com/google/uuid" "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/types" + "k8s.io/apimachinery/pkg/util/sets" ) const descriptionReportEntryName = "DESCRIPTION" @@ -97,11 +97,11 @@ func (r *result) createFromSpecReport(specReport ginkgo.SpecReport) *result { } attachmentEntries := filterForAttachments(specReport.ReportEntries) - var toSkip map[int]struct{} + var toSkip sets.Set[int] r.Steps, toSkip = createSteps(specReport.SpecEvents, attachmentEntries) for i, entry := range attachmentEntries { - if _, ok := toSkip[i]; !ok { + if !toSkip.Has(i) { var att attachment err := json.Unmarshal([]byte(entry.Value.GetRawValue().(string)), &att) @@ -135,9 +135,9 @@ func (r *result) createFromSpecReport(specReport ginkgo.SpecReport) *result { return r } -func createSteps(events types.SpecEvents, entries types.ReportEntries) (steps []stepObject, indicesToSkip map[int]struct{}) { +func createSteps(events types.SpecEvents, entries types.ReportEntries) (steps []stepObject, indicesToSkip sets.Set[int]) { currentEndIndex := -1 - indicesToSkip = make(map[int]struct{}) + indicesToSkip = sets.New[int]() steps = []stepObject{} for startEventIndex, startEvent := range events { @@ -162,7 +162,7 @@ func createSteps(events types.SpecEvents, entries types.ReportEntries) (steps [] step.ChildrenSteps = childrenSteps for i, entry := range entries { - if _, ok := toSkip[i]; !ok { + if !toSkip.Has(i) { if entry.TimelineLocation.Order > startEvent.TimelineLocation.Order && entry.TimelineLocation.Order < endEvent.TimelineLocation.Order { var att attachment @@ -174,12 +174,12 @@ func createSteps(events types.SpecEvents, entries types.ReportEntries) (steps [] } step.addAttachment(&att) - toSkip[i] = struct{}{} + toSkip.Insert(i) } } } - maps.Copy(indicesToSkip, toSkip) + indicesToSkip.Insert(toSkip.UnsortedList()...) currentEndIndex = endIndex } diff --git a/test/e2e/upgrade/upgrade_test.go b/test/e2e/upgrade/upgrade_test.go index 19df87fb2..46b560438 100644 --- a/test/e2e/upgrade/upgrade_test.go +++ b/test/e2e/upgrade/upgrade_test.go @@ -516,6 +516,7 @@ func ensureNoPodRollout(version string, genDeps func(string) []client.Object, ob for i, o := range objs { By(fmt.Sprintf("waiting for %s to become operational", displayNames[i])) wg.Go(func() { + defer GinkgoRecover() Eventually(func() error { return suite.ExpectObjectStatus(ctx, k8sClient, o, nsns[i], vmv1beta1.UpdateStatusOperational) }, waitTimeout, 5*time.Second).ShouldNot(HaveOccurred()) @@ -532,6 +533,7 @@ func ensureNoPodRollout(version string, genDeps func(string) []client.Object, ob for i, o := range objs { By(fmt.Sprintf("waiting for latest operator to reconcile %s", displayNames[i])) wg.Go(func() { + defer GinkgoRecover() Eventually(func() error { return suite.ExpectObjectStatus(ctx, k8sClient, o, nsns[i], vmv1beta1.UpdateStatusOperational) }, waitTimeout, 5*time.Second).ShouldNot(HaveOccurred()) @@ -642,6 +644,18 @@ var _ = Describe("operator upgrade", Label("upgrade"), func() { cr.Spec.K8sCollector.Enabled = true cr.Spec.ServiceAccountName = "vlagent-collector" })}, + {version: "v0.68.4", cr: with(vmagent)}, + {version: "v0.68.4", cr: with(vmagent, func(cr *vmv1beta1.VMAgent) { + cr.Spec.DaemonSetMode = true + })}, + {version: "v0.68.4", cr: with(vmagent, func(cr *vmv1beta1.VMAgent) { + cr.Spec.StatefulMode = true + })}, + {version: "v0.68.4", cr: with(vlagent)}, + {version: "v0.68.4", cr: with(vlagent, func(cr *vmv1.VLAgent) { + cr.Spec.K8sCollector.Enabled = true + cr.Spec.ServiceAccountName = "vlagent-collector" + })}, }, }, // nolint:dupl @@ -672,6 +686,10 @@ var _ = Describe("operator upgrade", Label("upgrade"), func() { {version: "v0.68.3", cr: with(vmauth)}, {version: "v0.68.3", cr: with(vmalertmanager)}, {version: "v0.68.3", cr: with(vmanomaly)}, + {version: "v0.68.4", cr: with(vmalert)}, + {version: "v0.68.4", cr: with(vmauth)}, + {version: "v0.68.4", cr: with(vmalertmanager)}, + {version: "v0.68.4", cr: with(vmanomaly)}, }, }, // nolint:dupl @@ -696,6 +714,9 @@ var _ = Describe("operator upgrade", Label("upgrade"), func() { {version: "v0.68.3", cr: with(vmsingle)}, {version: "v0.68.3", cr: with(vtsingle)}, {version: "v0.68.3", cr: with(vlsingle)}, + {version: "v0.68.4", cr: with(vmsingle)}, + {version: "v0.68.4", cr: with(vtsingle)}, + {version: "v0.68.4", cr: with(vlsingle)}, }, }, // nolint:dupl @@ -726,6 +747,10 @@ var _ = Describe("operator upgrade", Label("upgrade"), func() { {version: "v0.68.3", cr: with(vlcluster, func(cr *vmv1.VLCluster) { cr.Spec.RequestsLoadBalancer.Enabled = true })}, + {version: "v0.68.4", cr: with(vlcluster)}, + {version: "v0.68.4", cr: with(vlcluster, func(cr *vmv1.VLCluster) { + cr.Spec.RequestsLoadBalancer.Enabled = true + })}, }, }, // nolint:dupl @@ -756,6 +781,10 @@ var _ = Describe("operator upgrade", Label("upgrade"), func() { {version: "v0.68.3", cr: with(vtcluster, func(cr *vmv1.VTCluster) { cr.Spec.RequestsLoadBalancer.Enabled = true })}, + {version: "v0.68.4", cr: with(vtcluster)}, + {version: "v0.68.4", cr: with(vtcluster, func(cr *vmv1.VTCluster) { + cr.Spec.RequestsLoadBalancer.Enabled = true + })}, }, }, // nolint:dupl @@ -768,6 +797,7 @@ var _ = Describe("operator upgrade", Label("upgrade"), func() { {version: "v0.68.1", cr: with(vmcluster)}, {version: "v0.68.2", cr: with(vmcluster)}, {version: "v0.68.3", cr: with(vmcluster)}, + {version: "v0.68.4", cr: with(vmcluster)}, }, }, // nolint:dupl @@ -786,6 +816,7 @@ var _ = Describe("operator upgrade", Label("upgrade"), func() { Image: vmv1beta1.Image{ Tag: "v1.136.0-enterprise", }, + AcceptEULA: true, } cr.Spec.License = &vmv1beta1.License{ KeyRef: &corev1.SecretKeySelector{ @@ -808,6 +839,53 @@ var _ = Describe("operator upgrade", Label("upgrade"), func() { Image: vmv1beta1.Image{ Tag: "v1.136.0-enterprise", }, + AcceptEULA: true, + } + cr.Spec.License = &vmv1beta1.License{ + KeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "license", + }, + Key: "key", + }, + } + })}, + {version: "v0.68.4", cr: with(vmcluster, func(cr *vmv1beta1.VMCluster) { + cr.Spec.RequestsLoadBalancer.Enabled = true + cr.Spec.VMStorage.Image.Tag = "v1.136.0-enterprise-cluster" + cr.Spec.VMSelect.Image.Tag = "v1.136.0-enterprise-cluster" + cr.Spec.VMInsert.Image.Tag = "v1.136.0-enterprise-cluster" + cr.Spec.RequestsLoadBalancer.Spec.Image.Tag = "v1.136.0-enterprise" + cr.Spec.VMStorage.VMBackup = &vmv1beta1.VMBackup{ + Destination: "fs:///tmp", + DestinationDisableSuffixAdd: true, + Image: vmv1beta1.Image{ + Tag: "v1.136.0-enterprise", + }, + AcceptEULA: true, + } + cr.Spec.License = &vmv1beta1.License{ + KeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "license", + }, + Key: "key", + }, + } + })}, + {version: "v0.68.4", cr: with(vmcluster, func(cr *vmv1beta1.VMCluster) { + cr.Spec.RequestsLoadBalancer.Enabled = true + cr.Spec.VMStorage.Image.Tag = "v1.136.0-enterprise-cluster" + cr.Spec.VMSelect.Image.Tag = "v1.136.0-enterprise-cluster" + cr.Spec.VMInsert.Image.Tag = "v1.136.0-enterprise-cluster" + cr.Spec.RequestsLoadBalancer.Spec.Image.Tag = "v1.136.0-enterprise" + cr.Spec.VMStorage.VMBackup = &vmv1beta1.VMBackup{ + Destination: "fs:///tmp", + DestinationDisableSuffixAdd: true, + Image: vmv1beta1.Image{ + Tag: "v1.136.0-enterprise", + }, + AcceptEULA: true, } cr.Spec.License = &vmv1beta1.License{ KeyRef: &corev1.SecretKeySelector{ @@ -831,6 +909,7 @@ var _ = Describe("operator upgrade", Label("upgrade"), func() { {version: "v0.68.1", cr: with(vmdistributed)}, {version: "v0.68.2", cr: with(vmdistributed)}, {version: "v0.68.3", cr: with(vmdistributed)}, + {version: "v0.68.4", cr: with(vmdistributed)}, }, }, })) diff --git a/test/e2e/utils_test.go b/test/e2e/utils_test.go index af6e5f4b7..a54e73bd3 100644 --- a/test/e2e/utils_test.go +++ b/test/e2e/utils_test.go @@ -14,6 +14,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" @@ -33,7 +34,7 @@ func expectPodCount(ctx context.Context, rclient client.Client, obj client.Objec } podsByHash := make(map[string][]corev1.Pod) var labelName, kind string - owners := make(map[string]struct{}) + owners := sets.New[string]() switch obj.(type) { case *appsv1.StatefulSet: labelName = "controller-revision-hash" @@ -56,9 +57,7 @@ func expectPodCount(ctx context.Context, rclient client.Client, obj client.Objec if ref.Kind != kind { continue } - if _, ok := owners[ref.Name]; !ok { - owners[ref.Name] = struct{}{} - } + owners.Insert(ref.Name) } podsByHash[labelValue] = append(podsByHash[labelValue], pod) }