diff --git a/api/v1beta1/aerospikebackupservice_webhook.go b/api/v1beta1/aerospikebackupservice_webhook.go index b8df311ca..18057d82f 100644 --- a/api/v1beta1/aerospikebackupservice_webhook.go +++ b/api/v1beta1/aerospikebackupservice_webhook.go @@ -19,6 +19,7 @@ package v1beta1 import ( "fmt" + set "github.com/deckarep/golang-set/v2" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" @@ -60,6 +61,10 @@ func (r *AerospikeBackupService) ValidateCreate() (admission.Warnings, error) { return nil, err } + if err := r.validateBackupServiceSecrets(); err != nil { + return nil, err + } + return nil, nil } @@ -73,6 +78,10 @@ func (r *AerospikeBackupService) ValidateUpdate(_ runtime.Object) (admission.War return nil, err } + if err := r.validateBackupServiceSecrets(); err != nil { + return nil, err + } + return nil, nil } @@ -116,3 +125,17 @@ func (r *AerospikeBackupService) validateBackupServiceConfig() error { return config.Validate() } + +func (r *AerospikeBackupService) validateBackupServiceSecrets() error { + volumeNameSet := set.NewSet[string]() + + for _, secret := range r.Spec.SecretMounts { + if volumeNameSet.Contains(secret.VolumeMount.Name) { + return fmt.Errorf("duplicate volume name %s found in secrets field", secret.VolumeMount.Name) + } + + volumeNameSet.Add(secret.VolumeMount.Name) + } + + return nil +} diff --git a/config/samples/aerospikebackup.yaml b/config/samples/aerospikebackup.yaml index 2fa069139..2bc9392d0 100644 --- a/config/samples/aerospikebackup.yaml +++ b/config/samples/aerospikebackup.yaml @@ -12,7 +12,8 @@ spec: # routineName: aerospike-aerospikebackup-test-routine config: aerospike-cluster: - aerospike-aerospikebackup-test-cluster: # Name format: -- + # Name format: The name must begin with the prefix - + aerospike-aerospikebackup-test-cluster: credentials: password: admin123 user: admin @@ -20,7 +21,8 @@ spec: - host-name: aerocluster.aerospike.svc.cluster.local port: 3000 backup-routines: - aerospike-aerospikebackup-test-routine: # Name format: -- + # Name format: The name must begin with the prefix - + aerospike-aerospikebackup-test-routine: backup-policy: test-policy interval-cron: "@daily" incr-interval-cron: "@hourly" diff --git a/config/samples/aerospikebackupservice.yaml b/config/samples/aerospikebackupservice.yaml index c0c07c74a..893c92d69 100644 --- a/config/samples/aerospikebackupservice.yaml +++ b/config/samples/aerospikebackupservice.yaml @@ -24,6 +24,7 @@ spec: type: aws-s3 path: "s3://aerospike-kubernetes-operator-test" s3-region: us-east-1 + s3-endpoint-override: "" s3-profile: default secrets: diff --git a/config/samples/all_flash_cluster_cr.yaml b/config/samples/all_flash_cluster_cr.yaml index fd357098a..52bf312e9 100644 --- a/config/samples/all_flash_cluster_cr.yaml +++ b/config/samples/all_flash_cluster_cr.yaml @@ -12,7 +12,7 @@ metadata: spec: size: 2 - image: aerospike/aerospike-server-enterprise:7.1.0.0 + image: aerospike/aerospike-server-enterprise:7.2.0.1 storage: filesystemVolumePolicy: diff --git a/config/samples/dim_nostorage_cluster_cr.yaml b/config/samples/dim_nostorage_cluster_cr.yaml index 0f3f544d8..f33510ea8 100644 --- a/config/samples/dim_nostorage_cluster_cr.yaml +++ b/config/samples/dim_nostorage_cluster_cr.yaml @@ -5,7 +5,7 @@ metadata: namespace: aerospike spec: size: 2 - image: aerospike/aerospike-server-enterprise:7.1.0.0 + image: aerospike/aerospike-server-enterprise:7.2.0.1 podSpec: multiPodPerHost: true diff --git a/config/samples/dim_nostorage_cluster_skip_validation_cr.yaml b/config/samples/dim_nostorage_cluster_skip_validation_cr.yaml index 58a154e14..13e74d1a0 100644 --- a/config/samples/dim_nostorage_cluster_skip_validation_cr.yaml +++ b/config/samples/dim_nostorage_cluster_skip_validation_cr.yaml @@ -20,7 +20,7 @@ metadata: spec: size: 2 - image: aerospike/aerospike-server-enterprise:7.1.0.0 + image: aerospike/aerospike-server-enterprise:7.2.0.1 podSpec: multiPodPerHost: true diff --git a/config/samples/hdd_dim_storage_cluster_cr.yaml b/config/samples/hdd_dim_storage_cluster_cr.yaml index 9493c4fc5..b5b52666c 100644 --- a/config/samples/hdd_dim_storage_cluster_cr.yaml +++ b/config/samples/hdd_dim_storage_cluster_cr.yaml @@ -6,7 +6,7 @@ metadata: spec: size: 2 - image: aerospike/aerospike-server-enterprise:7.1.0.0 + image: aerospike/aerospike-server-enterprise:7.2.0.1 storage: filesystemVolumePolicy: diff --git a/config/samples/host_network_cluster_cr.yaml b/config/samples/host_network_cluster_cr.yaml index d7848da4c..e4b63b2e1 100644 --- a/config/samples/host_network_cluster_cr.yaml +++ b/config/samples/host_network_cluster_cr.yaml @@ -6,7 +6,7 @@ metadata: spec: size: 2 - image: aerospike/aerospike-server-enterprise:7.1.0.0 + image: aerospike/aerospike-server-enterprise:7.2.0.1 storage: filesystemVolumePolicy: diff --git a/config/samples/ldap_cluster_cr.yaml b/config/samples/ldap_cluster_cr.yaml index 3ccb12505..96c19214f 100644 --- a/config/samples/ldap_cluster_cr.yaml +++ b/config/samples/ldap_cluster_cr.yaml @@ -14,7 +14,7 @@ metadata: namespace: aerospike spec: size: 2 - image: aerospike/aerospike-server-enterprise:7.1.0.0 + image: aerospike/aerospike-server-enterprise:7.2.0.1 podSpec: multiPodPerHost: true diff --git a/config/samples/pmem_cluster_cr.yaml b/config/samples/pmem_cluster_cr.yaml index 62b34f54d..444977058 100644 --- a/config/samples/pmem_cluster_cr.yaml +++ b/config/samples/pmem_cluster_cr.yaml @@ -6,7 +6,7 @@ metadata: spec: size: 2 - image: aerospike/aerospike-server-enterprise:7.1.0.0 + image: aerospike/aerospike-server-enterprise:7.2.0.1 storage: filesystemVolumePolicy: diff --git a/config/samples/podspec_cr.yaml b/config/samples/podspec_cr.yaml index 8c1ed966f..d9642d2b8 100644 --- a/config/samples/podspec_cr.yaml +++ b/config/samples/podspec_cr.yaml @@ -6,7 +6,7 @@ metadata: spec: size: 2 - image: aerospike/aerospike-server-enterprise:7.1.0.0 + image: aerospike/aerospike-server-enterprise:7.2.0.1 storage: filesystemVolumePolicy: diff --git a/config/samples/rack_enabled_cluster_cr.yaml b/config/samples/rack_enabled_cluster_cr.yaml index 16444b474..7319e82f0 100644 --- a/config/samples/rack_enabled_cluster_cr.yaml +++ b/config/samples/rack_enabled_cluster_cr.yaml @@ -6,7 +6,7 @@ metadata: spec: size: 2 - image: aerospike/aerospike-server-enterprise:7.1.0.0 + image: aerospike/aerospike-server-enterprise:7.2.0.1 rackConfig: namespaces: - test diff --git a/config/samples/sc_mode_cluster_cr.yaml b/config/samples/sc_mode_cluster_cr.yaml index 8b2b1e914..845f34424 100644 --- a/config/samples/sc_mode_cluster_cr.yaml +++ b/config/samples/sc_mode_cluster_cr.yaml @@ -6,7 +6,7 @@ metadata: spec: size: 4 - image: aerospike/aerospike-server-enterprise:7.1.0.0 + image: aerospike/aerospike-server-enterprise:7.2.0.1 rosterNodeBlockList: - 1A0 diff --git a/config/samples/secrets/password_DC1.txt b/config/samples/secrets/password_DC1.txt index 6f203d3d0..32e9c6209 100644 --- a/config/samples/secrets/password_DC1.txt +++ b/config/samples/secrets/password_DC1.txt @@ -1 +1 @@ -admin123 +admin123 \ No newline at end of file diff --git a/config/samples/shadow_device_cluster_cr.yaml b/config/samples/shadow_device_cluster_cr.yaml index 160e30cb0..42656daea 100644 --- a/config/samples/shadow_device_cluster_cr.yaml +++ b/config/samples/shadow_device_cluster_cr.yaml @@ -7,7 +7,7 @@ metadata: spec: # Add fields here size: 2 - image: aerospike/aerospike-server-enterprise:7.1.0.0 + image: aerospike/aerospike-server-enterprise:7.2.0.1 storage: filesystemVolumePolicy: diff --git a/config/samples/shadow_file_cluster_cr.yaml b/config/samples/shadow_file_cluster_cr.yaml index 0d8571b4a..5d1a0e719 100644 --- a/config/samples/shadow_file_cluster_cr.yaml +++ b/config/samples/shadow_file_cluster_cr.yaml @@ -7,7 +7,7 @@ metadata: spec: # Add fields here size: 2 - image: aerospike/aerospike-server-enterprise:7.1.0.0 + image: aerospike/aerospike-server-enterprise:7.2.0.1 storage: filesystemVolumePolicy: diff --git a/config/samples/ssd_storage_cluster_cr.yaml b/config/samples/ssd_storage_cluster_cr.yaml index 7e10c726c..6cb158937 100644 --- a/config/samples/ssd_storage_cluster_cr.yaml +++ b/config/samples/ssd_storage_cluster_cr.yaml @@ -6,7 +6,7 @@ metadata: spec: size: 2 - image: aerospike/aerospike-server-enterprise:7.1.0.0 + image: aerospike/aerospike-server-enterprise:7.2.0.1 storage: filesystemVolumePolicy: diff --git a/config/samples/tls_cluster_cr.yaml b/config/samples/tls_cluster_cr.yaml index b7e65cfd1..97f5ddb1e 100644 --- a/config/samples/tls_cluster_cr.yaml +++ b/config/samples/tls_cluster_cr.yaml @@ -6,7 +6,7 @@ metadata: spec: size: 4 - image: aerospike/aerospike-server-enterprise:7.1.0.0 + image: aerospike/aerospike-server-enterprise:7.2.0.1 storage: filesystemVolumePolicy: diff --git a/config/samples/xdr_dst_cluster_cr.yaml b/config/samples/xdr_dst_cluster_cr.yaml index afa08f51d..01196be8f 100644 --- a/config/samples/xdr_dst_cluster_cr.yaml +++ b/config/samples/xdr_dst_cluster_cr.yaml @@ -6,7 +6,7 @@ metadata: spec: size: 2 - image: aerospike/aerospike-server-enterprise:7.1.0.0 + image: aerospike/aerospike-server-enterprise:7.2.0.1 storage: filesystemVolumePolicy: diff --git a/config/samples/xdr_src_cluster_cr.yaml b/config/samples/xdr_src_cluster_cr.yaml index 6d3dc936d..43cd5ecbe 100644 --- a/config/samples/xdr_src_cluster_cr.yaml +++ b/config/samples/xdr_src_cluster_cr.yaml @@ -6,7 +6,7 @@ metadata: spec: size: 2 - image: aerospike/aerospike-server-enterprise:7.1.0.0 + image: aerospike/aerospike-server-enterprise:7.2.0.1 storage: filesystemVolumePolicy: diff --git a/go.mod b/go.mod index 786d79fab..53a962a49 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/aerospike/aerospike-kubernetes-operator go 1.22 require ( - github.com/aerospike/aerospike-management-lib v1.4.0 + github.com/aerospike/aerospike-management-lib v1.5.0 github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d github.com/evanphx/json-patch v4.12.0+incompatible github.com/go-logr/logr v1.4.2 @@ -23,7 +23,7 @@ require ( github.com/aerospike/aerospike-backup-service v0.0.0-20240822110128-dc2b4811b9d3 github.com/aerospike/aerospike-client-go/v7 v7.6.1 github.com/deckarep/golang-set/v2 v2.3.1 - github.com/sirupsen/logrus v1.9.0 + github.com/sirupsen/logrus v1.9.1 golang.org/x/crypto v0.24.0 golang.org/x/net v0.26.0 gomodules.xyz/jsonpatch/v2 v2.4.0 diff --git a/go.sum b/go.sum index 5105dd518..fe32cdfd2 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ github.com/aerospike/aerospike-backup-service v0.0.0-20240822110128-dc2b4811b9d3 github.com/aerospike/aerospike-backup-service v0.0.0-20240822110128-dc2b4811b9d3/go.mod h1:PFWhqxcMsEEyoOZtQ70b+X8xWbbemDYuitT24EPBizk= github.com/aerospike/aerospike-client-go/v7 v7.6.1 h1:VZK6S9YKq2w6ptTk3kXXjTxG2U9M9Y7Oi3YQ+3T7wQQ= github.com/aerospike/aerospike-client-go/v7 v7.6.1/go.mod h1:uCbSYMpjlRcH/9f26VSF/luzDDXrcDaV8c6/WIcKtT4= -github.com/aerospike/aerospike-management-lib v1.4.0 h1:wT0l3kwzXv5DV5Cd+hD0BQq3hjSIyaPX1HaUb1304TI= -github.com/aerospike/aerospike-management-lib v1.4.0/go.mod h1:3JKrmC/mLSV8SygbrPQPNV8T7bFaTMjB8wfnX25gB+4= +github.com/aerospike/aerospike-management-lib v1.5.0 h1:uAEaBU+PkzbtwsSrPVokLIUeJaDrFSMtwqeCKba3xIs= +github.com/aerospike/aerospike-management-lib v1.5.0/go.mod h1:hsEptY/AmTmHoJnItJNmfJ4yCMG8LIB8YPnIpIyvGXI= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= @@ -112,8 +112,8 @@ github.com/reugn/go-quartz v0.12.0 h1:RsrklW++R5Swc7mCPYseXM06PTWN4N7/f1rsYkhHiw github.com/reugn/go-quartz v0.12.0/go.mod h1:no4ktgYbAAuY0E1SchR8cTx1LF4jYIzdgaQhzRPSkpk= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.1 h1:Ou41VVR3nMWWmTiEUnj0OlsgOSCUFgsPAOl6jRIcVtQ= +github.com/sirupsen/logrus v1.9.1/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/helm-charts/aerospike-cluster/README.md b/helm-charts/aerospike-cluster/README.md index 7c640dd24..4c297a8c5 100644 --- a/helm-charts/aerospike-cluster/README.md +++ b/helm-charts/aerospike-cluster/README.md @@ -44,30 +44,31 @@ helm install aerospike ./aerospike-cluster/ \ ## Configurations -| Name | Description | Default | -| ---------- |---------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------| -| `replicas` | Aerospike cluster size | `3` | -| `image.repository` | Aerospike server container image repository | `aerospike/aerospike-server-enterprise` | -| `image.tag` | Aerospike server container image tag | `7.1.0.0` | -| `imagePullSecrets` | Secrets containing credentials to pull Aerospike container image from a private registry | `{}` (nil) | -| `customLabels` | Custom labels to add on the aerospikecluster resource | `{}` (nil) | -| `aerospikeAccessControl` | Aerospike access control configuration. Define users and roles to be created on the cluster. | `{}` (nil) | -| `aerospikeConfig` | Aerospike configuration | `{}` (nil) | -| `aerospikeNetworkPolicy` | Network policy (client access configuration) | `{}` (nil) | +| Name | Description | Default | +| -- |---------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------| +| `replicas` | Aerospike cluster size | `3` | +| `image.repository` | Aerospike server container image repository | `aerospike/aerospike-server-enterprise` | +| `image.tag` | Aerospike server container image tag | `7.2.0.1` | +| `imagePullSecrets` | Secrets containing credentials to pull Aerospike container image from a private registry | `{}` (nil) | +| `customLabels` | Custom labels to add on the aerospikecluster resource | `{}` (nil) | +| `aerospikeAccessControl` | Aerospike access control configuration. Define users and roles to be created on the cluster. | `{}` (nil) | +| `aerospikeConfig` | Aerospike configuration | `{}` (nil) | +| `aerospikeNetworkPolicy` | Network policy (client access configuration) | `{}` (nil) | | `commonName` | Base string for naming pods, services, stateful sets, etc. | Release name truncated to 63 characters (without hyphens) | -| `podSpec` | Aerospike pod spec configuration | `{}` (nil) | -| `rackConfig` | Aerospike rack configuration | `{}` (nil) | -| `storage` | Aerospike pod storage configuration | `{}` (nil) | -| `validationPolicy` | Validation policy | `{}` (nil) | -| `operatorClientCert` | Client certificates to connect to Aerospike | `{}` (nil) | -| `seedsFinderServices` | Service (e.g. loadbalancer) for Aerospike cluster discovery | `{}` (nil) | -| `maxUnavailable` | maxUnavailable defines percentage/number of pods that can be allowed to go down or unavailable before application disruption | `1` | -| `disablePDB` | Disable the PodDisruptionBudget creation for the Aerospike cluster | `false` | -| `enableDynamicConfigUpdate` | enableDynamicConfigUpdate enables dynamic config update flow of the operator | `false` | -| `rosterNodeBlockList` | rosterNodeBlockList is a list of blocked nodeIDs from roster in a strong-consistency setup | `[]` | -| `k8sNodeBlockList` | k8sNodeBlockList is a list of Kubernetes nodes which are not used for Aerospike pods | `[]` | -| `paused` | Pause reconciliation of the cluster | `false` | -| `devMode` | Deploy Aerospike cluster in dev mode | `false` | +| `podSpec` | Aerospike pod spec configuration | `{}` (nil) | +| `rackConfig` | Aerospike rack configuration | `{}` (nil) | +| `storage` | Aerospike pod storage configuration | `{}` (nil) | +| `validationPolicy` | Validation policy | `{}` (nil) | +| `operatorClientCert` | Client certificates to connect to Aerospike | `{}` (nil) | +| `seedsFinderServices` | Service (e.g. loadbalancer) for Aerospike cluster discovery | `{}` (nil) | +| `maxUnavailable` | maxUnavailable defines percentage/number of pods that can be allowed to go down or unavailable before application disruption | `1` | +| `disablePDB` | Disable the PodDisruptionBudget creation for the Aerospike cluster | `false` | +| `enableDynamicConfigUpdate` | enableDynamicConfigUpdate enables dynamic config update flow of the operator | `false` | +| `rosterNodeBlockList` | rosterNodeBlockList is a list of blocked nodeIDs from roster in a strong-consistency setup | `[]` | +| `k8sNodeBlockList` | k8sNodeBlockList is a list of Kubernetes nodes which are not used for Aerospike pods | `[]` | +| `paused` | Pause reconciliation of the cluster | `false` | +| `devMode` | Deploy Aerospike cluster in dev mode | `false` | +| `operations` | Operations is a list of on-demand operations to be performed on the Aerospike cluster. | `[]` | ### Default values in "dev" mode (`devMode=true`): diff --git a/helm-charts/aerospike-cluster/templates/aerospike-cluster-cr.yaml b/helm-charts/aerospike-cluster/templates/aerospike-cluster-cr.yaml index 738ec9300..c4a9c8562 100644 --- a/helm-charts/aerospike-cluster/templates/aerospike-cluster-cr.yaml +++ b/helm-charts/aerospike-cluster/templates/aerospike-cluster-cr.yaml @@ -16,7 +16,7 @@ spec: size: {{ .Values.replicas }} # Aerospike server docker image - image: {{ .Values.image.repository | default "aerospike/aerospike-server-enterprise" }}:{{ .Values.image.tag | default "7.1.0.0" }} + image: {{ .Values.image.repository | default "aerospike/aerospike-server-enterprise" }}:{{ .Values.image.tag | default "7.2.0.1" }} ## maxUnavailable defines percentage/number of pods that can be allowed to go down or unavailable ## before application disruption. @@ -127,5 +127,10 @@ spec: k8sNodeBlockList: {{- toYaml . | nindent 4 }} {{- end }} + ## operations is a list of on-demand operations to be performed on the Aerospike cluster. + {{- with .Values.operations }} + operations: {{- toYaml . | nindent 4 }} + {{- end }} + ## Pause reconciliation of the cluster paused: {{ .Values.paused }} diff --git a/helm-charts/aerospike-cluster/values.yaml b/helm-charts/aerospike-cluster/values.yaml index e661a40d6..4dd1bea85 100644 --- a/helm-charts/aerospike-cluster/values.yaml +++ b/helm-charts/aerospike-cluster/values.yaml @@ -8,7 +8,7 @@ replicas: 3 ## Aerospike server docker image image: repository: aerospike/aerospike-server-enterprise - tag: 7.1.0.0 + tag: 7.2.0.1 ## In case the above image is pulled from a registry that requires ## authentication, a secret containing credentials can be added @@ -120,5 +120,11 @@ k8sNodeBlockList: [] ## Pause reconciliation of the cluster paused: false +operations: [] +# - kind: "WarmRestart" +# id: "1" +# podList: +# - podName: "aerospike-cluster-0" + ## Dev Mode devMode: false diff --git a/internal/controller/cluster/reconciler.go b/internal/controller/cluster/reconciler.go index d4b7fbe3c..15542b638 100644 --- a/internal/controller/cluster/reconciler.go +++ b/internal/controller/cluster/reconciler.go @@ -16,6 +16,7 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/record" + "k8s.io/client-go/util/retry" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -231,6 +232,17 @@ func (r *SingleClusterReconciler) Reconcile() (result ctrl.Result, recErr error) return reconcile.Result{}, recErr } + // Doing recluster before setting up roster to get the latest observed node list from server. + if r.IsReclusterNeeded() { + if err = deployment.InfoRecluster( + r.Log, + policy, allHostConns, + ); err != nil { + r.Log.Error(err, "Failed to do recluster") + return reconcile.Result{}, err + } + } + if asdbv1.IsClusterSCEnabled(r.aeroCluster) { if !r.IsStatusEmpty() { if res := r.waitForClusterStability(policy, allHostConns); !res.IsSuccess { @@ -452,9 +464,15 @@ func (r *SingleClusterReconciler) updateStatus() error { func (r *SingleClusterReconciler) setStatusPhase(phase asdbv1.AerospikeClusterPhase) error { if r.aeroCluster.Status.Phase != phase { - r.aeroCluster.Status.Phase = phase + if err := retry.RetryOnConflict(retry.DefaultRetry, func() error { + if err := r.Client.Get(context.TODO(), utils.GetNamespacedName(r.aeroCluster), r.aeroCluster); err != nil { + return err + } + + r.aeroCluster.Status.Phase = phase - if err := r.Client.Status().Update(context.Background(), r.aeroCluster); err != nil { + return r.Client.Status().Update(context.Background(), r.aeroCluster) + }); err != nil { r.Log.Error(err, fmt.Sprintf("Failed to set cluster status to %s", phase)) return err } @@ -1023,3 +1041,50 @@ func (r *SingleClusterReconciler) AddAPIVersionLabel(ctx context.Context) error return r.Client.Update(ctx, aeroCluster, common.UpdateOption) } + +func (r *SingleClusterReconciler) IsReclusterNeeded() bool { + // Return false if dynamic configuration updates are disabled + if !asdbv1.GetBool(r.aeroCluster.Spec.EnableDynamicConfigUpdate) { + return false + } + + // Check for any active-rack addition/update across all the namespaces. + // If there is any active-rack change, recluster is required. + for specIdx := range r.aeroCluster.Spec.RackConfig.Racks { + for statusIdx := range r.aeroCluster.Status.RackConfig.Racks { + if r.aeroCluster.Spec.RackConfig.Racks[specIdx].ID == r.aeroCluster.Status.RackConfig.Racks[statusIdx].ID && + r.IsReclusterNeededForRack(&r.aeroCluster.Spec.RackConfig.Racks[specIdx], + &r.aeroCluster.Status.RackConfig.Racks[statusIdx]) { + return true + } + } + } + + return false +} + +func (r *SingleClusterReconciler) IsReclusterNeededForRack(specRack, statusRack *asdbv1.Rack) bool { + specNamespaces, ok := specRack.AerospikeConfig.Value["namespaces"].([]interface{}) + if !ok { + return false + } + + statusNamespaces, ok := statusRack.AerospikeConfig.Value["namespaces"].([]interface{}) + if !ok { + return false + } + + for _, specNamespace := range specNamespaces { + for _, statusNamespace := range statusNamespaces { + if specNamespace.(map[string]interface{})["name"] != statusNamespace.(map[string]interface{})["name"] { + continue + } + + if specNamespace.(map[string]interface{})["active-rack"] != statusNamespace.(map[string]interface{})["active-rack"] { + return true + } + } + } + + return false +} diff --git a/internal/controller/cluster/statefulset.go b/internal/controller/cluster/statefulset.go index f137d828e..caac90874 100644 --- a/internal/controller/cluster/statefulset.go +++ b/internal/controller/cluster/statefulset.go @@ -92,6 +92,7 @@ func (r *SingleClusterReconciler) createSTS( ports := getSTSContainerPort( r.aeroCluster.Spec.PodSpec.MultiPodPerHost, r.aeroCluster.Spec.AerospikeConfig, + &r.aeroCluster.Spec.AerospikeNetworkPolicy, ) operatorDefinedLabels := utils.LabelsForAerospikeClusterRack( @@ -608,6 +609,7 @@ func (r *SingleClusterReconciler) updateSTSPorts( ports := getSTSContainerPort( r.aeroCluster.Spec.PodSpec.MultiPodPerHost, r.aeroCluster.Spec.AerospikeConfig, + &r.aeroCluster.Spec.AerospikeNetworkPolicy, ) st.Spec.Template.Spec.Containers[0].Ports = ports @@ -1539,12 +1541,24 @@ func addVolumeDeviceInContainer( } func getSTSContainerPort( - multiPodPerHost *bool, aeroConf *asdbv1.AerospikeConfigSpec, + multiPodPerHost *bool, aeroConf *asdbv1.AerospikeConfigSpec, aeroNetworkPolicy *asdbv1.AerospikeNetworkPolicy, ) []corev1.ContainerPort { ports := make([]corev1.ContainerPort, 0, len(defaultContainerPorts)) portNames := make([]string, 0, len(defaultContainerPorts)) + podOnlyNetwork := true - // Sorting defaultContainerPorts to fetch map in ordered manner. + // Check for podOnlyNetwork for all TLS and nonTLS fields. + if svcPort := asdbv1.GetServicePort(aeroConf); svcPort != nil { + podOnlyNetwork = aeroNetworkPolicy.AccessType == asdbv1.AerospikeNetworkTypePod && + aeroNetworkPolicy.AlternateAccessType == asdbv1.AerospikeNetworkTypePod + } + + if _, tlsSvcPort := asdbv1.GetServiceTLSNameAndPort(aeroConf); tlsSvcPort != nil { + podOnlyNetwork = podOnlyNetwork && aeroNetworkPolicy.TLSAccessType == asdbv1.AerospikeNetworkTypePod && + aeroNetworkPolicy.TLSAlternateAccessType == asdbv1.AerospikeNetworkTypePod + } + + // Sorting defaultContainerPorts to fetch map in an ordered manner. // Helps reduce unnecessary sts object updates. for portName := range defaultContainerPorts { portNames = append(portNames, portName) @@ -1567,11 +1581,12 @@ func getSTSContainerPort( ContainerPort: int32(*configPort), } // Single pod per host. Enable hostPort setting + // when pod only network is not defined. // The hostPort setting applies to the Kubernetes containers. // The container port will be exposed to the external network at :, // where the hostIP is the IP address of the Kubernetes node where // the container is running and the hostPort is the port requested by the user - if !asdbv1.GetBool(multiPodPerHost) && portInfo.exposedOnHost { + if !asdbv1.GetBool(multiPodPerHost) && portInfo.exposedOnHost && !podOnlyNetwork { containerPort.HostPort = containerPort.ContainerPort } diff --git a/pkg/backup-service/client.go b/pkg/backup-service/client.go index d8373edeb..b853ab038 100644 --- a/pkg/backup-service/client.go +++ b/pkg/backup-service/client.go @@ -117,7 +117,7 @@ func (c *Client) GetBackupServiceConfig() (map[string]interface{}, error) { func (c *Client) ApplyConfig() error { url := c.API("/config/apply") - resp, err := http.Get(url) + resp, err := http.Post(url, "application/json", nil) if err != nil { return err } diff --git a/pkg/configschema/schemas b/pkg/configschema/schemas index b98c65e83..50ca067d6 160000 --- a/pkg/configschema/schemas +++ b/pkg/configschema/schemas @@ -1 +1 @@ -Subproject commit b98c65e83ef7267f35847d94ada4ba3c76cb48e2 +Subproject commit 50ca067d6b9d248bf9dea2d78060e6f42a9c2c0e diff --git a/test/backup_service/backup_service_test.go b/test/backup_service/backup_service_test.go index 2517f0036..3719986d3 100644 --- a/test/backup_service/backup_service_test.go +++ b/test/backup_service/backup_service_test.go @@ -60,6 +60,17 @@ var _ = Describe( Expect(err).To(HaveOccurred()) }) + It("Should fail when duplicate volume names are given in secrets", func() { + backupService, err = NewBackupService() + Expect(err).ToNot(HaveOccurred()) + secretCopy := backupService.Spec.SecretMounts[0] + backupService.Spec.SecretMounts = append(backupService.Spec.SecretMounts, secretCopy) + + err = deployBackupServiceWithTO(k8sClient, backupService, 5*time.Second) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("duplicate volume name")) + }) + It("Should fail when aerospike-clusters field is given", func() { configMap := getBackupServiceConfMap() configMap[common.AerospikeClustersKey] = map[string]interface{}{ diff --git a/test/backup_service/test_utils.go b/test/backup_service/test_utils.go index 1ce54d2b8..a4a758840 100644 --- a/test/backup_service/test_utils.go +++ b/test/backup_service/test_utils.go @@ -243,10 +243,11 @@ func getBackupServiceConfMap() map[string]interface{} { "type": "local", }, "s3Storage": map[string]interface{}{ - "type": "aws-s3", - "path": "s3://aerospike-kubernetes-operator-test", - "s3-region": "us-east-1", - "s3-profile": "default", + "type": "aws-s3", + "path": "s3://aerospike-kubernetes-operator-test", + "s3-region": "us-east-1", + "s3-endpoint-override": "", + "s3-profile": "default", }, }, } diff --git a/test/cluster/batch_restart_pods_test.go b/test/cluster/batch_restart_pods_test.go index aa708b340..57f71d34b 100644 --- a/test/cluster/batch_restart_pods_test.go +++ b/test/cluster/batch_restart_pods_test.go @@ -20,7 +20,7 @@ import ( const batchClusterName = "batch-restart" var ( - unavailableImage = fmt.Sprintf("%s:%s", baseImage, "7.1.0.99") + unavailableImage = fmt.Sprintf("%s:%s", baseImage, "7.2.0.99") availableImage1 = nextImage ) diff --git a/test/cluster/cluster_helper.go b/test/cluster/cluster_helper.go index b15e86834..13d13c4de 100644 --- a/test/cluster/cluster_helper.go +++ b/test/cluster/cluster_helper.go @@ -33,15 +33,15 @@ import ( const ( baseImage = "aerospike/aerospike-server-enterprise" - nextServerVersion = "7.1.0.0_1" - latestServerVersion = "7.1.0.0" + nextServerVersion = "7.2.0.1_1" + latestServerVersion = "7.2.0.1" invalidVersion = "3.0.0.4" post6Version = "7.0.0.0" pre6Version = "5.7.0.17" version6 = "6.0.0.5" - latestSchemaVersion = "7.1.0" + latestSchemaVersion = "7.2.0" ) var ( diff --git a/test/cluster/dynamic_config_test.go b/test/cluster/dynamic_config_test.go index e318c9fb2..a6f7d9785 100644 --- a/test/cluster/dynamic_config_test.go +++ b/test/cluster/dynamic_config_test.go @@ -607,6 +607,114 @@ var _ = Describe( ) }, ) + + Context( + "When changing fields those need recluster", func() { + BeforeEach( + func() { + // Create a 4 node cluster + aeroCluster := createNonSCDummyAerospikeCluster( + clusterNamespacedName, 4, + ) + aeroCluster.Spec.Storage.Volumes = append( + aeroCluster.Spec.Storage.Volumes, + asdbv1.VolumeSpec{ + Name: "ns1", + Source: asdbv1.VolumeSource{ + PersistentVolume: &asdbv1.PersistentVolumeSpec{ + Size: resource.MustParse("1Gi"), + StorageClass: storageClass, + VolumeMode: v1.PersistentVolumeBlock, + }, + }, + Aerospike: &asdbv1.AerospikeServerVolumeAttachment{ + Path: "/test/dev/xvdf1", + }, + }, + ) + aeroCluster.Spec.EnableDynamicConfigUpdate = ptr.To(true) + + aeroCluster.Spec.RackConfig.Racks = append(aeroCluster.Spec.RackConfig.Racks, + asdbv1.Rack{ + ID: 1, + }, + asdbv1.Rack{ + ID: 2, + }) + aeroCluster.Spec.RackConfig.Namespaces = []string{ + "test", + "test1", + } + nsList := aeroCluster.Spec.AerospikeConfig.Value["namespaces"].([]interface{}) + nsList = append(nsList, getSCNamespaceConfig("test1", "/test/dev/xvdf1")) + aeroCluster.Spec.AerospikeConfig.Value["namespaces"] = nsList + err := deployCluster(k8sClient, ctx, aeroCluster) + Expect(err).ToNot(HaveOccurred()) + }, + ) + + AfterEach( + func() { + aeroCluster, err := getCluster(k8sClient, ctx, clusterNamespacedName) + Expect(err).ToNot(HaveOccurred()) + + _ = deleteCluster(k8sClient, ctx, aeroCluster) + }, + ) + + It( + "Should update active-rack dynamically", func() { + + By("Modify dynamic config by adding fields") + + aeroCluster, err := getCluster( + k8sClient, ctx, clusterNamespacedName, + ) + Expect(err).ToNot(HaveOccurred()) + + podPIDMap, err := getPodIDs(ctx, aeroCluster) + Expect(err).ToNot(HaveOccurred()) + + nsList := aeroCluster.Spec.AerospikeConfig.Value["namespaces"].([]interface{}) + nsList[0].(map[string]interface{})["active-rack"] = 1 + nsList[1].(map[string]interface{})["active-rack"] = 2 + + err = updateCluster(k8sClient, ctx, aeroCluster) + Expect(err).ToNot(HaveOccurred()) + + By("Fetch and verify dynamic configs") + + pod := aeroCluster.Status.Pods["dynamic-config-test-1-0"] + + info, err := requestInfoFromNode(logger, k8sClient, ctx, clusterNamespacedName, "namespace/test", &pod) + Expect(err).ToNot(HaveOccurred()) + + confs := strings.Split(info["namespace/test"], ";") + for _, conf := range confs { + if strings.Contains(conf, "effective_active_rack") { + keyValue := strings.Split(conf, "=") + Expect(keyValue[1]).To(Equal("1")) + } + } + + info, err = requestInfoFromNode(logger, k8sClient, ctx, clusterNamespacedName, "namespace/test1", &pod) + Expect(err).ToNot(HaveOccurred()) + + confs = strings.Split(info["namespace/test1"], ";") + for _, conf := range confs { + if strings.Contains(conf, "effective_active_rack") { + keyValue := strings.Split(conf, "=") + Expect(keyValue[1]).To(Equal("2")) + } + } + + By("Verify no warm/cold restarts in Pods") + + validateServerRestart(ctx, aeroCluster, podPIDMap, false) + }, + ) + }, + ) }, ) @@ -704,7 +812,6 @@ var sliceConfTypeVal = map[string][]string{ "security.log.report-data-op-user": {"admin"}, "xdr.dcs._.namespaces._.ship-sets": {"testset"}, "xdr.dcs._.namespaces._.ignore-sets": {"testset"}, - "xdr.dcs._.namespaces._.ship-bins": {"testbin"}, "xdr.dcs._.namespaces._.ignore-bins": {"testbin"}, } diff --git a/test/cluster/network_policy_test.go b/test/cluster/network_policy_test.go index 634772d87..c47f7c34c 100644 --- a/test/cluster/network_policy_test.go +++ b/test/cluster/network_policy_test.go @@ -708,9 +708,42 @@ func doTestNetworkPolicy( }, ) + It("PodOnlyNetwork: Should not set the hostPort", func() { + clusterNamespacedName := getNamespacedName( + "pod-network-cluster", test.MultiClusterNs1) + + networkPolicy := asdbv1.AerospikeNetworkPolicy{ + AccessType: asdbv1.AerospikeNetworkTypePod, + AlternateAccessType: asdbv1.AerospikeNetworkTypePod, + TLSAccessType: asdbv1.AerospikeNetworkTypePod, + TLSAlternateAccessType: asdbv1.AerospikeNetworkTypePod, + } + + aeroCluster = getAerospikeClusterSpecWithNetworkPolicy( + clusterNamespacedName, &networkPolicy, multiPodPerHost, + enableTLS, + ) + + err := aerospikeClusterCreateUpdate(k8sClient, aeroCluster, ctx) + Expect(err).ToNot(HaveOccurred()) + + podList, err := getPodList(aeroCluster, k8sClient) + Expect(err).ToNot(HaveOccurred()) + Expect(len(podList.Items)).ToNot(BeZero()) + + for idx := range podList.Items { + pod := &podList.Items[idx] + container := pod.Spec.Containers[0] + Expect(container.Ports).ToNot(BeNil()) + + for _, port := range container.Ports { + Expect(port.HostPort).To(BeZero()) + } + } + }) // test-case valid only for multiPodPerHost true if multiPodPerHost { - It("OnlyPodNetwork: should create cluster without nodePort service", func() { + It("PodOnlyNetwork: should create cluster without nodePort service", func() { clusterNamespacedName := getNamespacedName( "pod-network-cluster", test.MultiClusterNs1) diff --git a/test/cluster/utils.go b/test/cluster/utils.go index 345ce4483..a37b6cf6a 100644 --- a/test/cluster/utils.go +++ b/test/cluster/utils.go @@ -604,6 +604,30 @@ func getAerospikeConfigFromNode(log logr.Logger, k8sClient client.Client, ctx go return confs[configContext].(lib.Stats), nil } +func requestInfoFromNode(log logr.Logger, k8sClient client.Client, ctx goctx.Context, + clusterNamespacedName types.NamespacedName, cmd string, pod *asdbv1.AerospikePodStatus) (map[string]string, error) { + aeroCluster, err := getCluster(k8sClient, ctx, clusterNamespacedName) + if err != nil { + return nil, err + } + + host, err := createHost(pod) + if err != nil { + return nil, err + } + + asinfo := info.NewAsInfo( + log, host, getClientPolicy(aeroCluster, k8sClient), + ) + + confs, err := asinfo.RequestInfo(cmd) + if err != nil { + return nil, err + } + + return confs, nil +} + func getPasswordFromSecret(k8sClient client.Client, secretNamespcedName types.NamespacedName, passFileName string, ) (string, error) {