1
0
Fork 0
mirror of https://github.com/arangodb/kube-arangodb.git synced 2024-12-14 11:57:37 +00:00

[Feature] Compare Generic (#1480)

This commit is contained in:
Adam Janikowski 2023-11-13 20:18:58 +01:00 committed by GitHub
parent 5ebc821941
commit 934039fb41
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 681 additions and 352 deletions

View file

@ -9,6 +9,7 @@
- (Documentation) Do not use field type name for field URL hash - (Documentation) Do not use field type name for field URL hash
- (Maintenance) Bump Go to 1.20.11 - (Maintenance) Bump Go to 1.20.11
- (Feature) License ArangoDeployment Fetcher - (Feature) License ArangoDeployment Fetcher
- (Feature) K8S Resources Compare Generic
## [1.2.35](https://github.com/arangodb/kube-arangodb/tree/1.2.35) (2023-11-06) ## [1.2.35](https://github.com/arangodb/kube-arangodb/tree/1.2.35) (2023-11-06)
- (Maintenance) Update go-driver to v1.6.0, update IsNotFound() checks - (Maintenance) Update go-driver to v1.6.0, update IsNotFound() checks

View file

@ -63,13 +63,6 @@ type ArangoMemberPodTemplate struct {
Endpoint *string `json:"endpoint,omitempty"` Endpoint *string `json:"endpoint,omitempty"`
} }
func (a *ArangoMemberPodTemplate) GetChecksum() string {
if a == nil {
return ""
}
return a.Checksum
}
func (a *ArangoMemberPodTemplate) Equals(b *ArangoMemberPodTemplate) bool { func (a *ArangoMemberPodTemplate) Equals(b *ArangoMemberPodTemplate) bool {
if a == nil && b == nil { if a == nil && b == nil {
return true return true
@ -100,3 +93,45 @@ func (a *ArangoMemberPodTemplate) EqualPodSpecChecksum(checksum string) bool {
} }
return checksum == a.PodSpecChecksum return checksum == a.PodSpecChecksum
} }
func (a *ArangoMemberPodTemplate) GetTemplate() *core.PodTemplateSpec {
if a == nil {
return nil
}
return a.PodSpec.DeepCopy()
}
func (a *ArangoMemberPodTemplate) SetTemplate(t *core.PodTemplateSpec) {
if a == nil {
return
}
a.PodSpec = t.DeepCopy()
}
func (a *ArangoMemberPodTemplate) GetTemplateChecksum() string {
if a == nil {
return ""
}
return a.PodSpecChecksum
}
func (a *ArangoMemberPodTemplate) SetTemplateChecksum(s string) {
if a == nil {
return
}
a.PodSpecChecksum = s
}
func (a *ArangoMemberPodTemplate) SetChecksum(s string) {
if a == nil {
return
}
a.Checksum = s
}
func (a *ArangoMemberPodTemplate) GetChecksum() string {
if a == nil {
return ""
}
return a.Checksum
}

View file

@ -30,6 +30,7 @@ import (
"github.com/arangodb/kube-arangodb/pkg/deployment/actions" "github.com/arangodb/kube-arangodb/pkg/deployment/actions"
"github.com/arangodb/kube-arangodb/pkg/deployment/reconcile/shared" "github.com/arangodb/kube-arangodb/pkg/deployment/reconcile/shared"
"github.com/arangodb/kube-arangodb/pkg/deployment/rotation" "github.com/arangodb/kube-arangodb/pkg/deployment/rotation"
"github.com/arangodb/kube-arangodb/pkg/util/compare"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil" "github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
) )
@ -231,7 +232,7 @@ func (r *Reconciler) updateMemberRotationConditions(apiObject k8sutil.APIObject,
return nil, err return nil, err
} else { } else {
switch m { switch m {
case rotation.EnforcedRotation: case compare.EnforcedRotation:
if reason != "" { if reason != "" {
r.log.Bool("enforced", true).Info(reason) r.log.Bool("enforced", true).Info(reason)
} else { } else {
@ -239,7 +240,7 @@ func (r *Reconciler) updateMemberRotationConditions(apiObject k8sutil.APIObject,
} }
// We need to do enforced rotation // We need to do enforced rotation
return restartMemberConditionAction(group, member.ID, reason), nil return restartMemberConditionAction(group, member.ID, reason), nil
case rotation.InPlaceRotation: case compare.InPlaceRotation:
if member.Conditions.IsTrue(api.ConditionTypeUpdateFailed) { if member.Conditions.IsTrue(api.ConditionTypeUpdateFailed) {
if !(member.Conditions.IsTrue(api.ConditionTypePendingRestart) || member.Conditions.IsTrue(api.ConditionTypeRestart)) { if !(member.Conditions.IsTrue(api.ConditionTypePendingRestart) || member.Conditions.IsTrue(api.ConditionTypeRestart)) {
return api.Plan{pendingRestartMemberConditionAction(group, member.ID, reason)}, nil return api.Plan{pendingRestartMemberConditionAction(group, member.ID, reason)}, nil
@ -249,11 +250,11 @@ func (r *Reconciler) updateMemberRotationConditions(apiObject k8sutil.APIObject,
return nil, nil return nil, nil
} }
return api.Plan{shared.UpdateMemberConditionActionV2(reason, api.ConditionTypePendingUpdate, group, member.ID, true, reason, "", "")}, nil return api.Plan{shared.UpdateMemberConditionActionV2(reason, api.ConditionTypePendingUpdate, group, member.ID, true, reason, "", "")}, nil
case rotation.SilentRotation: case compare.SilentRotation:
// Propagate changes without restart, but apply plan if required // Propagate changes without restart, but apply plan if required
plan = append(plan, actions.NewAction(api.ActionTypeArangoMemberUpdatePodStatus, group, member, "Propagating status of pod").AddParam(ActionTypeArangoMemberUpdatePodStatusChecksum, checksum)) plan = append(plan, actions.NewAction(api.ActionTypeArangoMemberUpdatePodStatus, group, member, "Propagating status of pod").AddParam(ActionTypeArangoMemberUpdatePodStatusChecksum, checksum))
return plan, nil return plan, nil
case rotation.GracefulRotation: case compare.GracefulRotation:
if reason != "" { if reason != "" {
r.log.Bool("enforced", false).Info(reason) r.log.Bool("enforced", false).Info(reason)
} else { } else {

View file

@ -36,6 +36,7 @@ import (
"github.com/arangodb/kube-arangodb/pkg/deployment/resources" "github.com/arangodb/kube-arangodb/pkg/deployment/resources"
"github.com/arangodb/kube-arangodb/pkg/deployment/rotation" "github.com/arangodb/kube-arangodb/pkg/deployment/rotation"
"github.com/arangodb/kube-arangodb/pkg/util" "github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/compare"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil" "github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
) )
@ -213,7 +214,7 @@ func (r *Reconciler) createUpdatePlanInternal(apiObject k8sutil.APIObject, spec
if mode, p, checksum, reason, err := rotation.IsRotationRequired(context.ACS(), spec, m.Member, m.Group, p, arangoMember.Spec.Template, arangoMember.Status.Template); err != nil { if mode, p, checksum, reason, err := rotation.IsRotationRequired(context.ACS(), spec, m.Member, m.Group, p, arangoMember.Spec.Template, arangoMember.Status.Template); err != nil {
r.planLogger.Err(err).Str("member", m.Member.ID).Error("Error while generating update plan") r.planLogger.Err(err).Str("member", m.Member.ID).Error("Error while generating update plan")
continue continue
} else if mode != rotation.InPlaceRotation { } else if mode != compare.InPlaceRotation {
return api.Plan{ return api.Plan{
shared.RemoveMemberConditionActionV2(reason, api.ConditionTypePendingUpdate, m.Group, m.Member.ID), shared.RemoveMemberConditionActionV2(reason, api.ConditionTypePendingUpdate, m.Group, m.Member.ID),
shared.UpdateMemberConditionActionV2(reason, api.ConditionTypeUpdating, m.Group, m.Member.ID, true, reason, "", ""), shared.UpdateMemberConditionActionV2(reason, api.ConditionTypeUpdating, m.Group, m.Member.ID, true, reason, "", ""),

View file

@ -1,7 +1,7 @@
// //
// DISCLAIMER // DISCLAIMER
// //
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany // Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -25,36 +25,37 @@ import (
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
"github.com/arangodb/kube-arangodb/pkg/util" "github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/compare"
) )
func podCompare(_ api.DeploymentSpec, _ api.ServerGroup, spec, status *core.PodSpec) comparePodFunc { func podCompare(_ api.DeploymentSpec, _ api.ServerGroup, spec, status *core.PodTemplateSpec) compare.Func {
return func(builder api.ActionBuilder) (mode Mode, plan api.Plan, err error) { return func(builder api.ActionBuilder) (mode compare.Mode, plan api.Plan, err error) {
if spec.SchedulerName != status.SchedulerName { if spec.Spec.SchedulerName != status.Spec.SchedulerName {
status.SchedulerName = spec.SchedulerName status.Spec.SchedulerName = spec.Spec.SchedulerName
mode = mode.And(SilentRotation) mode = mode.And(compare.SilentRotation)
} }
if !util.CompareInt64p(spec.TerminationGracePeriodSeconds, status.TerminationGracePeriodSeconds) { if !util.CompareInt64p(spec.Spec.TerminationGracePeriodSeconds, status.Spec.TerminationGracePeriodSeconds) {
status.TerminationGracePeriodSeconds = spec.TerminationGracePeriodSeconds status.Spec.TerminationGracePeriodSeconds = spec.Spec.TerminationGracePeriodSeconds
mode = mode.And(SilentRotation) mode = mode.And(compare.SilentRotation)
} }
return return
} }
} }
func affinityCompare(_ api.DeploymentSpec, _ api.ServerGroup, spec, status *core.PodSpec) comparePodFunc { func affinityCompare(_ api.DeploymentSpec, _ api.ServerGroup, spec, status *core.PodTemplateSpec) compare.Func {
return func(builder api.ActionBuilder) (mode Mode, plan api.Plan, e error) { return func(builder api.ActionBuilder) (mode compare.Mode, plan api.Plan, e error) {
if specC, err := util.SHA256FromJSON(spec.Affinity); err != nil { if specC, err := util.SHA256FromJSON(spec.Spec.Affinity); err != nil {
e = err e = err
return return
} else { } else {
if statusC, err := util.SHA256FromJSON(status.Affinity); err != nil { if statusC, err := util.SHA256FromJSON(status.Spec.Affinity); err != nil {
e = err e = err
return return
} else if specC != statusC { } else if specC != statusC {
mode = mode.And(SilentRotation) mode = mode.And(compare.SilentRotation)
status.Affinity = spec.Affinity.DeepCopy() status.Spec.Affinity = spec.Spec.Affinity.DeepCopy()
return return
} else { } else {
return return

View file

@ -1,7 +1,7 @@
// //
// DISCLAIMER // DISCLAIMER
// //
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany // Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -30,8 +30,9 @@ import (
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" 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/features"
"github.com/arangodb/kube-arangodb/pkg/util" "github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/compare"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil" "github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
strings2 "github.com/arangodb/kube-arangodb/pkg/util/strings" arangoStrings "github.com/arangodb/kube-arangodb/pkg/util/strings"
) )
const ( const (
@ -39,37 +40,36 @@ const (
ContainerImage = "image" ContainerImage = "image"
) )
func containersCompare(ds api.DeploymentSpec, g api.ServerGroup, spec, status *core.PodSpec) comparePodFunc { func containersCompare(ds api.DeploymentSpec, g api.ServerGroup, spec, status *core.PodTemplateSpec) compare.Func {
return func(builder api.ActionBuilder) (mode Mode, plan api.Plan, err error) { return compare.SubElementsP2(func(in *core.PodTemplateSpec) *[]core.Container {
a, b := spec.Containers, status.Containers return &in.Spec.Containers
}, func(ds api.DeploymentSpec, g api.ServerGroup, specContainers, statusContainers *[]core.Container) compare.Func {
return compare.ArrayExtractorP2(func(ds api.DeploymentSpec, g api.ServerGroup, specContainer, statusContainer *core.Container) compare.Func {
return func(builder api.ActionBuilder) (mode compare.Mode, plan api.Plan, err error) {
if specContainer.Name != statusContainer.Name {
return compare.SkippedRotation, nil, nil
}
if len(a) == 0 || len(a) != len(b) { if specContainer.Name == api.ServerGroupReservedContainerNameServer {
// If the number of the containers is different or is zero then skip rotation. if isOnlyLogLevelChanged(specContainer.Command, statusContainer.Command) {
return SkippedRotation, nil, nil
}
for id := range a {
if ac, bc := &a[id], &b[id]; ac.Name == bc.Name {
if ac.Name == api.ServerGroupReservedContainerNameServer {
if isOnlyLogLevelChanged(ac.Command, bc.Command) {
plan = append(plan, builder.NewAction(api.ActionTypeRuntimeContainerArgsLogLevelUpdate). plan = append(plan, builder.NewAction(api.ActionTypeRuntimeContainerArgsLogLevelUpdate).
AddParam(ContainerName, ac.Name)) AddParam(ContainerName, specContainer.Name))
bc.Command = ac.Command statusContainer.Command = specContainer.Command
mode = mode.And(InPlaceRotation) mode = mode.And(compare.InPlaceRotation)
} }
g := podContainerFuncGenerator(ds, g, ac, bc) g := compare.NewFuncGenP2(ds, g, specContainer, statusContainer)
if m, p, err := comparePodContainer(builder, g(compareServerContainerVolumeMounts), g(compareServerContainerProbes), g(compareServerContainerEnvs)); err != nil { if m, p, err := compare.Evaluate(builder, g(compareServerContainerVolumeMounts), g(compareServerContainerProbes), g(compareServerContainerEnvs)); 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 compare.SkippedRotation, nil, err
} else { } else {
mode = mode.And(m) mode = mode.And(m)
plan = append(plan, p...) plan = append(plan, p...)
} }
if !equality.Semantic.DeepEqual(ac.EnvFrom, bc.EnvFrom) { if !equality.Semantic.DeepEqual(specContainer.EnvFrom, statusContainer.EnvFrom) {
// Check EnvFromSource differences. // Check EnvFromSource differences.
filter := func(a, b map[string]core.EnvFromSource) (map[string]core.EnvFromSource, map[string]core.EnvFromSource) { filter := func(a, b map[string]core.EnvFromSource) (map[string]core.EnvFromSource, map[string]core.EnvFromSource) {
delete(a, features.ConfigMapName()) delete(a, features.ConfigMapName())
@ -77,81 +77,81 @@ func containersCompare(ds api.DeploymentSpec, g api.ServerGroup, spec, status *c
return a, b return a, b
} }
if areEnvsFromEqual(ac.EnvFrom, bc.EnvFrom, filter) { if areEnvsFromEqual(specContainer.EnvFrom, statusContainer.EnvFrom, filter) {
// Envs are the same after filtering, but it were different before filtering, so it can be replaced. // Envs are the same after filtering, but it were different before filtering, so it can be replaced.
bc.EnvFrom = ac.EnvFrom statusContainer.EnvFrom = specContainer.EnvFrom
mode = mode.And(SilentRotation) mode = mode.And(compare.SilentRotation)
} }
} }
if !equality.Semantic.DeepEqual(ac.Ports, bc.Ports) { if !equality.Semantic.DeepEqual(specContainer.Ports, statusContainer.Ports) {
bc.Ports = ac.Ports statusContainer.Ports = specContainer.Ports
mode = mode.And(SilentRotation) mode = mode.And(compare.SilentRotation)
} }
} else { } else {
if ac.Image != bc.Image { if specContainer.Image != statusContainer.Image {
// Image changed // Image changed
plan = append(plan, builder.NewAction(api.ActionTypeRuntimeContainerImageUpdate).AddParam(ContainerName, ac.Name).AddParam(ContainerImage, ac.Image)) plan = append(plan, builder.NewAction(api.ActionTypeRuntimeContainerImageUpdate).AddParam(ContainerName, specContainer.Name).AddParam(ContainerImage, specContainer.Image))
bc.Image = ac.Image statusContainer.Image = specContainer.Image
mode = mode.And(InPlaceRotation) mode = mode.And(compare.InPlaceRotation)
} }
g := podContainerFuncGenerator(ds, g, ac, bc) g := compare.NewFuncGenP2(ds, g, specContainer, statusContainer)
if m, p, err := comparePodContainer(builder, g(compareAnyContainerVolumeMounts), g(compareAnyContainerEnvs)); err != nil { if m, p, err := compare.Evaluate(builder, g(compareAnyContainerVolumeMounts), g(compareAnyContainerEnvs)); 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 compare.SkippedRotation, nil, err
} else { } else {
mode = mode.And(m) mode = mode.And(m)
plan = append(plan, p...) plan = append(plan, p...)
} }
} }
if api.IsReservedServerGroupContainerName(ac.Name) { if api.IsReservedServerGroupContainerName(specContainer.Name) {
mode = mode.And(internalContainerLifecycleCompare(ac, bc)) mode = mode.And(internalContainerLifecycleCompare(specContainer, statusContainer))
} }
}
}
return return
} }
})(ds, g, specContainers, statusContainers)
})(ds, g, spec, status)
} }
func initContainersCompare(deploymentSpec api.DeploymentSpec, group api.ServerGroup, spec, status *core.PodSpec) comparePodFunc { func initContainersCompare(deploymentSpec api.DeploymentSpec, group api.ServerGroup, spec, status *core.PodTemplateSpec) compare.Func {
return func(builder api.ActionBuilder) (Mode, api.Plan, error) { return func(builder api.ActionBuilder) (compare.Mode, api.Plan, error) {
gs := deploymentSpec.GetServerGroupSpec(group) gs := deploymentSpec.GetServerGroupSpec(group)
equal, err := util.CompareJSON(spec.InitContainers, status.InitContainers) equal, err := util.CompareJSON(spec.Spec.InitContainers, status.Spec.InitContainers)
if err != nil { if err != nil {
return SkippedRotation, nil, err return compare.SkippedRotation, nil, err
} }
// if equal nothing to do // if equal nothing to do
if equal { if equal {
return SkippedRotation, nil, nil return compare.SkippedRotation, nil, nil
} }
switch gs.InitContainers.GetMode().Get() { switch gs.InitContainers.GetMode().Get() {
case api.ServerGroupInitContainerIgnoreMode: case api.ServerGroupInitContainerIgnoreMode:
// Just copy spec to status if different // Just copy spec to status if different
if !equal { if !equal {
status.InitContainers = spec.InitContainers status.Spec.InitContainers = spec.Spec.InitContainers
return SilentRotation, nil, err return compare.SilentRotation, nil, err
} else { } else {
return SkippedRotation, nil, err return compare.SkippedRotation, nil, err
} }
default: default:
statusInitContainers, specInitContainers := filterReservedInitContainers(status.InitContainers), filterReservedInitContainers(spec.InitContainers) statusInitContainers, specInitContainers := filterReservedInitContainers(status.Spec.InitContainers), filterReservedInitContainers(spec.Spec.InitContainers)
if equal, err := util.CompareJSON(specInitContainers, statusInitContainers); err != nil { if equal, err := util.CompareJSON(specInitContainers, statusInitContainers); err != nil {
return SkippedRotation, nil, err return compare.SkippedRotation, nil, err
} else if equal { } else if equal {
status.InitContainers = spec.InitContainers status.Spec.InitContainers = spec.Spec.InitContainers
return SilentRotation, nil, nil return compare.SilentRotation, nil, nil
} }
} }
return SkippedRotation, nil, nil return compare.SkippedRotation, nil, nil
} }
} }
@ -173,7 +173,7 @@ func filterReservedInitContainers(c []core.Container) []core.Container {
// isOnlyLogLevelChanged returns true when status and spec log level arguments are different. // isOnlyLogLevelChanged returns true when status and spec log level arguments are different.
// If any other argument than --log.level is different false is returned. // If any other argument than --log.level is different false is returned.
func isOnlyLogLevelChanged(specArgs, statusArgs []string) bool { func isOnlyLogLevelChanged(specArgs, statusArgs []string) bool {
diff := strings2.DiffStrings(specArgs, statusArgs) diff := arangoStrings.DiffStrings(specArgs, statusArgs)
if len(diff) == 0 { if len(diff) == 0 {
return false return false
} }
@ -187,27 +187,27 @@ func isOnlyLogLevelChanged(specArgs, statusArgs []string) bool {
return true return true
} }
func internalContainerLifecycleCompare(spec, status *core.Container) Mode { func internalContainerLifecycleCompare(spec, status *core.Container) compare.Mode {
if spec.Lifecycle == nil && status.Lifecycle == nil { if spec.Lifecycle == nil && status.Lifecycle == nil {
return SkippedRotation return compare.SkippedRotation
} }
if spec.Lifecycle == nil { if spec.Lifecycle == nil {
status.Lifecycle = nil status.Lifecycle = nil
return SilentRotation return compare.SilentRotation
} }
if status.Lifecycle == nil { if status.Lifecycle == nil {
status.Lifecycle = spec.Lifecycle status.Lifecycle = spec.Lifecycle
return SilentRotation return compare.SilentRotation
} }
if !equality.Semantic.DeepEqual(spec.Lifecycle, status.Lifecycle) { if !equality.Semantic.DeepEqual(spec.Lifecycle, status.Lifecycle) {
status.Lifecycle = spec.Lifecycle.DeepCopy() status.Lifecycle = spec.Lifecycle.DeepCopy()
return SilentRotation return compare.SilentRotation
} }
return SkippedRotation return compare.SkippedRotation
} }
func areProbesEqual(a, b *core.Probe) bool { func areProbesEqual(a, b *core.Probe) bool {

View file

@ -1,7 +1,7 @@
// //
// DISCLAIMER // DISCLAIMER
// //
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany // Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -29,6 +29,7 @@ import (
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
"github.com/arangodb/kube-arangodb/pkg/apis/shared" "github.com/arangodb/kube-arangodb/pkg/apis/shared"
"github.com/arangodb/kube-arangodb/pkg/deployment/actions" "github.com/arangodb/kube-arangodb/pkg/deployment/actions"
"github.com/arangodb/kube-arangodb/pkg/util/compare"
) )
func Test_ArangoDContainers_SidecarImages(t *testing.T) { func Test_ArangoDContainers_SidecarImages(t *testing.T) {
@ -39,7 +40,7 @@ func Test_ArangoDContainers_SidecarImages(t *testing.T) {
status: buildPodSpec(addContainer(shared.ServerContainerName), addSidecarWithImage("sidecar", "local:2.0")), status: buildPodSpec(addContainer(shared.ServerContainerName), addSidecarWithImage("sidecar", "local:2.0")),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: InPlaceRotation, expectedMode: compare.InPlaceRotation,
expectedPlan: api.Plan{ expectedPlan: api.Plan{
actions.NewClusterAction(api.ActionTypeRuntimeContainerImageUpdate), actions.NewClusterAction(api.ActionTypeRuntimeContainerImageUpdate),
}, },
@ -51,7 +52,7 @@ func Test_ArangoDContainers_SidecarImages(t *testing.T) {
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{ TestCaseOverride: TestCaseOverride{
expectedMode: InPlaceRotation, expectedMode: compare.InPlaceRotation,
expectedPlan: api.Plan{ expectedPlan: api.Plan{
actions.NewClusterAction(api.ActionTypeRuntimeContainerImageUpdate), actions.NewClusterAction(api.ActionTypeRuntimeContainerImageUpdate),
}, },
@ -75,7 +76,7 @@ func Test_InitContainers(t *testing.T) {
})), })),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: SkippedRotation, expectedMode: compare.SkippedRotation,
}, },
groupSpec: buildGroupSpec(func(depl *api.ServerGroupSpec) { groupSpec: buildGroupSpec(func(depl *api.ServerGroupSpec) {
@ -94,7 +95,7 @@ func Test_InitContainers(t *testing.T) {
})), })),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: SilentRotation, expectedMode: compare.SilentRotation,
}, },
groupSpec: buildGroupSpec(func(depl *api.ServerGroupSpec) { groupSpec: buildGroupSpec(func(depl *api.ServerGroupSpec) {
@ -120,7 +121,7 @@ func Test_InitContainers(t *testing.T) {
})), })),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: GracefulRotation, expectedMode: compare.GracefulRotation,
}, },
groupSpec: buildGroupSpec(func(depl *api.ServerGroupSpec) { groupSpec: buildGroupSpec(func(depl *api.ServerGroupSpec) {
@ -143,7 +144,7 @@ func Test_InitContainers(t *testing.T) {
})), })),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: SilentRotation, expectedMode: compare.SilentRotation,
}, },
groupSpec: buildGroupSpec(func(depl *api.ServerGroupSpec) { groupSpec: buildGroupSpec(func(depl *api.ServerGroupSpec) {
@ -164,7 +165,7 @@ func Test_InitContainers(t *testing.T) {
})), })),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: SilentRotation, expectedMode: compare.SilentRotation,
}, },
groupSpec: buildGroupSpec(func(depl *api.ServerGroupSpec) { groupSpec: buildGroupSpec(func(depl *api.ServerGroupSpec) {
@ -189,7 +190,7 @@ func Test_InitContainers(t *testing.T) {
})), })),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: SilentRotation, expectedMode: compare.SilentRotation,
}, },
groupSpec: buildGroupSpec(func(depl *api.ServerGroupSpec) { groupSpec: buildGroupSpec(func(depl *api.ServerGroupSpec) {
@ -214,7 +215,7 @@ func Test_InitContainers(t *testing.T) {
})), })),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: GracefulRotation, expectedMode: compare.GracefulRotation,
}, },
groupSpec: buildGroupSpec(func(depl *api.ServerGroupSpec) { groupSpec: buildGroupSpec(func(depl *api.ServerGroupSpec) {
@ -229,11 +230,11 @@ func Test_InitContainers(t *testing.T) {
}) })
} }
func logLevelTestCaseGen(name string, mode Mode, spec, status []string) TestCase { func logLevelTestCaseGen(name string, mode compare.Mode, spec, status []string) TestCase {
var c TestCase var c TestCase
c.name = name c.name = name
c.expectedMode = mode c.expectedMode = mode
if c.expectedMode == InPlaceRotation { if c.expectedMode == compare.InPlaceRotation {
c.expectedPlan = api.Plan{ c.expectedPlan = api.Plan{
actions.NewClusterAction(api.ActionTypeRuntimeContainerArgsLogLevelUpdate), actions.NewClusterAction(api.ActionTypeRuntimeContainerArgsLogLevelUpdate),
} }
@ -247,27 +248,27 @@ func logLevelTestCaseGen(name string, mode Mode, spec, status []string) TestCase
func Test_Container_LogArgs(t *testing.T) { func Test_Container_LogArgs(t *testing.T) {
testCases := []TestCase{ testCases := []TestCase{
logLevelTestCaseGen("Only log level arguments of the ArangoDB server have been changed", logLevelTestCaseGen("Only log level arguments of the ArangoDB server have been changed",
InPlaceRotation, compare.InPlaceRotation,
[]string{"--log.level=INFO", "--log.level=requests=error"}, []string{"--log.level=INFO", "--log.level=requests=error"},
[]string{"--log.level=INFO"}), []string{"--log.level=INFO"}),
logLevelTestCaseGen("ArangoDB server arguments have not been changed", logLevelTestCaseGen("ArangoDB server arguments have not been changed",
SkippedRotation, compare.SkippedRotation,
[]string{"--log.level=INFO"}, []string{"--log.level=INFO"},
[]string{"--log.level=INFO"}), []string{"--log.level=INFO"}),
logLevelTestCaseGen("Multi ArangoDB server arguments have not been changed", logLevelTestCaseGen("Multi ArangoDB server arguments have not been changed",
SkippedRotation, compare.SkippedRotation,
[]string{"--log.level=INFO", "--log.level=requests=debug"}, []string{"--log.level=INFO", "--log.level=requests=debug"},
[]string{"--log.level=INFO", "--log.level=requests=debug"}), []string{"--log.level=INFO", "--log.level=requests=debug"}),
logLevelTestCaseGen("Not only log argument changed", logLevelTestCaseGen("Not only log argument changed",
GracefulRotation, compare.GracefulRotation,
[]string{"--log.level=INFO", "--server.endpoint=localhost"}, []string{"--log.level=INFO", "--server.endpoint=localhost"},
[]string{"--log.level=INFO"}), []string{"--log.level=INFO"}),
logLevelTestCaseGen("Change of order with existing arg & switch to DEBUG", logLevelTestCaseGen("Change of order with existing arg & switch to DEBUG",
InPlaceRotation, compare.InPlaceRotation,
[]string{"--log.level=DEBUG", "--foo"}, []string{"--log.level=DEBUG", "--foo"},
[]string{"--foo", "--log.level=INFO"}), []string{"--foo", "--log.level=INFO"}),
logLevelTestCaseGen("Removal of arg", logLevelTestCaseGen("Removal of arg",
InPlaceRotation, compare.InPlaceRotation,
[]string{"--foo", "--log.level=INFO"}, []string{"--foo", "--log.level=INFO"},
[]string{"--foo"}), []string{"--foo"}),
} }
@ -283,7 +284,7 @@ func Test_Container_Args(t *testing.T) {
[]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{ TestCaseOverride: TestCaseOverride{
expectedMode: GracefulRotation, expectedMode: compare.GracefulRotation,
}, },
}, },
} }
@ -314,7 +315,7 @@ func Test_Container_Ports(t *testing.T) {
} }
})), })),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: SilentRotation, expectedMode: compare.SilentRotation,
}, },
}, },
{ {
@ -338,7 +339,7 @@ func Test_Container_Ports(t *testing.T) {
} }
})), })),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: GracefulRotation, expectedMode: compare.GracefulRotation,
}, },
}, },
} }

