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

[Bugfix] Prevent Runtime update restarts (#1205)

This commit is contained in:
Adam Janikowski 2022-12-08 20:26:58 +01:00 committed by GitHub
parent 463ab90b02
commit 93d8ba10be
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 250 additions and 7 deletions

View file

@ -38,6 +38,7 @@
- (Feature) Allow to exclude metrics - (Feature) Allow to exclude metrics
- (Bugfix) Do not stop Sync if Synchronization is in progress - (Bugfix) Do not stop Sync if Synchronization is in progress
- (Bugfix) Wait for Pod to be Ready in post-restart actions - (Bugfix) Wait for Pod to be Ready in post-restart actions
- (Bugfix) Prevent Runtime update restarts
## [1.2.20](https://github.com/arangodb/kube-arangodb/tree/1.2.20) (2022-10-25) ## [1.2.20](https://github.com/arangodb/kube-arangodb/tree/1.2.20) (2022-10-25)
- (Feature) Add action progress - (Feature) Add action progress

View file

@ -144,6 +144,8 @@ type Condition struct {
Message string `json:"message,omitempty"` Message string `json:"message,omitempty"`
// Hash keep propagation hash id, for example checksum of secret // Hash keep propagation hash id, for example checksum of secret
Hash string `json:"hash,omitempty"` Hash string `json:"hash,omitempty"`
// Params keeps additional params for the condition
Params ConditionParams `json:"params,omitempty"`
} }
func (c Condition) IsTrue() bool { func (c Condition) IsTrue() bool {
@ -154,6 +156,7 @@ func (c Condition) IsTrue() bool {
func (c Condition) Equal(other Condition) bool { func (c Condition) Equal(other Condition) bool {
return c.Type == other.Type && return c.Type == other.Type &&
c.Status == other.Status && c.Status == other.Status &&
c.Params.Equal(other.Params) &&
util.TimeCompareEqual(c.LastUpdateTime, other.LastUpdateTime) && util.TimeCompareEqual(c.LastUpdateTime, other.LastUpdateTime) &&
util.TimeCompareEqual(c.LastTransitionTime, other.LastTransitionTime) && util.TimeCompareEqual(c.LastTransitionTime, other.LastTransitionTime) &&
c.Reason == other.Reason && c.Reason == other.Reason &&

View file

@ -0,0 +1,46 @@
//
// DISCLAIMER
//
// Copyright 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 v1
const (
// ConditionParamContainerUpdatingName define parameter used during Image Runtime update
ConditionParamContainerUpdatingName ConditionParam = "updatingContainerName"
)
// ConditionParam is a strongly typed condition parameter
type ConditionParam string
type ConditionParams map[ConditionParam]string
// Equal compare two ConditionParams objects
func (c ConditionParams) Equal(b ConditionParams) bool {
if len(c) != len(b) {
return false
}
for k, v := range c {
if v2, ok := b[k]; !ok || v != v2 {
return false
}
}
return true
}

View file

@ -809,6 +809,13 @@ func (in *Condition) DeepCopyInto(out *Condition) {
*out = *in *out = *in
in.LastUpdateTime.DeepCopyInto(&out.LastUpdateTime) in.LastUpdateTime.DeepCopyInto(&out.LastUpdateTime)
in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
if in.Params != nil {
in, out := &in.Params, &out.Params
*out = make(ConditionParams, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
return return
} }
@ -844,6 +851,28 @@ func (in ConditionList) DeepCopy() ConditionList {
return *out return *out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in ConditionParams) DeepCopyInto(out *ConditionParams) {
{
in := &in
*out = make(ConditionParams, len(*in))
for key, val := range *in {
(*out)[key] = val
}
return
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConditionParams.
func (in ConditionParams) DeepCopy() ConditionParams {
if in == nil {
return nil
}
out := new(ConditionParams)
in.DeepCopyInto(out)
return *out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DatabaseSpec) DeepCopyInto(out *DatabaseSpec) { func (in *DatabaseSpec) DeepCopyInto(out *DatabaseSpec) {
*out = *in *out = *in

View file

@ -144,6 +144,8 @@ type Condition struct {
Message string `json:"message,omitempty"` Message string `json:"message,omitempty"`
// Hash keep propagation hash id, for example checksum of secret // Hash keep propagation hash id, for example checksum of secret
Hash string `json:"hash,omitempty"` Hash string `json:"hash,omitempty"`
// Params keeps additional params for the condition
Params ConditionParams `json:"params,omitempty"`
} }
func (c Condition) IsTrue() bool { func (c Condition) IsTrue() bool {
@ -154,6 +156,7 @@ func (c Condition) IsTrue() bool {
func (c Condition) Equal(other Condition) bool { func (c Condition) Equal(other Condition) bool {
return c.Type == other.Type && return c.Type == other.Type &&
c.Status == other.Status && c.Status == other.Status &&
c.Params.Equal(other.Params) &&
util.TimeCompareEqual(c.LastUpdateTime, other.LastUpdateTime) && util.TimeCompareEqual(c.LastUpdateTime, other.LastUpdateTime) &&
util.TimeCompareEqual(c.LastTransitionTime, other.LastTransitionTime) && util.TimeCompareEqual(c.LastTransitionTime, other.LastTransitionTime) &&
c.Reason == other.Reason && c.Reason == other.Reason &&

View file

@ -0,0 +1,46 @@
//
// DISCLAIMER
//
// Copyright 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 v2alpha1
const (
// ConditionParamContainerUpdatingName define parameter used during Image Runtime update
ConditionParamContainerUpdatingName ConditionParam = "updatingContainerName"
)
// ConditionParam is a strongly typed condition parameter
type ConditionParam string
type ConditionParams map[ConditionParam]string
// Equal compare two ConditionParams objects
func (c ConditionParams) Equal(b ConditionParams) bool {
if len(c) != len(b) {
return false
}
for k, v := range c {
if v2, ok := b[k]; !ok || v != v2 {
return false
}
}
return true
}

View file

@ -809,6 +809,13 @@ func (in *Condition) DeepCopyInto(out *Condition) {
*out = *in *out = *in
in.LastUpdateTime.DeepCopyInto(&out.LastUpdateTime) in.LastUpdateTime.DeepCopyInto(&out.LastUpdateTime)
in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
if in.Params != nil {
in, out := &in.Params, &out.Params
*out = make(ConditionParams, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
return return
} }
@ -844,6 +851,28 @@ func (in ConditionList) DeepCopy() ConditionList {
return *out return *out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in ConditionParams) DeepCopyInto(out *ConditionParams) {
{
in := &in
*out = make(ConditionParams, len(*in))
for key, val := range *in {
(*out)[key] = val
}
return
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConditionParams.
func (in ConditionParams) DeepCopy() ConditionParams {
if in == nil {
return nil
}
out := new(ConditionParams)
in.DeepCopyInto(out)
return *out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DatabaseSpec) DeepCopyInto(out *DatabaseSpec) { func (in *DatabaseSpec) DeepCopyInto(out *DatabaseSpec) {
*out = *in *out = *in

View file

@ -81,6 +81,22 @@ func getActionPost(a Action, ctx context.Context) error {
} }
} }
// ActionPre keep interface which is executed before action is started.
type ActionPre interface {
Action
// Pre execute after action is completed
Pre(ctx context.Context) error
}
func getActionPre(a Action, ctx context.Context) error {
if c, ok := a.(ActionPre); !ok {
return nil
} else {
return c.Pre(ctx)
}
}
// ActionReloadCachedStatus keeps information about CachedStatus reloading (executed after action has been executed) // ActionReloadCachedStatus keeps information about CachedStatus reloading (executed after action has been executed)
type ActionReloadCachedStatus interface { type ActionReloadCachedStatus interface {
Action Action

View file

@ -43,12 +43,44 @@ func newRuntimeContainerImageUpdateAction(action api.Action, actionCtx ActionCon
} }
var _ ActionPost = &actionRuntimeContainerImageUpdate{} var _ ActionPost = &actionRuntimeContainerImageUpdate{}
var _ ActionPre = &actionRuntimeContainerImageUpdate{}
type actionRuntimeContainerImageUpdate struct { type actionRuntimeContainerImageUpdate struct {
// actionImpl implement timeout and member id functions // actionImpl implement timeout and member id functions
actionImpl actionImpl
} }
func (a actionRuntimeContainerImageUpdate) Pre(ctx context.Context) error {
a.log.Info("Updating member condition")
m, ok := a.actionCtx.GetMemberStatusByID(a.action.MemberID)
if !ok {
a.log.Info("member is gone already")
return nil
}
cname, _, ok := a.getContainerDetails()
if !ok {
a.log.Info("Unable to find container details")
return nil
}
if c, ok := m.Conditions.Get(api.ConditionTypeUpdating); ok {
if c.Params == nil {
c.Params = api.ConditionParams{}
}
if c.Params[api.ConditionParamContainerUpdatingName] != cname {
c.Params[api.ConditionParamContainerUpdatingName] = cname
if err := a.actionCtx.UpdateMember(ctx, m); err != nil {
return err
}
}
}
return nil
}
func (a actionRuntimeContainerImageUpdate) Post(ctx context.Context) error { func (a actionRuntimeContainerImageUpdate) Post(ctx context.Context) error {
a.log.Info("Updating container image") a.log.Info("Updating container image")
m, ok := a.actionCtx.GetMemberStatusByID(a.action.MemberID) m, ok := a.actionCtx.GetMemberStatusByID(a.action.MemberID)
@ -57,6 +89,22 @@ func (a actionRuntimeContainerImageUpdate) Post(ctx context.Context) error {
return nil return nil
} }
if c, ok := m.Conditions.Get(api.ConditionTypeUpdating); ok {
if c.Params != nil {
if _, ok := c.Params[api.ConditionParamContainerUpdatingName]; ok {
delete(c.Params, api.ConditionParamContainerUpdatingName)
if len(c.Params) == 0 {
c.Params = nil
}
if err := a.actionCtx.UpdateMember(ctx, m); err != nil {
return err
}
}
}
}
cname, image, ok := a.getContainerDetails() cname, image, ok := a.getContainerDetails()
if !ok { if !ok {
a.log.Info("Unable to find container details") a.log.Info("Unable to find container details")

View file

@ -63,5 +63,9 @@ func (a *actionWaitForMemberReady) CheckProgress(ctx context.Context) (bool, boo
return true, false, nil return true, false, nil
} }
if a.actionCtx.GetMode() == api.DeploymentModeActiveFailover {
return true, false, nil
}
return member.Conditions.IsTrue(api.ConditionTypeReady), false, nil return member.Conditions.IsTrue(api.ConditionTypeReady), false, nil
} }

View file

@ -183,7 +183,7 @@ func (r *Reconciler) updateMemberRotationConditions(apiObject k8sutil.APIObject,
return nil, nil return nil, nil
} }
if m, _, reason, err := rotation.IsRotationRequired(context.ACS(), spec, member, group, p, arangoMember.Spec.Template, arangoMember.Status.Template); err != nil { if m, _, checksum, reason, err := rotation.IsRotationRequired(context.ACS(), spec, member, group, p, arangoMember.Spec.Template, arangoMember.Status.Template); err != nil {
r.log.Err(err).Error("Error while getting rotation details") r.log.Err(err).Error("Error while getting rotation details")
return nil, err return nil, err
} else { } else {
@ -208,7 +208,7 @@ func (r *Reconciler) updateMemberRotationConditions(apiObject k8sutil.APIObject,
return api.Plan{actions.NewAction(api.ActionTypeSetMemberCondition, group, member, reason).AddParam(api.ConditionTypePendingUpdate.String(), "T")}, nil return api.Plan{actions.NewAction(api.ActionTypeSetMemberCondition, group, member, reason).AddParam(api.ConditionTypePendingUpdate.String(), "T")}, nil
case rotation.SilentRotation: case rotation.SilentRotation:
// Propagate changes without restart // Propagate changes without restart
return api.Plan{actions.NewAction(api.ActionTypeArangoMemberUpdatePodStatus, group, member, "Propagating status of pod").AddParam(ActionTypeArangoMemberUpdatePodStatusChecksum, arangoMember.Spec.Template.GetChecksum())}, nil return api.Plan{actions.NewAction(api.ActionTypeArangoMemberUpdatePodStatus, group, member, "Propagating status of pod").AddParam(ActionTypeArangoMemberUpdatePodStatusChecksum, checksum)}, nil
case rotation.GracefulRotation: case rotation.GracefulRotation:
if reason != "" { if reason != "" {
r.log.Bool("enforced", false).Info(reason) r.log.Bool("enforced", false).Info(reason)

View file

@ -191,7 +191,7 @@ func (r *Reconciler) createUpdatePlanInternal(apiObject k8sutil.APIObject, spec
p = nil p = nil
} }
if mode, p, 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 != rotation.InPlaceRotation {
@ -201,6 +201,8 @@ func (r *Reconciler) createUpdatePlanInternal(apiObject k8sutil.APIObject, spec
} else { } else {
p = withWaitForMember(p, m.Group, m.Member) p = withWaitForMember(p, m.Group, m.Member)
p = append(p, actions.NewAction(api.ActionTypeArangoMemberUpdatePodStatus, m.Group, m.Member, "Propagating status of pod").AddParam(ActionTypeArangoMemberUpdatePodStatusChecksum, checksum))
p = p.Wrap(actions.NewAction(api.ActionTypeSetMemberCondition, m.Group, m.Member, reason). p = p.Wrap(actions.NewAction(api.ActionTypeSetMemberCondition, m.Group, m.Member, reason).
AddParam(api.ConditionTypePendingUpdate.String(), "").AddParam(api.ConditionTypeUpdating.String(), "T"), AddParam(api.ConditionTypePendingUpdate.String(), "").AddParam(api.ConditionTypeUpdating.String(), "T"),
actions.NewAction(api.ActionTypeSetMemberCondition, m.Group, m.Member, reason). actions.NewAction(api.ActionTypeSetMemberCondition, m.Group, m.Member, reason).

View file

@ -219,6 +219,13 @@ func (d *Reconciler) executePlan(ctx context.Context, statusPlan api.Plan, pg pl
action, actionContext := d.createAction(planAction) action, actionContext := d.createAction(planAction)
if !planAction.IsStarted() {
if err := getActionPre(action, ctx); err != nil {
d.planLogger.Err(err).Error("Pre action failed")
return nil, false, false, errors.WithStack(err)
}
}
done, abort, recall, retry, err := d.executeAction(ctx, planAction, action) done, abort, recall, retry, err := d.executeAction(ctx, planAction, action)
if err != nil { if err != nil {
if retry { if retry {

View file

@ -128,6 +128,15 @@ func (r *Resources) InspectPods(ctx context.Context, cachedStatus inspectorInter
spec := r.context.GetSpec() spec := r.context.GetSpec()
coreContainers := spec.GetCoreContainers(group) coreContainers := spec.GetCoreContainers(group)
if c, ok := memberStatus.Conditions.Get(api.ConditionTypeUpdating); ok {
if v, ok := c.Params[api.ConditionParamContainerUpdatingName]; ok {
// We are in update phase, container needs to be ignored
if v != "" {
coreContainers = coreContainers.Remove(v)
}
}
}
// Update state // Update state
updateMemberStatusNeeded := false updateMemberStatusNeeded := false
if k8sutil.IsPodSucceeded(pod, coreContainers) { if k8sutil.IsPodSucceeded(pod, coreContainers) {

View file

@ -57,7 +57,7 @@ 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, 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 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
@ -126,10 +126,10 @@ func IsRotationRequired(acs sutil.ACS, spec api.DeploymentSpec, member api.Membe
} }
if mode, plan, err := compare(spec, member, group, specTemplate, statusTemplate); err != nil { if mode, plan, err := compare(spec, member, group, specTemplate, statusTemplate); err != nil {
return SkippedRotation, nil, "", err return SkippedRotation, nil, specTemplate.Checksum, "", err
} else if mode == SkippedRotation { } else if mode == SkippedRotation {
return mode, plan, "No rotation needed", nil return mode, plan, specTemplate.Checksum, "No rotation needed", nil
} else { } else {
return mode, plan, "Pod needs rotation", nil return mode, plan, specTemplate.Checksum, "Pod needs rotation", nil
} }
} }