mirror of
https://github.com/arangodb/kube-arangodb.git
synced 2024-12-14 11:57:37 +00:00
[Feature] Timezone (#1088)
This commit is contained in:
parent
583532e665
commit
ef4c3c6161
36 changed files with 1343 additions and 132 deletions
|
@ -15,6 +15,7 @@
|
||||||
- (Logging) Internal client trace
|
- (Logging) Internal client trace
|
||||||
- (QA) Member maintenance feature
|
- (QA) Member maintenance feature
|
||||||
- (Feature) Extract Pod Details
|
- (Feature) Extract Pod Details
|
||||||
|
- (Feature) Add Timezone management
|
||||||
|
|
||||||
## [1.2.15](https://github.com/arangodb/kube-arangodb/tree/1.2.15) (2022-07-20)
|
## [1.2.15](https://github.com/arangodb/kube-arangodb/tree/1.2.15) (2022-07-20)
|
||||||
- (Bugfix) Ensure pod names not too long
|
- (Bugfix) Ensure pod names not too long
|
||||||
|
|
6
go.mod
6
go.mod
|
@ -47,6 +47,7 @@ require (
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.7.0
|
||||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
|
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
|
||||||
|
google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad // indirect
|
||||||
google.golang.org/grpc v1.47.0
|
google.golang.org/grpc v1.47.0
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||||
k8s.io/api v0.21.10
|
k8s.io/api v0.21.10
|
||||||
|
@ -56,11 +57,6 @@ require (
|
||||||
k8s.io/klog v1.0.0
|
k8s.io/klog v1.0.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/arangodb/rebalancer v0.1.1 // indirect
|
|
||||||
google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad // indirect
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/arangodb/go-velocypack v0.0.0-20200318135517-5af53c29c67e // indirect
|
github.com/arangodb/go-velocypack v0.0.0-20200318135517-5af53c29c67e // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
|
|
@ -171,6 +171,8 @@ type DeploymentSpec struct {
|
||||||
|
|
||||||
// Architecture definition of supported architectures
|
// Architecture definition of supported architectures
|
||||||
Architecture ArangoDeploymentArchitecture `json:"architecture,omitempty"`
|
Architecture ArangoDeploymentArchitecture `json:"architecture,omitempty"`
|
||||||
|
|
||||||
|
Timezone *string `json:"timezone,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAllowMemberRecreation returns member recreation policy based on group and settings
|
// GetAllowMemberRecreation returns member recreation policy based on group and settings
|
||||||
|
|
|
@ -91,6 +91,8 @@ type DeploymentStatus struct {
|
||||||
|
|
||||||
Version *Version `json:"version,omitempty"`
|
Version *Version `json:"version,omitempty"`
|
||||||
|
|
||||||
|
Timezone *string `json:"timezone,omitempty"`
|
||||||
|
|
||||||
Single *ServerGroupStatus `json:"single,omitempty"`
|
Single *ServerGroupStatus `json:"single,omitempty"`
|
||||||
Agents *ServerGroupStatus `json:"agents,omitempty"`
|
Agents *ServerGroupStatus `json:"agents,omitempty"`
|
||||||
DBServers *ServerGroupStatus `json:"dbservers,omitempty"`
|
DBServers *ServerGroupStatus `json:"dbservers,omitempty"`
|
||||||
|
@ -126,7 +128,8 @@ func (ds *DeploymentStatus) Equal(other DeploymentStatus) bool {
|
||||||
ds.DBServers.Equal(other.DBServers) &&
|
ds.DBServers.Equal(other.DBServers) &&
|
||||||
ds.Coordinators.Equal(other.Coordinators) &&
|
ds.Coordinators.Equal(other.Coordinators) &&
|
||||||
ds.SyncMasters.Equal(other.SyncMasters) &&
|
ds.SyncMasters.Equal(other.SyncMasters) &&
|
||||||
ds.SyncWorkers.Equal(other.SyncWorkers)
|
ds.SyncWorkers.Equal(other.SyncWorkers) &&
|
||||||
|
util.CompareStringPointers(ds.Timezone, other.Timezone)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsForceReload returns true if ForceStatusReload is set to true
|
// IsForceReload returns true if ForceStatusReload is set to true
|
||||||
|
|
|
@ -200,6 +200,7 @@ const (
|
||||||
|
|
||||||
// Resources
|
// Resources
|
||||||
ActionTypeResourceSync ActionType = "ResourceSync"
|
ActionTypeResourceSync ActionType = "ResourceSync"
|
||||||
|
ActionTypeTimezoneSecretSet ActionType = "TimezoneSecretSet"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -41,6 +41,7 @@ var (
|
||||||
shared.LifecycleVolumeName,
|
shared.LifecycleVolumeName,
|
||||||
shared.FoxxAppEphemeralVolumeName,
|
shared.FoxxAppEphemeralVolumeName,
|
||||||
shared.TMPEphemeralVolumeName,
|
shared.TMPEphemeralVolumeName,
|
||||||
|
shared.ArangoDTimezoneVolumeName,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
10
pkg/apis/deployment/v1/zz_generated.deepcopy.go
generated
10
pkg/apis/deployment/v1/zz_generated.deepcopy.go
generated
|
@ -1080,6 +1080,11 @@ func (in *DeploymentSpec) DeepCopyInto(out *DeploymentSpec) {
|
||||||
*out = make(ArangoDeploymentArchitecture, len(*in))
|
*out = make(ArangoDeploymentArchitecture, len(*in))
|
||||||
copy(*out, *in)
|
copy(*out, *in)
|
||||||
}
|
}
|
||||||
|
if in.Timezone != nil {
|
||||||
|
in, out := &in.Timezone, &out.Timezone
|
||||||
|
*out = new(string)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1183,6 +1188,11 @@ func (in *DeploymentStatus) DeepCopyInto(out *DeploymentStatus) {
|
||||||
*out = new(Version)
|
*out = new(Version)
|
||||||
**out = **in
|
**out = **in
|
||||||
}
|
}
|
||||||
|
if in.Timezone != nil {
|
||||||
|
in, out := &in.Timezone, &out.Timezone
|
||||||
|
*out = new(string)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
if in.Single != nil {
|
if in.Single != nil {
|
||||||
in, out := &in.Single, &out.Single
|
in, out := &in.Single, &out.Single
|
||||||
*out = new(ServerGroupStatus)
|
*out = new(ServerGroupStatus)
|
||||||
|
|
|
@ -171,6 +171,8 @@ type DeploymentSpec struct {
|
||||||
|
|
||||||
// Architecture definition of supported architectures
|
// Architecture definition of supported architectures
|
||||||
Architecture ArangoDeploymentArchitecture `json:"architecture,omitempty"`
|
Architecture ArangoDeploymentArchitecture `json:"architecture,omitempty"`
|
||||||
|
|
||||||
|
Timezone *string `json:"timezone,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAllowMemberRecreation returns member recreation policy based on group and settings
|
// GetAllowMemberRecreation returns member recreation policy based on group and settings
|
||||||
|
|
|
@ -91,6 +91,8 @@ type DeploymentStatus struct {
|
||||||
|
|
||||||
Version *Version `json:"version,omitempty"`
|
Version *Version `json:"version,omitempty"`
|
||||||
|
|
||||||
|
Timezone *string `json:"timezone,omitempty"`
|
||||||
|
|
||||||
Single *ServerGroupStatus `json:"single,omitempty"`
|
Single *ServerGroupStatus `json:"single,omitempty"`
|
||||||
Agents *ServerGroupStatus `json:"agents,omitempty"`
|
Agents *ServerGroupStatus `json:"agents,omitempty"`
|
||||||
DBServers *ServerGroupStatus `json:"dbservers,omitempty"`
|
DBServers *ServerGroupStatus `json:"dbservers,omitempty"`
|
||||||
|
@ -126,7 +128,8 @@ func (ds *DeploymentStatus) Equal(other DeploymentStatus) bool {
|
||||||
ds.DBServers.Equal(other.DBServers) &&
|
ds.DBServers.Equal(other.DBServers) &&
|
||||||
ds.Coordinators.Equal(other.Coordinators) &&
|
ds.Coordinators.Equal(other.Coordinators) &&
|
||||||
ds.SyncMasters.Equal(other.SyncMasters) &&
|
ds.SyncMasters.Equal(other.SyncMasters) &&
|
||||||
ds.SyncWorkers.Equal(other.SyncWorkers)
|
ds.SyncWorkers.Equal(other.SyncWorkers) &&
|
||||||
|
util.CompareStringPointers(ds.Timezone, other.Timezone)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsForceReload returns true if ForceStatusReload is set to true
|
// IsForceReload returns true if ForceStatusReload is set to true
|
||||||
|
|
|
@ -200,6 +200,7 @@ const (
|
||||||
|
|
||||||
// Resources
|
// Resources
|
||||||
ActionTypeResourceSync ActionType = "ResourceSync"
|
ActionTypeResourceSync ActionType = "ResourceSync"
|
||||||
|
ActionTypeTimezoneSecretSet ActionType = "TimezoneSecretSet"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -41,6 +41,7 @@ var (
|
||||||
shared.LifecycleVolumeName,
|
shared.LifecycleVolumeName,
|
||||||
shared.FoxxAppEphemeralVolumeName,
|
shared.FoxxAppEphemeralVolumeName,
|
||||||
shared.TMPEphemeralVolumeName,
|
shared.TMPEphemeralVolumeName,
|
||||||
|
shared.ArangoDTimezoneVolumeName,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1080,6 +1080,11 @@ func (in *DeploymentSpec) DeepCopyInto(out *DeploymentSpec) {
|
||||||
*out = make(ArangoDeploymentArchitecture, len(*in))
|
*out = make(ArangoDeploymentArchitecture, len(*in))
|
||||||
copy(*out, *in)
|
copy(*out, *in)
|
||||||
}
|
}
|
||||||
|
if in.Timezone != nil {
|
||||||
|
in, out := &in.Timezone, &out.Timezone
|
||||||
|
*out = new(string)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1183,6 +1188,11 @@ func (in *DeploymentStatus) DeepCopyInto(out *DeploymentStatus) {
|
||||||
*out = new(Version)
|
*out = new(Version)
|
||||||
**out = **in
|
**out = **in
|
||||||
}
|
}
|
||||||
|
if in.Timezone != nil {
|
||||||
|
in, out := &in.Timezone, &out.Timezone
|
||||||
|
*out = new(string)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
if in.Single != nil {
|
if in.Single != nil {
|
||||||
in, out := &in.Single, &out.Single
|
in, out := &in.Single, &out.Single
|
||||||
*out = new(ServerGroupStatus)
|
*out = new(ServerGroupStatus)
|
||||||
|
|
|
@ -56,6 +56,7 @@ const (
|
||||||
LifecycleVolumeName = "lifecycle"
|
LifecycleVolumeName = "lifecycle"
|
||||||
FoxxAppEphemeralVolumeName = "ephemeral-apps"
|
FoxxAppEphemeralVolumeName = "ephemeral-apps"
|
||||||
TMPEphemeralVolumeName = "ephemeral-tmp"
|
TMPEphemeralVolumeName = "ephemeral-tmp"
|
||||||
|
ArangoDTimezoneVolumeName = "arangod-timezone"
|
||||||
RocksdbEncryptionVolumeName = "rocksdb-encryption"
|
RocksdbEncryptionVolumeName = "rocksdb-encryption"
|
||||||
ExporterJWTVolumeName = "exporter-jwt"
|
ExporterJWTVolumeName = "exporter-jwt"
|
||||||
ArangodVolumeMountDir = "/data"
|
ArangodVolumeMountDir = "/data"
|
||||||
|
|
37
pkg/deployment/features/timezone.go
Normal file
37
pkg/deployment/features/timezone.go
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
//
|
||||||
|
// DISCLAIMER
|
||||||
|
//
|
||||||
|
// Copyright 2016-2022 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 features
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
registerFeature(timezone)
|
||||||
|
}
|
||||||
|
|
||||||
|
var timezone = &feature{
|
||||||
|
name: "timezone-management",
|
||||||
|
description: "Enable timezone management for pods",
|
||||||
|
version: "3.6.0",
|
||||||
|
enterpriseRequired: false,
|
||||||
|
enabledByDefault: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
func Timezone() Feature {
|
||||||
|
return timezone
|
||||||
|
}
|
91
pkg/deployment/pod/timezone.go
Normal file
91
pkg/deployment/pod/timezone.go
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
//
|
||||||
|
// DISCLAIMER
|
||||||
|
//
|
||||||
|
// Copyright 2016-2022 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 pod
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
core "k8s.io/api/core/v1"
|
||||||
|
|
||||||
|
"github.com/arangodb/kube-arangodb/pkg/apis/shared"
|
||||||
|
"github.com/arangodb/kube-arangodb/pkg/deployment/features"
|
||||||
|
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
|
||||||
|
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil/interfaces"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
TimezoneNameKey string = "name"
|
||||||
|
TimezoneDataKey string = "data"
|
||||||
|
TimezoneTZKey string = "timezone"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TimezoneSecret(name string) string {
|
||||||
|
return fmt.Sprintf("%s-timezone", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Timezone() Builder {
|
||||||
|
return timezone{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type timezone struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t timezone) Args(i Input) k8sutil.OptionPairs {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t timezone) Volumes(i Input) ([]core.Volume, []core.VolumeMount) {
|
||||||
|
if !features.Timezone().Enabled() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return []core.Volume{
|
||||||
|
{
|
||||||
|
Name: shared.ArangoDTimezoneVolumeName,
|
||||||
|
VolumeSource: core.VolumeSource{
|
||||||
|
Secret: &core.SecretVolumeSource{
|
||||||
|
SecretName: TimezoneSecret(i.ApiObject.GetName()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, []core.VolumeMount{
|
||||||
|
{
|
||||||
|
Name: shared.ArangoDTimezoneVolumeName,
|
||||||
|
ReadOnly: true,
|
||||||
|
MountPath: "/etc/localtime",
|
||||||
|
SubPath: TimezoneDataKey,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: shared.ArangoDTimezoneVolumeName,
|
||||||
|
ReadOnly: true,
|
||||||
|
MountPath: "/etc/timezone",
|
||||||
|
SubPath: TimezoneTZKey,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t timezone) Envs(i Input) []core.EnvVar {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t timezone) Verify(i Input, cachedStatus interfaces.Inspector) error {
|
||||||
|
return nil
|
||||||
|
}
|
135
pkg/deployment/reconcile/action_timezone_secret_set.go
Normal file
135
pkg/deployment/reconcile/action_timezone_secret_set.go
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
//
|
||||||
|
// DISCLAIMER
|
||||||
|
//
|
||||||
|
// Copyright 2016-2022 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 reconcile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
|
||||||
|
core "k8s.io/api/core/v1"
|
||||||
|
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
|
||||||
|
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
|
||||||
|
"github.com/arangodb/kube-arangodb/pkg/deployment/features"
|
||||||
|
"github.com/arangodb/kube-arangodb/pkg/deployment/patch"
|
||||||
|
"github.com/arangodb/kube-arangodb/pkg/deployment/pod"
|
||||||
|
"github.com/arangodb/kube-arangodb/pkg/util/errors"
|
||||||
|
"github.com/arangodb/kube-arangodb/pkg/util/globals"
|
||||||
|
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
registerAction(api.ActionTypeTimezoneSecretSet, newTimezoneCMSetAction, operationTLSCACertificateTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTimezoneCMSetAction(action api.Action, actionCtx ActionContext) Action {
|
||||||
|
a := &timezoneCMSetAction{}
|
||||||
|
|
||||||
|
a.actionImpl = newActionImplDefRef(action, actionCtx)
|
||||||
|
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
type timezoneCMSetAction struct {
|
||||||
|
actionImpl
|
||||||
|
|
||||||
|
actionEmptyCheckProgress
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *timezoneCMSetAction) Start(ctx context.Context) (bool, error) {
|
||||||
|
if !features.Timezone().Enabled() {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
secrets := a.actionCtx.ACS().CurrentClusterCache().Secret().V1()
|
||||||
|
|
||||||
|
tz, ok := GetTimezone(a.actionCtx.GetSpec().Timezone)
|
||||||
|
if !ok {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tzd, ok := tz.GetData()
|
||||||
|
if !ok {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if IsTimezoneValid(secrets, a.actionCtx.GetName(), tz) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if s, ok := secrets.GetSimple(pod.TimezoneSecret(a.actionCtx.GetName())); ok {
|
||||||
|
// Exists
|
||||||
|
// We need to prepare patch
|
||||||
|
data := map[string]string{}
|
||||||
|
|
||||||
|
data[pod.TimezoneNameKey] = base64.StdEncoding.EncodeToString([]byte(tz.Name))
|
||||||
|
data[pod.TimezoneDataKey] = base64.StdEncoding.EncodeToString(tzd)
|
||||||
|
data[pod.TimezoneTZKey] = base64.StdEncoding.EncodeToString([]byte(tz.Name))
|
||||||
|
|
||||||
|
p := patch.NewPatch()
|
||||||
|
p.ItemReplace(patch.NewPath("data"), data)
|
||||||
|
|
||||||
|
patch, err := p.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
a.log.Err(err).Error("Unable to encrypt patch")
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = globals.GetGlobalTimeouts().Kubernetes().RunWithTimeout(ctx, func(ctxChild context.Context) error {
|
||||||
|
_, err := a.actionCtx.ACS().CurrentClusterCache().SecretsModInterface().V1().Patch(ctxChild, s.GetName(), types.JSONPatchType, patch, meta.PatchOptions{})
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if !k8sutil.IsInvalid(err) {
|
||||||
|
return false, errors.Wrapf(err, "Unable to update secret: %s", s.GetName())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
} else {
|
||||||
|
s = &core.Secret{
|
||||||
|
ObjectMeta: meta.ObjectMeta{
|
||||||
|
Name: pod.TimezoneSecret(a.actionCtx.GetName()),
|
||||||
|
Namespace: a.actionCtx.GetNamespace(),
|
||||||
|
OwnerReferences: []meta.OwnerReference{
|
||||||
|
a.actionCtx.GetAPIObject().AsOwner(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Data: map[string][]byte{
|
||||||
|
pod.TimezoneNameKey: []byte(tz.Name),
|
||||||
|
pod.TimezoneDataKey: tzd,
|
||||||
|
pod.TimezoneTZKey: []byte(tz.Zone),
|
||||||
|
},
|
||||||
|
Type: core.SecretTypeOpaque,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := globals.GetGlobalTimeouts().Kubernetes().RunWithTimeout(ctx, func(ctxChild context.Context) error {
|
||||||
|
_, err := a.actionCtx.ACS().CurrentClusterCache().SecretsModInterface().V1().Create(ctxChild, s, meta.CreateOptions{})
|
||||||
|
return err
|
||||||
|
}); err != nil {
|
||||||
|
a.log.Err(err).Error("Unable to create cm secret")
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
|
@ -34,6 +34,7 @@ const (
|
||||||
const (
|
const (
|
||||||
BackOffCheck api.BackOffKey = "check"
|
BackOffCheck api.BackOffKey = "check"
|
||||||
LicenseCheck api.BackOffKey = "license"
|
LicenseCheck api.BackOffKey = "license"
|
||||||
|
TimezoneCheck api.BackOffKey = "timezone"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CreatePlan considers the current specification & status of the deployment creates a plan to
|
// CreatePlan considers the current specification & status of the deployment creates a plan to
|
||||||
|
|
|
@ -59,6 +59,7 @@ func (r *Reconciler) createHighPlan(ctx context.Context, apiObject k8sutil.APIOb
|
||||||
ApplyIfEmpty(r.createRebalancerCheckPlan).
|
ApplyIfEmpty(r.createRebalancerCheckPlan).
|
||||||
ApplyIfEmpty(r.createMemberFailedRestoreHighPlan).
|
ApplyIfEmpty(r.createMemberFailedRestoreHighPlan).
|
||||||
ApplyWithBackOff(BackOffCheck, time.Minute, r.emptyPlanBuilder)).
|
ApplyWithBackOff(BackOffCheck, time.Minute, r.emptyPlanBuilder)).
|
||||||
|
ApplyIfEmptyWithBackOff(TimezoneCheck, time.Minute, r.createTimezoneUpdatePlan).
|
||||||
Apply(r.createBackupInProgressConditionPlan). // Discover backups always
|
Apply(r.createBackupInProgressConditionPlan). // Discover backups always
|
||||||
Apply(r.createMaintenanceConditionPlan). // Discover maintenance always
|
Apply(r.createMaintenanceConditionPlan). // Discover maintenance always
|
||||||
Apply(r.cleanupConditions) // Cleanup Conditions
|
Apply(r.cleanupConditions) // Cleanup Conditions
|
||||||
|
|
51
pkg/deployment/reconcile/plan_builder_timezone.go
Normal file
51
pkg/deployment/reconcile/plan_builder_timezone.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
//
|
||||||
|
// DISCLAIMER
|
||||||
|
//
|
||||||
|
// Copyright 2016-2022 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 reconcile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
|
||||||
|
"github.com/arangodb/kube-arangodb/pkg/deployment/actions"
|
||||||
|
"github.com/arangodb/kube-arangodb/pkg/deployment/features"
|
||||||
|
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *Reconciler) createTimezoneUpdatePlan(ctx context.Context, apiObject k8sutil.APIObject,
|
||||||
|
spec api.DeploymentSpec, status api.DeploymentStatus,
|
||||||
|
context PlanBuilderContext) api.Plan {
|
||||||
|
if !features.Timezone().Enabled() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
secrets := context.ACS().CurrentClusterCache().Secret().V1()
|
||||||
|
|
||||||
|
tz, ok := GetTimezone(context.GetSpec().Timezone)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if IsTimezoneValid(secrets, context.GetName(), tz) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return api.Plan{actions.NewClusterAction(api.ActionTypeTimezoneSecretSet, "Update timezone")}
|
||||||
|
}
|
76
pkg/deployment/reconcile/utils_timezone.go
Normal file
76
pkg/deployment/reconcile/utils_timezone.go
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
//
|
||||||
|
// DISCLAIMER
|
||||||
|
//
|
||||||
|
// Copyright 2016-2022 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 reconcile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/arangodb/kube-arangodb/pkg/deployment/pod"
|
||||||
|
"github.com/arangodb/kube-arangodb/pkg/generated/timezones"
|
||||||
|
"github.com/arangodb/kube-arangodb/pkg/util"
|
||||||
|
secretv1 "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector/secret/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultTimezone = "UTC"
|
||||||
|
|
||||||
|
func GetTimezone(tz *string) (timezones.Timezone, bool) {
|
||||||
|
if tz == nil {
|
||||||
|
return timezones.GetTimezone(defaultTimezone)
|
||||||
|
}
|
||||||
|
return timezones.GetTimezone(*tz)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsTimezoneValid(cache secretv1.Inspector, name string, timezone timezones.Timezone) bool {
|
||||||
|
sn := pod.TimezoneSecret(name)
|
||||||
|
|
||||||
|
tzd, ok := timezone.GetData()
|
||||||
|
if !ok {
|
||||||
|
// Unable to get TZ Data, so ignoring
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if s, ok := cache.GetSimple(sn); ok {
|
||||||
|
// Secret exists, verify
|
||||||
|
if v, ok := s.Data[pod.TimezoneNameKey]; ok {
|
||||||
|
if string(v) != timezone.Name {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if v, ok := s.Data[pod.TimezoneDataKey]; ok {
|
||||||
|
if util.SHA256(v) != util.SHA256(tzd) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if v, ok := s.Data[pod.TimezoneTZKey]; ok {
|
||||||
|
if string(v) != timezone.Zone {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
37
pkg/deployment/reconcile/utils_timezone_test.go
Normal file
37
pkg/deployment/reconcile/utils_timezone_test.go
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
//
|
||||||
|
// DISCLAIMER
|
||||||
|
//
|
||||||
|
// Copyright 2016-2022 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 reconcile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/arangodb/kube-arangodb/pkg/generated/timezones"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Timezone_Default(t *testing.T) {
|
||||||
|
tz, ok := timezones.GetTimezone(defaultTimezone)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
_, ok = tz.GetData()
|
||||||
|
require.True(t, ok)
|
||||||
|
}
|
|
@ -590,6 +590,8 @@ func CreateArangoDVolumes(status api.MemberStatus, input pod.Input, spec api.Dep
|
||||||
// SNI
|
// SNI
|
||||||
volumes.Append(pod.SNI(), input)
|
volumes.Append(pod.SNI(), input)
|
||||||
|
|
||||||
|
volumes.Append(pod.Timezone(), input)
|
||||||
|
|
||||||
if len(groupSpec.Volumes) > 0 {
|
if len(groupSpec.Volumes) > 0 {
|
||||||
volumes.AddVolume(groupSpec.Volumes.RenderVolumes(input.ApiObject, input.Group, status)...)
|
volumes.AddVolume(groupSpec.Volumes.RenderVolumes(input.ApiObject, input.Group, status)...)
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ import (
|
||||||
"github.com/arangodb/kube-arangodb/pkg/util"
|
"github.com/arangodb/kube-arangodb/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func podCompare(_ api.DeploymentSpec, _ api.ServerGroup, spec, status *core.PodSpec) compareFunc {
|
func podCompare(_ api.DeploymentSpec, _ api.ServerGroup, spec, status *core.PodSpec) comparePodFunc {
|
||||||
return func(builder api.ActionBuilder) (mode Mode, plan api.Plan, err error) {
|
return func(builder api.ActionBuilder) (mode Mode, plan api.Plan, err error) {
|
||||||
if spec.SchedulerName != status.SchedulerName {
|
if spec.SchedulerName != status.SchedulerName {
|
||||||
status.SchedulerName = spec.SchedulerName
|
status.SchedulerName = spec.SchedulerName
|
||||||
|
@ -45,7 +45,7 @@ func podCompare(_ api.DeploymentSpec, _ api.ServerGroup, spec, status *core.PodS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func affinityCompare(_ api.DeploymentSpec, _ api.ServerGroup, spec, status *core.PodSpec) compareFunc {
|
func affinityCompare(_ api.DeploymentSpec, _ api.ServerGroup, spec, status *core.PodSpec) comparePodFunc {
|
||||||
return func(builder api.ActionBuilder) (mode Mode, plan api.Plan, e error) {
|
return func(builder api.ActionBuilder) (mode Mode, plan api.Plan, e error) {
|
||||||
if specC, err := util.SHA256FromJSON(spec.Affinity); err != nil {
|
if specC, err := util.SHA256FromJSON(spec.Affinity); err != nil {
|
||||||
e = err
|
e = err
|
||||||
|
|
|
@ -23,6 +23,7 @@ package rotation
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
core "k8s.io/api/core/v1"
|
core "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/api/equality"
|
"k8s.io/apimachinery/pkg/api/equality"
|
||||||
|
|
||||||
|
@ -36,7 +37,7 @@ const (
|
||||||
ContainerImage = "image"
|
ContainerImage = "image"
|
||||||
)
|
)
|
||||||
|
|
||||||
func containersCompare(_ api.DeploymentSpec, _ api.ServerGroup, spec, status *core.PodSpec) compareFunc {
|
func containersCompare(ds api.DeploymentSpec, g api.ServerGroup, spec, status *core.PodSpec) comparePodFunc {
|
||||||
return func(builder api.ActionBuilder) (mode Mode, plan api.Plan, err error) {
|
return func(builder api.ActionBuilder) (mode Mode, plan api.Plan, err error) {
|
||||||
a, b := spec.Containers, status.Containers
|
a, b := spec.Containers, status.Containers
|
||||||
|
|
||||||
|
@ -56,6 +57,16 @@ func containersCompare(_ api.DeploymentSpec, _ api.ServerGroup, spec, status *co
|
||||||
mode = mode.And(InPlaceRotation)
|
mode = mode.And(InPlaceRotation)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g := podContainerFuncGenerator(ds, g, ac, bc)
|
||||||
|
|
||||||
|
if m, p, err := comparePodContainer(builder, g(compareServerContainerVolumeMounts)); err != nil {
|
||||||
|
log.Err(err).Msg("Error while getting pod diff")
|
||||||
|
return SkippedRotation, nil, err
|
||||||
|
} else {
|
||||||
|
mode = mode.And(m)
|
||||||
|
plan = append(plan, p...)
|
||||||
|
}
|
||||||
|
|
||||||
if !equality.Semantic.DeepEqual(ac.Env, bc.Env) {
|
if !equality.Semantic.DeepEqual(ac.Env, bc.Env) {
|
||||||
if areEnvsEqual(ac.Env, bc.Env, func(a, b map[string]core.EnvVar) (map[string]core.EnvVar, map[string]core.EnvVar) {
|
if areEnvsEqual(ac.Env, bc.Env, func(a, b map[string]core.EnvVar) (map[string]core.EnvVar, map[string]core.EnvVar) {
|
||||||
delete(a, topology.ArangoDBZone)
|
delete(a, topology.ArangoDBZone)
|
||||||
|
@ -97,7 +108,7 @@ func containersCompare(_ api.DeploymentSpec, _ api.ServerGroup, spec, status *co
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func initContainersCompare(deploymentSpec api.DeploymentSpec, group api.ServerGroup, spec, status *core.PodSpec) compareFunc {
|
func initContainersCompare(deploymentSpec api.DeploymentSpec, group api.ServerGroup, spec, status *core.PodSpec) comparePodFunc {
|
||||||
return func(builder api.ActionBuilder) (Mode, api.Plan, error) {
|
return func(builder api.ActionBuilder) (Mode, api.Plan, error) {
|
||||||
gs := deploymentSpec.GetServerGroupSpec(group)
|
gs := deploymentSpec.GetServerGroupSpec(group)
|
||||||
|
|
||||||
|
|
|
@ -35,24 +35,28 @@ func Test_ArangoDContainers_SidecarImages(t *testing.T) {
|
||||||
testCases := []TestCase{
|
testCases := []TestCase{
|
||||||
{
|
{
|
||||||
name: "Sidecar Image Update",
|
name: "Sidecar Image Update",
|
||||||
spec: buildPodSpec(addContainer(shared.ServerContainerName, nil), addSidecarWithImage("sidecar", "local:1.0")),
|
spec: buildPodSpec(addContainer(shared.ServerContainerName), addSidecarWithImage("sidecar", "local:1.0")),
|
||||||
status: buildPodSpec(addContainer(shared.ServerContainerName, nil), addSidecarWithImage("sidecar", "local:2.0")),
|
status: buildPodSpec(addContainer(shared.ServerContainerName), addSidecarWithImage("sidecar", "local:2.0")),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
expectedMode: InPlaceRotation,
|
expectedMode: InPlaceRotation,
|
||||||
expectedPlan: api.Plan{
|
expectedPlan: api.Plan{
|
||||||
actions.NewClusterAction(api.ActionTypeRuntimeContainerImageUpdate),
|
actions.NewClusterAction(api.ActionTypeRuntimeContainerImageUpdate),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Sidecar Image Update with more than one sidecar",
|
name: "Sidecar Image Update with more than one sidecar",
|
||||||
spec: buildPodSpec(addSidecarWithImage("sidecar1", "local:1.0"), addSidecarWithImage("sidecar", "local:1.0")),
|
spec: buildPodSpec(addSidecarWithImage("sidecar1", "local:1.0"), addSidecarWithImage("sidecar", "local:1.0")),
|
||||||
status: buildPodSpec(addSidecarWithImage("sidecar1", "local:1.0"), addSidecarWithImage("sidecar", "local:2.0")),
|
status: buildPodSpec(addSidecarWithImage("sidecar1", "local:1.0"), addSidecarWithImage("sidecar", "local:2.0")),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
expectedMode: InPlaceRotation,
|
expectedMode: InPlaceRotation,
|
||||||
expectedPlan: api.Plan{
|
expectedPlan: api.Plan{
|
||||||
actions.NewClusterAction(api.ActionTypeRuntimeContainerImageUpdate),
|
actions.NewClusterAction(api.ActionTypeRuntimeContainerImageUpdate),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
runTestCases(t)(testCases...)
|
runTestCases(t)(testCases...)
|
||||||
|
@ -63,34 +67,38 @@ func Test_InitContainers(t *testing.T) {
|
||||||
testCases := []TestCase{
|
testCases := []TestCase{
|
||||||
{
|
{
|
||||||
name: "Same containers",
|
name: "Same containers",
|
||||||
spec: buildPodSpec(addInitContainer(api.ServerGroupReservedInitContainerNameUUID, nil), addInitContainer("sidecar", func(c *core.Container) {
|
spec: buildPodSpec(addInitContainer(api.ServerGroupReservedInitContainerNameUUID), addInitContainer("sidecar", func(c *core.Container) {
|
||||||
c.Image = "local:1.0"
|
c.Image = "local:1.0"
|
||||||
})),
|
})),
|
||||||
status: buildPodSpec(addInitContainer(api.ServerGroupReservedInitContainerNameUUID, nil), addInitContainer("sidecar", func(c *core.Container) {
|
status: buildPodSpec(addInitContainer(api.ServerGroupReservedInitContainerNameUUID), addInitContainer("sidecar", func(c *core.Container) {
|
||||||
c.Image = "local:1.0"
|
c.Image = "local:1.0"
|
||||||
})),
|
})),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
expectedMode: SkippedRotation,
|
expectedMode: SkippedRotation,
|
||||||
|
},
|
||||||
|
|
||||||
deploymentSpec: buildDeployment(func(depl *api.DeploymentSpec) {
|
groupSpec: buildGroupSpec(func(depl *api.ServerGroupSpec) {
|
||||||
depl.Agents.InitContainers = &api.ServerGroupInitContainers{
|
depl.InitContainers = &api.ServerGroupInitContainers{
|
||||||
Mode: api.ServerGroupInitContainerIgnoreMode.New(),
|
Mode: api.ServerGroupInitContainerIgnoreMode.New(),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Containers with different image",
|
name: "Containers with different image",
|
||||||
spec: buildPodSpec(addInitContainer(api.ServerGroupReservedInitContainerNameUUID, nil), addInitContainer("sidecar", func(c *core.Container) {
|
spec: buildPodSpec(addInitContainer(api.ServerGroupReservedInitContainerNameUUID), addInitContainer("sidecar", func(c *core.Container) {
|
||||||
c.Image = "local:1.0"
|
c.Image = "local:1.0"
|
||||||
})),
|
})),
|
||||||
status: buildPodSpec(addInitContainer(api.ServerGroupReservedInitContainerNameUUID, nil), addInitContainer("sidecar", func(c *core.Container) {
|
status: buildPodSpec(addInitContainer(api.ServerGroupReservedInitContainerNameUUID), addInitContainer("sidecar", func(c *core.Container) {
|
||||||
c.Image = "local:2.0"
|
c.Image = "local:2.0"
|
||||||
})),
|
})),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
expectedMode: SilentRotation,
|
expectedMode: SilentRotation,
|
||||||
|
},
|
||||||
|
|
||||||
deploymentSpec: buildDeployment(func(depl *api.DeploymentSpec) {
|
groupSpec: buildGroupSpec(func(depl *api.ServerGroupSpec) {
|
||||||
depl.Agents.InitContainers = &api.ServerGroupInitContainers{
|
depl.InitContainers = &api.ServerGroupInitContainers{
|
||||||
Mode: api.ServerGroupInitContainerIgnoreMode.New(),
|
Mode: api.ServerGroupInitContainerIgnoreMode.New(),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
@ -104,17 +112,19 @@ func Test_InitContainers(t *testing.T) {
|
||||||
testCases := []TestCase{
|
testCases := []TestCase{
|
||||||
{
|
{
|
||||||
name: "Containers with different image but init rotation enforced",
|
name: "Containers with different image but init rotation enforced",
|
||||||
spec: buildPodSpec(addInitContainer(api.ServerGroupReservedInitContainerNameUUID, nil), addInitContainer("sidecar", func(c *core.Container) {
|
spec: buildPodSpec(addInitContainer(api.ServerGroupReservedInitContainerNameUUID), addInitContainer("sidecar", func(c *core.Container) {
|
||||||
c.Image = "local:1.0"
|
c.Image = "local:1.0"
|
||||||
})),
|
})),
|
||||||
status: buildPodSpec(addInitContainer(api.ServerGroupReservedInitContainerNameUUID, nil), addInitContainer("sidecar", func(c *core.Container) {
|
status: buildPodSpec(addInitContainer(api.ServerGroupReservedInitContainerNameUUID), addInitContainer("sidecar", func(c *core.Container) {
|
||||||
c.Image = "local:2.0"
|
c.Image = "local:2.0"
|
||||||
})),
|
})),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
expectedMode: GracefulRotation,
|
expectedMode: GracefulRotation,
|
||||||
|
},
|
||||||
|
|
||||||
deploymentSpec: buildDeployment(func(depl *api.DeploymentSpec) {
|
groupSpec: buildGroupSpec(func(depl *api.ServerGroupSpec) {
|
||||||
depl.Agents.InitContainers = &api.ServerGroupInitContainers{
|
depl.InitContainers = &api.ServerGroupInitContainers{
|
||||||
Mode: api.ServerGroupInitContainerUpdateMode.New(),
|
Mode: api.ServerGroupInitContainerUpdateMode.New(),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
@ -132,10 +142,12 @@ func Test_InitContainers(t *testing.T) {
|
||||||
c.Image = "local:1.0"
|
c.Image = "local:1.0"
|
||||||
})),
|
})),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
expectedMode: SilentRotation,
|
expectedMode: SilentRotation,
|
||||||
|
},
|
||||||
|
|
||||||
deploymentSpec: buildDeployment(func(depl *api.DeploymentSpec) {
|
groupSpec: buildGroupSpec(func(depl *api.ServerGroupSpec) {
|
||||||
depl.Agents.InitContainers = &api.ServerGroupInitContainers{
|
depl.InitContainers = &api.ServerGroupInitContainers{
|
||||||
Mode: api.ServerGroupInitContainerUpdateMode.New(),
|
Mode: api.ServerGroupInitContainerUpdateMode.New(),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
@ -151,10 +163,12 @@ func Test_InitContainers(t *testing.T) {
|
||||||
c.Image = "local:2.0"
|
c.Image = "local:2.0"
|
||||||
})),
|
})),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
expectedMode: SilentRotation,
|
expectedMode: SilentRotation,
|
||||||
|
},
|
||||||
|
|
||||||
deploymentSpec: buildDeployment(func(depl *api.DeploymentSpec) {
|
groupSpec: buildGroupSpec(func(depl *api.ServerGroupSpec) {
|
||||||
depl.Agents.InitContainers = &api.ServerGroupInitContainers{
|
depl.InitContainers = &api.ServerGroupInitContainers{
|
||||||
Mode: api.ServerGroupInitContainerUpdateMode.New(),
|
Mode: api.ServerGroupInitContainerUpdateMode.New(),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
@ -174,10 +188,12 @@ func Test_InitContainers(t *testing.T) {
|
||||||
c.Image = "local:1.0"
|
c.Image = "local:1.0"
|
||||||
})),
|
})),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
expectedMode: SilentRotation,
|
expectedMode: SilentRotation,
|
||||||
|
},
|
||||||
|
|
||||||
deploymentSpec: buildDeployment(func(depl *api.DeploymentSpec) {
|
groupSpec: buildGroupSpec(func(depl *api.ServerGroupSpec) {
|
||||||
depl.Agents.InitContainers = &api.ServerGroupInitContainers{
|
depl.InitContainers = &api.ServerGroupInitContainers{
|
||||||
Mode: api.ServerGroupInitContainerUpdateMode.New(),
|
Mode: api.ServerGroupInitContainerUpdateMode.New(),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
@ -197,10 +213,12 @@ func Test_InitContainers(t *testing.T) {
|
||||||
c.Image = "local:2.0"
|
c.Image = "local:2.0"
|
||||||
})),
|
})),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
expectedMode: GracefulRotation,
|
expectedMode: GracefulRotation,
|
||||||
|
},
|
||||||
|
|
||||||
deploymentSpec: buildDeployment(func(depl *api.DeploymentSpec) {
|
groupSpec: buildGroupSpec(func(depl *api.ServerGroupSpec) {
|
||||||
depl.Agents.InitContainers = &api.ServerGroupInitContainers{
|
depl.InitContainers = &api.ServerGroupInitContainers{
|
||||||
Mode: api.ServerGroupInitContainerUpdateMode.New(),
|
Mode: api.ServerGroupInitContainerUpdateMode.New(),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
@ -264,8 +282,10 @@ func Test_Container_Args(t *testing.T) {
|
||||||
spec: buildPodSpec(addContainerWithCommand("sidecar",
|
spec: buildPodSpec(addContainerWithCommand("sidecar",
|
||||||
[]string{"--log.level=INFO", "--log.level=requests=error"})),
|
[]string{"--log.level=INFO", "--log.level=requests=error"})),
|
||||||
status: buildPodSpec(addContainerWithCommand("sidecar", []string{"--log.level=INFO"})),
|
status: buildPodSpec(addContainerWithCommand("sidecar", []string{"--log.level=INFO"})),
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
expectedMode: GracefulRotation,
|
expectedMode: GracefulRotation,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
runTestCases(t)(testCases...)
|
runTestCases(t)(testCases...)
|
||||||
|
@ -293,8 +313,10 @@ func Test_Container_Ports(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})),
|
})),
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
expectedMode: SilentRotation,
|
expectedMode: SilentRotation,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Ports of sidecar pod changed",
|
name: "Ports of sidecar pod changed",
|
||||||
spec: buildPodSpec(addContainer("sidecar", func(c *core.Container) {
|
spec: buildPodSpec(addContainer("sidecar", func(c *core.Container) {
|
||||||
|
@ -315,8 +337,10 @@ func Test_Container_Ports(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})),
|
})),
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
expectedMode: GracefulRotation,
|
expectedMode: GracefulRotation,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
runTestCases(t)(testCases...)
|
runTestCases(t)(testCases...)
|
||||||
|
|
|
@ -40,8 +40,10 @@ func Test_ArangoD_SchedulerName(t *testing.T) {
|
||||||
pod.Spec.SchedulerName = "new"
|
pod.Spec.SchedulerName = "new"
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
expectedMode: SilentRotation,
|
expectedMode: SilentRotation,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Change SchedulerName into Empty",
|
name: "Change SchedulerName into Empty",
|
||||||
spec: buildPodSpec(func(pod *core.PodTemplateSpec) {
|
spec: buildPodSpec(func(pod *core.PodTemplateSpec) {
|
||||||
|
@ -51,8 +53,10 @@ func Test_ArangoD_SchedulerName(t *testing.T) {
|
||||||
pod.Spec.SchedulerName = ""
|
pod.Spec.SchedulerName = ""
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
expectedMode: SilentRotation,
|
expectedMode: SilentRotation,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "SchedulerName equals",
|
name: "SchedulerName equals",
|
||||||
spec: buildPodSpec(func(pod *core.PodTemplateSpec) {
|
spec: buildPodSpec(func(pod *core.PodTemplateSpec) {
|
||||||
|
@ -62,8 +66,10 @@ func Test_ArangoD_SchedulerName(t *testing.T) {
|
||||||
pod.Spec.SchedulerName = ""
|
pod.Spec.SchedulerName = ""
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
expectedMode: SkippedRotation,
|
expectedMode: SkippedRotation,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
runTestCases(t)(testCases...)
|
runTestCases(t)(testCases...)
|
||||||
|
@ -80,8 +86,10 @@ func Test_ArangoD_TerminationGracePeriodSeconds(t *testing.T) {
|
||||||
pod.Spec.TerminationGracePeriodSeconds = util.NewInt64(30)
|
pod.Spec.TerminationGracePeriodSeconds = util.NewInt64(30)
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
expectedMode: SilentRotation,
|
expectedMode: SilentRotation,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Remove",
|
name: "Remove",
|
||||||
spec: buildPodSpec(func(pod *core.PodTemplateSpec) {
|
spec: buildPodSpec(func(pod *core.PodTemplateSpec) {
|
||||||
|
@ -91,8 +99,10 @@ func Test_ArangoD_TerminationGracePeriodSeconds(t *testing.T) {
|
||||||
pod.Spec.TerminationGracePeriodSeconds = nil
|
pod.Spec.TerminationGracePeriodSeconds = nil
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
expectedMode: SilentRotation,
|
expectedMode: SilentRotation,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Update",
|
name: "Update",
|
||||||
spec: buildPodSpec(func(pod *core.PodTemplateSpec) {
|
spec: buildPodSpec(func(pod *core.PodTemplateSpec) {
|
||||||
|
@ -102,8 +112,10 @@ func Test_ArangoD_TerminationGracePeriodSeconds(t *testing.T) {
|
||||||
pod.Spec.TerminationGracePeriodSeconds = util.NewInt64(30)
|
pod.Spec.TerminationGracePeriodSeconds = util.NewInt64(30)
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
expectedMode: SilentRotation,
|
expectedMode: SilentRotation,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
runTestCases(t)(testCases...)
|
runTestCases(t)(testCases...)
|
||||||
|
@ -137,8 +149,10 @@ func Test_ArangoD_Affinity(t *testing.T) {
|
||||||
status: buildPodSpec(func(pod *core.PodTemplateSpec) {
|
status: buildPodSpec(func(pod *core.PodTemplateSpec) {
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
expectedMode: GracefulRotation,
|
expectedMode: GracefulRotation,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Add affinity",
|
name: "Add affinity",
|
||||||
spec: buildPodSpec(func(pod *core.PodTemplateSpec) {
|
spec: buildPodSpec(func(pod *core.PodTemplateSpec) {
|
||||||
|
@ -165,8 +179,10 @@ func Test_ArangoD_Affinity(t *testing.T) {
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
expectedMode: GracefulRotation,
|
expectedMode: GracefulRotation,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Change affinity",
|
name: "Change affinity",
|
||||||
spec: buildPodSpec(func(pod *core.PodTemplateSpec) {
|
spec: buildPodSpec(func(pod *core.PodTemplateSpec) {
|
||||||
|
@ -212,8 +228,10 @@ func Test_ArangoD_Affinity(t *testing.T) {
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
expectedMode: SilentRotation,
|
expectedMode: SilentRotation,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Change affinity archs",
|
name: "Change affinity archs",
|
||||||
spec: buildPodSpec(func(pod *core.PodTemplateSpec) {
|
spec: buildPodSpec(func(pod *core.PodTemplateSpec) {
|
||||||
|
@ -259,8 +277,10 @@ func Test_ArangoD_Affinity(t *testing.T) {
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
expectedMode: GracefulRotation,
|
expectedMode: GracefulRotation,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Change affinity archs - swap arch order",
|
name: "Change affinity archs - swap arch order",
|
||||||
spec: buildPodSpec(func(pod *core.PodTemplateSpec) {
|
spec: buildPodSpec(func(pod *core.PodTemplateSpec) {
|
||||||
|
@ -306,8 +326,10 @@ func Test_ArangoD_Affinity(t *testing.T) {
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
expectedMode: SilentRotation,
|
expectedMode: SilentRotation,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
runTestCases(t)(testCases...)
|
runTestCases(t)(testCases...)
|
||||||
|
@ -328,8 +350,10 @@ func Test_ArangoD_Labels(t *testing.T) {
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
expectedMode: SkippedRotation,
|
expectedMode: SkippedRotation,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Remove label",
|
name: "Remove label",
|
||||||
|
|
||||||
|
@ -343,8 +367,10 @@ func Test_ArangoD_Labels(t *testing.T) {
|
||||||
pod.Labels = map[string]string{}
|
pod.Labels = map[string]string{}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
expectedMode: SkippedRotation,
|
expectedMode: SkippedRotation,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Change label",
|
name: "Change label",
|
||||||
|
|
||||||
|
@ -360,8 +386,10 @@ func Test_ArangoD_Labels(t *testing.T) {
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
expectedMode: SkippedRotation,
|
expectedMode: SkippedRotation,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
runTestCases(t)(testCases...)
|
runTestCases(t)(testCases...)
|
||||||
|
@ -385,8 +413,10 @@ func Test_ArangoD_Envs_Zone(t *testing.T) {
|
||||||
}
|
}
|
||||||
})),
|
})),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
expectedMode: SilentRotation,
|
expectedMode: SilentRotation,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Remove Zone env",
|
name: "Remove Zone env",
|
||||||
|
|
||||||
|
@ -403,8 +433,10 @@ func Test_ArangoD_Envs_Zone(t *testing.T) {
|
||||||
c.Env = []core.EnvVar{}
|
c.Env = []core.EnvVar{}
|
||||||
})),
|
})),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
expectedMode: SilentRotation,
|
expectedMode: SilentRotation,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Update Zone env",
|
name: "Update Zone env",
|
||||||
|
|
||||||
|
@ -426,8 +458,10 @@ func Test_ArangoD_Envs_Zone(t *testing.T) {
|
||||||
}
|
}
|
||||||
})),
|
})),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
expectedMode: SilentRotation,
|
expectedMode: SilentRotation,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Update other env",
|
name: "Update other env",
|
||||||
|
|
||||||
|
@ -453,8 +487,10 @@ func Test_ArangoD_Envs_Zone(t *testing.T) {
|
||||||
}
|
}
|
||||||
})),
|
})),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
expectedMode: GracefulRotation,
|
expectedMode: GracefulRotation,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
runTestCases(t)(testCases...)
|
runTestCases(t)(testCases...)
|
||||||
|
|
190
pkg/deployment/rotation/arangod_volumes.go
Normal file
190
pkg/deployment/rotation/arangod_volumes.go
Normal file
|
@ -0,0 +1,190 @@
|
||||||
|
//
|
||||||
|
// DISCLAIMER
|
||||||
|
//
|
||||||
|
// Copyright 2016-2022 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 rotation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
core "k8s.io/api/core/v1"
|
||||||
|
|
||||||
|
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
|
||||||
|
"github.com/arangodb/kube-arangodb/pkg/apis/shared"
|
||||||
|
)
|
||||||
|
|
||||||
|
type volumeDiff struct {
|
||||||
|
a, b *core.Volume
|
||||||
|
}
|
||||||
|
|
||||||
|
func comparePodVolumes(ds api.DeploymentSpec, g api.ServerGroup, spec, status *core.PodSpec) comparePodFunc {
|
||||||
|
return func(builder api.ActionBuilder) (mode Mode, plan api.Plan, err error) {
|
||||||
|
specV := mapVolumes(spec)
|
||||||
|
statusV := mapVolumes(status)
|
||||||
|
|
||||||
|
diff := getVolumesDiffFromPods(specV, statusV)
|
||||||
|
|
||||||
|
if len(diff) == 0 {
|
||||||
|
return SkippedRotation, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range diff {
|
||||||
|
switch k {
|
||||||
|
case shared.ArangoDTimezoneVolumeName:
|
||||||
|
// We are fine, should be just replaced
|
||||||
|
if v.a == nil {
|
||||||
|
// we remove volume
|
||||||
|
return GracefulRotation, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if ds.Mode.Get().ServingGroup() == g {
|
||||||
|
// Always enforce on serving group
|
||||||
|
return GracefulRotation, nil, nil
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return GracefulRotation, nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
status.Volumes = spec.Volumes
|
||||||
|
return SilentRotation, nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getVolumesDiffFromPods(a, b map[string]*core.Volume) map[string]volumeDiff {
|
||||||
|
d := map[string]volumeDiff{}
|
||||||
|
|
||||||
|
for k := range a {
|
||||||
|
if z, ok := b[k]; ok {
|
||||||
|
if !reflect.DeepEqual(a[k], z) {
|
||||||
|
d[k] = volumeDiff{
|
||||||
|
a: a[k],
|
||||||
|
b: z,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
d[k] = volumeDiff{
|
||||||
|
a: a[k],
|
||||||
|
b: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k := range b {
|
||||||
|
if _, ok := a[k]; !ok {
|
||||||
|
d[k] = volumeDiff{
|
||||||
|
a: nil,
|
||||||
|
b: b[k],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapVolumes(a *core.PodSpec) map[string]*core.Volume {
|
||||||
|
n := make(map[string]*core.Volume, len(a.Volumes))
|
||||||
|
|
||||||
|
for id := range a.Volumes {
|
||||||
|
v := &a.Volumes[id]
|
||||||
|
|
||||||
|
n[v.Name] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareServerContainerVolumeMounts(ds api.DeploymentSpec, g api.ServerGroup, spec, status *core.Container) comparePodContainerFunc {
|
||||||
|
return func(builder api.ActionBuilder) (mode Mode, plan api.Plan, err error) {
|
||||||
|
specV := mapVolumeMounts(spec)
|
||||||
|
statusV := mapVolumeMounts(status)
|
||||||
|
|
||||||
|
diff := getVolumeMountsDiffFromPods(specV, statusV)
|
||||||
|
|
||||||
|
if len(diff) == 0 {
|
||||||
|
return SkippedRotation, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range diff {
|
||||||
|
switch k {
|
||||||
|
case shared.ArangoDTimezoneVolumeName:
|
||||||
|
// We are fine, should be just replaced
|
||||||
|
if v.a == nil {
|
||||||
|
// we remove volume
|
||||||
|
return GracefulRotation, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if ds.Mode.Get().ServingGroup() == g {
|
||||||
|
// Always enforce on serving group
|
||||||
|
return GracefulRotation, nil, nil
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return GracefulRotation, nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
status.VolumeMounts = spec.VolumeMounts
|
||||||
|
return SilentRotation, nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type volumeMountDiff struct {
|
||||||
|
a, b []*core.VolumeMount
|
||||||
|
}
|
||||||
|
|
||||||
|
func getVolumeMountsDiffFromPods(a, b map[string][]*core.VolumeMount) map[string]volumeMountDiff {
|
||||||
|
d := map[string]volumeMountDiff{}
|
||||||
|
|
||||||
|
for k := range a {
|
||||||
|
if z, ok := b[k]; ok {
|
||||||
|
if !reflect.DeepEqual(a[k], z) {
|
||||||
|
d[k] = volumeMountDiff{
|
||||||
|
a: a[k],
|
||||||
|
b: z,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
d[k] = volumeMountDiff{
|
||||||
|
a: a[k],
|
||||||
|
b: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k := range b {
|
||||||
|
if _, ok := a[k]; !ok {
|
||||||
|
d[k] = volumeMountDiff{
|
||||||
|
a: nil,
|
||||||
|
b: a[k],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapVolumeMounts(a *core.Container) map[string][]*core.VolumeMount {
|
||||||
|
n := make(map[string][]*core.VolumeMount, len(a.VolumeMounts))
|
||||||
|
|
||||||
|
for id := range a.VolumeMounts {
|
||||||
|
v := &a.VolumeMounts[id]
|
||||||
|
|
||||||
|
n[v.Name] = append(n[v.Name], v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return n
|
||||||
|
}
|
264
pkg/deployment/rotation/arangod_volumes_test.go
Normal file
264
pkg/deployment/rotation/arangod_volumes_test.go
Normal file
|
@ -0,0 +1,264 @@
|
||||||
|
//
|
||||||
|
// DISCLAIMER
|
||||||
|
//
|
||||||
|
// Copyright 2016-2022 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 rotation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
core "k8s.io/api/core/v1"
|
||||||
|
|
||||||
|
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
|
||||||
|
"github.com/arangodb/kube-arangodb/pkg/apis/shared"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_ArangoD_Volumes(t *testing.T) {
|
||||||
|
testCases := []TestCase{
|
||||||
|
{
|
||||||
|
name: "Empty volumes",
|
||||||
|
spec: buildPodSpec(),
|
||||||
|
status: buildPodSpec(),
|
||||||
|
|
||||||
|
deploymentSpec: buildDeployment(),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
|
expectedMode: SkippedRotation,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Same volumes",
|
||||||
|
spec: buildPodSpec(addVolume("data", addVolumeConfigMapSource(&core.ConfigMapVolumeSource{}))),
|
||||||
|
status: buildPodSpec(addVolume("data", addVolumeConfigMapSource(&core.ConfigMapVolumeSource{}))),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
|
expectedMode: SkippedRotation,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Different volumes",
|
||||||
|
spec: buildPodSpec(addVolume("data", addVolumeConfigMapSource(&core.ConfigMapVolumeSource{}))),
|
||||||
|
status: buildPodSpec(addVolume("data", addVolumeConfigMapSource(&core.ConfigMapVolumeSource{
|
||||||
|
LocalObjectReference: core.LocalObjectReference{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
}))),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
|
expectedMode: GracefulRotation,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Missing volumes",
|
||||||
|
spec: buildPodSpec(addVolume("data", addVolumeConfigMapSource(&core.ConfigMapVolumeSource{}))),
|
||||||
|
status: buildPodSpec(),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
|
expectedMode: GracefulRotation,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Added volumes",
|
||||||
|
spec: buildPodSpec(),
|
||||||
|
status: buildPodSpec(addVolume("data", addVolumeConfigMapSource(&core.ConfigMapVolumeSource{
|
||||||
|
LocalObjectReference: core.LocalObjectReference{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
}))),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
|
expectedMode: GracefulRotation,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Timezone: Different volumes",
|
||||||
|
spec: buildPodSpec(addVolume(shared.ArangoDTimezoneVolumeName, addVolumeConfigMapSource(&core.ConfigMapVolumeSource{}))),
|
||||||
|
status: buildPodSpec(addVolume(shared.ArangoDTimezoneVolumeName, addVolumeConfigMapSource(&core.ConfigMapVolumeSource{
|
||||||
|
LocalObjectReference: core.LocalObjectReference{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
}))),
|
||||||
|
|
||||||
|
overrides: map[api.DeploymentMode]map[api.ServerGroup]TestCaseOverride{
|
||||||
|
api.DeploymentModeSingle: {
|
||||||
|
api.ServerGroupSingle: {
|
||||||
|
expectedMode: GracefulRotation,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
api.DeploymentModeActiveFailover: {
|
||||||
|
api.ServerGroupSingle: {
|
||||||
|
expectedMode: GracefulRotation,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
api.DeploymentModeCluster: {
|
||||||
|
api.ServerGroupCoordinators: {
|
||||||
|
expectedMode: GracefulRotation,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
|
expectedMode: SilentRotation,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Timezone: Missing volumes",
|
||||||
|
spec: buildPodSpec(addVolume(shared.ArangoDTimezoneVolumeName, addVolumeConfigMapSource(&core.ConfigMapVolumeSource{}))),
|
||||||
|
status: buildPodSpec(),
|
||||||
|
|
||||||
|
overrides: map[api.DeploymentMode]map[api.ServerGroup]TestCaseOverride{
|
||||||
|
api.DeploymentModeSingle: {
|
||||||
|
api.ServerGroupSingle: {
|
||||||
|
expectedMode: GracefulRotation,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
api.DeploymentModeActiveFailover: {
|
||||||
|
api.ServerGroupSingle: {
|
||||||
|
expectedMode: GracefulRotation,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
api.DeploymentModeCluster: {
|
||||||
|
api.ServerGroupCoordinators: {
|
||||||
|
expectedMode: GracefulRotation,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
|
expectedMode: SilentRotation,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Timezone: Added volumes",
|
||||||
|
spec: buildPodSpec(),
|
||||||
|
status: buildPodSpec(addVolume(shared.ArangoDTimezoneVolumeName, addVolumeConfigMapSource(&core.ConfigMapVolumeSource{
|
||||||
|
LocalObjectReference: core.LocalObjectReference{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
}))),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
|
expectedMode: GracefulRotation,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
runTestCases(t)(testCases...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_ArangoD_VolumeMounts(t *testing.T) {
|
||||||
|
testCases := []TestCase{
|
||||||
|
{
|
||||||
|
name: "Empty volume mounts",
|
||||||
|
spec: buildPodSpec(addContainer("server")),
|
||||||
|
status: buildPodSpec(addContainer("server")),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
|
expectedMode: SkippedRotation,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Same volumes",
|
||||||
|
spec: buildPodSpec(addContainer("server", addVolumeMount("mount", func(in *core.VolumeMount) {
|
||||||
|
|
||||||
|
}))),
|
||||||
|
status: buildPodSpec(addContainer("server", addVolumeMount("mount", func(in *core.VolumeMount) {
|
||||||
|
|
||||||
|
}))),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
|
expectedMode: SkippedRotation,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Different volumes",
|
||||||
|
spec: buildPodSpec(addContainer("server", addVolumeMount("mount"))),
|
||||||
|
status: buildPodSpec(addContainer("server", addVolumeMount("mount2"))),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
|
expectedMode: GracefulRotation,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Missing volumes",
|
||||||
|
spec: buildPodSpec(addContainer("server", addVolumeMount("mount"))),
|
||||||
|
status: buildPodSpec(addContainer("server")),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
|
expectedMode: GracefulRotation,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Added volumes",
|
||||||
|
spec: buildPodSpec(addContainer("server")),
|
||||||
|
status: buildPodSpec(addContainer("server", addVolumeMount("mount"))),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
|
expectedMode: GracefulRotation,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Timezone: Different volumes",
|
||||||
|
spec: buildPodSpec(addContainer("server", addVolumeMount(shared.ArangoDTimezoneVolumeName))),
|
||||||
|
status: buildPodSpec(addContainer("server", addVolumeMount("mount"))),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
|
expectedMode: GracefulRotation,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Timezone: Missing volumes",
|
||||||
|
spec: buildPodSpec(addContainer("server")),
|
||||||
|
status: buildPodSpec(addContainer("server", addVolumeMount(shared.ArangoDTimezoneVolumeName))),
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
|
expectedMode: GracefulRotation,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Timezone: Added volumes",
|
||||||
|
spec: buildPodSpec(addContainer("server", addVolumeMount(shared.ArangoDTimezoneVolumeName))),
|
||||||
|
status: buildPodSpec(addContainer("server")),
|
||||||
|
|
||||||
|
overrides: map[api.DeploymentMode]map[api.ServerGroup]TestCaseOverride{
|
||||||
|
api.DeploymentModeSingle: {
|
||||||
|
api.ServerGroupSingle: {
|
||||||
|
expectedMode: GracefulRotation,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
api.DeploymentModeActiveFailover: {
|
||||||
|
api.ServerGroupSingle: {
|
||||||
|
expectedMode: GracefulRotation,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
api.DeploymentModeCluster: {
|
||||||
|
api.ServerGroupCoordinators: {
|
||||||
|
expectedMode: GracefulRotation,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
TestCaseOverride: TestCaseOverride{
|
||||||
|
expectedMode: SilentRotation,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
runTestCases(t)(testCases...)
|
||||||
|
}
|
38
pkg/deployment/rotation/builder_utils_volume_mounts_test.go
Normal file
38
pkg/deployment/rotation/builder_utils_volume_mounts_test.go
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
//
|
||||||
|
// DISCLAIMER
|
||||||
|
//
|
||||||
|
// Copyright 2016-2022 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 rotation
|
||||||
|
|
||||||
|
import core "k8s.io/api/core/v1"
|
||||||
|
|
||||||
|
type podVolumeMountSpecBuilder func(in *core.VolumeMount)
|
||||||
|
|
||||||
|
func addVolumeMount(name string, builders ...podVolumeMountSpecBuilder) podContainerBuilder {
|
||||||
|
return func(c *core.Container) {
|
||||||
|
var v core.VolumeMount
|
||||||
|
v.Name = name
|
||||||
|
|
||||||
|
for _, b := range builders {
|
||||||
|
b(&v)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.VolumeMounts = append(c.VolumeMounts, v)
|
||||||
|
}
|
||||||
|
}
|
44
pkg/deployment/rotation/builder_utils_volume_test.go
Normal file
44
pkg/deployment/rotation/builder_utils_volume_test.go
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
//
|
||||||
|
// DISCLAIMER
|
||||||
|
//
|
||||||
|
// Copyright 2016-2022 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 rotation
|
||||||
|
|
||||||
|
import core "k8s.io/api/core/v1"
|
||||||
|
|
||||||
|
type podVolumeSpecBuilder func(in *core.Volume)
|
||||||
|
|
||||||
|
func addVolume(name string, builders ...podVolumeSpecBuilder) podSpecBuilder {
|
||||||
|
return func(pod *core.PodTemplateSpec) {
|
||||||
|
var v core.Volume
|
||||||
|
v.Name = name
|
||||||
|
|
||||||
|
for _, b := range builders {
|
||||||
|
b(&v)
|
||||||
|
}
|
||||||
|
|
||||||
|
pod.Spec.Volumes = append(pod.Spec.Volumes, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addVolumeConfigMapSource(cm *core.ConfigMapVolumeSource) podVolumeSpecBuilder {
|
||||||
|
return func(in *core.Volume) {
|
||||||
|
in.ConfigMap = cm.DeepCopy()
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,16 +31,38 @@ import (
|
||||||
"github.com/arangodb/kube-arangodb/pkg/deployment/resources"
|
"github.com/arangodb/kube-arangodb/pkg/deployment/resources"
|
||||||
)
|
)
|
||||||
|
|
||||||
type compareFuncGen func(deploymentSpec api.DeploymentSpec, group api.ServerGroup, spec, status *core.PodSpec) compareFunc
|
type comparePodFuncGen func(deploymentSpec api.DeploymentSpec, group api.ServerGroup, spec, status *core.PodSpec) comparePodFunc
|
||||||
type compareFunc func(builder api.ActionBuilder) (mode Mode, plan api.Plan, err error)
|
type comparePodFunc func(builder api.ActionBuilder) (mode Mode, plan api.Plan, err error)
|
||||||
|
|
||||||
func generator(deploymentSpec api.DeploymentSpec, group api.ServerGroup, spec, status *core.PodSpec) func(c compareFuncGen) compareFunc {
|
func podFuncGenerator(deploymentSpec api.DeploymentSpec, group api.ServerGroup, spec, status *core.PodSpec) func(c comparePodFuncGen) comparePodFunc {
|
||||||
return func(c compareFuncGen) compareFunc {
|
return func(c comparePodFuncGen) comparePodFunc {
|
||||||
return c(deploymentSpec, group, spec, status)
|
return c(deploymentSpec, group, spec, status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func compareFuncs(builder api.ActionBuilder, f ...compareFunc) (mode Mode, plan api.Plan, err error) {
|
type comparePodContainerFuncGen func(deploymentSpec api.DeploymentSpec, group api.ServerGroup, spec, status *core.Container) comparePodContainerFunc
|
||||||
|
type comparePodContainerFunc func(builder api.ActionBuilder) (mode Mode, plan api.Plan, err error)
|
||||||
|
|
||||||
|
func podContainerFuncGenerator(deploymentSpec api.DeploymentSpec, group api.ServerGroup, spec, status *core.Container) func(c comparePodContainerFuncGen) comparePodContainerFunc {
|
||||||
|
return func(c comparePodContainerFuncGen) comparePodContainerFunc {
|
||||||
|
return c(deploymentSpec, group, spec, status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func comparePodContainer(builder api.ActionBuilder, f ...comparePodContainerFunc) (mode Mode, plan api.Plan, err error) {
|
||||||
|
for _, q := range f {
|
||||||
|
if m, p, err := q(builder); err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
} else {
|
||||||
|
mode = mode.And(m)
|
||||||
|
plan = append(plan, p...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func comparePod(builder api.ActionBuilder, f ...comparePodFunc) (mode Mode, plan api.Plan, err error) {
|
||||||
for _, q := range f {
|
for _, q := range f {
|
||||||
if m, p, err := q(builder); err != nil {
|
if m, p, err := q(builder); err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
|
@ -69,9 +91,9 @@ func compare(deploymentSpec api.DeploymentSpec, member api.MemberStatus, group a
|
||||||
// Try to fill fields
|
// Try to fill fields
|
||||||
b := actions.NewActionBuilderWrap(group, member)
|
b := actions.NewActionBuilderWrap(group, member)
|
||||||
|
|
||||||
g := generator(deploymentSpec, group, &spec.PodSpec.Spec, &podStatus.Spec)
|
g := podFuncGenerator(deploymentSpec, group, &spec.PodSpec.Spec, &podStatus.Spec)
|
||||||
|
|
||||||
if m, p, err := compareFuncs(b, g(podCompare), g(affinityCompare), g(containersCompare), g(initContainersCompare)); err != nil {
|
if m, p, err := comparePod(b, g(podCompare), g(affinityCompare), g(comparePodVolumes), g(containersCompare), g(initContainersCompare)); err != nil {
|
||||||
log.Err(err).Msg("Error while getting pod diff")
|
log.Err(err).Msg("Error while getting pod diff")
|
||||||
return SkippedRotation, nil, err
|
return SkippedRotation, nil, err
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -30,44 +30,98 @@ import (
|
||||||
"github.com/arangodb/kube-arangodb/pkg/deployment/resources"
|
"github.com/arangodb/kube-arangodb/pkg/deployment/resources"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TestCase struct {
|
type TestCaseOverride struct {
|
||||||
name string
|
|
||||||
spec, status *core.PodTemplateSpec
|
|
||||||
|
|
||||||
deploymentSpec api.DeploymentSpec
|
|
||||||
expectedMode Mode
|
expectedMode Mode
|
||||||
expectedPlan api.Plan
|
expectedPlan api.Plan
|
||||||
expectedErr string
|
expectedErr string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TestCase struct {
|
||||||
|
name string
|
||||||
|
spec, status *core.PodTemplateSpec
|
||||||
|
|
||||||
|
deploymentSpec api.DeploymentSpec
|
||||||
|
groupSpec api.ServerGroupSpec
|
||||||
|
|
||||||
|
TestCaseOverride
|
||||||
|
|
||||||
|
overrides map[api.DeploymentMode]map[api.ServerGroup]TestCaseOverride
|
||||||
|
}
|
||||||
|
|
||||||
func runTestCases(t *testing.T) func(tcs ...TestCase) {
|
func runTestCases(t *testing.T) func(tcs ...TestCase) {
|
||||||
|
|
||||||
return func(tcs ...TestCase) {
|
return func(tcs ...TestCase) {
|
||||||
for _, tc := range tcs {
|
for _, tc := range tcs {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
runTestCasesForMode(t, api.DeploymentModeSingle, tc)
|
||||||
|
runTestCasesForMode(t, api.DeploymentModeActiveFailover, tc)
|
||||||
|
runTestCasesForMode(t, api.DeploymentModeCluster, tc)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pspec := newTemplateFromSpec(t, tc.spec, api.ServerGroupAgents, tc.deploymentSpec)
|
func runTestCasesForMode(t *testing.T, m api.DeploymentMode, tc TestCase) {
|
||||||
pstatus := newTemplateFromSpec(t, tc.status, api.ServerGroupAgents, tc.deploymentSpec)
|
t.Run(m.String(), func(t *testing.T) {
|
||||||
|
switch m {
|
||||||
|
case api.DeploymentModeSingle:
|
||||||
|
runTestCasesForModeAndGroup(t, m, api.ServerGroupSingle, tc)
|
||||||
|
case api.DeploymentModeCluster:
|
||||||
|
runTestCasesForModeAndGroup(t, m, api.ServerGroupAgents, tc)
|
||||||
|
runTestCasesForModeAndGroup(t, m, api.ServerGroupDBServers, tc)
|
||||||
|
runTestCasesForModeAndGroup(t, m, api.ServerGroupCoordinators, tc)
|
||||||
|
case api.DeploymentModeActiveFailover:
|
||||||
|
runTestCasesForModeAndGroup(t, m, api.ServerGroupAgents, tc)
|
||||||
|
runTestCasesForModeAndGroup(t, m, api.ServerGroupSingle, tc)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
mode, plan, err := compare(tc.deploymentSpec, api.MemberStatus{ID: "id"}, api.ServerGroupAgents, pspec, pstatus)
|
func runTestCasesForModeAndGroup(t *testing.T, m api.DeploymentMode, g api.ServerGroup, tc TestCase) {
|
||||||
|
t.Run(g.AsRole(), func(t *testing.T) {
|
||||||
|
ds := tc.deploymentSpec.DeepCopy()
|
||||||
|
if ds == nil {
|
||||||
|
ds = &api.DeploymentSpec{}
|
||||||
|
}
|
||||||
|
|
||||||
|
ds.Mode = m.New()
|
||||||
|
|
||||||
|
ds.UpdateServerGroupSpec(g, tc.groupSpec)
|
||||||
|
|
||||||
|
if tc.spec == nil {
|
||||||
|
tc.spec = buildPodSpec()
|
||||||
|
}
|
||||||
|
if tc.status == nil {
|
||||||
|
tc.status = buildPodSpec()
|
||||||
|
}
|
||||||
|
|
||||||
|
pspec := newTemplateFromSpec(t, tc.spec, g, *ds)
|
||||||
|
pstatus := newTemplateFromSpec(t, tc.status, g, *ds)
|
||||||
|
|
||||||
|
mode, plan, err := compare(*ds, api.MemberStatus{ID: "id"}, g, pspec, pstatus)
|
||||||
|
|
||||||
|
q := tc.TestCaseOverride
|
||||||
|
|
||||||
|
if v, ok := tc.overrides[m][g]; ok {
|
||||||
|
q = v
|
||||||
|
}
|
||||||
|
|
||||||
if tc.expectedErr != "" {
|
if tc.expectedErr != "" {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.EqualError(t, err, tc.expectedErr)
|
require.EqualError(t, err, q.expectedErr)
|
||||||
} else {
|
} else {
|
||||||
require.Equal(t, tc.expectedMode, mode)
|
require.Equal(t, q.expectedMode, mode)
|
||||||
|
|
||||||
switch mode {
|
switch mode {
|
||||||
case InPlaceRotation:
|
case InPlaceRotation:
|
||||||
require.Len(t, plan, len(tc.expectedPlan))
|
require.Len(t, plan, len(q.expectedPlan))
|
||||||
|
|
||||||
for i := range plan {
|
for i := range plan {
|
||||||
require.Equal(t, tc.expectedPlan[i].Type, plan[i].Type)
|
require.Equal(t, q.expectedPlan[i].Type, plan[i].Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTemplateFromSpec(t *testing.T, podSpec *core.PodTemplateSpec, group api.ServerGroup, deploymentSpec api.DeploymentSpec) *api.ArangoMemberPodTemplate {
|
func newTemplateFromSpec(t *testing.T, podSpec *core.PodTemplateSpec, group api.ServerGroup, deploymentSpec api.DeploymentSpec) *api.ArangoMemberPodTemplate {
|
||||||
|
@ -82,6 +136,8 @@ func newTemplateFromSpec(t *testing.T, podSpec *core.PodTemplateSpec, group api.
|
||||||
|
|
||||||
type podSpecBuilder func(pod *core.PodTemplateSpec)
|
type podSpecBuilder func(pod *core.PodTemplateSpec)
|
||||||
|
|
||||||
|
type podContainerBuilder func(c *core.Container)
|
||||||
|
|
||||||
func buildPodSpec(b ...podSpecBuilder) *core.PodTemplateSpec {
|
func buildPodSpec(b ...podSpecBuilder) *core.PodTemplateSpec {
|
||||||
p := &core.PodTemplateSpec{}
|
p := &core.PodTemplateSpec{}
|
||||||
|
|
||||||
|
@ -92,28 +148,28 @@ func buildPodSpec(b ...podSpecBuilder) *core.PodTemplateSpec {
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func addContainer(name string, f func(c *core.Container)) podSpecBuilder {
|
func addContainer(name string, f ...podContainerBuilder) podSpecBuilder {
|
||||||
return func(pod *core.PodTemplateSpec) {
|
return func(pod *core.PodTemplateSpec) {
|
||||||
var c core.Container
|
var c core.Container
|
||||||
|
|
||||||
c.Name = name
|
c.Name = name
|
||||||
|
|
||||||
if f != nil {
|
for _, q := range f {
|
||||||
f(&c)
|
q(&c)
|
||||||
}
|
}
|
||||||
|
|
||||||
pod.Spec.Containers = append(pod.Spec.Containers, c)
|
pod.Spec.Containers = append(pod.Spec.Containers, c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func addInitContainer(name string, f func(c *core.Container)) podSpecBuilder {
|
func addInitContainer(name string, f ...podContainerBuilder) podSpecBuilder {
|
||||||
return func(pod *core.PodTemplateSpec) {
|
return func(pod *core.PodTemplateSpec) {
|
||||||
var c core.Container
|
var c core.Container
|
||||||
|
|
||||||
c.Name = name
|
c.Name = name
|
||||||
|
|
||||||
if f != nil {
|
for _, q := range f {
|
||||||
f(&c)
|
q(&c)
|
||||||
}
|
}
|
||||||
|
|
||||||
pod.Spec.InitContainers = append(pod.Spec.InitContainers, c)
|
pod.Spec.InitContainers = append(pod.Spec.InitContainers, c)
|
||||||
|
@ -143,3 +199,15 @@ func buildDeployment(b ...deploymentBuilder) api.DeploymentSpec {
|
||||||
|
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type groupSpecBuilder func(depl *api.ServerGroupSpec)
|
||||||
|
|
||||||
|
func buildGroupSpec(b ...groupSpecBuilder) api.ServerGroupSpec {
|
||||||
|
p := api.ServerGroupSpec{}
|
||||||
|
|
||||||
|
for _, i := range b {
|
||||||
|
i(&p)
|
||||||
|
}
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
2
pkg/generated/timezones/timezones.go
generated
2
pkg/generated/timezones/timezones.go
generated
|
@ -34,7 +34,7 @@ type Timezone struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t Timezone) GetData() ([]byte, bool) {
|
func (t Timezone) GetData() ([]byte, bool) {
|
||||||
if d, ok := timezonesData[t.Name]; ok {
|
if d, ok := timezonesData[t.Parent]; ok {
|
||||||
if d, err := base64.StdEncoding.DecodeString(d); err == nil {
|
if d, err := base64.StdEncoding.DecodeString(d); err == nil {
|
||||||
return d, true
|
return d, true
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,10 @@ import "github.com/rs/zerolog"
|
||||||
|
|
||||||
type Level zerolog.Level
|
type Level zerolog.Level
|
||||||
|
|
||||||
|
func (l Level) New() *Level {
|
||||||
|
return &l
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Trace = Level(zerolog.TraceLevel)
|
Trace = Level(zerolog.TraceLevel)
|
||||||
Debug = Level(zerolog.DebugLevel)
|
Debug = Level(zerolog.DebugLevel)
|
||||||
|
|
|
@ -55,6 +55,8 @@ func NewFactory(root zerolog.Logger) Factory {
|
||||||
return &factory{
|
return &factory{
|
||||||
root: root,
|
root: root,
|
||||||
loggers: map[string]*zerolog.Logger{},
|
loggers: map[string]*zerolog.Logger{},
|
||||||
|
defaults: map[string]Level{},
|
||||||
|
levels: map[string]Level{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,6 +68,9 @@ type factory struct {
|
||||||
wrappers []Wrap
|
wrappers []Wrap
|
||||||
|
|
||||||
loggers map[string]*zerolog.Logger
|
loggers map[string]*zerolog.Logger
|
||||||
|
|
||||||
|
defaults map[string]Level
|
||||||
|
levels map[string]Level
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *factory) Names() []string {
|
func (f *factory) Names() []string {
|
||||||
|
@ -110,27 +115,39 @@ func (f *factory) ApplyLogLevels(in map[string]Level) {
|
||||||
f.lock.Lock()
|
f.lock.Lock()
|
||||||
defer f.lock.Unlock()
|
defer f.lock.Unlock()
|
||||||
|
|
||||||
if def, ok := in[AllLevels]; ok {
|
z := make(map[string]Level, len(in))
|
||||||
// Apply with default log level
|
|
||||||
|
for k, v := range in {
|
||||||
|
z[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
f.levels = z
|
||||||
|
|
||||||
for k := range f.loggers {
|
for k := range f.loggers {
|
||||||
if ov, ok := in[k]; ok {
|
f.applyForLogger(k)
|
||||||
// Override in place
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *factory) applyForLogger(name string) {
|
||||||
|
if def, ok := f.levels[AllLevels]; ok {
|
||||||
|
if ov, ok := f.levels[name]; ok {
|
||||||
|
// override on logger level
|
||||||
l := f.root.Level(zerolog.Level(ov))
|
l := f.root.Level(zerolog.Level(ov))
|
||||||
f.loggers[k] = &l
|
f.loggers[name] = &l
|
||||||
} else {
|
} else {
|
||||||
// Override in place
|
// override on global level
|
||||||
l := f.root.Level(zerolog.Level(def))
|
l := f.root.Level(zerolog.Level(def))
|
||||||
f.loggers[k] = &l
|
f.loggers[name] = &l
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for k := range f.loggers {
|
if ov, ok := f.levels[name]; ok {
|
||||||
if ov, ok := in[k]; ok {
|
// override on logger level
|
||||||
// Override in place
|
|
||||||
l := f.root.Level(zerolog.Level(ov))
|
l := f.root.Level(zerolog.Level(ov))
|
||||||
f.loggers[k] = &l
|
f.loggers[name] = &l
|
||||||
}
|
} else {
|
||||||
|
// override on global level
|
||||||
|
l := f.root.Level(zerolog.Level(f.defaults[name]))
|
||||||
|
f.loggers[name] = &l
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -143,8 +160,8 @@ func (f *factory) RegisterLogger(name string, level Level) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
l := f.root.Level(zerolog.Level(level))
|
f.defaults[name] = level
|
||||||
f.loggers[name] = &l
|
f.applyForLogger(name)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
30
pkg/util/timer/after_test.go
Normal file
30
pkg/util/timer/after_test.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
//
|
||||||
|
// DISCLAIMER
|
||||||
|
//
|
||||||
|
// Copyright 2016-2022 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 timer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_After(t *testing.T) {
|
||||||
|
<-After(250 * time.Millisecond)
|
||||||
|
}
|
Loading…
Reference in a new issue