View file

@ -1,7 +1,7 @@
// //
// DISCLAIMER // DISCLAIMER
// //
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany // Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -28,18 +28,19 @@ import (
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
"github.com/arangodb/kube-arangodb/pkg/deployment/resources" "github.com/arangodb/kube-arangodb/pkg/deployment/resources"
"github.com/arangodb/kube-arangodb/pkg/deployment/topology" "github.com/arangodb/kube-arangodb/pkg/deployment/topology"
"github.com/arangodb/kube-arangodb/pkg/util/compare"
"github.com/arangodb/kube-arangodb/pkg/util/constants" "github.com/arangodb/kube-arangodb/pkg/util/constants"
) )
func compareServerContainerEnvs(ds api.DeploymentSpec, g api.ServerGroup, spec, status *core.Container) comparePodContainerFunc { func compareServerContainerEnvs(ds api.DeploymentSpec, g api.ServerGroup, spec, status *core.Container) compare.Func {
return func(builder api.ActionBuilder) (mode Mode, plan api.Plan, err error) { return func(builder api.ActionBuilder) (mode compare.Mode, plan api.Plan, err error) {
specV := mapEnvs(spec) specV := mapEnvs(spec)
statusV := mapEnvs(status) statusV := mapEnvs(status)
diff := getEnvDiffFromPods(specV, statusV) diff := getEnvDiffFromPods(specV, statusV)
if len(diff) == 0 { if len(diff) == 0 {
return SkippedRotation, nil, nil return compare.SkippedRotation, nil, nil
} }
for k := range diff { for k := range diff {
@ -53,24 +54,24 @@ func compareServerContainerEnvs(ds api.DeploymentSpec, g api.ServerGroup, spec,
// Lifecycle envs can change without restart // Lifecycle envs can change without restart
continue continue
default: default:
return GracefulRotation, nil, nil return compare.GracefulRotation, nil, nil
} }
} }
status.Env = spec.Env status.Env = spec.Env
return SilentRotation, nil, nil return compare.SilentRotation, nil, nil
} }
} }
func compareAnyContainerEnvs(ds api.DeploymentSpec, g api.ServerGroup, spec, status *core.Container) comparePodContainerFunc { func compareAnyContainerEnvs(ds api.DeploymentSpec, g api.ServerGroup, spec, status *core.Container) compare.Func {
return func(builder api.ActionBuilder) (mode Mode, plan api.Plan, err error) { return func(builder api.ActionBuilder) (mode compare.Mode, plan api.Plan, err error) {
specV := mapEnvs(spec) specV := mapEnvs(spec)
statusV := mapEnvs(status) statusV := mapEnvs(status)
diff := getEnvDiffFromPods(specV, statusV) diff := getEnvDiffFromPods(specV, statusV)
if len(diff) == 0 { if len(diff) == 0 {
return SkippedRotation, nil, nil return compare.SkippedRotation, nil, nil
} }
for k := range diff { for k := range diff {
@ -79,12 +80,12 @@ func compareAnyContainerEnvs(ds api.DeploymentSpec, g api.ServerGroup, spec, sta
// Lifecycle envs can change without restart // Lifecycle envs can change without restart
continue continue
default: default:
return GracefulRotation, nil, nil return compare.GracefulRotation, nil, nil
} }
} }
status.Env = spec.Env status.Env = spec.Env
return SilentRotation, nil, nil return compare.SilentRotation, nil, nil
} }
} }

