Skip to content

Commit

Permalink
feat(recipe): add resync interval api fields (#122)
Browse files Browse the repository at this point in the history
This commit adds resync interval fields in kind: Recipe. One can
set them to control the reconcile interval of specific Recipe
instances. In addition, one will be able to set different reconcile
intervals for normal, error as well as not eligible phases.

Signed-off-by: AmitKumarDas <[email protected]>
  • Loading branch information
Amit Kumar Das authored Aug 5, 2020
1 parent 3d4daa5 commit 4c4068a
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 43 deletions.
50 changes: 43 additions & 7 deletions controller/recipe/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ type Reconciler struct {
commonctrl.Reconciler

ObservedRecipe *types.Recipe
RecipeStatus *types.RecipeStatus

// resulting status after executing the observed Recipe
recipeRunStatus types.RecipeStatus
}

func (r *Reconciler) eval() {
Expand All @@ -51,7 +53,7 @@ func (r *Reconciler) invoke() {
Recipe: *r.ObservedRecipe,
},
)
r.Err = runner.Run()
r.recipeRunStatus, r.Err = runner.Run()
}

func (r *Reconciler) setSyncResponse() {
Expand All @@ -62,12 +64,44 @@ func (r *Reconciler) setSyncResponse() {
r.SkipReason = "No attachments to reconcile"
}

func (r *Reconciler) setRecipeStatusAsError() {
func (r *Reconciler) setRecipeStatusFromResult() {
if r.ObservedRecipe == nil {
// nothing needs to be done
return
}
if r.recipeRunStatus.Phase == types.RecipeStatusNotEligible &&
r.ObservedRecipe.Spec.Resync.OnNotEligibleResyncInSeconds != nil {
// set configured resync interval when Recipe's phase
// is set to NotEligible
r.HookResponse.ResyncAfterSeconds =
*r.ObservedRecipe.Spec.Resync.OnNotEligibleResyncInSeconds

// Further logic should not be executed
//
// NOTE:
// Setting resync interval when phase is set to NotEligible
// holds more priority
return
}
if r.ObservedRecipe.Spec.Resync.IntervalInSeconds != nil {
// set configured resync interval during normal conditions
//
// NOTE:
// This is set when Recipe's phase is NOT IN following phases:
// - NotEligible
// - Error
r.HookResponse.ResyncAfterSeconds =
*r.ObservedRecipe.Spec.Resync.IntervalInSeconds
}
}

func (r *Reconciler) setRecipeStatusFromError() {
if r.ObservedRecipe != nil &&
r.ObservedRecipe.Spec.Refresh.OnErrorResyncAfterSeconds != nil {
// resync on error based on configuration
r.ObservedRecipe.Spec.Resync.OnErrorResyncInSeconds != nil {
// set configured resync interval when Recipe's phase
// is set to Error
r.HookResponse.ResyncAfterSeconds =
*r.ObservedRecipe.Spec.Refresh.OnErrorResyncAfterSeconds
*r.ObservedRecipe.Spec.Resync.OnErrorResyncInSeconds
}
r.HookResponse.Status = map[string]interface{}{
"phase": "Error",
Expand All @@ -82,9 +116,11 @@ func (r *Reconciler) setRecipeStatus() {
if r.Err != nil {
// reconciler is only concerned about the Recipes that
// result in error
r.setRecipeStatusAsError()
r.setRecipeStatusFromError()
return
}
// normal conditions i.e. when non error conditions
r.setRecipeStatusFromResult()
}

// Sync implements the idempotent logic to sync Recipe resource
Expand Down
46 changes: 15 additions & 31 deletions pkg/recipe/recipe.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,24 +238,6 @@ func (r *Runner) mayBePassedOrCompletedStatus() types.RecipeStatusPhase {
return types.RecipeStatusPassed
}

// func (r *Runner) getAPIDiscovery() *metacdiscovery.APIResourceDiscovery {
// // if !r.hasCRDTask {
// klog.V(3).Infof("Using metac api discovery instance")
// return metac.KubeDetails.GetMetacAPIDiscovery()
// // }

// // TODO
// // If we need a api discovery with more frequent refreshes
// // then we might use below. We need to stop the discovery
// // once this Recipe instance is done.
// //
// // klog.V(3).Infof("Using new instance of api discovery")
// // // return a discovery that refreshes more frequently
// // apiDiscovery := metac.KubeDetails.NewAPIDiscovery()
// // apiDiscovery.Start(5 * time.Second)
// // return apiDiscovery
// }

// updateRecipeWithRetries updates the kubernetes cluster with
// desired recipe
func (r *Runner) updateRecipeWithRetries() error {
Expand All @@ -277,12 +259,10 @@ func (r *Runner) updateRecipeWithRetries() error {
}

status := map[string]interface{}{
"phase": string(r.RecipeStatus.Phase),
"reason": r.RecipeStatus.Reason,
"message": r.RecipeStatus.Message,
"taskCount": r.RecipeStatus.TaskCount,
// "failedTaskCount": int64(r.RecipeStatus.TaskCount.Failed),
// "totalTaskCount": int64(r.RecipeStatus.TaskCount.Total),
"phase": string(r.RecipeStatus.Phase),
"reason": r.RecipeStatus.Reason,
"message": r.RecipeStatus.Message,
"taskCount": r.RecipeStatus.TaskCount,
"executionTimeInSeconds": r.RecipeStatus.ExecutionTimeInSeconds,
"taskListStatus": r.RecipeStatus.TaskResultList,
}
Expand All @@ -298,6 +278,7 @@ func (r *Runner) updateRecipeWithRetries() error {
}

labels := map[string]string{
// this label key is set with same value as that of status.phase
types.LblKeyRecipePhase: string(r.RecipeStatus.Phase),
}

Expand Down Expand Up @@ -492,10 +473,10 @@ func (r *Runner) runAllTasks() (err error) {
}

// Run executes the tasks in a sequential order
func (r *Runner) Run() (err error) {
func (r *Runner) Run() (status types.RecipeStatus, err error) {
err = r.init()
if err != nil {
return err
return types.RecipeStatus{}, err
}
// proceed further by verifying the presence of LOCK
//
Expand All @@ -507,7 +488,7 @@ func (r *Runner) Run() (err error) {
lockrunner := r.buildLockRunner()
locked, err := lockrunner.IsLocked()
if err != nil {
return errors.Wrapf(
return types.RecipeStatus{}, errors.Wrapf(
err,
"Verify lock failed: Recipe %q %q: Status %q %q",
r.Recipe.GetNamespace(),
Expand All @@ -524,7 +505,9 @@ func (r *Runner) Run() (err error) {
r.Recipe.Status.Phase,
r.Recipe.Status.Reason,
)
return nil
return types.RecipeStatus{
Phase: types.RecipeStatusLocked,
}, nil
}

klog.V(2).Infof(
Expand All @@ -538,7 +521,7 @@ func (r *Runner) Run() (err error) {
// Start executing by taking a LOCK
_, unlock, err := lockrunner.Lock()
if err != nil {
return errors.Wrapf(
return types.RecipeStatus{}, errors.Wrapf(
err,
"Create lock failed: Recipe %q %q: Status %q %q",
r.Recipe.Namespace,
Expand Down Expand Up @@ -604,6 +587,7 @@ func (r *Runner) Run() (err error) {
r.Recipe.Status.Reason,
unlockerr.Error(),
)
// return the executed state
return
}
klog.V(3).Infof(
Expand All @@ -618,8 +602,8 @@ func (r *Runner) Run() (err error) {

err = r.evalAllTasks()
if err != nil {
return err
return types.RecipeStatus{}, err
}

return r.runAllTasks()
return *r.RecipeStatus, r.runAllTasks()
}
25 changes: 20 additions & 5 deletions types/recipe/recipe.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,25 @@ type RecipeSpec struct {
ThinkTimeInSeconds *float64 `json:"thinkTimeInSeconds,omitempty"`
Enabled *Enabled `json:"enabled,omitempty"`
Eligible *Eligible `json:"eligible,omitempty"`
Refresh Refresh `json:"refresh,omitempty"`
Resync Resync `json:"resync,omitempty"`
Tasks []Task `json:"tasks"`
}

// Refresh options to reconcile Recipe
type Refresh struct {
ResyncAfterSeconds *float64 `json:"resyncAfterSeconds,omitempty"`
OnErrorResyncAfterSeconds *float64 `json:"onErrorResyncAfterSeconds,omitempty"`
// Resync options to continously reconcile the Recipe instance
type Resync struct {
// IntervalInSeconds triggers the next reconciliation of this
// Recipe based on this interval
IntervalInSeconds *float64 `json:"intervalInSeconds,omitempty"`

// OnErrorResyncInSeconds triggers the next reconciliation of
// the Recipe based on this interval if Recipe's status.phase
// was set to Error
OnErrorResyncInSeconds *float64 `json:"onErrorResyncInSeconds,omitempty"`

// OnNotEligibleResyncInSeconds triggers the next reconciliation
// of the Recipe based on this interval if Recipe's status.phase
// was set to NotEligible
OnNotEligibleResyncInSeconds *float64 `json:"onNotEligibleResyncInSeconds,omitempty"`
}

// Enabled defines if the recipe is enabled to be executed
Expand Down Expand Up @@ -147,6 +158,10 @@ const (
RecipeStatusLocked RecipeStatusPhase = "Locked"

// RecipeStatusDisabled implies a disabled Recipe
//
// NOTE:
// This might be a temporary phase. This may require
// some form of intervention to move out of this phase.
RecipeStatusDisabled RecipeStatusPhase = "Disabled"

// RecipeStatusNotEligible implies a Recipe that is not
Expand Down

0 comments on commit 4c4068a

Please sign in to comment.