diff --git a/CHANGELOG.md b/CHANGELOG.md index f76aa32b2..a6d58bda5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Change Log ## [master](https://github.com/arangodb/kube-arangodb/tree/master) (N/A) +- Split & Unify Lifecycle management functionality ## [1.2.4](https://github.com/arangodb/kube-arangodb/tree/1.2.4) (2021-10-22) - Replace `beta.kubernetes.io/arch` Pod label with `kubernetes.io/arch` using Silent Rotation diff --git a/lifecycle.go b/lifecycle.go index f04067575..54e626761 100644 --- a/lifecycle.go +++ b/lifecycle.go @@ -47,7 +47,11 @@ var ( cmdLifecyclePreStop = &cobra.Command{ Use: "preStop", - Run: cmdLifecyclePreStopRun, + Hidden: true, + } + cmdLifecyclePreStopFinalizers = &cobra.Command{ + Use: "finalizers", + Run: cmdLifecyclePreStopRunFinalizer, Hidden: true, } cmdLifecycleCopy = &cobra.Command{ @@ -63,6 +67,9 @@ var ( func init() { cmdMain.AddCommand(cmdLifecycle) + + cmdLifecyclePreStop.AddCommand(cmdLifecyclePreStopFinalizers) + cmdLifecycle.AddCommand(cmdLifecyclePreStop) cmdLifecycle.AddCommand(cmdLifecycleCopy) cmdLifecycle.AddCommand(cmdLifecycleProbe) @@ -71,7 +78,7 @@ func init() { } // Wait until all finalizers of the current pod have been removed. -func cmdLifecyclePreStopRun(cmd *cobra.Command, args []string) { +func cmdLifecyclePreStopRunFinalizer(cmd *cobra.Command, args []string) { cliLog.Info().Msgf("Starting arangodb-operator (%s), lifecycle preStop, version %s build %s", version.GetVersionV1().Edition.Title(), version.GetVersionV1().Version, version.GetVersionV1().Build) diff --git a/main.go b/main.go index 854b6af3f..2854d4f15 100644 --- a/main.go +++ b/main.go @@ -362,13 +362,12 @@ func newOperatorConfigAndDeps(id, namespace, name string) (operator.Config, oper Namespace: namespace, PodName: name, ServiceAccount: serviceAccount, - LifecycleImage: image, + OperatorImage: image, EnableDeployment: operatorOptions.enableDeployment, EnableDeploymentReplication: operatorOptions.enableDeploymentReplication, EnableStorage: operatorOptions.enableStorage, EnableBackup: operatorOptions.enableBackup, AllowChaos: chaosOptions.allowed, - MetricsExporterImage: operatorOptions.metricsExporterImage, ArangoImage: operatorOptions.arangoImage, SingleMode: operatorOptions.singleMode, Scope: scope, diff --git a/pkg/apis/deployment/v1/deployment_metrics_spec.go b/pkg/apis/deployment/v1/deployment_metrics_spec.go index 6fc1676c2..254188e72 100644 --- a/pkg/apis/deployment/v1/deployment_metrics_spec.go +++ b/pkg/apis/deployment/v1/deployment_metrics_spec.go @@ -41,6 +41,7 @@ func (m MetricsMode) New() *MetricsMode { return &m } +// deprecated func (m MetricsMode) GetMetricsEndpoint() string { switch m { case MetricsModeInternal: @@ -51,9 +52,12 @@ func (m MetricsMode) GetMetricsEndpoint() string { } const ( + // deprecated // MetricsModeExporter exporter mode for old exporter type MetricsModeExporter MetricsMode = "exporter" - MetricsModeSidecar MetricsMode = "sidecar" + // deprecated + MetricsModeSidecar MetricsMode = "sidecar" + // deprecated MetricsModeInternal MetricsMode = "internal" ) @@ -67,12 +71,14 @@ func (m *MetricsMode) Get() MetricsMode { // MetricsSpec contains spec for arangodb exporter type MetricsSpec struct { - Enabled *bool `json:"enabled,omitempty"` + Enabled *bool `json:"enabled,omitempty"` + // deprecated Image *string `json:"image,omitempty"` Authentication MetricsAuthenticationSpec `json:"authentication,omitempty"` Resources v1.ResourceRequirements `json:"resources,omitempty"` - Mode *MetricsMode `json:"mode,omitempty"` - TLS *bool `json:"tls,omitempty"` + // deprecated + Mode *MetricsMode `json:"mode,omitempty"` + TLS *bool `json:"tls,omitempty"` ServiceMonitor *MetricsServiceMonitorSpec `json:"serviceMonitor,omitempty"` @@ -100,11 +106,13 @@ func (s *MetricsSpec) IsEnabled() bool { return util.BoolOrDefault(s.Enabled, false) } +// deprecated // HasImage returns whether a image was specified or not func (s *MetricsSpec) HasImage() bool { return s.Image != nil } +// deprecated // GetImage returns the Image or empty string func (s *MetricsSpec) GetImage() string { return util.StringOrDefault(s.Image) diff --git a/pkg/apis/deployment/v1/server_group_containers.go b/pkg/apis/deployment/v1/server_group_containers.go new file mode 100644 index 000000000..2cd840dc5 --- /dev/null +++ b/pkg/apis/deployment/v1/server_group_containers.go @@ -0,0 +1,35 @@ +// +// DISCLAIMER +// +// Copyright 2016-2021 ArangoDB GmbH, Cologne, Germany +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package v1 + +const ( + ServerGroupReservedContainerNameServer = "server" + ServerGroupReservedContainerNameExporter = "exporter" +) + +func IsReservedServerGroupContainerName(name string) bool { + switch name { + case ServerGroupReservedContainerNameServer, ServerGroupReservedContainerNameExporter: + return true + default: + return false + } +} diff --git a/pkg/deployment/context_impl.go b/pkg/deployment/context_impl.go index 2e37f35c8..df107dc88 100644 --- a/pkg/deployment/context_impl.go +++ b/pkg/deployment/context_impl.go @@ -115,14 +115,8 @@ func (d *Deployment) GetScope() scope.Scope { return d.config.Scope } -// GetLifecycleImage returns the image name containing the lifecycle helper (== name of operator image) -func (d *Deployment) GetLifecycleImage() string { - return d.config.LifecycleImage -} - -// GetOperatorUUIDImage returns the image name containing the uuid helper (== name of operator image) -func (d *Deployment) GetOperatorUUIDImage() string { - return d.config.OperatorUUIDInitImage +func (d *Deployment) GetOperatorImage() string { + return d.config.OperatorImage } // GetNamespace returns the kubernetes namespace that contains @@ -591,10 +585,6 @@ func (d *Deployment) SelectImageForMember(spec api.DeploymentSpec, status api.De return d.resources.SelectImageForMember(spec, status, member) } -func (d *Deployment) GetMetricsExporterImage() string { - return d.config.MetricsExporterImage -} - func (d *Deployment) GetArangoImage() string { return d.config.ArangoImage } diff --git a/pkg/deployment/deployment.go b/pkg/deployment/deployment.go index c57d11e0d..5b7f85817 100644 --- a/pkg/deployment/deployment.go +++ b/pkg/deployment/deployment.go @@ -69,13 +69,11 @@ import ( // Config holds configuration settings for a Deployment type Config struct { - ServiceAccount string - AllowChaos bool - LifecycleImage string - OperatorUUIDInitImage string - MetricsExporterImage string - ArangoImage string - Scope scope.Scope + ServiceAccount string + AllowChaos bool + OperatorImage string + ArangoImage string + Scope scope.Scope } // Dependencies holds dependent services for a Deployment diff --git a/pkg/deployment/deployment_core_test.go b/pkg/deployment/deployment_core_test.go index 9911d95a8..b8947db5f 100644 --- a/pkg/deployment/deployment_core_test.go +++ b/pkg/deployment/deployment_core_test.go @@ -33,7 +33,6 @@ import ( api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" "github.com/arangodb/kube-arangodb/pkg/util" - "github.com/arangodb/kube-arangodb/pkg/util/constants" "github.com/arangodb/kube-arangodb/pkg/util/k8sutil" ) @@ -276,7 +275,7 @@ func TestEnsurePod_ArangoDB_Core(t *testing.T) { }, }, config: Config{ - OperatorUUIDInitImage: testImageOperatorUUIDInit, + OperatorImage: testImageOperator, }, Helper: func(t *testing.T, deployment *Deployment, testCase *testCaseStruct) { deployment.status.last = api.DeploymentStatus{ @@ -505,7 +504,7 @@ func TestEnsurePod_ArangoDB_Core(t *testing.T) { }, }, config: Config{ - OperatorUUIDInitImage: testImageOperatorUUIDInit, + OperatorImage: testImageOperator, }, Helper: func(t *testing.T, deployment *Deployment, testCase *testCaseStruct) { deployment.status.last = api.DeploymentStatus{ @@ -1090,7 +1089,7 @@ func TestEnsurePod_ArangoDB_Core(t *testing.T) { testCase.ExpectedPod.ObjectMeta.Labels[k8sutil.LabelKeyArangoExporter] = testYes }, config: Config{ - LifecycleImage: testImageLifecycle, + OperatorImage: testImageOperator, }, ExpectedEvent: "member dbserver is created", ExpectedPod: core.Pod{ @@ -1108,13 +1107,7 @@ func TestEnsurePod_ArangoDB_Core(t *testing.T) { Name: k8sutil.ServerContainerName, Image: testImage, Command: createTestCommandForDBServer(firstDBServerStatus.ID, false, false, false), - Env: []core.EnvVar{ - k8sutil.CreateEnvFieldPath(constants.EnvOperatorPodName, "metadata.name"), - k8sutil.CreateEnvFieldPath(constants.EnvOperatorPodNamespace, "metadata.namespace"), - k8sutil.CreateEnvFieldPath(constants.EnvOperatorNodeName, "spec.nodeName"), - k8sutil.CreateEnvFieldPath(constants.EnvOperatorNodeNameArango, "spec.nodeName"), - }, - Ports: createTestPorts(), + Ports: createTestPorts(), VolumeMounts: []core.VolumeMount{ k8sutil.ArangodVolumeMount(), k8sutil.LifecycleVolumeMount(), @@ -1160,8 +1153,7 @@ func TestEnsurePod_ArangoDB_Core(t *testing.T) { testCase.ExpectedPod.ObjectMeta.Labels[k8sutil.LabelKeyArangoExporter] = testYes }, config: Config{ - LifecycleImage: testImageLifecycle, - OperatorUUIDInitImage: testImageOperatorUUIDInit, + OperatorImage: testImageOperator, }, ExpectedEvent: "member dbserver is created", ExpectedPod: core.Pod{ @@ -1169,7 +1161,6 @@ func TestEnsurePod_ArangoDB_Core(t *testing.T) { Volumes: []core.Volume{ k8sutil.CreateVolumeEmptyDir(k8sutil.ArangodVolumeName), k8sutil.CreateVolumeWithSecret(k8sutil.ExporterJWTVolumeName, testExporterToken), - k8sutil.LifecycleVolume(), }, InitContainers: []core.Container{ createTestLifecycleContainer(emptyResources), @@ -1180,13 +1171,7 @@ func TestEnsurePod_ArangoDB_Core(t *testing.T) { Name: k8sutil.ServerContainerName, Image: testImage, Command: createTestCommandForDBServer(firstDBServerStatus.ID, false, false, false), - Env: []core.EnvVar{ - k8sutil.CreateEnvFieldPath(constants.EnvOperatorPodName, "metadata.name"), - k8sutil.CreateEnvFieldPath(constants.EnvOperatorPodNamespace, "metadata.namespace"), - k8sutil.CreateEnvFieldPath(constants.EnvOperatorNodeName, "spec.nodeName"), - k8sutil.CreateEnvFieldPath(constants.EnvOperatorNodeNameArango, "spec.nodeName"), - }, - Ports: createTestPorts(), + Ports: createTestPorts(), VolumeMounts: []core.VolumeMount{ k8sutil.ArangodVolumeMount(), k8sutil.LifecycleVolumeMount(), @@ -1208,102 +1193,6 @@ func TestEnsurePod_ArangoDB_Core(t *testing.T) { }, }, }, - { - Name: "DBserver Pod with metrics exporter, lifecycle, tls, authentication, license, rocksDB encryption, secured liveness", - ArangoDeployment: &api.ArangoDeployment{ - Spec: api.DeploymentSpec{ - Image: util.NewString(testImage), - Authentication: authenticationSpec, - TLS: tlsSpec, - Metrics: metricsSpec, - RocksDB: rocksDBSpec, - Environment: api.NewEnvironment(api.EnvironmentProduction), - License: api.LicenseSpec{ - SecretName: util.NewString(testLicense), - }, - }, - }, - Helper: func(t *testing.T, deployment *Deployment, testCase *testCaseStruct) { - deployment.status.last = api.DeploymentStatus{ - Members: api.DeploymentStatusMembers{ - DBServers: api.MemberStatusList{ - firstDBServerStatus, - }, - }, - Images: createTestImages(false), - } - - testCase.createTestPodData(deployment, api.ServerGroupDBServers, firstDBServerStatus) - testCase.ExpectedPod.ObjectMeta.Labels[k8sutil.LabelKeyArangoExporter] = testYes - - key := make([]byte, 32) - k8sutil.CreateEncryptionKeySecret(deployment.SecretsModInterface(), testRocksDBEncryptionKey, key) - - authorization, err := createTestToken(deployment, testCase, []string{"/_api/version"}) - require.NoError(t, err) - - testCase.ExpectedPod.Spec.Containers[0].LivenessProbe = createTestLivenessProbe(httpProbe, true, - authorization, k8sutil.ArangoPort) - }, - config: Config{ - LifecycleImage: testImageLifecycle, - }, - ExpectedEvent: "member dbserver is created", - ExpectedPod: core.Pod{ - Spec: core.PodSpec{ - Volumes: []core.Volume{ - k8sutil.CreateVolumeEmptyDir(k8sutil.ArangodVolumeName), - createTestTLSVolume(api.ServerGroupDBServersString, firstDBServerStatus.ID), - k8sutil.CreateVolumeWithSecret(k8sutil.RocksdbEncryptionVolumeName, testRocksDBEncryptionKey), - k8sutil.CreateVolumeWithSecret(k8sutil.ExporterJWTVolumeName, testExporterToken), - k8sutil.CreateVolumeWithSecret(k8sutil.ClusterJWTSecretVolumeName, testJWTSecretName), - k8sutil.LifecycleVolume(), - }, - InitContainers: []core.Container{ - createTestLifecycleContainer(emptyResources), - }, - Containers: []core.Container{ - { - Name: k8sutil.ServerContainerName, - Image: testImage, - Command: createTestCommandForDBServer(firstDBServerStatus.ID, true, true, true), - Env: []core.EnvVar{ - k8sutil.CreateEnvSecretKeySelector(constants.EnvArangoLicenseKey, - testLicense, constants.SecretKeyToken), - k8sutil.CreateEnvFieldPath(constants.EnvOperatorPodName, "metadata.name"), - k8sutil.CreateEnvFieldPath(constants.EnvOperatorPodNamespace, "metadata.namespace"), - k8sutil.CreateEnvFieldPath(constants.EnvOperatorNodeName, "spec.nodeName"), - k8sutil.CreateEnvFieldPath(constants.EnvOperatorNodeNameArango, "spec.nodeName"), - }, - Ports: createTestPorts(), - Lifecycle: createTestLifecycle(), - LivenessProbe: createTestLivenessProbe(httpProbe, false, "", k8sutil.ArangoPort), - ImagePullPolicy: core.PullIfNotPresent, - SecurityContext: securityContext.NewSecurityContext(), - VolumeMounts: []core.VolumeMount{ - k8sutil.ArangodVolumeMount(), - k8sutil.LifecycleVolumeMount(), - k8sutil.TlsKeyfileVolumeMount(), - k8sutil.RocksdbEncryptionVolumeMount(), - k8sutil.ClusterJWTVolumeMount(), - }, - Resources: emptyResources, - }, - func() core.Container { - c := testArangodbInternalExporterContainer(true, emptyResources) - c.VolumeMounts = append(c.VolumeMounts, k8sutil.TlsKeyfileVolumeMount()) - return c - }(), - }, - RestartPolicy: core.RestartPolicyNever, - TerminationGracePeriodSeconds: &defaultDBServerTerminationTimeout, - Hostname: testDeploymentName + "-" + api.ServerGroupDBServersString + "-" + firstDBServerStatus.ID, - Subdomain: testDeploymentName + "-int", - Affinity: k8sutil.CreateAffinity(testDeploymentName, api.ServerGroupDBServersString, - true, ""), - }, - }, - }, { Name: "Coordinator Pod with TLS and authentication and readiness and liveness", ArangoDeployment: &api.ArangoDeployment{ diff --git a/pkg/deployment/deployment_encryption_test.go b/pkg/deployment/deployment_encryption_test.go index 0c326bca5..f8a881ab2 100644 --- a/pkg/deployment/deployment_encryption_test.go +++ b/pkg/deployment/deployment_encryption_test.go @@ -32,7 +32,6 @@ import ( api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" "github.com/arangodb/kube-arangodb/pkg/util" - "github.com/arangodb/kube-arangodb/pkg/util/constants" "github.com/arangodb/kube-arangodb/pkg/util/k8sutil" ) @@ -136,7 +135,7 @@ func TestEnsurePod_ArangoDB_Encryption(t *testing.T) { authorization, k8sutil.ArangoPort) }, config: Config{ - LifecycleImage: testImageLifecycle, + OperatorImage: testImageOperator, }, ExpectedEvent: "member dbserver is created", ExpectedPod: core.Pod{ @@ -154,17 +153,9 @@ func TestEnsurePod_ArangoDB_Encryption(t *testing.T) { }, Containers: []core.Container{ { - Name: k8sutil.ServerContainerName, - Image: testImage, - Command: createTestCommandForDBServer(firstDBServerStatus.ID, true, true, true), - Env: []core.EnvVar{ - k8sutil.CreateEnvSecretKeySelector(constants.EnvArangoLicenseKey, - testLicense, constants.SecretKeyToken), - k8sutil.CreateEnvFieldPath(constants.EnvOperatorPodName, "metadata.name"), - k8sutil.CreateEnvFieldPath(constants.EnvOperatorPodNamespace, "metadata.namespace"), - k8sutil.CreateEnvFieldPath(constants.EnvOperatorNodeName, "spec.nodeName"), - k8sutil.CreateEnvFieldPath(constants.EnvOperatorNodeNameArango, "spec.nodeName"), - }, + Name: k8sutil.ServerContainerName, + Image: testImage, + Command: createTestCommandForDBServer(firstDBServerStatus.ID, true, true, true), Ports: createTestPorts(), Lifecycle: createTestLifecycle(), LivenessProbe: createTestLivenessProbe(httpProbe, false, "", k8sutil.ArangoPort), diff --git a/pkg/deployment/deployment_pod_sync_test.go b/pkg/deployment/deployment_pod_sync_test.go index 1fcd80cd6..88c6614ca 100644 --- a/pkg/deployment/deployment_pod_sync_test.go +++ b/pkg/deployment/deployment_pod_sync_test.go @@ -197,6 +197,7 @@ func TestEnsurePod_Sync_Master(t *testing.T) { "secrets \"" + testDeploymentName + "-sync-client-auth-ca\" not found"), }, { + DropInit: true, Name: "Sync Master Pod with authentication, monitoring, tls, service account, node selector, " + "liveness probe, priority class name, resource requirements", ArangoDeployment: &api.ArangoDeployment{ @@ -237,11 +238,15 @@ func TestEnsurePod_Sync_Master(t *testing.T) { ExpectedPod: core.Pod{ Spec: core.PodSpec{ Volumes: []core.Volume{ + k8sutil.LifecycleVolume(), createTestTLSVolume(api.ServerGroupSyncMastersString, firstSyncMaster.ID), k8sutil.CreateVolumeWithSecret(k8sutil.ClientAuthCAVolumeName, "test-sync-client-auth-ca"), k8sutil.CreateVolumeWithSecret(k8sutil.MasterJWTSecretVolumeName, "test-sync-jwt"), k8sutil.CreateVolumeWithSecret(k8sutil.ClusterJWTSecretVolumeName, testJWTSecretName), }, + InitContainers: []core.Container{ + createTestLifecycleContainer(emptyResources), + }, Containers: []core.Container{ { Name: k8sutil.ServerContainerName, @@ -251,11 +256,17 @@ func TestEnsurePod_Sync_Master(t *testing.T) { Env: []core.EnvVar{ k8sutil.CreateEnvSecretKeySelector(constants.EnvArangoSyncMonitoringToken, testDeploymentName+"-sync-mt", constants.SecretKeyToken), + k8sutil.CreateEnvFieldPath(constants.EnvOperatorPodName, "metadata.name"), + k8sutil.CreateEnvFieldPath(constants.EnvOperatorPodNamespace, "metadata.namespace"), + k8sutil.CreateEnvFieldPath(constants.EnvOperatorNodeName, "spec.nodeName"), + k8sutil.CreateEnvFieldPath(constants.EnvOperatorNodeNameArango, "spec.nodeName"), }, ImagePullPolicy: core.PullIfNotPresent, + Lifecycle: createTestLifecycle(), Resources: resourcesUnfiltered, SecurityContext: securityContext.NewSecurityContext(), VolumeMounts: []core.VolumeMount{ + k8sutil.LifecycleVolumeMount(), k8sutil.TlsKeyfileVolumeMount(), k8sutil.ClientAuthCACertificateVolumeMount(), k8sutil.MasterJWTVolumeMount(), @@ -277,9 +288,10 @@ func TestEnsurePod_Sync_Master(t *testing.T) { }, }, { - Name: "Sync Master Pod with lifecycle, license, monitoring without authentication and alpine", + DropInit: true, + Name: "Sync Master Pod with lifecycle, license, monitoring without authentication and alpine", config: Config{ - LifecycleImage: testImageLifecycle, + OperatorImage: testImageOperator, }, ArangoDeployment: &api.ArangoDeployment{ Spec: api.DeploymentSpec{ @@ -372,10 +384,11 @@ func TestEnsurePod_Sync_Master(t *testing.T) { func TestEnsurePod_Sync_Worker(t *testing.T) { testCases := []testCaseStruct{ { + DropInit: true, Name: "Sync Worker Pod with monitoring, service account, node selector, lifecycle, license " + "liveness probe, priority class name, resource requirements without alpine", config: Config{ - LifecycleImage: testImageLifecycle, + OperatorImage: testImageOperator, }, ArangoDeployment: &api.ArangoDeployment{ Spec: api.DeploymentSpec{ @@ -440,8 +453,8 @@ func TestEnsurePod_Sync_Worker(t *testing.T) { k8sutil.CreateEnvFieldPath(constants.EnvOperatorNodeName, "spec.nodeName"), k8sutil.CreateEnvFieldPath(constants.EnvOperatorNodeNameArango, "spec.nodeName"), }, - Lifecycle: createTestLifecycle(), ImagePullPolicy: core.PullIfNotPresent, + Lifecycle: createTestLifecycle(), Resources: k8sutil.ExtractPodResourceRequirement(resourcesUnfiltered), SecurityContext: securityContext.NewSecurityContext(), VolumeMounts: []core.VolumeMount{ diff --git a/pkg/deployment/deployment_run_test.go b/pkg/deployment/deployment_run_test.go index 76cd1c4ac..b79774fba 100644 --- a/pkg/deployment/deployment_run_test.go +++ b/pkg/deployment/deployment_run_test.go @@ -62,8 +62,14 @@ func runTestCases(t *testing.T, testCases ...testCaseStruct) { func runTestCase(t *testing.T, testCase testCaseStruct) { t.Run(testCase.Name, func(t *testing.T) { // Arrange + if testCase.config.OperatorImage == "" { + testCase.config.OperatorImage = testImageOperator + } + d, eventRecorder := createTestDeployment(t, testCase.config, testCase.ArangoDeployment) + startDepl := d.status.last.DeepCopy() + errs := 0 for { require.NoError(t, d.currentState.Refresh(context.Background())) @@ -89,6 +95,21 @@ func runTestCase(t *testing.T, testCase testCaseStruct) { testCase.Helper(t, d, &testCase) } + f := startDepl.Members.AsList() + if len(f) == 0 { + f = d.status.last.Members.AsList() + } + + // Add Expected pod defaults + if !testCase.DropInit { + testCase.ExpectedPod = *defaultPodAppender(t, &testCase.ExpectedPod, + addLifecycle(f[0].Member.ID, + f[0].Group == api.ServerGroupDBServers && f[0].Member.IsInitialized, + testCase.ArangoDeployment.Spec.License.GetSecretName(), + f[0].Group), + podDataSort()) + } + // Create custom resource in the fake kubernetes API _, err := d.deps.DatabaseCRCli.DatabaseV1().ArangoDeployments(testNamespace).Create(context.Background(), d.apiObject, metav1.CreateOptions{}) require.NoError(t, err) diff --git a/pkg/deployment/deployment_suite_test.go b/pkg/deployment/deployment_suite_test.go index d8158ff3a..882439257 100644 --- a/pkg/deployment/deployment_suite_test.go +++ b/pkg/deployment/deployment_suite_test.go @@ -29,6 +29,8 @@ import ( "io/ioutil" "os" "path/filepath" + "sort" + "strings" "testing" "github.com/arangodb/kube-arangodb/pkg/deployment/resources/inspector" @@ -70,8 +72,7 @@ const ( testLicense = "testLicense" testServiceAccountName = "testServiceAccountName" testPriorityClassName = "testPriority" - testImageLifecycle = "arangodb/kube-arangodb:0.3.16" - testImageOperatorUUIDInit = "image/test-1234:3.7" + testImageOperator = "arangodb/kube-arangodb:0.3.16" testYes = "yes" ) @@ -91,6 +92,7 @@ type testCaseStruct struct { ExpectedEvent string ExpectedPod core.Pod Features testCaseFeatures + DropInit bool } func createTestTLSVolume(serverGroupString, ID string) core.Volume { @@ -536,7 +538,7 @@ func createTestLifecycleContainer(resources core.ResourceRequirements) core.Cont return core.Container{ Name: "init-lifecycle", - Image: testImageLifecycle, + Image: testImageOperator, Command: []string{binaryPath, "lifecycle", "copy", "--target", "/lifecycle/tools"}, VolumeMounts: []core.VolumeMount{ k8sutil.LifecycleVolumeMount(), @@ -550,7 +552,7 @@ func createTestLifecycleContainer(resources core.ResourceRequirements) core.Cont func createTestAlpineContainer(name string, requireUUID bool) core.Container { binaryPath, _ := os.Executable() var securityContext api.ServerGroupSpecSecurityContext - return k8sutil.ArangodInitContainer("uuid", name, "rocksdb", binaryPath, testImageOperatorUUIDInit, requireUUID, securityContext.NewSecurityContext()) + return k8sutil.ArangodInitContainer("uuid", name, "rocksdb", binaryPath, testImageOperator, requireUUID, securityContext.NewSecurityContext()) } func (testCase *testCaseStruct) createTestPodData(deployment *Deployment, group api.ServerGroup, @@ -566,7 +568,7 @@ func (testCase *testCaseStruct) createTestPodData(deployment *Deployment, group OwnerReferences: []metav1.OwnerReference{ testCase.ArangoDeployment.AsOwner(), }, - Finalizers: deployment.resources.CreatePodFinalizers(group), + Finalizers: finalizers(group), } groupSpec := testCase.ArangoDeployment.Spec.GetServerGroupSpec(group) @@ -579,3 +581,175 @@ func (testCase *testCaseStruct) createTestPodData(deployment *Deployment, group deployment.apiObject.Status.Members.Update(member, group) } } + +func finalizers(group api.ServerGroup) []string { + var finalizers []string + switch group { + case api.ServerGroupAgents: + finalizers = append(finalizers, constants.FinalizerPodAgencyServing) + case api.ServerGroupDBServers: + finalizers = append(finalizers, constants.FinalizerPodDrainDBServer) + } + + return finalizers +} + +func defaultPodAppender(t *testing.T, pod *core.Pod, f ...func(t *testing.T, p *core.Pod)) *core.Pod { + n := pod.DeepCopy() + + for _, a := range f { + a(t, n) + } + + return n +} + +func podDataSort() func(t *testing.T, p *core.Pod) { + sortVolumes := map[string]int{ + "rocksdb-encryption": -1, + "cluster-jwt": 1, + "tls-keyfile": -2, + "arangod-data": -3, + "exporter-jwt": 0, + "lifecycle": 2, + "uuid": 3, + "volume": 40, + "volume2": 40, + } + sortVolumeMounts := map[string]int{ + "tls-keyfile": 1, + "arangod-data": -1, + "lifecycle": 0, + "cluster-jwt": 5, + "rocksdb-encryption": 4, + "volume": 40, + "volume2": 40, + } + sortInitContainers := map[string]int{ + "init-lifecycle": 0, + "uuid": 1, + } + + return func(t *testing.T, p *core.Pod) { + sort.Slice(p.Spec.Volumes, func(i, j int) bool { + av, ak := sortVolumes[p.Spec.Volumes[i].Name] + if strings.HasPrefix(p.Spec.Volumes[i].Name, "sni-") { + av = 100 + ak = true + } + bv, bk := sortVolumes[p.Spec.Volumes[j].Name] + if strings.HasPrefix(p.Spec.Volumes[j].Name, "sni-") { + bv = 100 + bk = true + } + + if !ak && !bk { + return false + } + + if !ak { + return true + } + + if !bk { + return false + } + + return av < bv + }) + + if len(p.Spec.Containers) > 0 { + sort.Slice(p.Spec.Containers[0].VolumeMounts, func(i, j int) bool { + av, ak := sortVolumeMounts[p.Spec.Containers[0].VolumeMounts[i].Name] + if strings.HasPrefix(p.Spec.Containers[0].VolumeMounts[i].Name, "sni-") { + av = 100 + ak = true + } + bv, bk := sortVolumeMounts[p.Spec.Containers[0].VolumeMounts[j].Name] + if strings.HasPrefix(p.Spec.Containers[0].VolumeMounts[j].Name, "sni-") { + bv = 100 + bk = true + } + + if !ak && !bk { + return false + } + + if !ak { + return true + } + + if !bk { + return false + } + + return av < bv + }) + } + + sort.Slice(p.Spec.InitContainers, func(i, j int) bool { + av, ak := sortInitContainers[p.Spec.InitContainers[i].Name] + bv, bk := sortInitContainers[p.Spec.InitContainers[j].Name] + + if !ak && !bk { + return false + } + + if !ak { + return true + } + + if !bk { + return false + } + + return av < bv + }) + } +} + +func addLifecycle(name string, uuidRequired bool, license string, group api.ServerGroup) func(t *testing.T, p *core.Pod) { + return func(t *testing.T, p *core.Pod) { + if group.IsArangosync() { + + return + } + + if len(p.Spec.Containers) > 0 { + p.Spec.Containers[0].Env = append(k8sutil.GetLifecycleEnv(), p.Spec.Containers[0].Env...) + if license != "" { + p.Spec.Containers[0].Env = append([]core.EnvVar{ + k8sutil.CreateEnvSecretKeySelector(constants.EnvArangoLicenseKey, + license, constants.SecretKeyToken)}, p.Spec.Containers[0].Env...) + } + } + + if _, ok := k8sutil.GetAnyVolumeByName(p.Spec.Volumes, k8sutil.LifecycleVolumeName); !ok { + p.Spec.Volumes = append([]core.Volume{k8sutil.LifecycleVolume()}, p.Spec.Volumes...) + } + if _, ok := k8sutil.GetAnyVolumeByName(p.Spec.Volumes, "arangod-data"); !ok { + p.Spec.Volumes = append([]core.Volume{k8sutil.LifecycleVolume()}, p.Spec.Volumes...) + } + + if len(p.Spec.Containers) > 0 { + p.Spec.Containers[0].Lifecycle = createTestLifecycle() + } + + if len(p.Spec.Containers) > 0 { + if _, ok := k8sutil.GetAnyVolumeMountByName(p.Spec.Containers[0].VolumeMounts, "lifecycle"); !ok { + p.Spec.Containers[0].VolumeMounts = append(p.Spec.Containers[0].VolumeMounts, k8sutil.LifecycleVolumeMount()) + } + + if _, ok := k8sutil.GetAnyContainerByName(p.Spec.InitContainers, "init-lifecycle"); !ok { + p.Spec.InitContainers = append([]core.Container{createTestLifecycleContainer(emptyResources)}, p.Spec.InitContainers...) + + } + } + + if _, ok := k8sutil.GetAnyContainerByName(p.Spec.InitContainers, "uuid"); !ok { + binaryPath, _ := os.Executable() + p.Spec.InitContainers = append([]core.Container{k8sutil.ArangodInitContainer("uuid", name, "rocksdb", binaryPath, testImageOperator, uuidRequired, securityContext.NewSecurityContext())}, p.Spec.InitContainers...) + + } + } +} diff --git a/pkg/deployment/features/metrics.go b/pkg/deployment/features/metrics.go index 02f4c2be3..a8a83b38c 100644 --- a/pkg/deployment/features/metrics.go +++ b/pkg/deployment/features/metrics.go @@ -41,6 +41,7 @@ var metricsExporter = &feature{ constValue: util.NewBool(true), } +// deprecated func MetricsExporter() Feature { return metricsExporter } diff --git a/pkg/deployment/resources/context.go b/pkg/deployment/resources/context.go index 77553332f..106ac5ea8 100644 --- a/pkg/deployment/resources/context.go +++ b/pkg/deployment/resources/context.go @@ -149,12 +149,8 @@ type Context interface { // UpdateStatus replaces the status of the deployment with the given status and // updates the resources in k8s. UpdateStatus(ctx context.Context, status api.DeploymentStatus, lastVersion int32, force ...bool) error - // GetLifecycleImage returns the image name containing the lifecycle helper (== name of operator image) - GetLifecycleImage() string - // GetOperatorUUIDImage returns the image name containing the uuid helper (== name of operator image) - GetOperatorUUIDImage() string - // GetMetricsExporterImage returns the image name containing the default metrics exporter image - GetMetricsExporterImage() string + // GetOperatorImage returns the image name of operator image + GetOperatorImage() string // GetArangoImage returns the image name containing the default arango image GetArangoImage() string // GetName returns the name of the deployment diff --git a/pkg/deployment/resources/exporter.go b/pkg/deployment/resources/exporter.go index 7c403c3fd..cf33856ea 100644 --- a/pkg/deployment/resources/exporter.go +++ b/pkg/deployment/resources/exporter.go @@ -94,34 +94,6 @@ func createInternalExporterArgs(spec api.DeploymentSpec, groupSpec api.ServerGro return options.Sort().AsArgs() } -func createExporterArgs(spec api.DeploymentSpec, groupSpec api.ServerGroupSpec) []string { - tokenpath := filepath.Join(k8sutil.ExporterJWTVolumeMountDir, constants.SecretKeyToken) - options := k8sutil.CreateOptionPairs(64) - - options.Add("--arangodb.jwt-file", tokenpath) - - if port := groupSpec.InternalPort; port == nil { - scheme := "http" - if spec.IsSecure() { - scheme = "https" - } - options.Addf("--arangodb.endpoint", "%s://localhost:%d", scheme, k8sutil.ArangoPort) - } else { - options.Addf("--arangodb.endpoint", "http://localhost:%d", *port) - } - - keyPath := filepath.Join(k8sutil.TLSKeyfileVolumeMountDir, constants.SecretTLSKeyfile) - if spec.IsSecure() && spec.Metrics.IsTLS() { - options.Add("--ssl.keyfile", keyPath) - } - - if port := spec.Metrics.GetPort(); port != k8sutil.ArangoExporterPort { - options.Addf("--server.address", ":%d", port) - } - - return options.Sort().AsArgs() -} - func createExporterLivenessProbe(isSecure bool) *probes.HTTPProbeConfig { probeCfg := &probes.HTTPProbeConfig{ LocalPath: "/", diff --git a/pkg/deployment/resources/pod_creator.go b/pkg/deployment/resources/pod_creator.go index 97b156176..696d9bda5 100644 --- a/pkg/deployment/resources/pod_creator.go +++ b/pkg/deployment/resources/pod_creator.go @@ -250,23 +250,6 @@ func createArangoSyncArgs(apiObject meta.Object, spec api.DeploymentSpec, group return args } -// CreatePodFinalizers creates a list of finalizers for a pod created for the given group. -func (r *Resources) CreatePodFinalizers(group api.ServerGroup) []string { - var finalizers []string - if d := r.context.GetSpec().GetServerGroupSpec(group).ShutdownDelay; d != nil { - finalizers = append(finalizers, constants.FinalizerDelayPodTermination) - } - - switch group { - case api.ServerGroupAgents: - finalizers = append(finalizers, constants.FinalizerPodAgencyServing) - case api.ServerGroupDBServers: - finalizers = append(finalizers, constants.FinalizerPodDrainDBServer) - } - - return finalizers -} - // CreatePodTolerations creates a list of tolerations for a pod created for the given group. func (r *Resources) CreatePodTolerations(group api.ServerGroup, groupSpec api.ServerGroupSpec) []core.Toleration { notReadyDur := k8sutil.TolerationDuration{Forever: false, TimeSpan: time.Minute} diff --git a/pkg/deployment/resources/pod_creator_arangod.go b/pkg/deployment/resources/pod_creator_arangod.go index 5ba8f4f05..2b67002d2 100644 --- a/pkg/deployment/resources/pod_creator_arangod.go +++ b/pkg/deployment/resources/pod_creator_arangod.go @@ -29,8 +29,6 @@ import ( "github.com/arangodb/kube-arangodb/pkg/deployment/topology" - "github.com/arangodb/kube-arangodb/pkg/deployment/features" - "github.com/arangodb/kube-arangodb/pkg/util/collection" "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/interfaces" @@ -157,9 +155,7 @@ func (a *ArangoDContainer) GetEnvs() []core.EnvVar { envs.Add(true, env) } - if a.resources.context.GetLifecycleImage() != "" { - envs.Add(true, k8sutil.GetLifecycleEnv()...) - } + envs.Add(true, k8sutil.GetLifecycleEnv()...) if a.groupSpec.Resources.Limits != nil { if a.groupSpec.GetOverrideDetectedTotalMemory() { @@ -201,10 +197,7 @@ func (a *ArangoDContainer) GetResourceRequirements() core.ResourceRequirements { } func (a *ArangoDContainer) GetLifecycle() (*core.Lifecycle, error) { - if a.resources.context.GetLifecycleImage() != "" { - return k8sutil.NewLifecycle() - } - return nil, nil + return k8sutil.NewLifecycle() } func (a *ArangoDContainer) GetImagePullPolicy() core.PullPolicy { @@ -312,28 +305,11 @@ func (m *MemberArangoDPod) GetSidecars(pod *core.Pod) error { if m.spec.Metrics.IsEnabled() { var c *core.Container - if features.MetricsExporter().Enabled() { - pod.Labels[k8sutil.LabelKeyArangoExporter] = "yes" - if container, err := m.createMetricsExporterSidecarInternalExporter(); err != nil { - return err - } else { - c = container - } + pod.Labels[k8sutil.LabelKeyArangoExporter] = "yes" + if container, err := m.createMetricsExporterSidecarInternalExporter(); err != nil { + return err } else { - switch m.spec.Metrics.Mode.Get() { - case api.MetricsModeExporter: - if !m.group.IsExportMetrics() { - break - } - fallthrough - case api.MetricsModeSidecar: - c = m.createMetricsExporterSidecarExternalExporter() - - pod.Labels[k8sutil.LabelKeyArangoExporter] = "yes" - default: - pod.Labels[k8sutil.LabelKeyArangoExporter] = "yes" - } - + c = container } if c != nil { pod.Spec.Containers = append(pod.Spec.Containers, *c) @@ -354,9 +330,7 @@ func (m *MemberArangoDPod) GetVolumes() ([]core.Volume, []core.VolumeMount) { volumes.AddVolumeMount(k8sutil.ArangodVolumeMount()) - if m.resources.context.GetLifecycleImage() != "" { - volumes.AddVolumeMount(k8sutil.LifecycleVolumeMount()) - } + volumes.AddVolumeMount(k8sutil.LifecycleVolumeMount()) if m.status.PersistentVolumeClaimName != "" { vol := k8sutil.CreateVolumeWithPersitantVolumeClaim(k8sutil.ArangodVolumeName, @@ -377,34 +351,16 @@ func (m *MemberArangoDPod) GetVolumes() ([]core.Volume, []core.VolumeMount) { volumes.Append(pod.Security(), m.AsInput()) if m.spec.Metrics.IsEnabled() { - if features.MetricsExporter().Enabled() { - token := m.spec.Metrics.GetJWTTokenSecretName() - if token != "" { - vol := k8sutil.CreateVolumeWithSecret(k8sutil.ExporterJWTVolumeName, token) - volumes.AddVolume(vol) - } - } else { - switch m.spec.Metrics.Mode.Get() { - case api.MetricsModeExporter: - if !m.group.IsExportMetrics() { - break - } - fallthrough - case api.MetricsModeSidecar: - token := m.spec.Metrics.GetJWTTokenSecretName() - if token != "" { - vol := k8sutil.CreateVolumeWithSecret(k8sutil.ExporterJWTVolumeName, token) - volumes.AddVolume(vol) - } - } + token := m.spec.Metrics.GetJWTTokenSecretName() + if token != "" { + vol := k8sutil.CreateVolumeWithSecret(k8sutil.ExporterJWTVolumeName, token) + volumes.AddVolume(vol) } } volumes.Append(pod.JWT(), m.AsInput()) - if m.resources.context.GetLifecycleImage() != "" { - volumes.AddVolume(k8sutil.LifecycleVolume()) - } + volumes.AddVolume(k8sutil.LifecycleVolume()) // SNI volumes.Append(pod.SNI(), m.AsInput()) @@ -436,9 +392,8 @@ func (m *MemberArangoDPod) GetInitContainers(cachedStatus interfaces.Inspector) return nil, err } - lifecycleImage := m.resources.context.GetLifecycleImage() - if lifecycleImage != "" { - c, err := k8sutil.InitLifecycleContainer(lifecycleImage, &m.spec.Lifecycle.Resources, + { + c, err := k8sutil.InitLifecycleContainer(m.resources.context.GetOperatorImage(), &m.spec.Lifecycle.Resources, m.groupSpec.SecurityContext.NewSecurityContext()) if err != nil { return nil, err @@ -446,12 +401,11 @@ func (m *MemberArangoDPod) GetInitContainers(cachedStatus interfaces.Inspector) initContainers = append(initContainers, c) } - operatorUUIDImage := m.resources.context.GetOperatorUUIDImage() - if operatorUUIDImage != "" { + { engine := m.spec.GetStorageEngine().AsArangoArgument() requireUUID := m.group == api.ServerGroupDBServers && m.status.IsInitialized - c := k8sutil.ArangodInitContainer(api.ServerGroupReservedInitContainerNameUUID, m.status.ID, engine, executable, operatorUUIDImage, requireUUID, + c := k8sutil.ArangodInitContainer(api.ServerGroupReservedInitContainerNameUUID, m.status.ID, engine, executable, m.resources.context.GetOperatorImage(), requireUUID, m.groupSpec.SecurityContext.NewSecurityContext()) initContainers = append(initContainers, c) } @@ -509,7 +463,19 @@ func (m *MemberArangoDPod) GetInitContainers(cachedStatus interfaces.Inspector) } func (m *MemberArangoDPod) GetFinalizers() []string { - return m.resources.CreatePodFinalizers(m.group) + var finalizers []string + if d := m.spec.GetServerGroupSpec(m.group).ShutdownDelay; d != nil { + finalizers = append(finalizers, constants.FinalizerDelayPodTermination) + } + + switch m.group { + case api.ServerGroupAgents: + finalizers = append(finalizers, constants.FinalizerPodAgencyServing) + case api.ServerGroupDBServers: + finalizers = append(finalizers, constants.FinalizerPodDrainDBServer) + } + + return finalizers } func (m *MemberArangoDPod) GetTolerations() []core.Toleration { @@ -551,33 +517,6 @@ func (m *MemberArangoDPod) createMetricsExporterSidecarInternalExporter() (*core return &c, nil } -func (m *MemberArangoDPod) createMetricsExporterSidecarExternalExporter() *core.Container { - image := m.context.GetMetricsExporterImage() - if m.spec.Metrics.HasImage() { - image = m.spec.Metrics.GetImage() - } - - args := createExporterArgs(m.spec, m.groupSpec) - if m.spec.Metrics.Mode.Get() == api.MetricsModeSidecar { - args = append(args, "--mode=passthru") - } - - c := ArangodbExporterContainer(image, args, - createExporterLivenessProbe(m.spec.IsSecure() && m.spec.Metrics.IsTLS()), m.spec.Metrics.Resources, - m.groupSpec.SecurityContext.NewSecurityContext(), - m.spec) - - if m.spec.Metrics.GetJWTTokenSecretName() != "" { - c.VolumeMounts = append(c.VolumeMounts, k8sutil.ExporterJWTVolumeMount()) - } - - if pod.IsTLSEnabled(m.AsInput()) { - c.VolumeMounts = append(c.VolumeMounts, k8sutil.TlsKeyfileVolumeMount()) - } - - return &c -} - func (m *MemberArangoDPod) ApplyPodSpec(p *core.PodSpec) error { p.SecurityContext = m.groupSpec.SecurityContext.NewPodSecurityContext() diff --git a/pkg/deployment/resources/pod_creator_sync.go b/pkg/deployment/resources/pod_creator_sync.go index 8c0dc664c..676c722d1 100644 --- a/pkg/deployment/resources/pod_creator_sync.go +++ b/pkg/deployment/resources/pod_creator_sync.go @@ -113,10 +113,7 @@ func (a *ArangoSyncContainer) GetResourceRequirements() core.ResourceRequirement } func (a *ArangoSyncContainer) GetLifecycle() (*core.Lifecycle, error) { - if a.resources.context.GetLifecycleImage() != "" { - return k8sutil.NewLifecycle() - } - return nil, nil + return k8sutil.NewLifecycle() } func (a *ArangoSyncContainer) GetImagePullPolicy() core.PullPolicy { @@ -144,9 +141,7 @@ func (a *ArangoSyncContainer) GetEnvs() []core.EnvVar { envs.Add(true, env) } - if a.resources.context.GetLifecycleImage() != "" { - envs.Add(true, k8sutil.GetLifecycleEnv()...) - } + envs.Add(true, k8sutil.GetLifecycleEnv()...) if len(a.groupSpec.Envs) > 0 { for _, env := range a.groupSpec.Envs { @@ -227,10 +222,8 @@ func (m *MemberSyncPod) GetVolumes() ([]core.Volume, []core.VolumeMount) { var volumes []core.Volume var volumeMounts []core.VolumeMount - if m.resources.context.GetLifecycleImage() != "" { - volumes = append(volumes, k8sutil.LifecycleVolume()) - volumeMounts = append(volumeMounts, k8sutil.LifecycleVolumeMount()) - } + volumes = append(volumes, k8sutil.LifecycleVolume()) + volumeMounts = append(volumeMounts, k8sutil.LifecycleVolumeMount()) if m.tlsKeyfileSecretName != "" { vol := k8sutil.CreateVolumeWithSecret(k8sutil.TlsKeyfileVolumeName, m.tlsKeyfileSecretName) @@ -273,9 +266,8 @@ func (m *MemberSyncPod) GetInitContainers(cachedStatus interfaces.Inspector) ([] initContainers = append(initContainers, c...) } - lifecycleImage := m.resources.context.GetLifecycleImage() - if lifecycleImage != "" { - c, err := k8sutil.InitLifecycleContainer(lifecycleImage, &m.spec.Lifecycle.Resources, + { + c, err := k8sutil.InitLifecycleContainer(m.resources.context.GetOperatorImage(), &m.spec.Lifecycle.Resources, m.groupSpec.SecurityContext.NewSecurityContext()) if err != nil { return nil, err diff --git a/pkg/deployment/rotation/arangod_containers.go b/pkg/deployment/rotation/arangod_containers.go index a485fce32..ade046864 100644 --- a/pkg/deployment/rotation/arangod_containers.go +++ b/pkg/deployment/rotation/arangod_containers.go @@ -26,9 +26,10 @@ package rotation import ( "strings" + "k8s.io/apimachinery/pkg/api/equality" + api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" "github.com/arangodb/kube-arangodb/pkg/util" - "github.com/arangodb/kube-arangodb/pkg/util/k8sutil" core "k8s.io/api/core/v1" ) @@ -47,23 +48,29 @@ func containersCompare(_ api.DeploymentSpec, _ api.ServerGroup, spec, status *co } for id := range a { - if ac, bc := &a[id], &b[id]; ac.Name == k8sutil.ServerContainerName && ac.Name == bc.Name { - if !IsOnlyLogLevelChanged(ac.Command, bc.Command) { - continue - } - - plan = append(plan, builder.NewAction(api.ActionTypeRuntimeContainerArgsLogLevelUpdate). - AddParam(ContainerName, ac.Name)) + if ac, bc := &a[id], &b[id]; ac.Name == bc.Name { + if ac.Name == api.ServerGroupReservedContainerNameServer { + if !isOnlyLogLevelChanged(ac.Command, bc.Command) { + continue + } - bc.Command = ac.Command - mode = mode.And(InPlaceRotation) - } else if ac.Name == bc.Name { - if ac.Image != bc.Image { - // Image changed - plan = append(plan, builder.NewAction(api.ActionTypeRuntimeContainerImageUpdate).AddParam(ContainerName, ac.Name).AddParam(ContainerImage, ac.Image)) + plan = append(plan, builder.NewAction(api.ActionTypeRuntimeContainerArgsLogLevelUpdate). + AddParam(ContainerName, ac.Name)) - bc.Image = ac.Image + bc.Command = ac.Command mode = mode.And(InPlaceRotation) + } else { + if ac.Image != bc.Image { + // Image changed + plan = append(plan, builder.NewAction(api.ActionTypeRuntimeContainerImageUpdate).AddParam(ContainerName, ac.Name).AddParam(ContainerImage, ac.Image)) + + bc.Image = ac.Image + mode = mode.And(InPlaceRotation) + } + } + + if api.IsReservedServerGroupContainerName(ac.Name) { + mode = mode.And(internalContainerLifecycleCompare(ac, bc)) } } } @@ -116,9 +123,9 @@ func initContainersCompare(deploymentSpec api.DeploymentSpec, group api.ServerGr } } -// IsOnlyLogLevelChanged returns true when status and spec log level arguments are different. +// isOnlyLogLevelChanged returns true when status and spec log level arguments are different. // If any other argument than --log.level is different false is returned. -func IsOnlyLogLevelChanged(specArgs, statusArgs []string) bool { +func isOnlyLogLevelChanged(specArgs, statusArgs []string) bool { diff := util.DiffStrings(specArgs, statusArgs) if len(diff) == 0 { return false @@ -132,3 +139,26 @@ func IsOnlyLogLevelChanged(specArgs, statusArgs []string) bool { return true } + +func internalContainerLifecycleCompare(spec, status *core.Container) Mode { + if spec.Lifecycle == nil && status.Lifecycle == nil { + return SkippedRotation + } + + if spec.Lifecycle == nil { + status.Lifecycle = nil + return SilentRotation + } + + if status.Lifecycle == nil { + status.Lifecycle = spec.Lifecycle + return SilentRotation + } + + if !equality.Semantic.DeepEqual(spec.Lifecycle, status.Lifecycle) { + status.Lifecycle = spec.Lifecycle.DeepCopy() + return SilentRotation + } + + return SkippedRotation +} diff --git a/pkg/deployment/rotation/arangod_containers_test.go b/pkg/deployment/rotation/arangod_containers_test.go index 1855e9d6f..d1340a565 100644 --- a/pkg/deployment/rotation/arangod_containers_test.go +++ b/pkg/deployment/rotation/arangod_containers_test.go @@ -216,7 +216,7 @@ func TestIsOnlyLogLevelChanged(t *testing.T) { for testName, testCase := range tests { t.Run(testName, func(t *testing.T) { - got := IsOnlyLogLevelChanged(testCase.args.specArgs, testCase.args.statusArgs) + got := isOnlyLogLevelChanged(testCase.args.specArgs, testCase.args.statusArgs) assert.Equal(t, testCase.want, got) }) diff --git a/pkg/operator/operator.go b/pkg/operator/operator.go index ac1d9a010..fd900ee9d 100644 --- a/pkg/operator/operator.go +++ b/pkg/operator/operator.go @@ -87,9 +87,8 @@ type Config struct { Namespace string PodName string ServiceAccount string - LifecycleImage string + OperatorImage string ArangoImage string - MetricsExporterImage string EnableDeployment bool EnableDeploymentReplication bool EnableStorage bool diff --git a/pkg/operator/operator_deployment.go b/pkg/operator/operator_deployment.go index c3ff073d0..2e4ca2b0f 100644 --- a/pkg/operator/operator_deployment.go +++ b/pkg/operator/operator_deployment.go @@ -203,13 +203,11 @@ func (o *Operator) handleDeploymentEvent(event *Event) error { // makeDeploymentConfigAndDeps creates a Config & Dependencies object for a new Deployment. func (o *Operator) makeDeploymentConfigAndDeps(apiObject *api.ArangoDeployment) (deployment.Config, deployment.Dependencies) { cfg := deployment.Config{ - ServiceAccount: o.Config.ServiceAccount, - LifecycleImage: o.Config.LifecycleImage, - OperatorUUIDInitImage: o.Config.LifecycleImage, - MetricsExporterImage: o.MetricsExporterImage, - ArangoImage: o.ArangoImage, - AllowChaos: o.Config.AllowChaos, - Scope: o.Scope, + ServiceAccount: o.Config.ServiceAccount, + OperatorImage: o.Config.OperatorImage, + ArangoImage: o.ArangoImage, + AllowChaos: o.Config.AllowChaos, + Scope: o.Scope, } deps := deployment.Dependencies{ Log: o.Dependencies.LogService.MustGetLogger(logging.LoggerNameDeployment).With(). diff --git a/pkg/util/k8sutil/container.go b/pkg/util/k8sutil/container.go index 25586560f..56b50e1df 100644 --- a/pkg/util/k8sutil/container.go +++ b/pkg/util/k8sutil/container.go @@ -46,6 +46,17 @@ func GetContainerStatusByName(p *core.Pod, name string) (core.ContainerStatus, b return core.ContainerStatus{}, false } +// GetAnyContainerByName returns the container in the given containers with the given name. +// Returns false if not found. +func GetAnyContainerByName(containers []core.Container, name string) (core.Container, bool) { + for _, c := range containers { + if c.Name == name { + return c, true + } + } + return core.Container{}, false +} + // GetAnyContainerStatusByName returns the container status in the given ContainerStatus list with the given name. // Returns false if not found. func GetAnyContainerStatusByName(containers []core.ContainerStatus, name string) (core.ContainerStatus, bool) { diff --git a/pkg/util/k8sutil/pods.go b/pkg/util/k8sutil/pods.go index 6d3f43f2f..e1f6d87fc 100644 --- a/pkg/util/k8sutil/pods.go +++ b/pkg/util/k8sutil/pods.go @@ -73,6 +73,28 @@ const ( ServerContainerConditionPrefix = "containers with unready status: " ) +// GetAnyVolumeByName returns the volume in the given volumes with the given name. +// Returns false if not found. +func GetAnyVolumeByName(volumes []core.Volume, name string) (core.Volume, bool) { + for _, c := range volumes { + if c.Name == name { + return c, true + } + } + return core.Volume{}, false +} + +// GetAnyVolumeMountByName returns the volumemount in the given volumemountss with the given name. +// Returns false if not found. +func GetAnyVolumeMountByName(volumes []core.VolumeMount, name string) (core.VolumeMount, bool) { + for _, c := range volumes { + if c.Name == name { + return c, true + } + } + return core.VolumeMount{}, false +} + // IsPodReady returns true if the PodReady condition on // the given pod is set to true. func IsPodReady(pod *core.Pod) bool { diff --git a/pkg/util/retry/timeout.go b/pkg/util/retry/timeout.go new file mode 100644 index 000000000..2fe4afa81 --- /dev/null +++ b/pkg/util/retry/timeout.go @@ -0,0 +1,79 @@ +// +// DISCLAIMER +// +// Copyright 2016-2021 ArangoDB GmbH, Cologne, Germany +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package retry + +import "time" + +func Interrput() error { + return interrupt{} +} + +type interrupt struct { +} + +func (i interrupt) Error() string { + return "interrupt" +} + +func IsInterrupt(err error) bool { + _, ok := err.(interrupt) + return ok +} + +type TimeoutError struct { +} + +func (i TimeoutError) Error() string { + return "timeout" +} + +func NewTimeout(t Timeout) Timeout { + return t +} + +type Timeout func() error + +func (t Timeout) Timeout(interval, timeout time.Duration) error { + timeoutI := time.NewTimer(timeout) + defer timeoutI.Stop() + + intervalI := time.NewTicker(interval) + defer intervalI.Stop() + + for { + err := t() + + if err != nil { + if IsInterrupt(err) { + return nil + } + + return err + } + + select { + case <-timeoutI.C: + return TimeoutError{} + case <-intervalI.C: + continue + } + } +}