View file

@ -1,7 +1,7 @@
// //
// DISCLAIMER // DISCLAIMER
// //
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany // Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -25,13 +25,14 @@ import (
"k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/equality"
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
"github.com/arangodb/kube-arangodb/pkg/util/compare"
) )
func compareServerContainerProbes(ds api.DeploymentSpec, g api.ServerGroup, spec, status *core.Container) comparePodContainerFunc { func compareServerContainerProbes(ds api.DeploymentSpec, g api.ServerGroup, spec, status *core.Container) compare.Func {
return func(builder api.ActionBuilder) (mode Mode, plan api.Plan, err error) { return func(builder api.ActionBuilder) (mode compare.Mode, plan api.Plan, err error) {
if !areProbesEqual(spec.StartupProbe, status.StartupProbe) { if !areProbesEqual(spec.StartupProbe, status.StartupProbe) {
status.StartupProbe = spec.StartupProbe status.StartupProbe = spec.StartupProbe
mode = mode.And(SilentRotation) mode = mode.And(compare.SilentRotation)
} }
if !areProbesEqual(spec.ReadinessProbe, status.ReadinessProbe) { if !areProbesEqual(spec.ReadinessProbe, status.ReadinessProbe) {
@ -42,7 +43,7 @@ func compareServerContainerProbes(ds api.DeploymentSpec, g api.ServerGroup, spec
if equality.Semantic.DeepDerivative(spec.ReadinessProbe, q) { if equality.Semantic.DeepDerivative(spec.ReadinessProbe, q) {
status.ReadinessProbe = spec.ReadinessProbe status.ReadinessProbe = spec.ReadinessProbe
mode = mode.And(SilentRotation) mode = mode.And(compare.SilentRotation)
} }
} }
} }
@ -55,7 +56,7 @@ func compareServerContainerProbes(ds api.DeploymentSpec, g api.ServerGroup, spec
if equality.Semantic.DeepDerivative(spec.LivenessProbe, q) { if equality.Semantic.DeepDerivative(spec.LivenessProbe, q) {
status.LivenessProbe = spec.LivenessProbe status.LivenessProbe = spec.LivenessProbe
mode = mode.And(SilentRotation) mode = mode.And(compare.SilentRotation)
} }
} }
} }

