From 1df282629d0b87899f65652cf039f00a59cc9575 Mon Sep 17 00:00:00 2001 From: ajanikow <12255597+ajanikow@users.noreply.github.com> Date: Tue, 4 Aug 2020 20:27:32 +0000 Subject: [PATCH] Configurable foxx services --- pkg/apis/deployment/v1/deployment_features.go | 36 ++ pkg/apis/deployment/v1/deployment_spec.go | 2 + .../deployment/v1/zz_generated.deepcopy.go | 26 + pkg/deployment/deployment_features_test.go | 537 ++++++++++++++++++ pkg/deployment/deployment_suite_test.go | 139 +++-- pkg/deployment/resources/pod_creator.go | 8 +- pkg/util/k8sutil/pair.go | 44 +- 7 files changed, 734 insertions(+), 58 deletions(-) create mode 100644 pkg/apis/deployment/v1/deployment_features.go create mode 100644 pkg/deployment/deployment_features_test.go diff --git a/pkg/apis/deployment/v1/deployment_features.go b/pkg/apis/deployment/v1/deployment_features.go new file mode 100644 index 000000000..1b8cd45a5 --- /dev/null +++ b/pkg/apis/deployment/v1/deployment_features.go @@ -0,0 +1,36 @@ +// +// DISCLAIMER +// +// Copyright 2020 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 +// +// Author Ewout Prangsma +// + +package v1 + +type DeploymentFeatures struct { + FoxxQueues *bool `json:"foxx.queues,omitempty"` +} + +// GetFoxxQueues return if foxx queues are enabled. Defaults to true. +func (d *DeploymentFeatures) GetFoxxQueues() bool { + if d == nil || d.FoxxQueues == nil { + return true + } + + return *d.FoxxQueues +} diff --git a/pkg/apis/deployment/v1/deployment_spec.go b/pkg/apis/deployment/v1/deployment_spec.go index 6fe3e9f3b..447157b18 100644 --- a/pkg/apis/deployment/v1/deployment_spec.go +++ b/pkg/apis/deployment/v1/deployment_spec.go @@ -60,6 +60,8 @@ type DeploymentSpec struct { DowntimeAllowed *bool `json:"downtimeAllowed,omitempty"` DisableIPv6 *bool `json:"disableIPv6,omitempty"` + Features *DeploymentFeatures `json:"features,omitempty"` + NetworkAttachedVolumes *bool `json:"networkAttachedVolumes,omitempty"` // Annotations specified the annotations added to all resources diff --git a/pkg/apis/deployment/v1/zz_generated.deepcopy.go b/pkg/apis/deployment/v1/zz_generated.deepcopy.go index b896d58d6..618a29b80 100644 --- a/pkg/apis/deployment/v1/zz_generated.deepcopy.go +++ b/pkg/apis/deployment/v1/zz_generated.deepcopy.go @@ -258,6 +258,27 @@ func (in ConditionList) DeepCopy() ConditionList { return *out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DeploymentFeatures) DeepCopyInto(out *DeploymentFeatures) { + *out = *in + if in.FoxxQueues != nil { + in, out := &in.FoxxQueues, &out.FoxxQueues + *out = new(bool) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeploymentFeatures. +func (in *DeploymentFeatures) DeepCopy() *DeploymentFeatures { + if in == nil { + return nil + } + out := new(DeploymentFeatures) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DeploymentRestoreResult) DeepCopyInto(out *DeploymentRestoreResult) { *out = *in @@ -322,6 +343,11 @@ func (in *DeploymentSpec) DeepCopyInto(out *DeploymentSpec) { *out = new(bool) **out = **in } + if in.Features != nil { + in, out := &in.Features, &out.Features + *out = new(DeploymentFeatures) + (*in).DeepCopyInto(*out) + } if in.NetworkAttachedVolumes != nil { in, out := &in.NetworkAttachedVolumes, &out.NetworkAttachedVolumes *out = new(bool) diff --git a/pkg/deployment/deployment_features_test.go b/pkg/deployment/deployment_features_test.go new file mode 100644 index 000000000..bd6afd824 --- /dev/null +++ b/pkg/deployment/deployment_features_test.go @@ -0,0 +1,537 @@ +// +// DISCLAIMER +// +// Copyright 2020 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 +// +// Author Tomasz Mielech +// + +package deployment + +import ( + "testing" + + 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" +) + +func TestEnsurePod_ArangoDB_Features(t *testing.T) { + testCases := []testCaseStruct{ + { + Name: "DBserver POD with disabled foxx services", + ArangoDeployment: &api.ArangoDeployment{ + Spec: api.DeploymentSpec{ + Image: util.NewString(testImage), + Authentication: noAuthentication, + TLS: noTLS, + }, + }, + Helper: func(t *testing.T, deployment *Deployment, testCase *testCaseStruct) { + deployment.status.last = api.DeploymentStatus{ + Members: api.DeploymentStatusMembers{ + DBServers: api.MemberStatusList{ + firstDBServerStatus, + }, + }, + Images: createTestImages(false), + } + deployment.status.last.Members.DBServers[0].IsInitialized = true + + testCase.createTestPodData(deployment, api.ServerGroupDBServers, firstDBServerStatus) + }, + ExpectedEvent: "member dbserver is created", + ExpectedPod: core.Pod{ + Spec: core.PodSpec{ + Volumes: []core.Volume{ + k8sutil.CreateVolumeEmptyDir(k8sutil.ArangodVolumeName), + }, + Containers: []core.Container{ + { + Name: k8sutil.ServerContainerName, + Image: testImage, + Command: createTestCommandForDBServer(firstDBServerStatus.ID, false, false, false, func() k8sutil.OptionPairs { + args := k8sutil.NewOptionPair() + + args.Add("--foxx.queues", false) + + return args + }), + Ports: createTestPorts(), + Resources: emptyResources, + VolumeMounts: []core.VolumeMount{ + k8sutil.ArangodVolumeMount(), + }, + LivenessProbe: createTestLivenessProbe(httpProbe, false, "", k8sutil.ArangoPort), + ImagePullPolicy: core.PullIfNotPresent, + SecurityContext: securityContext.NewSecurityContext(), + }, + }, + RestartPolicy: core.RestartPolicyNever, + TerminationGracePeriodSeconds: &defaultDBServerTerminationTimeout, + Hostname: testDeploymentName + "-" + api.ServerGroupDBServersString + "-" + + firstDBServerStatus.ID, + Subdomain: testDeploymentName + "-int", + Affinity: k8sutil.CreateAffinity(testDeploymentName, api.ServerGroupDBServersString, + false, ""), + }, + }, + }, + { + Name: "DBserver POD with enabled foxx services", + ArangoDeployment: &api.ArangoDeployment{ + Spec: api.DeploymentSpec{ + Image: util.NewString(testImage), + Authentication: noAuthentication, + TLS: noTLS, + }, + }, + Helper: func(t *testing.T, deployment *Deployment, testCase *testCaseStruct) { + deployment.status.last = api.DeploymentStatus{ + Members: api.DeploymentStatusMembers{ + DBServers: api.MemberStatusList{ + firstDBServerStatus, + }, + }, + Images: createTestImages(false), + } + deployment.status.last.Members.DBServers[0].IsInitialized = true + + deployment.apiObject.Spec.Features = &api.DeploymentFeatures{ + FoxxQueues: util.NewBool(false), + } + + testCase.createTestPodData(deployment, api.ServerGroupDBServers, firstDBServerStatus) + }, + ExpectedEvent: "member dbserver is created", + ExpectedPod: core.Pod{ + Spec: core.PodSpec{ + Volumes: []core.Volume{ + k8sutil.CreateVolumeEmptyDir(k8sutil.ArangodVolumeName), + }, + Containers: []core.Container{ + { + Name: k8sutil.ServerContainerName, + Image: testImage, + Command: createTestCommandForDBServer(firstDBServerStatus.ID, false, false, false, func() k8sutil.OptionPairs { + args := k8sutil.NewOptionPair() + + args.Add("--foxx.queues", false) + + return args + }), + Ports: createTestPorts(), + Resources: emptyResources, + VolumeMounts: []core.VolumeMount{ + k8sutil.ArangodVolumeMount(), + }, + LivenessProbe: createTestLivenessProbe(httpProbe, false, "", k8sutil.ArangoPort), + ImagePullPolicy: core.PullIfNotPresent, + SecurityContext: securityContext.NewSecurityContext(), + }, + }, + RestartPolicy: core.RestartPolicyNever, + TerminationGracePeriodSeconds: &defaultDBServerTerminationTimeout, + Hostname: testDeploymentName + "-" + api.ServerGroupDBServersString + "-" + + firstDBServerStatus.ID, + Subdomain: testDeploymentName + "-int", + Affinity: k8sutil.CreateAffinity(testDeploymentName, api.ServerGroupDBServersString, + false, ""), + }, + }, + }, + { + Name: "Coordinator POD with undefined foxx services", + ArangoDeployment: &api.ArangoDeployment{ + Spec: api.DeploymentSpec{ + Image: util.NewString(testImage), + Authentication: noAuthentication, + TLS: noTLS, + }, + }, + Helper: func(t *testing.T, deployment *Deployment, testCase *testCaseStruct) { + deployment.status.last = api.DeploymentStatus{ + Members: api.DeploymentStatusMembers{ + Coordinators: api.MemberStatusList{ + firstCoordinatorStatus, + }, + }, + Images: createTestImages(false), + } + deployment.status.last.Members.Coordinators[0].IsInitialized = true + + testCase.createTestPodData(deployment, api.ServerGroupCoordinators, firstCoordinatorStatus) + }, + ExpectedEvent: "member coordinator is created", + ExpectedPod: core.Pod{ + Spec: core.PodSpec{ + Volumes: []core.Volume{ + k8sutil.CreateVolumeEmptyDir(k8sutil.ArangodVolumeName), + }, + Containers: []core.Container{ + { + Name: k8sutil.ServerContainerName, + Image: testImage, + Command: createTestCommandForCoordinator(firstCoordinatorStatus.ID, false, false, false, func() k8sutil.OptionPairs { + args := k8sutil.NewOptionPair() + + args.Add("--foxx.queues", true) + + return args + }), + Ports: createTestPorts(), + Resources: emptyResources, + VolumeMounts: []core.VolumeMount{ + k8sutil.ArangodVolumeMount(), + }, + ReadinessProbe: createTestReadinessProbe(httpProbe, false, ""), + ImagePullPolicy: core.PullIfNotPresent, + SecurityContext: securityContext.NewSecurityContext(), + }, + }, + RestartPolicy: core.RestartPolicyNever, + TerminationGracePeriodSeconds: &defaultCoordinatorTerminationTimeout, + Hostname: testDeploymentName + "-" + api.ServerGroupCoordinatorsString + "-" + + firstCoordinatorStatus.ID, + Subdomain: testDeploymentName + "-int", + Affinity: k8sutil.CreateAffinity(testDeploymentName, api.ServerGroupCoordinatorsString, + false, ""), + }, + }, + }, + { + Name: "Coordinator POD with disabled foxx services", + ArangoDeployment: &api.ArangoDeployment{ + Spec: api.DeploymentSpec{ + Image: util.NewString(testImage), + Authentication: noAuthentication, + TLS: noTLS, + }, + }, + Helper: func(t *testing.T, deployment *Deployment, testCase *testCaseStruct) { + deployment.status.last = api.DeploymentStatus{ + Members: api.DeploymentStatusMembers{ + Coordinators: api.MemberStatusList{ + firstCoordinatorStatus, + }, + }, + Images: createTestImages(false), + } + deployment.status.last.Members.Coordinators[0].IsInitialized = true + + deployment.apiObject.Spec.Features = &api.DeploymentFeatures{ + FoxxQueues: util.NewBool(false), + } + + testCase.createTestPodData(deployment, api.ServerGroupCoordinators, firstCoordinatorStatus) + }, + ExpectedEvent: "member coordinator is created", + ExpectedPod: core.Pod{ + Spec: core.PodSpec{ + Volumes: []core.Volume{ + k8sutil.CreateVolumeEmptyDir(k8sutil.ArangodVolumeName), + }, + Containers: []core.Container{ + { + Name: k8sutil.ServerContainerName, + Image: testImage, + Command: createTestCommandForCoordinator(firstCoordinatorStatus.ID, false, false, false, func() k8sutil.OptionPairs { + args := k8sutil.NewOptionPair() + + args.Add("--foxx.queues", false) + + return args + }), + Ports: createTestPorts(), + Resources: emptyResources, + VolumeMounts: []core.VolumeMount{ + k8sutil.ArangodVolumeMount(), + }, + ReadinessProbe: createTestReadinessProbe(httpProbe, false, ""), + ImagePullPolicy: core.PullIfNotPresent, + SecurityContext: securityContext.NewSecurityContext(), + }, + }, + RestartPolicy: core.RestartPolicyNever, + TerminationGracePeriodSeconds: &defaultCoordinatorTerminationTimeout, + Hostname: testDeploymentName + "-" + api.ServerGroupCoordinatorsString + "-" + + firstCoordinatorStatus.ID, + Subdomain: testDeploymentName + "-int", + Affinity: k8sutil.CreateAffinity(testDeploymentName, api.ServerGroupCoordinatorsString, + false, ""), + }, + }, + }, + { + Name: "Coordinator POD with enabled foxx services", + ArangoDeployment: &api.ArangoDeployment{ + Spec: api.DeploymentSpec{ + Image: util.NewString(testImage), + Authentication: noAuthentication, + TLS: noTLS, + }, + }, + Helper: func(t *testing.T, deployment *Deployment, testCase *testCaseStruct) { + deployment.status.last = api.DeploymentStatus{ + Members: api.DeploymentStatusMembers{ + Coordinators: api.MemberStatusList{ + firstCoordinatorStatus, + }, + }, + Images: createTestImages(false), + } + deployment.status.last.Members.Coordinators[0].IsInitialized = true + + deployment.apiObject.Spec.Features = &api.DeploymentFeatures{ + FoxxQueues: util.NewBool(true), + } + + testCase.createTestPodData(deployment, api.ServerGroupCoordinators, firstCoordinatorStatus) + }, + ExpectedEvent: "member coordinator is created", + ExpectedPod: core.Pod{ + Spec: core.PodSpec{ + Volumes: []core.Volume{ + k8sutil.CreateVolumeEmptyDir(k8sutil.ArangodVolumeName), + }, + Containers: []core.Container{ + { + Name: k8sutil.ServerContainerName, + Image: testImage, + Command: createTestCommandForCoordinator(firstCoordinatorStatus.ID, false, false, false, func() k8sutil.OptionPairs { + args := k8sutil.NewOptionPair() + + args.Add("--foxx.queues", true) + + return args + }), + Ports: createTestPorts(), + Resources: emptyResources, + VolumeMounts: []core.VolumeMount{ + k8sutil.ArangodVolumeMount(), + }, + ReadinessProbe: createTestReadinessProbe(httpProbe, false, ""), + ImagePullPolicy: core.PullIfNotPresent, + SecurityContext: securityContext.NewSecurityContext(), + }, + }, + RestartPolicy: core.RestartPolicyNever, + TerminationGracePeriodSeconds: &defaultCoordinatorTerminationTimeout, + Hostname: testDeploymentName + "-" + api.ServerGroupCoordinatorsString + "-" + + firstCoordinatorStatus.ID, + Subdomain: testDeploymentName + "-int", + Affinity: k8sutil.CreateAffinity(testDeploymentName, api.ServerGroupCoordinatorsString, + false, ""), + }, + }, + }, + { + Name: "Single POD with undefined foxx services", + ArangoDeployment: &api.ArangoDeployment{ + Spec: api.DeploymentSpec{ + Image: util.NewString(testImage), + Authentication: noAuthentication, + TLS: noTLS, + }, + }, + Helper: func(t *testing.T, deployment *Deployment, testCase *testCaseStruct) { + deployment.status.last = api.DeploymentStatus{ + Members: api.DeploymentStatusMembers{ + Single: api.MemberStatusList{ + singleStatus, + }, + }, + Images: createTestImages(false), + } + deployment.status.last.Members.Single[0].IsInitialized = true + + testCase.createTestPodData(deployment, api.ServerGroupSingle, singleStatus) + + testCase.ExpectedPod.Spec.Containers[0].LivenessProbe = createTestLivenessProbe(httpProbe, false, "", 0) + testCase.ExpectedPod.Spec.Containers[0].ReadinessProbe = createTestReadinessProbe(httpProbe, false, "") + }, + ExpectedEvent: "member single is created", + ExpectedPod: core.Pod{ + Spec: core.PodSpec{ + Volumes: []core.Volume{ + k8sutil.CreateVolumeEmptyDir(k8sutil.ArangodVolumeName), + }, + Containers: []core.Container{ + { + Name: k8sutil.ServerContainerName, + Image: testImage, + Command: createTestCommandForSingleMode(singleStatus.ID, false, false, false, func() k8sutil.OptionPairs { + args := k8sutil.NewOptionPair() + + args.Add("--foxx.queues", true) + + return args + }), + Ports: createTestPorts(), + Resources: emptyResources, + VolumeMounts: []core.VolumeMount{ + k8sutil.ArangodVolumeMount(), + }, + ImagePullPolicy: core.PullIfNotPresent, + SecurityContext: securityContext.NewSecurityContext(), + }, + }, + RestartPolicy: core.RestartPolicyNever, + TerminationGracePeriodSeconds: &defaultSingleTerminationTimeout, + Hostname: testDeploymentName + "-" + api.ServerGroupSingleString + "-" + + singleStatus.ID, + Subdomain: testDeploymentName + "-int", + Affinity: k8sutil.CreateAffinity(testDeploymentName, api.ServerGroupSingleString, + false, ""), + }, + }, + }, + { + Name: "Single POD with disabled foxx services", + ArangoDeployment: &api.ArangoDeployment{ + Spec: api.DeploymentSpec{ + Image: util.NewString(testImage), + Authentication: noAuthentication, + TLS: noTLS, + }, + }, + Helper: func(t *testing.T, deployment *Deployment, testCase *testCaseStruct) { + deployment.status.last = api.DeploymentStatus{ + Members: api.DeploymentStatusMembers{ + Single: api.MemberStatusList{ + singleStatus, + }, + }, + Images: createTestImages(false), + } + deployment.status.last.Members.Single[0].IsInitialized = true + + deployment.apiObject.Spec.Features = &api.DeploymentFeatures{ + FoxxQueues: util.NewBool(false), + } + + testCase.createTestPodData(deployment, api.ServerGroupSingle, singleStatus) + + testCase.ExpectedPod.Spec.Containers[0].LivenessProbe = createTestLivenessProbe(httpProbe, false, "", 0) + testCase.ExpectedPod.Spec.Containers[0].ReadinessProbe = createTestReadinessProbe(httpProbe, false, "") + }, + ExpectedEvent: "member single is created", + ExpectedPod: core.Pod{ + Spec: core.PodSpec{ + Volumes: []core.Volume{ + k8sutil.CreateVolumeEmptyDir(k8sutil.ArangodVolumeName), + }, + Containers: []core.Container{ + { + Name: k8sutil.ServerContainerName, + Image: testImage, + Command: createTestCommandForSingleMode(singleStatus.ID, false, false, false, func() k8sutil.OptionPairs { + args := k8sutil.NewOptionPair() + + args.Add("--foxx.queues", false) + + return args + }), + Ports: createTestPorts(), + Resources: emptyResources, + VolumeMounts: []core.VolumeMount{ + k8sutil.ArangodVolumeMount(), + }, + ImagePullPolicy: core.PullIfNotPresent, + SecurityContext: securityContext.NewSecurityContext(), + }, + }, + RestartPolicy: core.RestartPolicyNever, + TerminationGracePeriodSeconds: &defaultSingleTerminationTimeout, + Hostname: testDeploymentName + "-" + api.ServerGroupSingleString + "-" + + singleStatus.ID, + Subdomain: testDeploymentName + "-int", + Affinity: k8sutil.CreateAffinity(testDeploymentName, api.ServerGroupSingleString, + false, ""), + }, + }, + }, + { + Name: "Single POD with enabled foxx services", + ArangoDeployment: &api.ArangoDeployment{ + Spec: api.DeploymentSpec{ + Image: util.NewString(testImage), + Authentication: noAuthentication, + TLS: noTLS, + }, + }, + Helper: func(t *testing.T, deployment *Deployment, testCase *testCaseStruct) { + deployment.status.last = api.DeploymentStatus{ + Members: api.DeploymentStatusMembers{ + Single: api.MemberStatusList{ + singleStatus, + }, + }, + Images: createTestImages(false), + } + deployment.status.last.Members.Single[0].IsInitialized = true + + deployment.apiObject.Spec.Features = &api.DeploymentFeatures{ + FoxxQueues: util.NewBool(true), + } + + testCase.createTestPodData(deployment, api.ServerGroupSingle, singleStatus) + + testCase.ExpectedPod.Spec.Containers[0].LivenessProbe = createTestLivenessProbe(httpProbe, false, "", 0) + testCase.ExpectedPod.Spec.Containers[0].ReadinessProbe = createTestReadinessProbe(httpProbe, false, "") + }, + ExpectedEvent: "member single is created", + ExpectedPod: core.Pod{ + Spec: core.PodSpec{ + Volumes: []core.Volume{ + k8sutil.CreateVolumeEmptyDir(k8sutil.ArangodVolumeName), + }, + Containers: []core.Container{ + { + Name: k8sutil.ServerContainerName, + Image: testImage, + Command: createTestCommandForSingleMode(singleStatus.ID, false, false, false, func() k8sutil.OptionPairs { + args := k8sutil.NewOptionPair() + + args.Add("--foxx.queues", true) + + return args + }), + Ports: createTestPorts(), + Resources: emptyResources, + VolumeMounts: []core.VolumeMount{ + k8sutil.ArangodVolumeMount(), + }, + ImagePullPolicy: core.PullIfNotPresent, + SecurityContext: securityContext.NewSecurityContext(), + }, + }, + RestartPolicy: core.RestartPolicyNever, + TerminationGracePeriodSeconds: &defaultSingleTerminationTimeout, + Hostname: testDeploymentName + "-" + api.ServerGroupSingleString + "-" + + singleStatus.ID, + Subdomain: testDeploymentName + "-int", + Affinity: k8sutil.CreateAffinity(testDeploymentName, api.ServerGroupSingleString, + false, ""), + }, + }, + }, + } + + runTestCases(t, testCases...) +} diff --git a/pkg/deployment/deployment_suite_test.go b/pkg/deployment/deployment_suite_test.go index bff6e82a1..ecaad22b3 100644 --- a/pkg/deployment/deployment_suite_test.go +++ b/pkg/deployment/deployment_suite_test.go @@ -197,120 +197,153 @@ func createHTTPTestProbe(secure bool, authorization string, endpoint string, por } } -func createTestCommandForDBServer(name string, tls, auth, encryptionRocksDB bool) []string { +func createTestCommandForDBServer(name string, tls, auth, encryptionRocksDB bool, mods ...func() k8sutil.OptionPairs) []string { command := []string{resources.ArangoDExecutor} + + args := k8sutil.OptionPairs{} + if tls { - command = append(command, "--cluster.my-address=ssl://"+testDeploymentName+"-"+ - api.ServerGroupDBServersString+"-"+name+".test-int.default.svc:8529") + args.Addf("--cluster.my-address", "ssl://%s-%s-%s.test-int.default.svc:8529", + testDeploymentName, + api.ServerGroupDBServersString, + name) } else { - command = append(command, "--cluster.my-address=tcp://"+testDeploymentName+"-"+ - api.ServerGroupDBServersString+"-"+name+".test-int.default.svc:8529") + args.Addf("--cluster.my-address", "tcp://%s-%s-%s.test-int.default.svc:8529", + testDeploymentName, + api.ServerGroupDBServersString, + name) } - command = append(command, "--cluster.my-role=PRIMARY", "--database.directory=/data", - "--foxx.queues=false", "--log.level=INFO", "--log.output=+") + args.Add("--cluster.my-role", "PRIMARY") + args.Add("--database.directory", "/data") + args.Add("--foxx.queues", "false") + args.Add("--log.level", "INFO") + args.Add("--log.output", "+") if encryptionRocksDB { - command = append(command, "--rocksdb.encryption-keyfile=/secrets/rocksdb/encryption/key") + args.Add("--rocksdb.encryption-keyfile", "/secrets/rocksdb/encryption/key") } - if auth { - command = append(command, "--server.authentication=true") - } else { - command = append(command, "--server.authentication=false") - } + args.Add("--server.authentication", auth) if tls { - command = append(command, "--server.endpoint=ssl://[::]:8529") + args.Add("--server.endpoint", "ssl://[::]:8529") } else { - command = append(command, "--server.endpoint=tcp://[::]:8529") + args.Add("--server.endpoint", "tcp://[::]:8529") } if auth { - command = append(command, "--server.jwt-secret-keyfile=/secrets/cluster/jwt/token") + args.Add("--server.jwt-secret-keyfile", "/secrets/cluster/jwt/token") } - command = append(command, "--server.statistics=true", "--server.storage-engine=rocksdb") + args.Add("--server.statistics", "true") + args.Add("--server.storage-engine", "rocksdb") if tls { - command = append(command, "--ssl.ecdh-curve=", "--ssl.keyfile=/secrets/tls/tls.keyfile") + args.Add("--ssl.ecdh-curve", "") + args.Add("--ssl.keyfile", "/secrets/tls/tls.keyfile") } - return command + + for _, mod := range mods { + args.Merge(mod()) + } + + return append(command, args.Unique().AsArgs()...) } -func createTestCommandForCoordinator(name string, tls, auth, encryptionRocksDB bool) []string { +func createTestCommandForCoordinator(name string, tls, auth, encryptionRocksDB bool, mods ...func() k8sutil.OptionPairs) []string { command := []string{resources.ArangoDExecutor} + + args := k8sutil.OptionPairs{} + if tls { - command = append(command, "--cluster.my-address=ssl://"+testDeploymentName+"-"+ - api.ServerGroupCoordinatorsString+"-"+name+".test-int.default.svc:8529") + args.Addf("--cluster.my-address", "ssl://%s-%s-%s.test-int.default.svc:8529", + testDeploymentName, + api.ServerGroupCoordinatorsString, + name) } else { - command = append(command, "--cluster.my-address=tcp://"+testDeploymentName+"-"+ - api.ServerGroupCoordinatorsString+"-"+name+".test-int.default.svc:8529") + args.Addf("--cluster.my-address", "tcp://%s-%s-%s.test-int.default.svc:8529", + testDeploymentName, + api.ServerGroupCoordinatorsString, + name) } - command = append(command, "--cluster.my-role=COORDINATOR", "--database.directory=/data", - "--foxx.queues=true", "--log.level=INFO", "--log.output=+") + args.Add("--cluster.my-role", "COORDINATOR") + args.Add("--database.directory", "/data") + args.Add("--foxx.queues", "true") + args.Add("--log.level", "INFO") + args.Add("--log.output", "+") if encryptionRocksDB { - command = append(command, "--rocksdb.encryption-keyfile=/secrets/rocksdb/encryption/key") + args.Add("--rocksdb.encryption-keyfile", "/secrets/rocksdb/encryption/key") } - if auth { - command = append(command, "--server.authentication=true") - } else { - command = append(command, "--server.authentication=false") - } + args.Add("--server.authentication", auth) if tls { - command = append(command, "--server.endpoint=ssl://[::]:8529") + args.Add("--server.endpoint", "ssl://[::]:8529") } else { - command = append(command, "--server.endpoint=tcp://[::]:8529") + args.Add("--server.endpoint", "tcp://[::]:8529") } if auth { - command = append(command, "--server.jwt-secret-keyfile=/secrets/cluster/jwt/token") + args.Add("--server.jwt-secret-keyfile", "/secrets/cluster/jwt/token") } - command = append(command, "--server.statistics=true", "--server.storage-engine=rocksdb") + args.Add("--server.statistics", "true") + args.Add("--server.storage-engine", "rocksdb") if tls { - command = append(command, "--ssl.ecdh-curve=", "--ssl.keyfile=/secrets/tls/tls.keyfile") + args.Add("--ssl.ecdh-curve", "") + args.Add("--ssl.keyfile", "/secrets/tls/tls.keyfile") } - return command + + for _, mod := range mods { + args.Merge(mod()) + } + + return append(command, args.Unique().AsArgs()...) } -func createTestCommandForSingleMode(name string, tls, auth, encryptionRocksDB bool) []string { +func createTestCommandForSingleMode(name string, tls, auth, encryptionRocksDB bool, mods ...func() k8sutil.OptionPairs) []string { command := []string{resources.ArangoDExecutor} - command = append(command, "--database.directory=/data", "--foxx.queues=true", "--log.level=INFO", - "--log.output=+") + args := k8sutil.OptionPairs{} + + args.Add("--database.directory", "/data") + args.Add("--foxx.queues", "true") + args.Add("--log.level", "INFO") + args.Add("--log.output", "+") if encryptionRocksDB { - command = append(command, "--rocksdb.encryption-keyfile=/secrets/rocksdb/encryption/key") + args.Add("--rocksdb.encryption-keyfile", "/secrets/rocksdb/encryption/key") } - if auth { - command = append(command, "--server.authentication=true") - } else { - command = append(command, "--server.authentication=false") - } + args.Add("--server.authentication", auth) if tls { - command = append(command, "--server.endpoint=ssl://[::]:8529") + args.Add("--server.endpoint", "ssl://[::]:8529") } else { - command = append(command, "--server.endpoint=tcp://[::]:8529") + args.Add("--server.endpoint", "tcp://[::]:8529") } if auth { - command = append(command, "--server.jwt-secret-keyfile=/secrets/cluster/jwt/token") + args.Add("--server.jwt-secret-keyfile", "/secrets/cluster/jwt/token") } - command = append(command, "--server.statistics=true", "--server.storage-engine=rocksdb") + args.Add("--server.statistics", "true") + args.Add("--server.storage-engine", "rocksdb") if tls { - command = append(command, "--ssl.ecdh-curve=", "--ssl.keyfile=/secrets/tls/tls.keyfile") + args.Add("--ssl.ecdh-curve", "") + args.Add("--ssl.keyfile", "/secrets/tls/tls.keyfile") } - return command + + for _, mod := range mods { + args.Merge(mod()) + } + + return append(command, args.Unique().AsArgs()...) } func createTestCommandForAgent(name string, tls, auth, encryptionRocksDB bool) []string { diff --git a/pkg/deployment/resources/pod_creator.go b/pkg/deployment/resources/pod_creator.go index ae9685736..7c0d5d5aa 100644 --- a/pkg/deployment/resources/pod_creator.go +++ b/pkg/deployment/resources/pod_creator.go @@ -108,7 +108,7 @@ func createArangodArgs(input pod.Input) []string { options.Add("--agency.my-address", myTCPURL) options.Addf("--agency.size", "%d", input.Deployment.Agents.GetCount()) options.Add("--agency.supervision", "true") - options.Add("--foxx.queues", "false") + options.Add("--foxx.queues", false) options.Add("--server.statistics", "false") for _, p := range input.Status.Members.Agents { if p.ID != input.ID { @@ -120,19 +120,19 @@ func createArangodArgs(input pod.Input) []string { addAgentEndpoints = true options.Add("--cluster.my-address", myTCPURL) options.Add("--cluster.my-role", "PRIMARY") - options.Add("--foxx.queues", "false") + options.Add("--foxx.queues", false) options.Add("--server.statistics", "true") case api.ServerGroupCoordinators: addAgentEndpoints = true options.Add("--cluster.my-address", myTCPURL) options.Add("--cluster.my-role", "COORDINATOR") - options.Add("--foxx.queues", "true") + options.Add("--foxx.queues", input.Deployment.Features.GetFoxxQueues()) options.Add("--server.statistics", "true") if input.Deployment.ExternalAccess.HasAdvertisedEndpoint() && versionHasAdvertisedEndpoint { options.Add("--cluster.my-advertised-endpoint", input.Deployment.ExternalAccess.GetAdvertisedEndpoint()) } case api.ServerGroupSingle: - options.Add("--foxx.queues", "true") + options.Add("--foxx.queues", input.Deployment.Features.GetFoxxQueues()) options.Add("--server.statistics", "true") if input.Deployment.GetMode() == api.DeploymentModeActiveFailover { addAgentEndpoints = true diff --git a/pkg/util/k8sutil/pair.go b/pkg/util/k8sutil/pair.go index 51735d0a5..5af85152a 100644 --- a/pkg/util/k8sutil/pair.go +++ b/pkg/util/k8sutil/pair.go @@ -54,7 +54,24 @@ func (o *OptionPairs) Addf(key, format string, i ...interface{}) { o.Add(key, fmt.Sprintf(format, i...)) } -func (o *OptionPairs) Add(key, value string) { +func (o *OptionPairs) Add(key string, value interface{}) { + switch v := value.(type) { + case string: + o.add(key, v) + case bool: + f := "false" + if v { + f = "true" + } + o.add(key, f) + case int: + o.add(key, fmt.Sprintf("%d", v)) + default: + o.add(key, fmt.Sprintf("%s", v)) + } +} + +func (o *OptionPairs) add(key, value string) { o.Append(OptionPair{ Key: key, Value: value, @@ -71,6 +88,31 @@ func (o *OptionPairs) Merge(pairs ...OptionPairs) { } } +func (o OptionPairs) Unique() OptionPairs { + r := make(OptionPairs, 0, len(o)) + + for _, pair := range o { + replaced := false + for id, existing := range r { + if replaced { + break + } + if existing.Key == pair.Key { + r[id] = pair + replaced = true + } + } + + if replaced { + continue + } + + r = append(r, pair) + } + + return r +} + func (o OptionPairs) Copy() OptionPairs { r := make(OptionPairs, len(o))