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
|
||||
- (QA) Member maintenance feature
|
||||
- (Feature) Extract Pod Details
|
||||
- (Feature) Add Timezone management
|
||||
|
||||
## [1.2.15](https://github.com/arangodb/kube-arangodb/tree/1.2.15) (2022-07-20)
|
||||
- (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
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
|
||||
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
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
k8s.io/api v0.21.10
|
||||
|
@ -56,11 +57,6 @@ require (
|
|||
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 (
|
||||
github.com/arangodb/go-velocypack v0.0.0-20200318135517-5af53c29c67e // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
|
|
|
@ -171,6 +171,8 @@ type DeploymentSpec struct {
|
|||
|
||||
// Architecture definition of supported architectures
|
||||
Architecture ArangoDeploymentArchitecture `json:"architecture,omitempty"`
|
||||
|
||||
Timezone *string `json:"timezone,omitempty"`
|
||||
}
|
||||
|
||||
// GetAllowMemberRecreation returns member recreation policy based on group and settings
|
||||
|
|
|
@ -91,6 +91,8 @@ type DeploymentStatus struct {
|
|||
|
||||
Version *Version `json:"version,omitempty"`
|
||||
|
||||
Timezone *string `json:"timezone,omitempty"`
|
||||
|
||||
Single *ServerGroupStatus `json:"single,omitempty"`
|
||||
Agents *ServerGroupStatus `json:"agents,omitempty"`
|
||||
DBServers *ServerGroupStatus `json:"dbservers,omitempty"`
|
||||
|
@ -126,7 +128,8 @@ func (ds *DeploymentStatus) Equal(other DeploymentStatus) bool {
|
|||
ds.DBServers.Equal(other.DBServers) &&
|
||||
ds.Coordinators.Equal(other.Coordinators) &&
|
||||
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
|
||||
|
|
|
@ -200,6 +200,7 @@ const (
|
|||
|
||||
// Resources
|
||||
ActionTypeResourceSync ActionType = "ResourceSync"
|
||||
ActionTypeTimezoneSecretSet ActionType = "TimezoneSecretSet"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -41,6 +41,7 @@ var (
|
|||
shared.LifecycleVolumeName,
|
||||
shared.FoxxAppEphemeralVolumeName,
|
||||
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))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Timezone != nil {
|
||||
in, out := &in.Timezone, &out.Timezone
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -1183,6 +1188,11 @@ func (in *DeploymentStatus) DeepCopyInto(out *DeploymentStatus) {
|
|||
*out = new(Version)
|
||||
**out = **in
|
||||
}
|
||||
if in.Timezone != nil {
|
||||
in, out := &in.Timezone, &out.Timezone
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.Single != nil {
|
||||
in, out := &in.Single, &out.Single
|
||||
*out = new(ServerGroupStatus)
|
||||
|
|
|
@ -171,6 +171,8 @@ type DeploymentSpec struct {
|
|||
|
||||
// Architecture definition of supported architectures
|
||||
Architecture ArangoDeploymentArchitecture `json:"architecture,omitempty"`
|
||||
|
||||
Timezone *string `json:"timezone,omitempty"`
|
||||
}
|
||||
|
||||
// GetAllowMemberRecreation returns member recreation policy based on group and settings
|
||||
|
|
|
@ -91,6 +91,8 @@ type DeploymentStatus struct {
|
|||
|
||||
Version *Version `json:"version,omitempty"`
|
||||
|
||||
Timezone *string `json:"timezone,omitempty"`
|
||||
|
||||
Single *ServerGroupStatus `json:"single,omitempty"`
|
||||
Agents *ServerGroupStatus `json:"agents,omitempty"`
|
||||
DBServers *ServerGroupStatus `json:"dbservers,omitempty"`
|
||||
|
@ -126,7 +128,8 @@ func (ds *DeploymentStatus) Equal(other DeploymentStatus) bool {
|
|||
ds.DBServers.Equal(other.DBServers) &&
|
||||
ds.Coordinators.Equal(other.Coordinators) &&
|
||||
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
|
||||
|
|
|
@ -200,6 +200,7 @@ const (
|
|||
|
||||
// Resources
|
||||
ActionTypeResourceSync ActionType = "ResourceSync"
|
||||
ActionTypeTimezoneSecretSet ActionType = "TimezoneSecretSet"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -41,6 +41,7 @@ var (
|
|||
shared.LifecycleVolumeName,
|
||||
shared.FoxxAppEphemeralVolumeName,
|
||||
shared.TMPEphemeralVolumeName,
|
||||
shared.ArangoDTimezoneVolumeName,
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
@ -1080,6 +1080,11 @@ func (in *DeploymentSpec) DeepCopyInto(out *DeploymentSpec) {
|
|||
*out = make(ArangoDeploymentArchitecture, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Timezone != nil {
|
||||
in, out := &in.Timezone, &out.Timezone
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -1183,6 +1188,11 @@ func (in *DeploymentStatus) DeepCopyInto(out *DeploymentStatus) {
|
|||
*out = new(Version)
|
||||
**out = **in
|
||||
}
|
||||
if in.Timezone != nil {
|
||||
in, out := &in.Timezone, &out.Timezone
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.Single != nil {
|
||||
in, out := &in.Single, &out.Single
|
||||
*out = new(ServerGroupStatus)
|
||||
|
|
|
@ -56,6 +56,7 @@ const (
|
|||
LifecycleVolumeName = "lifecycle"
|
||||
FoxxAppEphemeralVolumeName = "ephemeral-apps"
|
||||
TMPEphemeralVolumeName = "ephemeral-tmp"
|
||||
ArangoDTimezoneVolumeName = "arangod-timezone"
|
||||
RocksdbEncryptionVolumeName = "rocksdb-encryption"
|
||||
ExporterJWTVolumeName = "exporter-jwt"
|
||||
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 (
|
||||
BackOffCheck api.BackOffKey = "check"
|
||||
LicenseCheck api.BackOffKey = "license"
|
||||
TimezoneCheck api.BackOffKey = "timezone"
|
||||
)
|
||||
|
||||
// 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.createMemberFailedRestoreHighPlan).
|
||||
ApplyWithBackOff(BackOffCheck, time.Minute, r.emptyPlanBuilder)).
|
||||
ApplyIfEmptyWithBackOff(TimezoneCheck, time.Minute, r.createTimezoneUpdatePlan).
|
||||
Apply(r.createBackupInProgressConditionPlan). // Discover backups always
|
||||
Apply(r.createMaintenanceConditionPlan). // Discover maintenance always
|
||||
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
|
||||
volumes.Append(pod.SNI(), input)
|
||||
|
||||
volumes.Append(pod.Timezone(), input)
|
||||
|
||||
if len(groupSpec.Volumes) > 0 {
|
||||
volumes.AddVolume(groupSpec.Volumes.RenderVolumes(input.ApiObject, input.Group, status)...)
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ import (
|
|||
"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) {
|
||||
if spec.SchedulerName != status.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) {
|
||||
if specC, err := util.SHA256FromJSON(spec.Affinity); err != nil {
|
||||
e = err
|
||||
|
|
|
@ -23,6 +23,7 @@ package rotation
|
|||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
core "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/equality"
|
||||
|
||||
|
@ -36,7 +37,7 @@ const (
|
|||
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) {
|
||||
a, b := spec.Containers, status.Containers
|
||||
|
||||
|
@ -56,6 +57,16 @@ func containersCompare(_ api.DeploymentSpec, _ api.ServerGroup, spec, status *co
|
|||
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 areEnvsEqual(ac.Env, bc.Env, func(a, b map[string]core.EnvVar) (map[string]core.EnvVar, map[string]core.EnvVar) {
|
||||
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) {
|
||||
gs := deploymentSpec.GetServerGroupSpec(group)
|
||||
|
||||
|
|
|
@ -35,24 +35,28 @@ func Test_ArangoDContainers_SidecarImages(t *testing.T) {
|
|||
testCases := []TestCase{
|
||||
{
|
||||
name: "Sidecar Image Update",
|
||||
spec: buildPodSpec(addContainer(shared.ServerContainerName, nil), addSidecarWithImage("sidecar", "local:1.0")),
|
||||
status: buildPodSpec(addContainer(shared.ServerContainerName, nil), addSidecarWithImage("sidecar", "local:2.0")),
|
||||
spec: buildPodSpec(addContainer(shared.ServerContainerName), addSidecarWithImage("sidecar", "local:1.0")),
|
||||
status: buildPodSpec(addContainer(shared.ServerContainerName), addSidecarWithImage("sidecar", "local:2.0")),
|
||||
|
||||
TestCaseOverride: TestCaseOverride{
|
||||
expectedMode: InPlaceRotation,
|
||||
expectedPlan: api.Plan{
|
||||
actions.NewClusterAction(api.ActionTypeRuntimeContainerImageUpdate),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Sidecar Image Update with more than one sidecar",
|
||||
spec: buildPodSpec(addSidecarWithImage("sidecar1", "local:1.0"), addSidecarWithImage("sidecar", "local:1.0")),
|
||||
status: buildPodSpec(addSidecarWithImage("sidecar1", "local:1.0"), addSidecarWithImage("sidecar", "local:2.0")),
|
||||
|
||||
TestCaseOverride: TestCaseOverride{
|
||||
expectedMode: InPlaceRotation,
|
||||
expectedPlan: api.Plan{
|
||||
actions.NewClusterAction(api.ActionTypeRuntimeContainerImageUpdate),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
runTestCases(t)(testCases...)
|
||||
|
@ -63,34 +67,38 @@ func Test_InitContainers(t *testing.T) {
|
|||
testCases := []TestCase{
|
||||
{
|
||||
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"
|
||||
})),
|
||||
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"
|
||||
})),
|
||||
|
||||
TestCaseOverride: TestCaseOverride{
|
||||
expectedMode: SkippedRotation,
|
||||
},
|
||||
|
||||
deploymentSpec: buildDeployment(func(depl *api.DeploymentSpec) {
|
||||
depl.Agents.InitContainers = &api.ServerGroupInitContainers{
|
||||
groupSpec: buildGroupSpec(func(depl *api.ServerGroupSpec) {
|
||||
depl.InitContainers = &api.ServerGroupInitContainers{
|
||||
Mode: api.ServerGroupInitContainerIgnoreMode.New(),
|
||||
}
|
||||
}),
|
||||
},
|
||||
{
|
||||
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"
|
||||
})),
|
||||
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"
|
||||
})),
|
||||
|
||||
TestCaseOverride: TestCaseOverride{
|
||||
expectedMode: SilentRotation,
|
||||
},
|
||||
|
||||
deploymentSpec: buildDeployment(func(depl *api.DeploymentSpec) {
|
||||
depl.Agents.InitContainers = &api.ServerGroupInitContainers{
|
||||
groupSpec: buildGroupSpec(func(depl *api.ServerGroupSpec) {
|
||||
depl.InitContainers = &api.ServerGroupInitContainers{
|
||||
Mode: api.ServerGroupInitContainerIgnoreMode.New(),
|
||||
}
|
||||
}),
|
||||
|
@ -104,17 +112,19 @@ func Test_InitContainers(t *testing.T) {
|
|||
testCases := []TestCase{
|
||||
{
|
||||
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"
|
||||
})),
|
||||
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"
|
||||
})),
|
||||
|
||||
TestCaseOverride: TestCaseOverride{
|
||||
expectedMode: GracefulRotation,
|
||||
},
|
||||
|
||||
deploymentSpec: buildDeployment(func(depl *api.DeploymentSpec) {
|
||||
depl.Agents.InitContainers = &api.ServerGroupInitContainers{
|
||||
groupSpec: buildGroupSpec(func(depl *api.ServerGroupSpec) {
|
||||
depl.InitContainers = &api.ServerGroupInitContainers{
|
||||
Mode: api.ServerGroupInitContainerUpdateMode.New(),
|
||||
}
|
||||
}),
|
||||
|
@ -132,10 +142,12 @@ func Test_InitContainers(t *testing.T) {
|
|||
c.Image = "local:1.0"
|
||||
})),
|
||||
|
||||
TestCaseOverride: TestCaseOverride{
|
||||
expectedMode: SilentRotation,
|
||||
},
|
||||
|
||||
deploymentSpec: buildDeployment(func(depl *api.DeploymentSpec) {
|
||||
depl.Agents.InitContainers = &api.ServerGroupInitContainers{
|
||||
groupSpec: buildGroupSpec(func(depl *api.ServerGroupSpec) {
|
||||
depl.InitContainers = &api.ServerGroupInitContainers{
|
||||
Mode: api.ServerGroupInitContainerUpdateMode.New(),
|
||||
}
|
||||
}),
|
||||
|
@ -151,10 +163,12 @@ func Test_InitContainers(t *testing.T) {
|
|||
c.Image = "local:2.0"
|
||||
})),
|
||||
|
||||
TestCaseOverride: TestCaseOverride{
|
||||
expectedMode: SilentRotation,
|
||||
},
|
||||
|
||||
deploymentSpec: buildDeployment(func(depl *api.DeploymentSpec) {
|
||||
depl.Agents.InitContainers = &api.ServerGroupInitContainers{
|
||||
groupSpec: buildGroupSpec(func(depl *api.ServerGroupSpec) {
|
||||
depl.InitContainers = &api.ServerGroupInitContainers{
|
||||
Mode: api.ServerGroupInitContainerUpdateMode.New(),
|
||||
}
|
||||
}),
|
||||
|
@ -174,10 +188,12 @@ func Test_InitContainers(t *testing.T) {
|
|||
c.Image = "local:1.0"
|
||||
})),
|
||||
|
||||
TestCaseOverride: TestCaseOverride{
|
||||
expectedMode: SilentRotation,
|
||||
},
|
||||
|
||||
deploymentSpec: buildDeployment(func(depl *api.DeploymentSpec) {
|
||||
depl.Agents.InitContainers = &api.ServerGroupInitContainers{
|
||||
groupSpec: buildGroupSpec(func(depl *api.ServerGroupSpec) {
|
||||
depl.InitContainers = &api.ServerGroupInitContainers{
|
||||
Mode: api.ServerGroupInitContainerUpdateMode.New(),
|
||||
}
|
||||
}),
|
||||
|
@ -197,10 +213,12 @@ func Test_InitContainers(t *testing.T) {
|
|||
c.Image = "local:2.0"
|
||||
})),
|
||||
|
||||
TestCaseOverride: TestCaseOverride{
|
||||
expectedMode: GracefulRotation,
|
||||
},
|
||||
|
||||
deploymentSpec: buildDeployment(func(depl *api.DeploymentSpec) {
|
||||
depl.Agents.InitContainers = &api.ServerGroupInitContainers{
|
||||
groupSpec: buildGroupSpec(func(depl *api.ServerGroupSpec) {
|
||||
depl.InitContainers = &api.ServerGroupInitContainers{
|
||||
Mode: api.ServerGroupInitContainerUpdateMode.New(),
|
||||
}
|
||||
}),
|
||||
|
@ -264,8 +282,10 @@ func Test_Container_Args(t *testing.T) {
|
|||
spec: buildPodSpec(addContainerWithCommand("sidecar",
|
||||
[]string{"--log.level=INFO", "--log.level=requests=error"})),
|
||||
status: buildPodSpec(addContainerWithCommand("sidecar", []string{"--log.level=INFO"})),
|
||||
TestCaseOverride: TestCaseOverride{
|
||||
expectedMode: GracefulRotation,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
runTestCases(t)(testCases...)
|
||||
|
@ -293,8 +313,10 @@ func Test_Container_Ports(t *testing.T) {
|
|||
},
|
||||
}
|
||||
})),
|
||||
TestCaseOverride: TestCaseOverride{
|
||||
expectedMode: SilentRotation,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Ports of sidecar pod changed",
|
||||
spec: buildPodSpec(addContainer("sidecar", func(c *core.Container) {
|
||||
|
@ -315,8 +337,10 @@ func Test_Container_Ports(t *testing.T) {
|
|||
},
|
||||
}
|
||||
})),
|
||||
TestCaseOverride: TestCaseOverride{
|
||||
expectedMode: GracefulRotation,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
runTestCases(t)(testCases...)
|
||||
|
|
|
@ -40,8 +40,10 @@ func Test_ArangoD_SchedulerName(t *testing.T) {
|
|||
pod.Spec.SchedulerName = "new"
|
||||
}),
|
||||
|
||||
TestCaseOverride: TestCaseOverride{
|
||||
expectedMode: SilentRotation,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Change SchedulerName into Empty",
|
||||
spec: buildPodSpec(func(pod *core.PodTemplateSpec) {
|
||||
|
@ -51,8 +53,10 @@ func Test_ArangoD_SchedulerName(t *testing.T) {
|
|||
pod.Spec.SchedulerName = ""
|
||||
}),
|
||||
|
||||
TestCaseOverride: TestCaseOverride{
|
||||
expectedMode: SilentRotation,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SchedulerName equals",
|
||||
spec: buildPodSpec(func(pod *core.PodTemplateSpec) {
|
||||
|
@ -62,8 +66,10 @@ func Test_ArangoD_SchedulerName(t *testing.T) {
|
|||
pod.Spec.SchedulerName = ""
|
||||
}),
|
||||
|
||||
TestCaseOverride: TestCaseOverride{
|
||||
expectedMode: SkippedRotation,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
runTestCases(t)(testCases...)
|
||||
|
@ -80,8 +86,10 @@ func Test_ArangoD_TerminationGracePeriodSeconds(t *testing.T) {
|
|||
pod.Spec.TerminationGracePeriodSeconds = util.NewInt64(30)
|
||||
}),
|
||||
|
||||
TestCaseOverride: TestCaseOverride{
|
||||
expectedMode: SilentRotation,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Remove",
|
||||
spec: buildPodSpec(func(pod *core.PodTemplateSpec) {
|
||||
|
@ -91,8 +99,10 @@ func Test_ArangoD_TerminationGracePeriodSeconds(t *testing.T) {
|
|||
pod.Spec.TerminationGracePeriodSeconds = nil
|
||||
}),
|
||||
|
||||
TestCaseOverride: TestCaseOverride{
|
||||
expectedMode: SilentRotation,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Update",
|
||||
spec: buildPodSpec(func(pod *core.PodTemplateSpec) {
|
||||
|
@ -102,8 +112,10 @@ func Test_ArangoD_TerminationGracePeriodSeconds(t *testing.T) {
|
|||
pod.Spec.TerminationGracePeriodSeconds = util.NewInt64(30)
|
||||
}),
|
||||
|
||||
TestCaseOverride: TestCaseOverride{
|
||||
expectedMode: SilentRotation,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
runTestCases(t)(testCases...)
|
||||
|
@ -137,8 +149,10 @@ func Test_ArangoD_Affinity(t *testing.T) {
|
|||
status: buildPodSpec(func(pod *core.PodTemplateSpec) {
|
||||
}),
|
||||
|
||||
TestCaseOverride: TestCaseOverride{
|
||||
expectedMode: GracefulRotation,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Add affinity",
|
||||
spec: buildPodSpec(func(pod *core.PodTemplateSpec) {
|
||||
|
@ -165,8 +179,10 @@ func Test_ArangoD_Affinity(t *testing.T) {
|
|||
}
|
||||
}),
|
||||
|
||||
TestCaseOverride: TestCaseOverride{
|
||||
expectedMode: GracefulRotation,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Change affinity",
|
||||
spec: buildPodSpec(func(pod *core.PodTemplateSpec) {
|
||||
|
@ -212,8 +228,10 @@ func Test_ArangoD_Affinity(t *testing.T) {
|
|||
}
|
||||
}),
|
||||
|
||||
TestCaseOverride: TestCaseOverride{
|
||||
expectedMode: SilentRotation,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Change affinity archs",
|
||||
spec: buildPodSpec(func(pod *core.PodTemplateSpec) {
|
||||
|
@ -259,8 +277,10 @@ func Test_ArangoD_Affinity(t *testing.T) {
|
|||
}
|
||||
}),
|
||||
|
||||
TestCaseOverride: TestCaseOverride{
|
||||
expectedMode: GracefulRotation,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Change affinity archs - swap arch order",
|
||||
spec: buildPodSpec(func(pod *core.PodTemplateSpec) {
|
||||
|
@ -306,8 +326,10 @@ func Test_ArangoD_Affinity(t *testing.T) {
|
|||
}
|
||||
}),
|
||||
|
||||
TestCaseOverride: TestCaseOverride{
|
||||
expectedMode: SilentRotation,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
runTestCases(t)(testCases...)
|
||||
|
@ -328,8 +350,10 @@ func Test_ArangoD_Labels(t *testing.T) {
|
|||
}
|
||||
}),
|
||||
|
||||
TestCaseOverride: TestCaseOverride{
|
||||
expectedMode: SkippedRotation,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Remove label",
|
||||
|
||||
|
@ -343,8 +367,10 @@ func Test_ArangoD_Labels(t *testing.T) {
|
|||
pod.Labels = map[string]string{}
|
||||
}),
|
||||
|
||||
TestCaseOverride: TestCaseOverride{
|
||||
expectedMode: SkippedRotation,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Change label",
|
||||
|
||||
|
@ -360,8 +386,10 @@ func Test_ArangoD_Labels(t *testing.T) {
|
|||
}
|
||||
}),
|
||||
|
||||
TestCaseOverride: TestCaseOverride{
|
||||
expectedMode: SkippedRotation,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
runTestCases(t)(testCases...)
|
||||
|
@ -385,8 +413,10 @@ func Test_ArangoD_Envs_Zone(t *testing.T) {
|
|||
}
|
||||
})),
|
||||
|
||||
TestCaseOverride: TestCaseOverride{
|
||||
expectedMode: SilentRotation,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Remove Zone env",
|
||||
|
||||
|
@ -403,8 +433,10 @@ func Test_ArangoD_Envs_Zone(t *testing.T) {
|
|||
c.Env = []core.EnvVar{}
|
||||
})),
|
||||
|
||||
TestCaseOverride: TestCaseOverride{
|
||||
expectedMode: SilentRotation,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Update Zone env",
|
||||
|
||||
|
@ -426,8 +458,10 @@ func Test_ArangoD_Envs_Zone(t *testing.T) {
|
|||
}
|
||||
})),
|
||||
|
||||
TestCaseOverride: TestCaseOverride{
|
||||
expectedMode: SilentRotation,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Update other env",
|
||||
|
||||
|
@ -453,8 +487,10 @@ func Test_ArangoD_Envs_Zone(t *testing.T) {
|
|||
}
|
||||
})),
|
||||
|
||||
TestCaseOverride: TestCaseOverride{
|
||||
expectedMode: GracefulRotation,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
type compareFuncGen func(deploymentSpec api.DeploymentSpec, group api.ServerGroup, spec, status *core.PodSpec) compareFunc
|
||||
type compareFunc func(builder api.ActionBuilder) (mode Mode, plan api.Plan, err error)
|
||||
type comparePodFuncGen func(deploymentSpec api.DeploymentSpec, group api.ServerGroup, spec, status *core.PodSpec) comparePodFunc
|
||||
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 {
|
||||
return func(c compareFuncGen) compareFunc {
|
||||
func podFuncGenerator(deploymentSpec api.DeploymentSpec, group api.ServerGroup, spec, status *core.PodSpec) func(c comparePodFuncGen) comparePodFunc {
|
||||
return func(c comparePodFuncGen) comparePodFunc {
|
||||
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 {
|
||||
if m, p, err := q(builder); err != nil {
|
||||
return 0, nil, err
|
||||
|
@ -69,9 +91,9 @@ func compare(deploymentSpec api.DeploymentSpec, member api.MemberStatus, group a
|
|||
// Try to fill fields
|
||||
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")
|
||||
return SkippedRotation, nil, err
|
||||
} else {
|
||||
|
|
|
@ -30,44 +30,98 @@ import (
|
|||
"github.com/arangodb/kube-arangodb/pkg/deployment/resources"
|
||||
)
|
||||
|
||||
type TestCase struct {
|
||||
name string
|
||||
spec, status *core.PodTemplateSpec
|
||||
|
||||
deploymentSpec api.DeploymentSpec
|
||||
type TestCaseOverride struct {
|
||||
expectedMode Mode
|
||||
expectedPlan api.Plan
|
||||
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) {
|
||||
|
||||
return func(tcs ...TestCase) {
|
||||
for _, tc := range tcs {
|
||||
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)
|
||||
pstatus := newTemplateFromSpec(t, tc.status, api.ServerGroupAgents, tc.deploymentSpec)
|
||||
func runTestCasesForMode(t *testing.T, m api.DeploymentMode, tc TestCase) {
|
||||
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 != "" {
|
||||
require.Error(t, err)
|
||||
require.EqualError(t, err, tc.expectedErr)
|
||||
require.EqualError(t, err, q.expectedErr)
|
||||
} else {
|
||||
require.Equal(t, tc.expectedMode, mode)
|
||||
require.Equal(t, q.expectedMode, mode)
|
||||
|
||||
switch mode {
|
||||
case InPlaceRotation:
|
||||
require.Len(t, plan, len(tc.expectedPlan))
|
||||
require.Len(t, plan, len(q.expectedPlan))
|
||||
|
||||
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 {
|
||||
|
@ -82,6 +136,8 @@ func newTemplateFromSpec(t *testing.T, podSpec *core.PodTemplateSpec, group api.
|
|||
|
||||
type podSpecBuilder func(pod *core.PodTemplateSpec)
|
||||
|
||||
type podContainerBuilder func(c *core.Container)
|
||||
|
||||
func buildPodSpec(b ...podSpecBuilder) *core.PodTemplateSpec {
|
||||
p := &core.PodTemplateSpec{}
|
||||
|
||||
|
@ -92,28 +148,28 @@ func buildPodSpec(b ...podSpecBuilder) *core.PodTemplateSpec {
|
|||
return p
|
||||
}
|
||||
|
||||
func addContainer(name string, f func(c *core.Container)) podSpecBuilder {
|
||||
func addContainer(name string, f ...podContainerBuilder) podSpecBuilder {
|
||||
return func(pod *core.PodTemplateSpec) {
|
||||
var c core.Container
|
||||
|
||||
c.Name = name
|
||||
|
||||
if f != nil {
|
||||
f(&c)
|
||||
for _, q := range f {
|
||||
q(&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) {
|
||||
var c core.Container
|
||||
|
||||
c.Name = name
|
||||
|
||||
if f != nil {
|
||||
f(&c)
|
||||
for _, q := range f {
|
||||
q(&c)
|
||||
}
|
||||
|
||||
pod.Spec.InitContainers = append(pod.Spec.InitContainers, c)
|
||||
|
@ -143,3 +199,15 @@ func buildDeployment(b ...deploymentBuilder) api.DeploymentSpec {
|
|||
|
||||
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) {
|
||||
if d, ok := timezonesData[t.Name]; ok {
|
||||
if d, ok := timezonesData[t.Parent]; ok {
|
||||
if d, err := base64.StdEncoding.DecodeString(d); err == nil {
|
||||
return d, true
|
||||
}
|
||||
|
|
|
@ -24,6 +24,10 @@ import "github.com/rs/zerolog"
|
|||
|
||||
type Level zerolog.Level
|
||||
|
||||
func (l Level) New() *Level {
|
||||
return &l
|
||||
}
|
||||
|
||||
const (
|
||||
Trace = Level(zerolog.TraceLevel)
|
||||
Debug = Level(zerolog.DebugLevel)
|
||||
|
|
|
@ -55,6 +55,8 @@ func NewFactory(root zerolog.Logger) Factory {
|
|||
return &factory{
|
||||
root: root,
|
||||
loggers: map[string]*zerolog.Logger{},
|
||||
defaults: map[string]Level{},
|
||||
levels: map[string]Level{},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,6 +68,9 @@ type factory struct {
|
|||
wrappers []Wrap
|
||||
|
||||
loggers map[string]*zerolog.Logger
|
||||
|
||||
defaults map[string]Level
|
||||
levels map[string]Level
|
||||
}
|
||||
|
||||
func (f *factory) Names() []string {
|
||||
|
@ -110,27 +115,39 @@ func (f *factory) ApplyLogLevels(in map[string]Level) {
|
|||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
if def, ok := in[AllLevels]; ok {
|
||||
// Apply with default log level
|
||||
z := make(map[string]Level, len(in))
|
||||
|
||||
for k, v := range in {
|
||||
z[k] = v
|
||||
}
|
||||
|
||||
f.levels = z
|
||||
|
||||
for k := range f.loggers {
|
||||
if ov, ok := in[k]; ok {
|
||||
// Override in place
|
||||
f.applyForLogger(k)
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
f.loggers[k] = &l
|
||||
f.loggers[name] = &l
|
||||
} else {
|
||||
// Override in place
|
||||
// override on global level
|
||||
l := f.root.Level(zerolog.Level(def))
|
||||
f.loggers[k] = &l
|
||||
}
|
||||
f.loggers[name] = &l
|
||||
}
|
||||
} else {
|
||||
for k := range f.loggers {
|
||||
if ov, ok := in[k]; ok {
|
||||
// Override in place
|
||||
if ov, ok := f.levels[name]; ok {
|
||||
// override on logger level
|
||||
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
|
||||
}
|
||||
|
||||
l := f.root.Level(zerolog.Level(level))
|
||||
f.loggers[name] = &l
|
||||
f.defaults[name] = level
|
||||
f.applyForLogger(name)
|
||||
|
||||
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