mirror of
https://github.com/arangodb/kube-arangodb.git
synced 2024-12-14 11:57:37 +00:00
[Feature] Split & Unify Lifecycle management functionality (#821)
This commit is contained in:
parent
57eb6e95de
commit
a2a55530e3
25 changed files with 497 additions and 349 deletions
|
@ -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
|
||||
|
|
11
lifecycle.go
11
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)
|
||||
|
||||
|
|
3
main.go
3
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,
|
||||
|
|
|
@ -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)
|
||||
|
|
35
pkg/apis/deployment/v1/server_group_containers.go
Normal file
35
pkg/apis/deployment/v1/server_group_containers.go
Normal file
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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...)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ var metricsExporter = &feature{
|
|||
constValue: util.NewBool(true),
|
||||
}
|
||||
|
||||
// deprecated
|
||||
func MetricsExporter() Feature {
|
||||
return metricsExporter
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: "/",
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
if ac, bc := &a[id], &b[id]; ac.Name == bc.Name {
|
||||
if ac.Name == api.ServerGroupReservedContainerNameServer {
|
||||
if !isOnlyLogLevelChanged(ac.Command, bc.Command) {
|
||||
continue
|
||||
}
|
||||
|
||||
plan = append(plan, builder.NewAction(api.ActionTypeRuntimeContainerArgsLogLevelUpdate).
|
||||
AddParam(ContainerName, ac.Name))
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
plan = append(plan, builder.NewAction(api.ActionTypeRuntimeContainerArgsLogLevelUpdate).
|
||||
AddParam(ContainerName, ac.Name))
|
||||
|
||||
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))
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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().
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
79
pkg/util/retry/timeout.go
Normal file
79
pkg/util/retry/timeout.go
Normal file
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue