From e4ec72f6885df7b03cf1b1d7669fd572e994f18e Mon Sep 17 00:00:00 2001 From: ajanikow Date: Fri, 27 Mar 2020 12:31:26 +0000 Subject: [PATCH 1/3] Add UpToDate Confition --- CHANGELOG.md | 1 + pkg/apis/deployment/v1/conditions.go | 13 +- pkg/apis/deployment/v1/deployment.go | 10 + pkg/apis/deployment/v1/deployment_spec.go | 13 + pkg/apis/deployment/v1/deployment_status.go | 3 + pkg/apis/deployment/v1/plan.go | 30 +- pkg/deployment/context_impl.go | 23 ++ pkg/deployment/deployment_inspector.go | 277 ++++++++++-------- pkg/deployment/reconcile/action.go | 32 ++ pkg/deployment/reconcile/action_add_member.go | 45 ++- .../reconcile/action_cleanout_member.go | 34 +-- pkg/deployment/reconcile/action_context.go | 8 + .../action_disable_scaling_cluster.go | 46 ++- .../action_enable_scaling_cluster.go | 46 ++- pkg/deployment/reconcile/action_helper.go | 62 ++++ pkg/deployment/reconcile/action_pvc_resize.go | 34 +-- .../reconcile/action_pvc_resized.go | 42 +-- .../reconcile/action_recreate_member.go | 44 +-- .../reconcile/action_remove_member.go | 44 +-- .../action_renew_tls_ca_certificate.go | 26 +- .../reconcile/action_renew_tls_certificate.go | 43 +-- .../reconcile/action_rotate_member.go | 34 +-- .../reconcile/action_rotate_start_member.go | 34 +-- .../reconcile/action_rotate_stop_member.go | 43 +-- .../reconcile/action_shutdown_member.go | 36 +-- .../reconcile/action_upgrade_current_image.go | 34 +-- .../reconcile/action_upgrade_member.go | 34 +-- .../reconcile/action_wait_for_member_up.go | 34 +-- pkg/deployment/reconcile/context.go | 2 + pkg/deployment/reconcile/plan_executor.go | 45 +-- pkg/deployment/reconcile/timeouts.go | 3 + pkg/deployment/resources/context.go | 2 + 32 files changed, 615 insertions(+), 562 deletions(-) create mode 100644 pkg/deployment/reconcile/action_helper.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cd2f63e0..441575412 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Change Log ## [master](https://github.com/arangodb/kube-arangodb/tree/master) (N/A) +- Added UpToDate condition in ArangoDeployment Status ## [1.0.1](https://github.com/arangodb/kube-arangodb/tree/1.0.1) (2020-03-25) - Added Customizable Affinity settings for ArangoDB Member Pods diff --git a/pkg/apis/deployment/v1/conditions.go b/pkg/apis/deployment/v1/conditions.go index 1525ed7fe..1fc8bcb3f 100644 --- a/pkg/apis/deployment/v1/conditions.go +++ b/pkg/apis/deployment/v1/conditions.go @@ -59,6 +59,8 @@ const ( ConditionTypeBootstrapSucceded ConditionType = "BootstrapSucceded" // ConditionTypeTerminating indicates that the member is terminating but not yet terminated. ConditionTypeTerminating ConditionType = "Terminating" + // ConditionTypeTerminating indicates that the deployment is up to date. + ConditionTypeUpToDate ConditionType = "UpToDate" ) // Condition represents one current condition of a deployment or deployment member. @@ -79,6 +81,10 @@ type Condition struct { Message string `json:"message,omitempty"` } +func (c Condition) IsTrue() bool { + return c.Status == v1.ConditionTrue +} + // ConditionList is a list of conditions. // Each type is allowed only once. type ConditionList []Condition @@ -116,12 +122,17 @@ func (c Condition) Equal(other Condition) bool { // IsTrue return true when a condition with given type exists and its status is `True`. func (list ConditionList) IsTrue(conditionType ConditionType) bool { c, found := list.Get(conditionType) - return found && c.Status == v1.ConditionTrue + return found && c.IsTrue() } // Get a condition by type. // Returns true if found, false if not found. func (list ConditionList) Get(conditionType ConditionType) (Condition, bool) { + // Covers nil and empty lists + if len(list) == 0 { + return Condition{}, false + } + for _, x := range list { if x.Type == conditionType { return x, true diff --git a/pkg/apis/deployment/v1/deployment.go b/pkg/apis/deployment/v1/deployment.go index b9d14d80a..39bffa8dc 100644 --- a/pkg/apis/deployment/v1/deployment.go +++ b/pkg/apis/deployment/v1/deployment.go @@ -93,3 +93,13 @@ func (d *ArangoDeployment) ForeachServerGroup(cb ServerGroupFunc, status *Deploy } return nil } + +// IsUpToDate checks if applied version match current version in spec +func (d ArangoDeployment) IsUpToDate() (bool, error) { + sha, err := d.Spec.Checksum() + if err != nil { + return false, err + } + + return sha == d.Status.AppliedVersion && d.Status.Conditions.IsTrue(ConditionTypeUpToDate), nil +} diff --git a/pkg/apis/deployment/v1/deployment_spec.go b/pkg/apis/deployment/v1/deployment_spec.go index 311d63875..4644e7565 100644 --- a/pkg/apis/deployment/v1/deployment_spec.go +++ b/pkg/apis/deployment/v1/deployment_spec.go @@ -23,6 +23,9 @@ package v1 import ( + "crypto/sha256" + "encoding/json" + "fmt" "reflect" "github.com/arangodb/kube-arangodb/pkg/util" @@ -413,3 +416,13 @@ func (s DeploymentSpec) ResetImmutableFields(target *DeploymentSpec) []string { } return resetFields } + +// Checksum return checksum of current ArangoDeployment Spec section +func (s DeploymentSpec) Checksum() (string, error) { + data, err := json.Marshal(s) + if err != nil { + return "", err + } + + return fmt.Sprintf("%0x", sha256.Sum256(data)), nil +} diff --git a/pkg/apis/deployment/v1/deployment_status.go b/pkg/apis/deployment/v1/deployment_status.go index ff64cca98..8fc4baab5 100644 --- a/pkg/apis/deployment/v1/deployment_status.go +++ b/pkg/apis/deployment/v1/deployment_status.go @@ -33,6 +33,9 @@ type DeploymentStatus struct { // Reason contains a human readable reason for reaching the current state (can be empty) Reason string `json:"reason,omitempty"` // Reason for current state + // AppliedVersion defines checksum of applied spec + AppliedVersion string `json:"appliedVersion"` + // ServiceName holds the name of the Service a client can use (inside the k8s cluster) // to access ArangoDB. ServiceName string `json:"serviceName,omitempty"` diff --git a/pkg/apis/deployment/v1/plan.go b/pkg/apis/deployment/v1/plan.go index 6715f0347..5eb3dbd2b 100644 --- a/pkg/apis/deployment/v1/plan.go +++ b/pkg/apis/deployment/v1/plan.go @@ -25,6 +25,7 @@ package v1 import ( "github.com/arangodb/kube-arangodb/pkg/util" "github.com/dchest/uniuri" + "k8s.io/apimachinery/pkg/api/equality" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -66,6 +67,8 @@ const ( ActionTypePVCResize ActionType = "PVCResize" // ActionTypePVCResized waits for PVC to resize for defined time ActionTypePVCResized ActionType = "PVCResized" + // UpToDateUpdateResized define up to date annotation in spec + UpToDateUpdate ActionType = "UpToDateUpdate" ) const ( @@ -92,6 +95,8 @@ type Action struct { Reason string `json:"reason,omitempty"` // Image used in can of a SetCurrentImage action. Image string `json:"image,omitempty"` + // Params additional parameters used for action + Params map[string]interface{} `json:"params,omitempty"` } // Equal compares two Actions @@ -103,7 +108,30 @@ func (a Action) Equal(other Action) bool { util.TimeCompareEqual(a.CreationTime, other.CreationTime) && util.TimeCompareEqualPointer(a.StartTime, other.StartTime) && a.Reason == other.Reason && - a.Image == other.Image + a.Image == other.Image && + equality.Semantic.DeepEqual(a.Params, other.Params) +} + +// AddParam returns copy of action with set parameter +func (a Action) AddParam(key string, value interface{}) Action { + if a.Params == nil { + a.Params = map[string]interface{}{} + } + + a.Params[key] = value + + return a +} + +// GetParam returns action parameter +func (a Action) GetParam(key string) (interface{}, bool) { + if a.Params == nil { + return nil, false + } + + i, ok := a.Params[key] + + return i, ok } // NewAction instantiates a new Action. diff --git a/pkg/deployment/context_impl.go b/pkg/deployment/context_impl.go index d0563c743..bf9ee9a46 100644 --- a/pkg/deployment/context_impl.go +++ b/pkg/deployment/context_impl.go @@ -103,6 +103,10 @@ func (d *Deployment) GetStatus() (api.DeploymentStatus, int32) { d.status.mutex.Lock() defer d.status.mutex.Unlock() + return d.getStatus() +} + +func (d *Deployment) getStatus() (api.DeploymentStatus, int32) { version := d.status.version return *d.status.last.DeepCopy(), version } @@ -115,6 +119,10 @@ func (d *Deployment) UpdateStatus(status api.DeploymentStatus, lastVersion int32 d.status.mutex.Lock() defer d.status.mutex.Unlock() + return d.updateStatus(status, lastVersion, force...) +} + +func (d *Deployment) updateStatus(status api.DeploymentStatus, lastVersion int32, force ...bool) error { if d.status.version != lastVersion { // Status is obsolete d.deps.Log.Error(). @@ -483,3 +491,18 @@ func (d *Deployment) GetMetricsExporterImage() string { func (d *Deployment) GetArangoImage() string { return d.config.ArangoImage } + +func (d *Deployment) WithStatusUpdate(action func(s *api.DeploymentStatus) bool, force ...bool) error { + d.status.mutex.Lock() + defer d.status.mutex.Unlock() + + status, version := d.getStatus() + + changed := action(&status) + + if !changed { + return nil + } + + return d.updateStatus(status, version, force...) +} diff --git a/pkg/deployment/deployment_inspector.go b/pkg/deployment/deployment_inspector.go index 98686ba15..d31061817 100644 --- a/pkg/deployment/deployment_inspector.go +++ b/pkg/deployment/deployment_inspector.go @@ -26,6 +26,8 @@ import ( "context" "time" + "github.com/pkg/errors" + "github.com/arangodb/kube-arangodb/pkg/apis/deployment" api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" @@ -84,151 +86,188 @@ func (d *Deployment) inspectDeployment(lastInterval util.Interval) util.Interval return nextInterval } - // Inspect secret hashes - if err := d.resources.ValidateSecretHashes(); err != nil { + if inspectNextInterval, err := d.inspectDeploymentWithError(ctx, nextInterval); err != nil { + nextInterval = inspectNextInterval hasError = true - d.CreateEvent(k8sutil.NewErrorEvent("Secret hash validation failed", err, d.apiObject)) - } - // Check for LicenseKeySecret - if err := d.resources.ValidateLicenseKeySecret(); err != nil { - hasError = true - d.CreateEvent(k8sutil.NewErrorEvent("License Key Secret invalid", err, d.apiObject)) - } - - // Is the deployment in a good state? - status, _ := d.GetStatus() - if status.Conditions.IsTrue(api.ConditionTypeSecretsChanged) { - log.Debug().Msg("Condition SecretsChanged is true. Revert secrets before we can continue") - return nextInterval + d.CreateEvent(k8sutil.NewErrorEvent("Reconcilation failed", err, d.apiObject)) } + } - // Ensure we have image info - if retrySoon, err := d.ensureImages(d.apiObject); err != nil { - hasError = true - d.CreateEvent(k8sutil.NewErrorEvent("Image detection failed", err, d.apiObject)) - } else if retrySoon { + // Update next interval (on errors) + if hasError { + if d.recentInspectionErrors == 0 { nextInterval = minInspectionInterval + d.recentInspectionErrors++ } + } else { + d.recentInspectionErrors = 0 + } + return nextInterval.ReduceTo(maxInspectionInterval) +} - // Inspection of generated resources needed - if x, err := d.resources.InspectPods(ctx); err != nil { - hasError = true - d.CreateEvent(k8sutil.NewErrorEvent("Pod inspection failed", err, d.apiObject)) - } else { - nextInterval = nextInterval.ReduceTo(x) - } - if x, err := d.resources.InspectPVCs(ctx); err != nil { - hasError = true - d.CreateEvent(k8sutil.NewErrorEvent("PVC inspection failed", err, d.apiObject)) - } else { - nextInterval = nextInterval.ReduceTo(x) - } +func (d *Deployment) inspectDeploymentWithError(ctx context.Context, lastInterval util.Interval) (nextInterval util.Interval, inspectError error) { + // Ensure that spec and status checksum are same + spec := d.GetSpec() + status, _ := d.getStatus() - // Check members for resilience - if err := d.resilience.CheckMemberFailure(); err != nil { - hasError = true - d.CreateEvent(k8sutil.NewErrorEvent("Member failure detection failed", err, d.apiObject)) - } + nextInterval = lastInterval + inspectError = nil - // Immediate actions - if err := d.reconciler.CheckDeployment(); err != nil { - hasError = true - d.CreateEvent(k8sutil.NewErrorEvent("Reconciler immediate actions failed", err, d.apiObject)) - } + checksum, err := spec.Checksum() + if err != nil { + return minInspectionInterval, errors.Wrapf(err, "Calculation of spec failed") + } else { + condition, exists := status.Conditions.Get(api.ConditionTypeUpToDate) + if (checksum != status.AppliedVersion && (!exists || condition.IsTrue())) || + (checksum == status.AppliedVersion && (!exists || !condition.IsTrue())) { + if err = d.WithStatusUpdate(func(s *api.DeploymentStatus) bool { + if checksum == status.AppliedVersion { + return s.Conditions.Update(api.ConditionTypeUpToDate, true, "Everything is UpToDate", "Spec applied") + } + return s.Conditions.Update(api.ConditionTypeUpToDate, false, "Spec Changed", "Spec Object changed. Waiting until plan will be applied") + }); err != nil { + return minInspectionInterval, errors.Wrapf(err, "Unable to update UpToDate condition") + } - // Create scale/update plan - if err := d.reconciler.CreatePlan(); err != nil { - hasError = true - d.CreateEvent(k8sutil.NewErrorEvent("Plan creation failed", err, d.apiObject)) + return minInspectionInterval, nil // Retry ASAP } + } - // Execute current step of scale/update plan - retrySoon, err := d.reconciler.ExecutePlan(ctx) - if err != nil { - hasError = true - d.CreateEvent(k8sutil.NewErrorEvent("Plan execution failed", err, d.apiObject)) - } - if retrySoon { - nextInterval = minInspectionInterval - } + // Inspect secret hashes + if err := d.resources.ValidateSecretHashes(); err != nil { + return minInspectionInterval, errors.Wrapf(err, "Secret hash validation failed") + } - // Ensure all resources are created - if err := d.resources.EnsureSecrets(); err != nil { - hasError = true - d.CreateEvent(k8sutil.NewErrorEvent("Secret creation failed", err, d.apiObject)) - } - if err := d.resources.EnsureServices(); err != nil { - hasError = true - d.CreateEvent(k8sutil.NewErrorEvent("Service creation failed", err, d.apiObject)) - } - if d.haveServiceMonitorCRD { - if err := d.resources.EnsureServiceMonitor(); err != nil { - hasError = true - d.CreateEvent(k8sutil.NewErrorEvent("Service monitor creation failed", err, d.apiObject)) + // Check for LicenseKeySecret + if err := d.resources.ValidateLicenseKeySecret(); err != nil { + return minInspectionInterval, errors.Wrapf(err, "License Key Secret invalid") + } + + // Is the deployment in a good state? + if status.Conditions.IsTrue(api.ConditionTypeSecretsChanged) { + return minInspectionInterval, errors.Errorf("Secrets changed") + } + + // Ensure we have image info + if retrySoon, err := d.ensureImages(d.apiObject); err != nil { + return minInspectionInterval, errors.Wrapf(err, "Image detection failed") + } else if retrySoon { + return minInspectionInterval, nil + } + + // Inspection of generated resources needed + if x, err := d.resources.InspectPods(ctx); err != nil { + return minInspectionInterval, errors.Wrapf(err, "Pod inspection failed") + } else { + nextInterval = nextInterval.ReduceTo(x) + } + if x, err := d.resources.InspectPVCs(ctx); err != nil { + return minInspectionInterval, errors.Wrapf(err, "PVC inspection failed") + } else { + nextInterval = nextInterval.ReduceTo(x) + } + + // Check members for resilience + if err := d.resilience.CheckMemberFailure(); err != nil { + return minInspectionInterval, errors.Wrapf(err, "Member failure detection failed") + } + + // Immediate actions + if err := d.reconciler.CheckDeployment(); err != nil { + return minInspectionInterval, errors.Wrapf(err, "Reconciler immediate actions failed") + } + + if interval, err := d.ensureResources(nextInterval); err != nil { + return minInspectionInterval, errors.Wrapf(err, "Reconciler resource recreation failed") + } else { + nextInterval = interval + } + + // Create scale/update plan + if err := d.reconciler.CreatePlan(); err != nil { + return minInspectionInterval, errors.Wrapf(err, "Plan creation failed") + } + + // Execute current step of scale/update plan + retrySoon, err := d.reconciler.ExecutePlan(ctx) + if err != nil { + return minInspectionInterval, errors.Wrapf(err, "Plan execution failed") + } + if retrySoon { + nextInterval = minInspectionInterval + } else { + // Do not retry - so plan is empty + if status.AppliedVersion != checksum { + if err := d.WithStatusUpdate(func(s *api.DeploymentStatus) bool { + s.AppliedVersion = checksum + return true + }); err != nil { + return minInspectionInterval, errors.Wrapf(err, "Unable to update UpToDate condition") } - } - if err := d.resources.EnsurePVCs(); err != nil { - hasError = true - d.CreateEvent(k8sutil.NewErrorEvent("PVC creation failed", err, d.apiObject)) - } - if err := d.resources.EnsurePods(); err != nil { - hasError = true - d.CreateEvent(k8sutil.NewErrorEvent("Pod creation failed", err, d.apiObject)) - } - if err := d.resources.EnsurePDBs(); err != nil { - hasError = true - d.CreateEvent(k8sutil.NewErrorEvent("PDB creation failed", err, d.apiObject)) + return minInspectionInterval, nil } + } - if err := d.resources.EnsureAnnotations(); err != nil { - hasError = true - d.CreateEvent(k8sutil.NewErrorEvent("Annotation update failed", err, d.apiObject)) - } + // Create access packages + if err := d.createAccessPackages(); err != nil { + return minInspectionInterval, errors.Wrapf(err, "AccessPackage creation failed") + } - // Create access packages - if err := d.createAccessPackages(); err != nil { - hasError = true - d.CreateEvent(k8sutil.NewErrorEvent("AccessPackage creation failed", err, d.apiObject)) - } + // Ensure deployment bootstrap + if err := d.EnsureBootstrap(); err != nil { + return minInspectionInterval, errors.Wrapf(err, "Bootstrap failed") + } - // Ensure deployment bootstrap - if err := d.EnsureBootstrap(); err != nil { - hasError = true - d.CreateEvent(k8sutil.NewErrorEvent("Bootstrap failed", err, d.apiObject)) - } + // Inspect deployment for obsolete members + if err := d.resources.CleanupRemovedMembers(); err != nil { + return minInspectionInterval, errors.Wrapf(err, "Removed member cleanup failed") + } - // Inspect deployment for obsolete members - if err := d.resources.CleanupRemovedMembers(); err != nil { - hasError = true - d.CreateEvent(k8sutil.NewErrorEvent("Removed member cleanup failed", err, d.apiObject)) - } + if err := d.backup.CheckRestore(); err != nil { + return minInspectionInterval, errors.Wrapf(err, "Restore operation failed") + } - if err := d.backup.CheckRestore(); err != nil { - hasError = true - d.CreateEvent(k8sutil.NewErrorEvent("Restore operation failed", err, d.apiObject)) - } + // At the end of the inspect, we cleanup terminated pods. + if x, err := d.resources.CleanupTerminatedPods(); err != nil { + return minInspectionInterval, errors.Wrapf(err, "Pod cleanup failed") + } else { + nextInterval = nextInterval.ReduceTo(x) + } - // At the end of the inspect, we cleanup terminated pods. - if x, err := d.resources.CleanupTerminatedPods(); err != nil { - hasError = true - d.CreateEvent(k8sutil.NewErrorEvent("Pod cleanup failed", err, d.apiObject)) - } else { - nextInterval = nextInterval.ReduceTo(x) + return +} + +func ( d *Deployment) ensureResources(lastInterval util.Interval) (util.Interval, error) { + // Ensure all resources are created + if err := d.resources.EnsureSecrets(); err != nil { + return minInspectionInterval, errors.Wrapf(err, "Secret creation failed") + } + if err := d.resources.EnsureServices(); err != nil { + return minInspectionInterval, errors.Wrapf(err, "Service creation failed") + } + if d.haveServiceMonitorCRD { + if err := d.resources.EnsureServiceMonitor(); err != nil { + return minInspectionInterval, errors.Wrapf(err, "Service monitor creation failed") } } - // Update next interval (on errors) - if hasError { - if d.recentInspectionErrors == 0 { - nextInterval = minInspectionInterval - d.recentInspectionErrors++ - } - } else { - d.recentInspectionErrors = 0 + if err := d.resources.EnsurePVCs(); err != nil { + return minInspectionInterval, errors.Wrapf(err, "PVC creation failed") } - return nextInterval.ReduceTo(maxInspectionInterval) + if err := d.resources.EnsurePods(); err != nil { + return minInspectionInterval, errors.Wrapf(err, "Pod creation failed") + } + if err := d.resources.EnsurePDBs(); err != nil { + return minInspectionInterval, errors.Wrapf(err, "PDB creation failed") + } + + if err := d.resources.EnsureAnnotations(); err != nil { + return minInspectionInterval, errors.Wrapf(err, "Annotation update failed") + } + + return lastInterval, nil } // triggerInspection ensures that an inspection is run soon. diff --git a/pkg/deployment/reconcile/action.go b/pkg/deployment/reconcile/action.go index c4b1accb1..4922707c8 100644 --- a/pkg/deployment/reconcile/action.go +++ b/pkg/deployment/reconcile/action.go @@ -24,7 +24,12 @@ package reconcile import ( "context" + "fmt" + "sync" "time" + + api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" + "github.com/rs/zerolog" ) // Action executes a single Plan item. @@ -41,3 +46,30 @@ type Action interface { // Return the MemberID used / created in this action MemberID() string } + +type actionFactory func(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action + +var ( + actions = map[api.ActionType]actionFactory{} + actionsLock sync.Mutex +) + +func registerAction(t api.ActionType, f actionFactory) { + actionsLock.Lock() + defer actionsLock.Unlock() + + _, ok := actions[t] + if ok { + panic(fmt.Sprintf("Action already defined %s", t)) + } + + actions[t] = f +} + +func getActionFactory(t api.ActionType) (actionFactory, bool) { + actionsLock.Lock() + defer actionsLock.Unlock() + + f, ok := actions[t] + return f, ok +} diff --git a/pkg/deployment/reconcile/action_add_member.go b/pkg/deployment/reconcile/action_add_member.go index 71f13dde6..f85211fa1 100644 --- a/pkg/deployment/reconcile/action_add_member.go +++ b/pkg/deployment/reconcile/action_add_member.go @@ -24,28 +24,34 @@ package reconcile import ( "context" - "time" api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) -// NewAddMemberAction creates a new Action that implements the given +func init() { + registerAction(api.ActionTypeAddMember, newAddMemberAction) +} + +// newAddMemberAction creates a new Action that implements the given // planned AddMember action. -func NewAddMemberAction(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { - return &actionAddMember{ - log: log, - action: action, - actionCtx: actionCtx, - } +func newAddMemberAction(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { + a := &actionAddMember{} + + a.actionImpl = newActionImpl(log, action, actionCtx, addMemberTimeout, &a.newMemberID) + + return a } // actionAddMember implements an AddMemberAction. type actionAddMember struct { - log zerolog.Logger - action api.Action - actionCtx ActionContext + // actionImpl implement timeout and member id functions + actionImpl + + // actionEmptyCheckProgress implement check progress with empty implementation + actionEmptyCheckProgress + newMemberID string } @@ -61,20 +67,3 @@ func (a *actionAddMember) Start(ctx context.Context) (bool, error) { a.newMemberID = newID return true, nil } - -// CheckProgress checks the progress of the action. -// Returns true if the action is completely finished, false otherwise. -func (a *actionAddMember) CheckProgress(ctx context.Context) (bool, bool, error) { - // Nothing todo - return true, false, nil -} - -// Timeout returns the amount of time after which this action will timeout. -func (a *actionAddMember) Timeout() time.Duration { - return addMemberTimeout -} - -// Return the MemberID used / created in this action -func (a *actionAddMember) MemberID() string { - return a.newMemberID -} diff --git a/pkg/deployment/reconcile/action_cleanout_member.go b/pkg/deployment/reconcile/action_cleanout_member.go index 4bb223508..470a5339f 100644 --- a/pkg/deployment/reconcile/action_cleanout_member.go +++ b/pkg/deployment/reconcile/action_cleanout_member.go @@ -24,7 +24,6 @@ package reconcile import ( "context" - "time" driver "github.com/arangodb/go-driver" api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" @@ -33,21 +32,24 @@ import ( "github.com/arangodb/kube-arangodb/pkg/util/arangod" ) -// NewCleanOutMemberAction creates a new Action that implements the given +func init() { + registerAction(api.ActionTypeCleanOutMember, newCleanOutMemberAction) +} + +// newCleanOutMemberAction creates a new Action that implements the given // planned CleanOutMember action. -func NewCleanOutMemberAction(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { - return &actionCleanoutMember{ - log: log, - action: action, - actionCtx: actionCtx, - } +func newCleanOutMemberAction(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { + a := &actionCleanoutMember{} + + a.actionImpl = newActionImplDefRef(log, action, actionCtx, cleanoutMemberTimeout) + + return a } // actionCleanoutMember implements an CleanOutMemberAction. type actionCleanoutMember struct { - log zerolog.Logger - action api.Action - actionCtx ActionContext + // actionImpl implement timeout and member id functions + actionImpl } // Start performs the start of the action. @@ -154,13 +156,3 @@ func (a *actionCleanoutMember) CheckProgress(ctx context.Context) (bool, bool, e // Cleanout completed return true, false, nil } - -// Timeout returns the amount of time after which this action will timeout. -func (a *actionCleanoutMember) Timeout() time.Duration { - return cleanoutMemberTimeout -} - -// Return the MemberID used / created in this action -func (a *actionCleanoutMember) MemberID() string { - return a.action.MemberID -} diff --git a/pkg/deployment/reconcile/action_context.go b/pkg/deployment/reconcile/action_context.go index 30a7f07ad..a29047ab1 100644 --- a/pkg/deployment/reconcile/action_context.go +++ b/pkg/deployment/reconcile/action_context.go @@ -109,6 +109,8 @@ type ActionContext interface { DisableScalingCluster() error // EnableScalingCluster enables scaling DBservers and coordinators EnableScalingCluster() error + // WithStatusUpdate update status of ArangoDeployment with defined modifier. If action returns True action is taken + UpdateClusterCondition(conditionType api.ConditionType, status bool, reason, message string) error } // newActionContext creates a new ActionContext implementation. @@ -125,6 +127,12 @@ type actionContext struct { context Context } +func (ac *actionContext) UpdateClusterCondition(conditionType api.ConditionType, status bool, reason, message string) error { + return ac.context.WithStatusUpdate(func(s *api.DeploymentStatus) bool { + return s.Conditions.Update(conditionType, status, reason, message) + }) +} + func (ac *actionContext) GetPv(pvName string) (*v1.PersistentVolume, error) { return ac.context.GetPv(pvName) } diff --git a/pkg/deployment/reconcile/action_disable_scaling_cluster.go b/pkg/deployment/reconcile/action_disable_scaling_cluster.go index eff097270..b54f9691c 100644 --- a/pkg/deployment/reconcile/action_disable_scaling_cluster.go +++ b/pkg/deployment/reconcile/action_disable_scaling_cluster.go @@ -22,27 +22,32 @@ package reconcile import ( "context" - "time" api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" + "github.com/arangodb/kube-arangodb/pkg/util" "github.com/rs/zerolog" ) +func init() { + registerAction(api.ActionTypeDisableClusterScaling, newDisableScalingCluster) +} + +// newDisableScalingCluster creates the new action with disabling scaling DBservers and coordinators. +func newDisableScalingCluster(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { + a := &actionDisableScalingCluster{} + + a.actionImpl = newActionImpl(log, action, actionCtx, 0, util.NewString("")) + + return a +} + // actionDisableScalingCluster implements disabling scaling DBservers and coordinators. type actionDisableScalingCluster struct { - log zerolog.Logger - action api.Action - actionCtx ActionContext - newMemberID string -} + // actionImpl implement timeout and member id functions + actionImpl -// NewDisableScalingCluster creates the new action with disabling scaling DBservers and coordinators. -func NewDisableScalingCluster(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { - return &actionDisableScalingCluster{ - log: log, - action: action, - actionCtx: actionCtx, - } + // actionEmptyCheckProgress implement check progress with empty implementation + actionEmptyCheckProgress } // Start disables scaling DBservers and coordinators @@ -53,18 +58,3 @@ func (a *actionDisableScalingCluster) Start(ctx context.Context) (bool, error) { } return true, nil } - -// CheckProgress does not matter. Everything is done in 'Start' function -func (a *actionDisableScalingCluster) CheckProgress(ctx context.Context) (bool, bool, error) { - return true, false, nil -} - -// Timeout does not matter. Everything is done in 'Start' function -func (a *actionDisableScalingCluster) Timeout() time.Duration { - return 0 -} - -// MemberID is not used -func (a *actionDisableScalingCluster) MemberID() string { - return "" -} diff --git a/pkg/deployment/reconcile/action_enable_scaling_cluster.go b/pkg/deployment/reconcile/action_enable_scaling_cluster.go index ad46397f1..feb539862 100644 --- a/pkg/deployment/reconcile/action_enable_scaling_cluster.go +++ b/pkg/deployment/reconcile/action_enable_scaling_cluster.go @@ -22,27 +22,32 @@ package reconcile import ( "context" - "time" api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" + "github.com/arangodb/kube-arangodb/pkg/util" "github.com/rs/zerolog" ) +func init() { + registerAction(api.ActionTypeEnableClusterScaling, newEnableScalingCluster) +} + +// newEnableScalingCluster creates the new action with enabling scaling DBservers and coordinators. +func newEnableScalingCluster(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { + a := &actionEnableScalingCluster{} + + a.actionImpl = newActionImpl(log, action, actionCtx, 0, util.NewString("")) + + return a +} + // actionEnableScalingCluster implements enabling scaling DBservers and coordinators. type actionEnableScalingCluster struct { - log zerolog.Logger - action api.Action - actionCtx ActionContext - newMemberID string -} + // actionImpl implement timeout and member id functions + actionImpl -// NewEnableScalingCluster creates the new action with enabling scaling DBservers and coordinators. -func NewEnableScalingCluster(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { - return &actionEnableScalingCluster{ - log: log, - action: action, - actionCtx: actionCtx, - } + // actionEmptyCheckProgress implement check progress with empty implementation + actionEmptyCheckProgress } // Start enables scaling DBservers and coordinators @@ -53,18 +58,3 @@ func (a *actionEnableScalingCluster) Start(ctx context.Context) (bool, error) { } return true, nil } - -// CheckProgress does not matter. Everything is done in 'Start' function -func (a *actionEnableScalingCluster) CheckProgress(ctx context.Context) (bool, bool, error) { - return true, false, nil -} - -// Timeout does not matter. Everything is done in 'Start' function -func (a *actionEnableScalingCluster) Timeout() time.Duration { - return 0 -} - -// MemberID is not used -func (a *actionEnableScalingCluster) MemberID() string { - return "" -} diff --git a/pkg/deployment/reconcile/action_helper.go b/pkg/deployment/reconcile/action_helper.go new file mode 100644 index 000000000..f35211bb9 --- /dev/null +++ b/pkg/deployment/reconcile/action_helper.go @@ -0,0 +1,62 @@ +package reconcile + +import ( + "context" + "time" + + api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" + "github.com/rs/zerolog" +) + +type actionEmptyCheckProgress struct { +} + +// CheckProgress define optional check progress for action +// Returns: ready, abort, error. +func (e actionEmptyCheckProgress) CheckProgress(ctx context.Context) (bool, bool, error) { + return true, false, nil +} + +type actionEmptyStart struct { +} + +func (e actionEmptyStart) Start(ctx context.Context) (bool, error) { + return false, nil +} + +func newActionImplDefRef(log zerolog.Logger, action api.Action, actionCtx ActionContext, timeout time.Duration) actionImpl { + return newActionImpl(log, action, actionCtx, timeout, &action.MemberID) +} + +func newActionImpl(log zerolog.Logger, action api.Action, actionCtx ActionContext, timeout time.Duration, memberIDRef *string) actionImpl { + if memberIDRef == nil { + panic("Action cannot have nil reference to member!") + } + + return actionImpl{ + log: log, + action: action, + actionCtx: actionCtx, + timeout: timeout, + memberIDRef: memberIDRef, + } +} + +type actionImpl struct { + log zerolog.Logger + action api.Action + actionCtx ActionContext + + timeout time.Duration + memberIDRef *string +} + +// Timeout returns the amount of time after which this action will timeout. +func (a actionImpl) Timeout() time.Duration { + return a.timeout +} + +// Return the MemberID used / created in this action +func (a actionImpl) MemberID() string { + return *a.memberIDRef +} diff --git a/pkg/deployment/reconcile/action_pvc_resize.go b/pkg/deployment/reconcile/action_pvc_resize.go index d8238d708..e993cbb18 100644 --- a/pkg/deployment/reconcile/action_pvc_resize.go +++ b/pkg/deployment/reconcile/action_pvc_resize.go @@ -24,7 +24,6 @@ package reconcile import ( "context" - "time" "github.com/arangodb/kube-arangodb/pkg/util/k8sutil" core "k8s.io/api/core/v1" @@ -34,21 +33,24 @@ import ( "github.com/rs/zerolog" ) -// NewRotateMemberAction creates a new Action that implements the given +func init() { + registerAction(api.ActionTypePVCResize, newPVCResizeAction) +} + +// newRotateMemberAction creates a new Action that implements the given // planned RotateMember action. -func NewPVCResizeAction(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { - return &actionPVCResize{ - log: log, - action: action, - actionCtx: actionCtx, - } +func newPVCResizeAction(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { + a := &actionPVCResize{} + + a.actionImpl = newActionImplDefRef(log, action, actionCtx, pvcResizeTimeout) + + return a } // actionRotateMember implements an RotateMember. type actionPVCResize struct { - log zerolog.Logger - action api.Action - actionCtx ActionContext + // actionImpl implement timeout and member id functions + actionImpl } // Start performs the start of the action. @@ -147,13 +149,3 @@ func (a *actionPVCResize) CheckProgress(ctx context.Context) (bool, bool, error) return false, false, nil } - -// Timeout returns the amount of time after which this action will timeout. -func (a *actionPVCResize) Timeout() time.Duration { - return pvcResizeTimeout -} - -// Return the MemberID used / created in this action -func (a *actionPVCResize) MemberID() string { - return a.action.MemberID -} diff --git a/pkg/deployment/reconcile/action_pvc_resized.go b/pkg/deployment/reconcile/action_pvc_resized.go index 1dbb18620..d5a937e23 100644 --- a/pkg/deployment/reconcile/action_pvc_resized.go +++ b/pkg/deployment/reconcile/action_pvc_resized.go @@ -24,7 +24,6 @@ package reconcile import ( "context" - "time" "github.com/arangodb/kube-arangodb/pkg/util/k8sutil" core "k8s.io/api/core/v1" @@ -34,28 +33,27 @@ import ( "github.com/rs/zerolog" ) -// NewRotateMemberAction creates a new Action that implements the given +func init() { + registerAction(api.ActionTypePVCResized, newPVCResizedAction) +} + +// newRotateMemberAction creates a new Action that implements the given // planned RotateMember action. -func NewPVCResizedAction(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { - return &actionPVCResized{ - log: log, - action: action, - actionCtx: actionCtx, - } +func newPVCResizedAction(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { + a := &actionPVCResized{} + + a.actionImpl = newActionImplDefRef(log, action, actionCtx, pvcResizedTimeout) + + return a } // actionRotateMember implements an RotateMember. type actionPVCResized struct { - log zerolog.Logger - action api.Action - actionCtx ActionContext -} + // actionImpl implement timeout and member id functions + actionImpl -// Start performs the start of the action. -// Returns true if the action is completely finished, false in case -// the start time needs to be recorded and a ready condition needs to be checked. -func (a *actionPVCResized) Start(ctx context.Context) (bool, error) { - return false, nil + // actionEmptyStart empty start function + actionEmptyStart } // CheckProgress checks the progress of the action. @@ -94,13 +92,3 @@ func (a *actionPVCResized) CheckProgress(ctx context.Context) (bool, bool, error return false, false, nil } - -// Timeout returns the amount of time after which this action will timeout. -func (a *actionPVCResized) Timeout() time.Duration { - return pvcResizedTimeout -} - -// Return the MemberID used / created in this action -func (a *actionPVCResized) MemberID() string { - return a.action.MemberID -} diff --git a/pkg/deployment/reconcile/action_recreate_member.go b/pkg/deployment/reconcile/action_recreate_member.go index 2d360898f..0981848af 100644 --- a/pkg/deployment/reconcile/action_recreate_member.go +++ b/pkg/deployment/reconcile/action_recreate_member.go @@ -25,7 +25,6 @@ package reconcile import ( "context" "fmt" - "time" kubeErrors "k8s.io/apimachinery/pkg/api/errors" @@ -34,21 +33,27 @@ import ( api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" ) -// NewRecreateMemberAction creates a new Action that implements the given +func init() { + registerAction(api.ActionTypeRecreateMember, newRecreateMemberAction) +} + +// newRecreateMemberAction creates a new Action that implements the given // planned RecreateMember action. -func NewRecreateMemberAction(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { - return &actionRecreateMember{ - log: log, - action: action, - actionCtx: actionCtx, - } +func newRecreateMemberAction(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { + a := &actionRecreateMember{} + + a.actionImpl = newActionImplDefRef(log, action, actionCtx, recreateMemberTimeout) + + return a } // actionRecreateMember implements an RecreateMemberAction. type actionRecreateMember struct { - log zerolog.Logger - action api.Action - actionCtx ActionContext + // actionImpl implement timeout and member id functions + actionImpl + + // actionEmptyCheckProgress implement check progress with empty implementation + actionEmptyCheckProgress } // Start performs the start of the action. @@ -80,20 +85,3 @@ func (a *actionRecreateMember) Start(ctx context.Context) (bool, error) { return true, nil } - -// CheckProgress checks the progress of the action. -// Returns true if the action is completely finished, false otherwise. -func (a *actionRecreateMember) CheckProgress(ctx context.Context) (bool, bool, error) { - // Nothing todo - return true, false, nil -} - -// Timeout returns the amount of time after which this action will timeout. -func (a *actionRecreateMember) Timeout() time.Duration { - return recreateMemberTimeout -} - -// Return the MemberID used / created in this action -func (a *actionRecreateMember) MemberID() string { - return a.action.MemberID -} diff --git a/pkg/deployment/reconcile/action_remove_member.go b/pkg/deployment/reconcile/action_remove_member.go index 5c63733e1..80d0b0ddf 100644 --- a/pkg/deployment/reconcile/action_remove_member.go +++ b/pkg/deployment/reconcile/action_remove_member.go @@ -25,7 +25,6 @@ package reconcile import ( "context" "fmt" - "time" "github.com/pkg/errors" "github.com/rs/zerolog" @@ -35,21 +34,27 @@ import ( "github.com/arangodb/kube-arangodb/pkg/util/arangod" ) -// NewRemoveMemberAction creates a new Action that implements the given +func init() { + registerAction(api.ActionTypeRemoveMember, newRemoveMemberAction) +} + +// newRemoveMemberAction creates a new Action that implements the given // planned RemoveMember action. -func NewRemoveMemberAction(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { - return &actionRemoveMember{ - log: log, - action: action, - actionCtx: actionCtx, - } +func newRemoveMemberAction(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { + a := &actionRemoveMember{} + + a.actionImpl = newActionImplDefRef(log, action, actionCtx, removeMemberTimeout) + + return a } // actionRemoveMember implements an RemoveMemberAction. type actionRemoveMember struct { - log zerolog.Logger - action api.Action - actionCtx ActionContext + // actionImpl implement timeout and member id functions + actionImpl + + // actionEmptyCheckProgress implement check progress with empty implementation + actionEmptyCheckProgress } // Start performs the start of the action. @@ -114,20 +119,3 @@ func (a *actionRemoveMember) Start(ctx context.Context) (bool, error) { } return true, nil } - -// CheckProgress checks the progress of the action. -// Returns true if the action is completely finished, false otherwise. -func (a *actionRemoveMember) CheckProgress(ctx context.Context) (bool, bool, error) { - // Nothing todo - return true, false, nil -} - -// Timeout returns the amount of time after which this action will timeout. -func (a *actionRemoveMember) Timeout() time.Duration { - return removeMemberTimeout -} - -// Return the MemberID used / created in this action -func (a *actionRemoveMember) MemberID() string { - return a.action.MemberID -} diff --git a/pkg/deployment/reconcile/action_renew_tls_ca_certificate.go b/pkg/deployment/reconcile/action_renew_tls_ca_certificate.go index 06ea70997..ec8584720 100644 --- a/pkg/deployment/reconcile/action_renew_tls_ca_certificate.go +++ b/pkg/deployment/reconcile/action_renew_tls_ca_certificate.go @@ -30,21 +30,27 @@ import ( "github.com/rs/zerolog" ) -// NewRenewTLSCACertificateAction creates a new Action that implements the given +func init() { + registerAction(api.ActionTypeRenewTLSCACertificate, newRenewTLSCACertificateAction) +} + +// newRenewTLSCACertificateAction creates a new Action that implements the given // planned RenewTLSCACertificate action. -func NewRenewTLSCACertificateAction(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { - return &renewTLSCACertificateAction{ - log: log, - action: action, - actionCtx: actionCtx, - } +func newRenewTLSCACertificateAction(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { + a := &renewTLSCACertificateAction{} + + a.actionImpl = newActionImplDefRef(log, action, actionCtx, renewTLSCACertificateTimeout) + + return a } // renewTLSCACertificateAction implements a RenewTLSCACertificate action. type renewTLSCACertificateAction struct { - log zerolog.Logger - action api.Action - actionCtx ActionContext + // actionImpl implement timeout and member id functions + actionImpl + + // actionEmptyCheckProgress implement check progress with empty implementation + actionEmptyCheckProgress } // Start performs the start of the action. diff --git a/pkg/deployment/reconcile/action_renew_tls_certificate.go b/pkg/deployment/reconcile/action_renew_tls_certificate.go index 3b1faf222..86725a928 100644 --- a/pkg/deployment/reconcile/action_renew_tls_certificate.go +++ b/pkg/deployment/reconcile/action_renew_tls_certificate.go @@ -24,27 +24,32 @@ package reconcile import ( "context" - "time" api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" "github.com/rs/zerolog" ) -// NewRenewTLSCertificateAction creates a new Action that implements the given +func init() { + registerAction(api.ActionTypeRenewTLSCertificate, newRenewTLSCertificateAction) +} + +// newRenewTLSCertificateAction creates a new Action that implements the given // planned RenewTLSCertificate action. -func NewRenewTLSCertificateAction(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { - return &renewTLSCertificateAction{ - log: log, - action: action, - actionCtx: actionCtx, - } +func newRenewTLSCertificateAction(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { + a := &renewTLSCertificateAction{} + + a.actionImpl = newActionImplDefRef(log, action, actionCtx, renewTLSCertificateTimeout) + + return a } // renewTLSCertificateAction implements a RenewTLSCertificate action. type renewTLSCertificateAction struct { - log zerolog.Logger - action api.Action - actionCtx ActionContext + // actionImpl implement timeout and member id functions + actionImpl + + // actionEmptyCheckProgress implement check progress with empty implementation + actionEmptyCheckProgress } // Start performs the start of the action. @@ -64,19 +69,3 @@ func (a *renewTLSCertificateAction) Start(ctx context.Context) (bool, error) { } return false, nil } - -// CheckProgress checks the progress of the action. -// Returns true if the action is completely finished, false otherwise. -func (a *renewTLSCertificateAction) CheckProgress(ctx context.Context) (bool, bool, error) { - return true, false, nil -} - -// Timeout returns the amount of time after which this action will timeout. -func (a *renewTLSCertificateAction) Timeout() time.Duration { - return renewTLSCertificateTimeout -} - -// Return the MemberID used / created in this action -func (a *renewTLSCertificateAction) MemberID() string { - return a.action.MemberID -} diff --git a/pkg/deployment/reconcile/action_rotate_member.go b/pkg/deployment/reconcile/action_rotate_member.go index 9560b786e..2d4d2b16f 100644 --- a/pkg/deployment/reconcile/action_rotate_member.go +++ b/pkg/deployment/reconcile/action_rotate_member.go @@ -24,27 +24,29 @@ package reconcile import ( "context" - "time" api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" "github.com/rs/zerolog" ) -// NewRotateMemberAction creates a new Action that implements the given +func init() { + registerAction(api.ActionTypeRotateMember, newRotateMemberAction) +} + +// newRotateMemberAction creates a new Action that implements the given // planned RotateMember action. -func NewRotateMemberAction(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { - return &actionRotateMember{ - log: log, - action: action, - actionCtx: actionCtx, - } +func newRotateMemberAction(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { + a := &actionRotateMember{} + + a.actionImpl = newActionImplDefRef(log, action, actionCtx, rotateMemberTimeout) + + return a } // actionRotateMember implements an RotateMember. type actionRotateMember struct { - log zerolog.Logger - action api.Action - actionCtx ActionContext + // actionImpl implement timeout and member id functions + actionImpl } // Start performs the start of the action. @@ -123,13 +125,3 @@ func (a *actionRotateMember) CheckProgress(ctx context.Context) (bool, bool, err } return true, false, nil } - -// Timeout returns the amount of time after which this action will timeout. -func (a *actionRotateMember) Timeout() time.Duration { - return rotateMemberTimeout -} - -// Return the MemberID used / created in this action -func (a *actionRotateMember) MemberID() string { - return a.action.MemberID -} diff --git a/pkg/deployment/reconcile/action_rotate_start_member.go b/pkg/deployment/reconcile/action_rotate_start_member.go index 885d5041b..d847b334e 100644 --- a/pkg/deployment/reconcile/action_rotate_start_member.go +++ b/pkg/deployment/reconcile/action_rotate_start_member.go @@ -24,27 +24,29 @@ package reconcile import ( "context" - "time" api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" "github.com/rs/zerolog" ) -// NewRotateStartMemberAction creates a new Action that implements the given +func init() { + registerAction(api.ActionTypeRotateStartMember, newRotateStartMemberAction) +} + +// newRotateStartMemberAction creates a new Action that implements the given // planned RotateStartMember action. -func NewRotateStartMemberAction(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { - return &actionRotateStartMember{ - log: log, - action: action, - actionCtx: actionCtx, - } +func newRotateStartMemberAction(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { + a := &actionRotateStartMember{} + + a.actionImpl = newActionImplDefRef(log, action, actionCtx, rotateMemberTimeout) + + return a } // actionRotateStartMember implements an RotateStartMember. type actionRotateStartMember struct { - log zerolog.Logger - action api.Action - actionCtx ActionContext + // actionImpl implement timeout and member id functions + actionImpl } // Start performs the start of the action. @@ -116,13 +118,3 @@ func (a *actionRotateStartMember) CheckProgress(ctx context.Context) (bool, bool } return true, false, nil } - -// Timeout returns the amount of time after which this action will timeout. -func (a *actionRotateStartMember) Timeout() time.Duration { - return rotateMemberTimeout -} - -// Return the MemberID used / created in this action -func (a *actionRotateStartMember) MemberID() string { - return a.action.MemberID -} diff --git a/pkg/deployment/reconcile/action_rotate_stop_member.go b/pkg/deployment/reconcile/action_rotate_stop_member.go index 428b40817..2c228df68 100644 --- a/pkg/deployment/reconcile/action_rotate_stop_member.go +++ b/pkg/deployment/reconcile/action_rotate_stop_member.go @@ -24,27 +24,32 @@ package reconcile import ( "context" - "time" api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" "github.com/rs/zerolog" ) -// NewRotateStopMemberAction creates a new Action that implements the given +func init() { + registerAction(api.ActionTypeRotateStopMember, newRotateStopMemberAction) +} + +// newRotateStopMemberAction creates a new Action that implements the given // planned RotateStopMember action. -func NewRotateStopMemberAction(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { - return &actionRotateStopMember{ - log: log, - action: action, - actionCtx: actionCtx, - } +func newRotateStopMemberAction(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { + a := &actionRotateStopMember{} + + a.actionImpl = newActionImplDefRef(log, action, actionCtx, rotateMemberTimeout) + + return a } // actionRotateStopMember implements an RotateStopMember. type actionRotateStopMember struct { - log zerolog.Logger - action api.Action - actionCtx ActionContext + // actionImpl implement timeout and member id functions + actionImpl + + // actionEmptyCheckProgress implement check progress with empty implementation + actionEmptyCheckProgress } // Start performs the start of the action. @@ -65,19 +70,3 @@ func (a *actionRotateStopMember) Start(ctx context.Context) (bool, error) { } return false, nil } - -// CheckProgress checks the progress of the action. -// Returns: ready, abort, error. -func (a *actionRotateStopMember) CheckProgress(ctx context.Context) (bool, bool, error) { - return true, false, nil -} - -// Timeout returns the amount of time after which this action will timeout. -func (a *actionRotateStopMember) Timeout() time.Duration { - return rotateMemberTimeout -} - -// Return the MemberID used / created in this action -func (a *actionRotateStopMember) MemberID() string { - return a.action.MemberID -} diff --git a/pkg/deployment/reconcile/action_shutdown_member.go b/pkg/deployment/reconcile/action_shutdown_member.go index 9415ff819..b36ce4185 100644 --- a/pkg/deployment/reconcile/action_shutdown_member.go +++ b/pkg/deployment/reconcile/action_shutdown_member.go @@ -24,31 +24,29 @@ package reconcile import ( "context" - "time" api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" "github.com/rs/zerolog" ) -const ( - shutdownTimeout = time.Second * 15 -) +func init() { + registerAction(api.ActionTypeShutdownMember, newShutdownMemberAction) +} -// NewShutdownMemberAction creates a new Action that implements the given +// newShutdownMemberAction creates a new Action that implements the given // planned ShutdownMember action. -func NewShutdownMemberAction(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { - return &actionShutdownMember{ - log: log, - action: action, - actionCtx: actionCtx, - } +func newShutdownMemberAction(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { + a := &actionShutdownMember{} + + a.actionImpl = newActionImplDefRef(log, action, actionCtx, shutdownMemberTimeout) + + return a } // actionShutdownMember implements an ShutdownMemberAction. type actionShutdownMember struct { - log zerolog.Logger - action api.Action - actionCtx ActionContext + // actionImpl implement timeout and member id functions + actionImpl } // Start performs the start of the action. @@ -115,13 +113,3 @@ func (a *actionShutdownMember) CheckProgress(ctx context.Context) (bool, bool, e // Member still not shutdown, retry soon return false, false, nil } - -// Timeout returns the amount of time after which this action will timeout. -func (a *actionShutdownMember) Timeout() time.Duration { - return shutdownMemberTimeout -} - -// Return the MemberID used / created in this action -func (a *actionShutdownMember) MemberID() string { - return a.action.MemberID -} diff --git a/pkg/deployment/reconcile/action_upgrade_current_image.go b/pkg/deployment/reconcile/action_upgrade_current_image.go index a596de671..be9703c42 100644 --- a/pkg/deployment/reconcile/action_upgrade_current_image.go +++ b/pkg/deployment/reconcile/action_upgrade_current_image.go @@ -24,27 +24,29 @@ package reconcile import ( "context" - "time" api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" "github.com/rs/zerolog" ) -// NewSetCurrentImageAction creates a new Action that implements the given +func init() { + registerAction(api.ActionTypeSetCurrentImage, newSetCurrentImageAction) +} + +// newSetCurrentImageAction creates a new Action that implements the given // planned SetCurrentImage action. -func NewSetCurrentImageAction(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { - return &setCurrentImageAction{ - log: log, - action: action, - actionCtx: actionCtx, - } +func newSetCurrentImageAction(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { + a := &setCurrentImageAction{} + + a.actionImpl = newActionImplDefRef(log, action, actionCtx, upgradeMemberTimeout) + + return a } // setCurrentImageAction implements an SetCurrentImage. type setCurrentImageAction struct { - log zerolog.Logger - action api.Action - actionCtx ActionContext + // actionImpl implement timeout and member id functions + actionImpl } // Start performs the start of the action. @@ -73,13 +75,3 @@ func (a *setCurrentImageAction) CheckProgress(ctx context.Context) (bool, bool, log.Info().Str("image", a.action.Image).Msg("Changed current image") return true, false, nil } - -// Timeout returns the amount of time after which this action will timeout. -func (a *setCurrentImageAction) Timeout() time.Duration { - return upgradeMemberTimeout -} - -// Return the MemberID used / created in this action -func (a *setCurrentImageAction) MemberID() string { - return "" -} diff --git a/pkg/deployment/reconcile/action_upgrade_member.go b/pkg/deployment/reconcile/action_upgrade_member.go index 12eaf0fe4..0b5a0d00c 100644 --- a/pkg/deployment/reconcile/action_upgrade_member.go +++ b/pkg/deployment/reconcile/action_upgrade_member.go @@ -24,27 +24,29 @@ package reconcile import ( "context" - "time" api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" "github.com/rs/zerolog" ) -// NewUpgradeMemberAction creates a new Action that implements the given +func init() { + registerAction(api.ActionTypeUpgradeMember, newUpgradeMemberAction) +} + +// newUpgradeMemberAction creates a new Action that implements the given // planned UpgradeMember action. -func NewUpgradeMemberAction(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { - return &actionUpgradeMember{ - log: log, - action: action, - actionCtx: actionCtx, - } +func newUpgradeMemberAction(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { + a := &actionUpgradeMember{} + + a.actionImpl = newActionImplDefRef(log, action, actionCtx, upgradeMemberTimeout) + + return a } // actionUpgradeMember implements an UpgradeMember. type actionUpgradeMember struct { - log zerolog.Logger - action api.Action - actionCtx ActionContext + // actionImpl implement timeout and member id functions + actionImpl } // Start performs the start of the action. @@ -128,13 +130,3 @@ func (a *actionUpgradeMember) CheckProgress(ctx context.Context) (bool, bool, er } return isUpgrading, false, nil } - -// Timeout returns the amount of time after which this action will timeout. -func (a *actionUpgradeMember) Timeout() time.Duration { - return upgradeMemberTimeout -} - -// Return the MemberID used / created in this action -func (a *actionUpgradeMember) MemberID() string { - return a.action.MemberID -} diff --git a/pkg/deployment/reconcile/action_wait_for_member_up.go b/pkg/deployment/reconcile/action_wait_for_member_up.go index a5dc845e5..8b5b9206d 100644 --- a/pkg/deployment/reconcile/action_wait_for_member_up.go +++ b/pkg/deployment/reconcile/action_wait_for_member_up.go @@ -24,7 +24,6 @@ package reconcile import ( "context" - "time" driver "github.com/arangodb/go-driver" "github.com/arangodb/go-driver/agency" @@ -34,21 +33,24 @@ import ( api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" ) -// NewWaitForMemberUpAction creates a new Action that implements the given +func init() { + registerAction(api.ActionTypeWaitForMemberUp, newWaitForMemberUpAction) +} + +// newWaitForMemberUpAction creates a new Action that implements the given // planned WaitForMemberUp action. -func NewWaitForMemberUpAction(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { - return &actionWaitForMemberUp{ - log: log, - action: action, - actionCtx: actionCtx, - } +func newWaitForMemberUpAction(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { + a := &actionWaitForMemberUp{} + + a.actionImpl = newActionImplDefRef(log, action, actionCtx, waitForMemberUpTimeout) + + return a } // actionWaitForMemberUp implements an WaitForMemberUp. type actionWaitForMemberUp struct { - log zerolog.Logger - action api.Action - actionCtx ActionContext + // actionImpl implement timeout and member id functions + actionImpl } // Start performs the start of the action. @@ -189,13 +191,3 @@ func (a *actionWaitForMemberUp) checkProgressArangoSync(ctx context.Context) (bo } return true, false, nil } - -// Timeout returns the amount of time after which this action will timeout. -func (a *actionWaitForMemberUp) Timeout() time.Duration { - return waitForMemberUpTimeout -} - -// Return the MemberID used / created in this action -func (a *actionWaitForMemberUp) MemberID() string { - return a.action.MemberID -} diff --git a/pkg/deployment/reconcile/context.go b/pkg/deployment/reconcile/context.go index 1627972cb..11cfffbd2 100644 --- a/pkg/deployment/reconcile/context.go +++ b/pkg/deployment/reconcile/context.go @@ -116,4 +116,6 @@ type Context interface { RenderPodForMember(spec api.DeploymentSpec, status api.DeploymentStatus, memberID string, imageInfo api.ImageInfo) (*v1.Pod, error) // SelectImage select currently used image by pod SelectImage(spec api.DeploymentSpec, status api.DeploymentStatus) (api.ImageInfo, bool) + // WithStatusUpdate update status of ArangoDeployment with defined modifier. If action returns True action is taken + WithStatusUpdate(action func(s *api.DeploymentStatus) bool, force ...bool) error } diff --git a/pkg/deployment/reconcile/plan_executor.go b/pkg/deployment/reconcile/plan_executor.go index 01b231964..0f0c7acc5 100644 --- a/pkg/deployment/reconcile/plan_executor.go +++ b/pkg/deployment/reconcile/plan_executor.go @@ -157,47 +157,14 @@ func (d *Reconciler) ExecutePlan(ctx context.Context) (bool, error) { } } -// startAction performs the start of the given action -// Returns true if the action is completely finished, false in case -// the start time needs to be recorded and a ready condition needs to be checked. +// createAction create action object based on action type func (d *Reconciler) createAction(ctx context.Context, log zerolog.Logger, action api.Action) Action { actionCtx := newActionContext(log, d.context) - switch action.Type { - case api.ActionTypeAddMember: - return NewAddMemberAction(log, action, actionCtx) - case api.ActionTypeRemoveMember: - return NewRemoveMemberAction(log, action, actionCtx) - case api.ActionTypeRecreateMember: - return NewRecreateMemberAction(log, action, actionCtx) - case api.ActionTypeCleanOutMember: - return NewCleanOutMemberAction(log, action, actionCtx) - case api.ActionTypeShutdownMember: - return NewShutdownMemberAction(log, action, actionCtx) - case api.ActionTypeRotateMember: - return NewRotateMemberAction(log, action, actionCtx) - case api.ActionTypeRotateStartMember: - return NewRotateStartMemberAction(log, action, actionCtx) - case api.ActionTypeRotateStopMember: - return NewRotateStopMemberAction(log, action, actionCtx) - case api.ActionTypeUpgradeMember: - return NewUpgradeMemberAction(log, action, actionCtx) - case api.ActionTypeWaitForMemberUp: - return NewWaitForMemberUpAction(log, action, actionCtx) - case api.ActionTypeRenewTLSCertificate: - return NewRenewTLSCertificateAction(log, action, actionCtx) - case api.ActionTypeRenewTLSCACertificate: - return NewRenewTLSCACertificateAction(log, action, actionCtx) - case api.ActionTypeSetCurrentImage: - return NewSetCurrentImageAction(log, action, actionCtx) - case api.ActionTypeDisableClusterScaling: - return NewDisableScalingCluster(log, action, actionCtx) - case api.ActionTypeEnableClusterScaling: - return NewEnableScalingCluster(log, action, actionCtx) - case api.ActionTypePVCResize: - return NewPVCResizeAction(log, action, actionCtx) - case api.ActionTypePVCResized: - return NewPVCResizedAction(log, action, actionCtx) - default: + + f, ok := getActionFactory(action.Type) + if !ok { panic(fmt.Sprintf("Unknown action type '%s'", action.Type)) } + + return f(log, action, actionCtx) } diff --git a/pkg/deployment/reconcile/timeouts.go b/pkg/deployment/reconcile/timeouts.go index 8f10f2100..adc35ab95 100644 --- a/pkg/deployment/reconcile/timeouts.go +++ b/pkg/deployment/reconcile/timeouts.go @@ -37,4 +37,7 @@ const ( shutdownMemberTimeout = time.Minute * 30 upgradeMemberTimeout = time.Hour * 6 waitForMemberUpTimeout = time.Minute * 15 + upToDateUpdateTimeout = time.Minute + + shutdownTimeout = time.Second * 15 ) diff --git a/pkg/deployment/resources/context.go b/pkg/deployment/resources/context.go index 6b7085857..a21ec6d1b 100644 --- a/pkg/deployment/resources/context.go +++ b/pkg/deployment/resources/context.go @@ -92,4 +92,6 @@ type Context interface { GetDatabaseClient(ctx context.Context) (driver.Client, error) // GetAgency returns a connection to the entire agency. GetAgency(ctx context.Context) (agency.Agency, error) + // WithStatusUpdate update status of ArangoDeployment with defined modifier. If action returns True action is taken + WithStatusUpdate(action func(s *api.DeploymentStatus) bool, force ...bool) error } From 51846907c5d55150799ecc7b539f5ad88d9511cc Mon Sep 17 00:00:00 2001 From: ajanikow Date: Mon, 30 Mar 2020 20:21:00 +0000 Subject: [PATCH 2/3] Fix UT --- pkg/deployment/deployment_inspector.go | 2 +- pkg/deployment/reconcile/plan_builder_test.go | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/deployment/deployment_inspector.go b/pkg/deployment/deployment_inspector.go index d31061817..4e9cc1355 100644 --- a/pkg/deployment/deployment_inspector.go +++ b/pkg/deployment/deployment_inspector.go @@ -239,7 +239,7 @@ func (d *Deployment) inspectDeploymentWithError(ctx context.Context, lastInterva return } -func ( d *Deployment) ensureResources(lastInterval util.Interval) (util.Interval, error) { +func (d *Deployment) ensureResources(lastInterval util.Interval) (util.Interval, error) { // Ensure all resources are created if err := d.resources.EnsureSecrets(); err != nil { return minInspectionInterval, errors.Wrapf(err, "Secret creation failed") diff --git a/pkg/deployment/reconcile/plan_builder_test.go b/pkg/deployment/reconcile/plan_builder_test.go index 2a3907b56..fd2abd0db 100644 --- a/pkg/deployment/reconcile/plan_builder_test.go +++ b/pkg/deployment/reconcile/plan_builder_test.go @@ -44,6 +44,7 @@ import ( ) var _ PlanBuilderContext = &testContext{} +var _ Context = &testContext{} type testContext struct { Pods []core.Pod @@ -54,6 +55,10 @@ type testContext struct { RecordedEvent *k8sutil.Event } +func (c *testContext) WithStatusUpdate(action func(s *api.DeploymentStatus) bool, force ...bool) error { + panic("implement me") +} + func (c *testContext) RenderPodForMember(spec api.DeploymentSpec, status api.DeploymentStatus, memberID string, imageInfo api.ImageInfo) (*core.Pod, error) { panic("implement me") } From c5a498bbe49d55d4e3517225269b7cf536faab6d Mon Sep 17 00:00:00 2001 From: ajanikow Date: Wed, 1 Apr 2020 09:51:44 +0000 Subject: [PATCH 3/3] Add header --- pkg/deployment/reconcile/action_helper.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/pkg/deployment/reconcile/action_helper.go b/pkg/deployment/reconcile/action_helper.go index f35211bb9..b82f0ce30 100644 --- a/pkg/deployment/reconcile/action_helper.go +++ b/pkg/deployment/reconcile/action_helper.go @@ -1,3 +1,25 @@ +// +// DISCLAIMER +// +// Copyright 2020 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 +// +// Author Adam Janikowski +// + package reconcile import (