View file

@ -27,6 +27,7 @@ import (
"github.com/arangodb/kube-arangodb/pkg/deployment/topology" "github.com/arangodb/kube-arangodb/pkg/deployment/topology"
"github.com/arangodb/kube-arangodb/pkg/util" "github.com/arangodb/kube-arangodb/pkg/util"
compare2 "github.com/arangodb/kube-arangodb/pkg/util/compare"
) )
func Test_ArangoD_SchedulerName(t *testing.T) { func Test_ArangoD_SchedulerName(t *testing.T) {
@ -41,7 +42,7 @@ func Test_ArangoD_SchedulerName(t *testing.T) {
}), }),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: SilentRotation, expectedMode: compare2.SilentRotation,
}, },
}, },
{ {
@ -54,7 +55,7 @@ func Test_ArangoD_SchedulerName(t *testing.T) {
}), }),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: SilentRotation, expectedMode: compare2.SilentRotation,
}, },
}, },
{ {
@ -67,7 +68,7 @@ func Test_ArangoD_SchedulerName(t *testing.T) {
}), }),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: SkippedRotation, expectedMode: compare2.SkippedRotation,
}, },
}, },
} }
@ -87,7 +88,7 @@ func Test_ArangoD_TerminationGracePeriodSeconds(t *testing.T) {
}), }),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: SilentRotation, expectedMode: compare2.SilentRotation,
}, },
}, },
{ {
@ -100,7 +101,7 @@ func Test_ArangoD_TerminationGracePeriodSeconds(t *testing.T) {
}), }),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: SilentRotation, expectedMode: compare2.SilentRotation,
}, },
}, },
{ {
@ -113,7 +114,7 @@ func Test_ArangoD_TerminationGracePeriodSeconds(t *testing.T) {
}), }),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: SilentRotation, expectedMode: compare2.SilentRotation,
}, },
}, },
} }
@ -150,7 +151,7 @@ func Test_ArangoD_Affinity(t *testing.T) {
}), }),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: SilentRotation, expectedMode: compare2.SilentRotation,
}, },
}, },
{ {
@ -180,7 +181,7 @@ func Test_ArangoD_Affinity(t *testing.T) {
}), }),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: SilentRotation, expectedMode: compare2.SilentRotation,
}, },
}, },
{ {
@ -229,7 +230,7 @@ func Test_ArangoD_Affinity(t *testing.T) {
}), }),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: SilentRotation, expectedMode: compare2.SilentRotation,
}, },
}, },
{ {
@ -278,7 +279,7 @@ func Test_ArangoD_Affinity(t *testing.T) {
}), }),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: SilentRotation, expectedMode: compare2.SilentRotation,
}, },
}, },
{ {
@ -327,7 +328,7 @@ func Test_ArangoD_Affinity(t *testing.T) {
}), }),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: SilentRotation, expectedMode: compare2.SilentRotation,
}, },
}, },
} }
@ -351,7 +352,7 @@ func Test_ArangoD_Labels(t *testing.T) {
}), }),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: SkippedRotation, expectedMode: compare2.SkippedRotation,
}, },
}, },
{ {
@ -368,7 +369,7 @@ func Test_ArangoD_Labels(t *testing.T) {
}), }),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: SkippedRotation, expectedMode: compare2.SkippedRotation,
}, },
}, },
{ {
@ -387,7 +388,7 @@ func Test_ArangoD_Labels(t *testing.T) {
}), }),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: SkippedRotation, expectedMode: compare2.SkippedRotation,
}, },
}, },
} }
@ -414,7 +415,7 @@ func Test_ArangoD_Envs_Zone(t *testing.T) {
})), })),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: SilentRotation, expectedMode: compare2.SilentRotation,
}, },
}, },
{ {
@ -434,7 +435,7 @@ func Test_ArangoD_Envs_Zone(t *testing.T) {
})), })),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: SilentRotation, expectedMode: compare2.SilentRotation,
}, },
}, },
{ {
@ -459,7 +460,7 @@ func Test_ArangoD_Envs_Zone(t *testing.T) {
})), })),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: SilentRotation, expectedMode: compare2.SilentRotation,
}, },
}, },
{ {
@ -488,7 +489,7 @@ func Test_ArangoD_Envs_Zone(t *testing.T) {
})), })),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: GracefulRotation, expectedMode: compare2.GracefulRotation,
}, },
}, },
} }

View file

@ -26,15 +26,16 @@ import (
core "k8s.io/api/core/v1" core "k8s.io/api/core/v1"
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
"github.com/arangodb/kube-arangodb/pkg/util/compare"
) )
func comparePodTolerations(_ api.DeploymentSpec, _ api.ServerGroup, spec, status *core.PodSpec) comparePodFunc { func comparePodTolerations(_ api.DeploymentSpec, _ api.ServerGroup, spec, status *core.PodTemplateSpec) compare.Func {
return func(builder api.ActionBuilder) (mode Mode, plan api.Plan, err error) { return func(builder api.ActionBuilder) (mode compare.Mode, plan api.Plan, err error) {
if !reflect.DeepEqual(spec.Tolerations, status.Tolerations) { if !reflect.DeepEqual(spec.Spec.Tolerations, status.Spec.Tolerations) {
plan = append(plan, builder.NewAction(api.ActionTypeRuntimeContainerSyncTolerations)) plan = append(plan, builder.NewAction(api.ActionTypeRuntimeContainerSyncTolerations))
status.Tolerations = spec.Tolerations status.Spec.Tolerations = spec.Spec.Tolerations
mode = mode.And(InPlaceRotation) mode = mode.And(compare.InPlaceRotation)
return return
} }

View file

@ -1,7 +1,7 @@
// //
// DISCLAIMER // DISCLAIMER
// //
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany // Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -27,17 +27,18 @@ import (
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
"github.com/arangodb/kube-arangodb/pkg/apis/shared" "github.com/arangodb/kube-arangodb/pkg/apis/shared"
"github.com/arangodb/kube-arangodb/pkg/util/compare"
) )
func compareServerContainerVolumeMounts(ds api.DeploymentSpec, g api.ServerGroup, spec, status *core.Container) comparePodContainerFunc { func compareServerContainerVolumeMounts(ds api.DeploymentSpec, g api.ServerGroup, spec, status *core.Container) compare.Func {
return func(builder api.ActionBuilder) (mode Mode, plan api.Plan, err error) { return func(builder api.ActionBuilder) (mode compare.Mode, plan api.Plan, err error) {
specV := mapVolumeMounts(spec) specV := mapVolumeMounts(spec)
statusV := mapVolumeMounts(status) statusV := mapVolumeMounts(status)
diff := getVolumeMountsDiffFromPods(specV, statusV) diff := getVolumeMountsDiffFromPods(specV, statusV)
if len(diff) == 0 { if len(diff) == 0 {
return SkippedRotation, nil, nil return compare.SkippedRotation, nil, nil
} }
for k, v := range diff { for k, v := range diff {
@ -46,34 +47,34 @@ func compareServerContainerVolumeMounts(ds api.DeploymentSpec, g api.ServerGroup
// We are fine, should be just replaced // We are fine, should be just replaced
if v.a == nil { if v.a == nil {
// we remove volume // we remove volume
return GracefulRotation, nil, nil return compare.GracefulRotation, nil, nil
} }
if ds.Mode.Get().ServingGroup() == g { if ds.Mode.Get().ServingGroup() == g {
// Always enforce on serving group // Always enforce on serving group
return GracefulRotation, nil, nil return compare.GracefulRotation, nil, nil
} }
case shared.LifecycleVolumeName: case shared.LifecycleVolumeName:
// Do nothing // Do nothing
default: default:
return GracefulRotation, nil, nil return compare.GracefulRotation, nil, nil
} }
} }
status.VolumeMounts = spec.VolumeMounts status.VolumeMounts = spec.VolumeMounts
return SilentRotation, nil, nil return compare.SilentRotation, nil, nil
} }
} }
func compareAnyContainerVolumeMounts(ds api.DeploymentSpec, g api.ServerGroup, spec, status *core.Container) comparePodContainerFunc { func compareAnyContainerVolumeMounts(ds api.DeploymentSpec, g api.ServerGroup, spec, status *core.Container) compare.Func {
return func(builder api.ActionBuilder) (mode Mode, plan api.Plan, err error) { return func(builder api.ActionBuilder) (mode compare.Mode, plan api.Plan, err error) {
specV := mapVolumeMounts(spec) specV := mapVolumeMounts(spec)
statusV := mapVolumeMounts(status) statusV := mapVolumeMounts(status)
diff := getVolumeMountsDiffFromPods(specV, statusV) diff := getVolumeMountsDiffFromPods(specV, statusV)
if len(diff) == 0 { if len(diff) == 0 {
return SkippedRotation, nil, nil return compare.SkippedRotation, nil, nil
} }
for k := range diff { for k := range diff {
@ -81,12 +82,12 @@ func compareAnyContainerVolumeMounts(ds api.DeploymentSpec, g api.ServerGroup, s
case shared.LifecycleVolumeName: case shared.LifecycleVolumeName:
// Do nothing // Do nothing
default: default:
return GracefulRotation, nil, nil return compare.GracefulRotation, nil, nil
} }
} }
status.VolumeMounts = spec.VolumeMounts status.VolumeMounts = spec.VolumeMounts
return SilentRotation, nil, nil return compare.SilentRotation, nil, nil
} }
} }

View file

@ -1,7 +1,7 @@
// //
// DISCLAIMER // DISCLAIMER
// //
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany // Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -27,21 +27,22 @@ import (
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
"github.com/arangodb/kube-arangodb/pkg/apis/shared" "github.com/arangodb/kube-arangodb/pkg/apis/shared"
"github.com/arangodb/kube-arangodb/pkg/util/compare"
) )
type volumeDiff struct { type volumeDiff struct {
a, b *core.Volume a, b *core.Volume
} }
func comparePodVolumes(ds api.DeploymentSpec, g api.ServerGroup, spec, status *core.PodSpec) comparePodFunc { func comparePodVolumes(ds api.DeploymentSpec, g api.ServerGroup, spec, status *core.PodTemplateSpec) compare.Func {
return func(builder api.ActionBuilder) (mode Mode, plan api.Plan, err error) { return func(builder api.ActionBuilder) (mode compare.Mode, plan api.Plan, err error) {
specV := mapVolumes(spec) specV := mapVolumes(spec.Spec)
statusV := mapVolumes(status) statusV := mapVolumes(status.Spec)
diff := getVolumesDiffFromPods(specV, statusV) diff := getVolumesDiffFromPods(specV, statusV)
if len(diff) == 0 { if len(diff) == 0 {
return SkippedRotation, nil, nil return compare.SkippedRotation, nil, nil
} }
for k, v := range diff { for k, v := range diff {
@ -50,20 +51,20 @@ func comparePodVolumes(ds api.DeploymentSpec, g api.ServerGroup, spec, status *c
// We are fine, should be just replaced // We are fine, should be just replaced
if v.a == nil { if v.a == nil {
// we remove volume // we remove volume
return GracefulRotation, nil, nil return compare.GracefulRotation, nil, nil
} }
if ds.Mode.Get().ServingGroup() == g { if ds.Mode.Get().ServingGroup() == g {
// Always enforce on serving group // Always enforce on serving group
return GracefulRotation, nil, nil return compare.GracefulRotation, nil, nil
} }
default: default:
return GracefulRotation, nil, nil return compare.GracefulRotation, nil, nil
} }
} }
status.Volumes = spec.Volumes status.Spec.Volumes = spec.Spec.Volumes
return SilentRotation, nil, nil return compare.SilentRotation, nil, nil
} }
} }
@ -97,7 +98,7 @@ func getVolumesDiffFromPods(a, b map[string]*core.Volume) map[string]volumeDiff
return d return d
} }
func mapVolumes(a *core.PodSpec) map[string]*core.Volume { func mapVolumes(a core.PodSpec) map[string]*core.Volume {
n := make(map[string]*core.Volume, len(a.Volumes)) n := make(map[string]*core.Volume, len(a.Volumes))
for id := range a.Volumes { for id := range a.Volumes {

View file

@ -1,7 +1,7 @@
// //
// DISCLAIMER // DISCLAIMER
// //
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany // Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -27,6 +27,7 @@ import (
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
"github.com/arangodb/kube-arangodb/pkg/apis/shared" "github.com/arangodb/kube-arangodb/pkg/apis/shared"
"github.com/arangodb/kube-arangodb/pkg/util/compare"
) )
func Test_ArangoD_Volumes(t *testing.T) { func Test_ArangoD_Volumes(t *testing.T) {
@ -39,7 +40,7 @@ func Test_ArangoD_Volumes(t *testing.T) {
deploymentSpec: buildDeployment(), deploymentSpec: buildDeployment(),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: SkippedRotation, expectedMode: compare.SkippedRotation,
}, },
}, },
{ {
@ -48,7 +49,7 @@ func Test_ArangoD_Volumes(t *testing.T) {
status: buildPodSpec(addVolume("data", addVolumeConfigMapSource(&core.ConfigMapVolumeSource{}))), status: buildPodSpec(addVolume("data", addVolumeConfigMapSource(&core.ConfigMapVolumeSource{}))),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: SkippedRotation, expectedMode: compare.SkippedRotation,
}, },
}, },
{ {
@ -61,7 +62,7 @@ func Test_ArangoD_Volumes(t *testing.T) {
}))), }))),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: GracefulRotation, expectedMode: compare.GracefulRotation,
}, },
}, },
{ {
@ -70,7 +71,7 @@ func Test_ArangoD_Volumes(t *testing.T) {
status: buildPodSpec(), status: buildPodSpec(),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: GracefulRotation, expectedMode: compare.GracefulRotation,
}, },
}, },
{ {
@ -83,7 +84,7 @@ func Test_ArangoD_Volumes(t *testing.T) {
}))), }))),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: GracefulRotation, expectedMode: compare.GracefulRotation,
}, },
}, },
{ {
@ -98,23 +99,23 @@ func Test_ArangoD_Volumes(t *testing.T) {
overrides: map[api.DeploymentMode]map[api.ServerGroup]TestCaseOverride{ overrides: map[api.DeploymentMode]map[api.ServerGroup]TestCaseOverride{
api.DeploymentModeSingle: { api.DeploymentModeSingle: {
api.ServerGroupSingle: { api.ServerGroupSingle: {
expectedMode: GracefulRotation, expectedMode: compare.GracefulRotation,
}, },
}, },
api.DeploymentModeActiveFailover: { api.DeploymentModeActiveFailover: {
api.ServerGroupSingle: { api.ServerGroupSingle: {
expectedMode: GracefulRotation, expectedMode: compare.GracefulRotation,
}, },
}, },
api.DeploymentModeCluster: { api.DeploymentModeCluster: {
api.ServerGroupCoordinators: { api.ServerGroupCoordinators: {
expectedMode: GracefulRotation, expectedMode: compare.GracefulRotation,
}, },
}, },
}, },
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: SilentRotation, expectedMode: compare.SilentRotation,
}, },
}, },
{ {
@ -125,23 +126,23 @@ func Test_ArangoD_Volumes(t *testing.T) {
overrides: map[api.DeploymentMode]map[api.ServerGroup]TestCaseOverride{ overrides: map[api.DeploymentMode]map[api.ServerGroup]TestCaseOverride{
api.DeploymentModeSingle: { api.DeploymentModeSingle: {
api.ServerGroupSingle: { api.ServerGroupSingle: {
expectedMode: GracefulRotation, expectedMode: compare.GracefulRotation,
}, },
}, },
api.DeploymentModeActiveFailover: { api.DeploymentModeActiveFailover: {
api.ServerGroupSingle: { api.ServerGroupSingle: {
expectedMode: GracefulRotation, expectedMode: compare.GracefulRotation,
}, },
}, },
api.DeploymentModeCluster: { api.DeploymentModeCluster: {
api.ServerGroupCoordinators: { api.ServerGroupCoordinators: {
expectedMode: GracefulRotation, expectedMode: compare.GracefulRotation,
}, },
}, },
}, },
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: SilentRotation, expectedMode: compare.SilentRotation,
}, },
}, },
{ {
@ -154,7 +155,7 @@ func Test_ArangoD_Volumes(t *testing.T) {
}))), }))),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: GracefulRotation, expectedMode: compare.GracefulRotation,
}, },
}, },
} }
@ -170,7 +171,7 @@ func Test_ArangoD_VolumeMounts(t *testing.T) {
status: buildPodSpec(addContainer("server")), status: buildPodSpec(addContainer("server")),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: SkippedRotation, expectedMode: compare.SkippedRotation,
}, },
}, },
{ {
@ -183,7 +184,7 @@ func Test_ArangoD_VolumeMounts(t *testing.T) {
}))), }))),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: SkippedRotation, expectedMode: compare.SkippedRotation,
}, },
}, },
{ {
@ -192,7 +193,7 @@ func Test_ArangoD_VolumeMounts(t *testing.T) {
status: buildPodSpec(addContainer("server", addVolumeMount("mount2"))), status: buildPodSpec(addContainer("server", addVolumeMount("mount2"))),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: GracefulRotation, expectedMode: compare.GracefulRotation,
}, },
}, },
{ {
@ -201,7 +202,7 @@ func Test_ArangoD_VolumeMounts(t *testing.T) {
status: buildPodSpec(addContainer("server")), status: buildPodSpec(addContainer("server")),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: GracefulRotation, expectedMode: compare.GracefulRotation,
}, },
}, },
{ {
@ -210,7 +211,7 @@ func Test_ArangoD_VolumeMounts(t *testing.T) {
status: buildPodSpec(addContainer("server", addVolumeMount("mount"))), status: buildPodSpec(addContainer("server", addVolumeMount("mount"))),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: GracefulRotation, expectedMode: compare.GracefulRotation,
}, },
}, },
{ {
@ -219,7 +220,7 @@ func Test_ArangoD_VolumeMounts(t *testing.T) {
status: buildPodSpec(addContainer("server", addVolumeMount("mount"))), status: buildPodSpec(addContainer("server", addVolumeMount("mount"))),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: GracefulRotation, expectedMode: compare.GracefulRotation,
}, },
}, },
{ {
@ -228,7 +229,7 @@ func Test_ArangoD_VolumeMounts(t *testing.T) {
status: buildPodSpec(addContainer("server", addVolumeMount(shared.ArangoDTimezoneVolumeName))), status: buildPodSpec(addContainer("server", addVolumeMount(shared.ArangoDTimezoneVolumeName))),
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: GracefulRotation, expectedMode: compare.GracefulRotation,
}, },
}, },
{ {
@ -239,23 +240,23 @@ func Test_ArangoD_VolumeMounts(t *testing.T) {
overrides: map[api.DeploymentMode]map[api.ServerGroup]TestCaseOverride{ overrides: map[api.DeploymentMode]map[api.ServerGroup]TestCaseOverride{
api.DeploymentModeSingle: { api.DeploymentModeSingle: {
api.ServerGroupSingle: { api.ServerGroupSingle: {
expectedMode: GracefulRotation, expectedMode: compare.GracefulRotation,
}, },
}, },
api.DeploymentModeActiveFailover: { api.DeploymentModeActiveFailover: {
api.ServerGroupSingle: { api.ServerGroupSingle: {
expectedMode: GracefulRotation, expectedMode: compare.GracefulRotation,
}, },
}, },
api.DeploymentModeCluster: { api.DeploymentModeCluster: {
api.ServerGroupCoordinators: { api.ServerGroupCoordinators: {
expectedMode: GracefulRotation, expectedMode: compare.GracefulRotation,
}, },
}, },
}, },
TestCaseOverride: TestCaseOverride{ TestCaseOverride: TestCaseOverride{
expectedMode: SilentRotation, expectedMode: compare.SilentRotation,
}, },
}, },
} }

View file

@ -29,49 +29,27 @@ import (
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
"github.com/arangodb/kube-arangodb/pkg/deployment/acs/sutil" "github.com/arangodb/kube-arangodb/pkg/deployment/acs/sutil"
"github.com/arangodb/kube-arangodb/pkg/handlers/utils" "github.com/arangodb/kube-arangodb/pkg/handlers/utils"
"github.com/arangodb/kube-arangodb/pkg/util/compare"
"github.com/arangodb/kube-arangodb/pkg/util/constants" "github.com/arangodb/kube-arangodb/pkg/util/constants"
) )
type Mode int
const (
SkippedRotation Mode = iota
// SilentRotation Propagates changes without restart. Returned plan is executed in High actions
SilentRotation
// InPlaceRotation Silently accept changes. Returned plan is executed in Normal actions
InPlaceRotation
// GracefulRotation Schedule pod restart. Returned plan is ignored
GracefulRotation
// EnforcedRotation Enforce pod restart. Returned plan is ignored
EnforcedRotation
)
// And returns the higher value of the rotation mode.
func (m Mode) And(b Mode) Mode {
if m > b {
return m
}
return b
}
// CheckPossible returns true if rotation is possible // CheckPossible returns true if rotation is possible
func CheckPossible(member api.MemberStatus) bool { func CheckPossible(member api.MemberStatus) bool {
return !member.Conditions.IsTrue(api.ConditionTypeTerminated) return !member.Conditions.IsTrue(api.ConditionTypeTerminated)
} }
func IsRotationRequired(acs sutil.ACS, spec api.DeploymentSpec, member api.MemberStatus, group api.ServerGroup, pod *core.Pod, specTemplate, statusTemplate *api.ArangoMemberPodTemplate) (mode Mode, plan api.Plan, specChecksum string, reason string, err error) { func IsRotationRequired(acs sutil.ACS, spec api.DeploymentSpec, member api.MemberStatus, group api.ServerGroup, pod *core.Pod, specTemplate, statusTemplate *api.ArangoMemberPodTemplate) (mode compare.Mode, plan api.Plan, specChecksum string, reason string, err error) {
// Determine if rotation is required based on plan and actions // Determine if rotation is required based on plan and actions
// Set default mode for return value // Set default mode for return value
mode = SkippedRotation mode = compare.SkippedRotation
// We are under termination // We are under termination
if pod != nil { if pod != nil {
if member.Conditions.IsTrue(api.ConditionTypeTerminating) || pod.DeletionTimestamp != nil { if member.Conditions.IsTrue(api.ConditionTypeTerminating) || pod.DeletionTimestamp != nil {
if l := utils.StringList(pod.Finalizers); l.Has(constants.FinalizerPodGracefulShutdown) && !l.Has(constants.FinalizerDelayPodTermination) { if l := utils.StringList(pod.Finalizers); l.Has(constants.FinalizerPodGracefulShutdown) && !l.Has(constants.FinalizerDelayPodTermination) {
reason = "Recreation enforced by deleted state" reason = "Recreation enforced by deleted state"
mode = EnforcedRotation mode = compare.EnforcedRotation
} }
return return
@ -85,7 +63,7 @@ func IsRotationRequired(acs sutil.ACS, spec api.DeploymentSpec, member api.Membe
if spec.MemberPropagationMode.Get() == api.DeploymentMemberPropagationModeAlways && member.Conditions.IsTrue(api.ConditionTypePendingRestart) { if spec.MemberPropagationMode.Get() == api.DeploymentMemberPropagationModeAlways && member.Conditions.IsTrue(api.ConditionTypePendingRestart) {
reason = "Restart is pending" reason = "Restart is pending"
mode = EnforcedRotation mode = compare.EnforcedRotation
return return
} }
@ -93,20 +71,20 @@ func IsRotationRequired(acs sutil.ACS, spec api.DeploymentSpec, member api.Membe
if pod != nil { if pod != nil {
if member.Pod.GetUID() != pod.UID { if member.Pod.GetUID() != pod.UID {
reason = "Pod UID does not match, this pod is not managed by Operator. Recreating" reason = "Pod UID does not match, this pod is not managed by Operator. Recreating"
mode = EnforcedRotation mode = compare.EnforcedRotation
return return
} }
if _, ok := pod.Annotations[deployment.ArangoDeploymentPodRotateAnnotation]; ok { if _, ok := pod.Annotations[deployment.ArangoDeploymentPodRotateAnnotation]; ok {
reason = "Recreation enforced by annotation" reason = "Recreation enforced by annotation"
mode = EnforcedRotation mode = compare.EnforcedRotation
return return
} }
} }
if p := member.Pod; p != nil && p.SpecVersion == "" { if p := member.Pod; p != nil && p.SpecVersion == "" {
reason = "Pod Spec Version is nil - recreating pod" reason = "Pod Spec Version is nil - recreating pod"
mode = EnforcedRotation mode = compare.EnforcedRotation
return return
} }
@ -118,19 +96,19 @@ func IsRotationRequired(acs sutil.ACS, spec api.DeploymentSpec, member api.Membe
// Check if any of resize events are in place // Check if any of resize events are in place
if member.Conditions.IsTrue(api.ConditionTypePendingTLSRotation) { if member.Conditions.IsTrue(api.ConditionTypePendingTLSRotation) {
reason = "TLS Rotation pending" reason = "TLS Rotation pending"
mode = EnforcedRotation mode = compare.EnforcedRotation
return return
} }
if member.Conditions.Check(api.ConditionTypePVCResizePending).Exists().LastTransition(3 * time.Minute).Evaluate() { if member.Conditions.Check(api.ConditionTypePVCResizePending).Exists().LastTransition(3 * time.Minute).Evaluate() {
reason = "PVC Resize pending for more than 3 min" reason = "PVC Resize pending for more than 3 min"
mode = EnforcedRotation mode = compare.EnforcedRotation
return return
} }
if mode, plan, err := compare(spec, member, group, specTemplate, statusTemplate); err != nil { if mode, plan, err := compareFunc(spec, member, group, specTemplate, statusTemplate); err != nil {
return SkippedRotation, nil, specTemplate.Checksum, "", err return compare.SkippedRotation, nil, specTemplate.Checksum, "", err
} else if mode == SkippedRotation { } else if mode == compare.SkippedRotation {
return mode, plan, specTemplate.Checksum, "No rotation needed", nil return mode, plan, specTemplate.Checksum, "No rotation needed", nil
} else { } else {
return mode, plan, specTemplate.Checksum, "Pod needs rotation", nil return mode, plan, specTemplate.Checksum, "Pod needs rotation", nil

View file

@ -1,7 +1,7 @@
// //
// DISCLAIMER // DISCLAIMER
// //
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany // Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -21,124 +21,32 @@
package rotation package rotation
import ( import (
"crypto/sha256"
"encoding/json" "encoding/json"
"fmt"
jd "github.com/josephburnett/jd/lib"
"github.com/rs/zerolog/log"
core "k8s.io/api/core/v1" core "k8s.io/api/core/v1"
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" 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/actions"
"github.com/arangodb/kube-arangodb/pkg/deployment/resources" "github.com/arangodb/kube-arangodb/pkg/util/compare"
) )
type comparePodFuncGen func(deploymentSpec api.DeploymentSpec, group api.ServerGroup, spec, status *core.PodSpec) comparePodFunc func compareFunc(deploymentSpec api.DeploymentSpec, member api.MemberStatus, group api.ServerGroup,
type comparePodFunc func(builder api.ActionBuilder) (mode Mode, plan api.Plan, err error) spec, status *api.ArangoMemberPodTemplate) (mode compare.Mode, plan api.Plan, err error) {
return compare.P2[core.PodTemplateSpec, api.DeploymentSpec, api.ServerGroup](logger,
func podFuncGenerator(deploymentSpec api.DeploymentSpec, group api.ServerGroup, spec, status *core.PodSpec) func(c comparePodFuncGen) comparePodFunc { deploymentSpec, group,
return func(c comparePodFuncGen) comparePodFunc { actions.NewActionBuilderWrap(group, member),
return c(deploymentSpec, group, spec, status) func(in *core.PodTemplateSpec) (string, error) {
} data, err := json.Marshal(in.Spec)
} if err != nil {
return "", err
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
} else {
mode = mode.And(m)
plan = append(plan, p...)
}
}
return
}
func compare(deploymentSpec api.DeploymentSpec, member api.MemberStatus, group api.ServerGroup,
spec, status *api.ArangoMemberPodTemplate) (mode Mode, plan api.Plan, err error) {
if spec.Checksum == status.Checksum {
return SkippedRotation, nil, nil
}
// If checksums are different and rotation is not needed and there are no changes between containers
// then silent rotation must be applied to adjust status checksum.
mode = SilentRotation
podStatus := status.PodSpec.DeepCopy()
// Try to fill fields
b := actions.NewActionBuilderWrap(group, member)
g := podFuncGenerator(deploymentSpec, group, &spec.PodSpec.Spec, &podStatus.Spec)
if m, p, err := comparePod(b, g(podCompare), g(affinityCompare), g(comparePodVolumes), g(containersCompare), g(initContainersCompare), g(comparePodTolerations)); err != nil {
log.Err(err).Msg("Error while getting pod diff")
return SkippedRotation, nil, err
} else {
mode = mode.And(m)
plan = append(plan, p...)
}
checksum, err := resources.ChecksumArangoPod(deploymentSpec.GetServerGroupSpec(group), resources.CreatePodFromTemplate(podStatus))
if err != nil {
log.Err(err).Msg("Error while getting pod checksum")
return SkippedRotation, nil, err
}
newStatus, err := api.GetArangoMemberPodTemplate(podStatus, checksum)
if err != nil {
log.Err(err).Msg("Error while getting template")
return SkippedRotation, nil, err
}
if spec.RotationNeeded(newStatus) {
line := logger.Str("id", member.ID)
specBytes, errA := json.Marshal(spec.PodSpec)
if errA == nil {
line = line.Str("spec", string(specBytes))
}
statusBytes, errB := json.Marshal(newStatus.PodSpec)
if errB == nil {
line = line.Str("status", string(statusBytes))
}
if errA == nil && errB == nil {
if specData, err := jd.ReadJsonString(string(specBytes)); err == nil && specData != nil {
if statusData, err := jd.ReadJsonString(string(statusBytes)); err == nil && statusData != nil {
line = line.Str("diff", specData.Diff(statusData).Render())
}
} }
}
line.Info("Pod needs rotation - templates does not match") checksum := fmt.Sprintf("%0x", sha256.Sum256(data))
return GracefulRotation, nil, nil return checksum, nil
} },
spec, status,
return podCompare, affinityCompare, comparePodVolumes, containersCompare, initContainersCompare, comparePodTolerations)
} }

View file

@ -1,7 +1,7 @@
// //
// DISCLAIMER // DISCLAIMER
// //
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany // Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -30,6 +30,7 @@ import (
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
"github.com/arangodb/kube-arangodb/pkg/deployment/resources" "github.com/arangodb/kube-arangodb/pkg/deployment/resources"
"github.com/arangodb/kube-arangodb/pkg/util/compare"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil" "github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
) )
@ -43,7 +44,7 @@ var podLifecycleChange000Spec []byte
//go:embed testdata/pod_lifecycle_change.000.status.json //go:embed testdata/pod_lifecycle_change.000.status.json
var podLifecycleChange000Status []byte var podLifecycleChange000Status []byte
func runPredefinedTests(t *testing.T, spec, status []byte) (mode Mode, plan api.Plan, err error) { func runPredefinedTests(t *testing.T, spec, status []byte) (mode compare.Mode, plan api.Plan, err error) {
var specO, statusO core.PodTemplateSpec var specO, statusO core.PodTemplateSpec
require.NoError(t, json.Unmarshal(spec, &specO)) require.NoError(t, json.Unmarshal(spec, &specO))
@ -63,7 +64,7 @@ func runPredefinedTests(t *testing.T, spec, status []byte) (mode Mode, plan api.
statusT, err := api.GetArangoMemberPodTemplate(&statusO, statusC) statusT, err := api.GetArangoMemberPodTemplate(&statusO, statusC)
require.NoError(t, err) require.NoError(t, err)
return compare(obj, member, api.ServerGroupUnknown, specT, statusT) return compareFunc(obj, member, api.ServerGroupUnknown, specT, statusT)
} }
func Test_PredefinedTests(t *testing.T) { func Test_PredefinedTests(t *testing.T) {
@ -71,6 +72,6 @@ func Test_PredefinedTests(t *testing.T) {
mode, plan, err := runPredefinedTests(t, podLifecycleChange000Spec, podLifecycleChange000Status) mode, plan, err := runPredefinedTests(t, podLifecycleChange000Spec, podLifecycleChange000Status)
require.NoError(t, err) require.NoError(t, err)
require.Empty(t, plan) require.Empty(t, plan)
require.Equal(t, SilentRotation, mode) require.Equal(t, compare.SilentRotation, mode)
}) })
} }

View file

@ -1,7 +1,7 @@
// //
// DISCLAIMER // DISCLAIMER
// //
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany // Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -28,10 +28,11 @@ import (
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
"github.com/arangodb/kube-arangodb/pkg/deployment/resources" "github.com/arangodb/kube-arangodb/pkg/deployment/resources"
compare2 "github.com/arangodb/kube-arangodb/pkg/util/compare"
) )
type TestCaseOverride struct { type TestCaseOverride struct {
expectedMode Mode expectedMode compare2.Mode
expectedPlan api.Plan expectedPlan api.Plan
expectedErr string expectedErr string
} }
@ -98,7 +99,7 @@ func runTestCasesForModeAndGroup(t *testing.T, m api.DeploymentMode, g api.Serve
pspec := newTemplateFromSpec(t, tc.spec, g, *ds) pspec := newTemplateFromSpec(t, tc.spec, g, *ds)
pstatus := newTemplateFromSpec(t, tc.status, g, *ds) pstatus := newTemplateFromSpec(t, tc.status, g, *ds)
mode, plan, err := compare(*ds, api.MemberStatus{ID: "id"}, g, pspec, pstatus) mode, plan, err := compareFunc(*ds, api.MemberStatus{ID: "id"}, g, pspec, pstatus)
q := tc.TestCaseOverride q := tc.TestCaseOverride
@ -113,7 +114,7 @@ func runTestCasesForModeAndGroup(t *testing.T, m api.DeploymentMode, g api.Serve
require.Equal(t, q.expectedMode, mode) require.Equal(t, q.expectedMode, mode)
switch mode { switch mode {
case InPlaceRotation: case compare2.InPlaceRotation:
require.Len(t, plan, len(q.expectedPlan)) require.Len(t, plan, len(q.expectedPlan))
for i := range plan { for i := range plan {

View file

@ -0,0 +1,98 @@
//
// DISCLAIMER
//
// Copyright 2023 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 compare
import (
"reflect"
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
"github.com/arangodb/kube-arangodb/pkg/logging"
)
// P2 runs compare of the provided templates with 2 generic parameters
func P2[T interface{}, P1, P2 interface{}](
log logging.Logger,
p1 P1, p2 P2,
actionBuilder api.ActionBuilder,
checksum Checksum[T],
spec, status Template[T],
evaluators ...GenP2[T, P1, P2]) (mode Mode, plan api.Plan, err error) {
if spec.GetChecksum() == status.GetChecksum() {
return SkippedRotation, nil, nil
}
mode = SilentRotation
// Try to fill fields
newStatus := status.GetTemplate()
currentSpec := spec.GetTemplate()
g := NewFuncGenP2[T](p1, p2, currentSpec, newStatus)
evaluatorsFunc := make([]Func, len(evaluators))
for id := range evaluators {
evaluatorsFunc[id] = g(evaluators[id])
}
if m, p, err := Evaluate(actionBuilder, evaluatorsFunc...); err != nil {
log.Err(err).Error("Error while getting diff")
return SkippedRotation, nil, err
} else {
mode = mode.And(m)
plan = append(plan, p...)
}
// Diff has been generated! Proceed with calculations
checksumString, err := checksum(newStatus)
if err != nil {
log.Err(err).Error("Error while getting checksum")
return SkippedRotation, nil, err
}
if checksumString != spec.GetChecksum() {
line := log
// Rotate anyway!
specData, statusData, diff, err := Diff(currentSpec, newStatus)
if err == nil {
if diff != "" {
line = line.Str("diff", diff)
}
if specData != "" {
line = line.Str("spec", specData)
}
if statusData != "" {
line = line.Str("status", statusData)
}
}
line.Info("Resource %s needs rotation - templates does not match", reflect.TypeOf(currentSpec).String())
return GracefulRotation, nil, nil
}
return
}

53
pkg/util/compare/diff.go Normal file
View file

@ -0,0 +1,53 @@
//
// DISCLAIMER
//
// Copyright 2023 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 compare
import (
"encoding/json"
jd "github.com/josephburnett/jd/lib"
)
func Diff[T interface{}](spec, status *T) (specData, statusData, diff string, outErr error) {
specBytes, err := json.Marshal(spec)
if err != nil {
return "", "", "", err
}
specData = string(specBytes)
statusBytes, err := json.Marshal(status)
if err != nil {
return "", "", "", err
}
statusData = string(statusBytes)
if specData, err := jd.ReadJsonString(string(specBytes)); err != nil {
return "", "", "", err
} else if specData != nil {
if statusData, err := jd.ReadJsonString(string(statusBytes)); err != nil {
return "", "", "", err
} else if statusData != nil {
diff = specData.Diff(statusData).Render()
}
}
return
}

31
pkg/util/compare/gen.go Normal file
View file

@ -0,0 +1,31 @@
//
// DISCLAIMER
//
// Copyright 2023 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 compare
type GenP2[T, P1, P2 interface{}] func(p1 P1, p2 P2, spec, status *T) Func
type FuncGenP2[T, P1, P2 interface{}] func(in GenP2[T, P1, P2]) Func
func NewFuncGenP2[T, P1, P2 interface{}](p1 P1, p2 P2, spec, status *T) FuncGenP2[T, P1, P2] {
return func(in GenP2[T, P1, P2]) Func {
return in(p1, p2, spec, status)
}
}

View file

@ -0,0 +1,70 @@
//
// DISCLAIMER
//
// Copyright 2023 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 compare
import (
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
)
type Template[T interface{}] interface {
GetTemplate() *T
SetTemplate(*T)
GetTemplateChecksum() string
SetTemplateChecksum(string)
GetChecksum() string
SetChecksum(string)
}
type Checksum[T interface{}] func(in *T) (string, error)
type FuncGen[T interface{}] func(spec, status *T) Func
type Func func(builder api.ActionBuilder) (mode Mode, plan api.Plan, err error)
func Merge(f ...Func) Func {
return func(builder api.ActionBuilder) (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 Evaluate(builder api.ActionBuilder, f ...Func) (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
}

54
pkg/util/compare/mode.go Normal file
View file

@ -0,0 +1,54 @@
//
// DISCLAIMER
//
// Copyright 2023 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 compare
import (
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
)
type Mode int
const (
SkippedRotation Mode = iota
// SilentRotation Propagates changes without restart. Returned plan is executed in High actions
SilentRotation
// InPlaceRotation Silently accept changes. Returned plan is executed in Normal actions
InPlaceRotation
// GracefulRotation Schedule pod restart. Returned plan is ignored
GracefulRotation
// EnforcedRotation Enforce pod restart. Returned plan is ignored
EnforcedRotation
)
// And returns the higher value of the rotation mode.
func (m Mode) And(b Mode) Mode {
if m > b {
return m
}
return b
}
func (m Mode) Func() Func {
return func(builder api.ActionBuilder) (mode Mode, plan api.Plan, err error) {
return m, nil, nil
}
}

63
pkg/util/compare/sub.go Normal file
View file

@ -0,0 +1,63 @@
//
// DISCLAIMER
//
// Copyright 2023 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 compare
type SubElementExtractor[T1, T2 interface{}] func(in *T1) *T2
func SubElementsP2[T1, T2, P1, P2 interface{}](extractor SubElementExtractor[T1, T2], gens ...GenP2[T2, P1, P2]) GenP2[T1, P1, P2] {
return func(p1 P1, p2 P2, spec, status *T1) Func {
specF := extractor(spec)
statusF := extractor(status)
funcs := make([]Func, len(gens))
for id := range gens {
funcs[id] = gens[id](p1, p2, specF, statusF)
}
return Merge(funcs...)
}
}
func ArrayExtractorP2[T, P1, P2 interface{}](gens ...GenP2[T, P1, P2]) GenP2[[]T, P1, P2] {
return func(p1 P1, p2 P2, spec, status *[]T) Func {
if spec == nil || status == nil {
return SkippedRotation.Func()
}
specA := *spec
statusA := *status
if len(specA) != len(statusA) {
return SkippedRotation.Func()
}
funcs := make([]Func, 0, len(specA)*len(gens))
// Iterate over ids
for id := range specA {
for _, gen := range gens {
funcs = append(funcs, gen(p1, p2, &specA[id], &statusA[id]))
}
}
return Merge(funcs...)
}
}

View file

@ -0,0 +1,25 @@
//
// DISCLAIMER
//
// Copyright 2023 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 k8sutil
type DeepCopy[T interface{}] interface {
DeepCopy() T
}