github.com/docker/compose-on-kubernetes@v0.5.0/internal/controller/stackreconciler.go (about)

     1  package controller
     2  
     3  import (
     4  	"time"
     5  
     6  	"github.com/docker/compose-on-kubernetes/api/compose/latest"
     7  	"github.com/docker/compose-on-kubernetes/internal/convert"
     8  	"github.com/docker/compose-on-kubernetes/internal/deduplication"
     9  	"github.com/docker/compose-on-kubernetes/internal/stackresources"
    10  	"github.com/docker/compose-on-kubernetes/internal/stackresources/diff"
    11  	"github.com/pkg/errors"
    12  	log "github.com/sirupsen/logrus"
    13  	coretypes "k8s.io/api/core/v1"
    14  	kerrors "k8s.io/apimachinery/pkg/api/errors"
    15  )
    16  
    17  // childrenStore provides access to children resource indexed cache
    18  type childrenStore interface {
    19  	getCurrentStackState(objKey string) (*stackresources.StackState, error)
    20  }
    21  
    22  // stackStore provides access to the stack cache
    23  type stackStore interface {
    24  	get(key string) (*latest.Stack, error)
    25  }
    26  
    27  type resourceUpdater interface {
    28  	applyStackDiff(d *diff.StackStateDiff) error
    29  	updateStackStatus(status latest.StackStatus) (*latest.Stack, error)
    30  	deleteSecretsAndConfigMaps() error
    31  }
    32  
    33  // ResourceUpdaterProvider is a factory providing resource updaters for a given stack (default implementation generates an impersonating clientset)
    34  type ResourceUpdaterProvider interface {
    35  	getUpdater(stack *latest.Stack, isDirty bool) (resourceUpdater, error)
    36  }
    37  
    38  // StackReconciler reconciles stack into children objects
    39  type StackReconciler struct {
    40  	children            childrenStore
    41  	stacks              stackStore
    42  	serviceStrategy     convert.ServiceStrategy
    43  	resourceUpdater     ResourceUpdaterProvider
    44  	ownerCache          StackOwnerCacher
    45  	reconcileRetryQueue *deduplication.StringChan
    46  }
    47  
    48  // NewStackReconciler creates a StackReconciler
    49  func NewStackReconciler(stackStore stackStore,
    50  	childrenStore childrenStore,
    51  	defaultServiceType coretypes.ServiceType,
    52  	resourceUpdater ResourceUpdaterProvider,
    53  	ownerCache StackOwnerCacher) (*StackReconciler, error) {
    54  	strategy, err := convert.ServiceStrategyFor(defaultServiceType)
    55  	if err != nil {
    56  		return nil, err
    57  	}
    58  	return &StackReconciler{
    59  		children:            childrenStore,
    60  		stacks:              stackStore,
    61  		serviceStrategy:     strategy,
    62  		resourceUpdater:     resourceUpdater,
    63  		ownerCache:          ownerCache,
    64  		reconcileRetryQueue: deduplication.NewStringChan(20),
    65  	}, nil
    66  }
    67  
    68  // Start starts the reconciliation loop
    69  func (r *StackReconciler) Start(reconcileQueue <-chan string, deletionQueue <-chan *latest.Stack, stop <-chan struct{}) {
    70  	go func() {
    71  		for {
    72  			select {
    73  			case <-stop:
    74  				return
    75  			case key := <-reconcileQueue:
    76  				r.reconcileStack(key)
    77  			case key := <-r.reconcileRetryQueue.Out():
    78  				r.reconcileStack(key)
    79  			case stack := <-deletionQueue:
    80  				r.deleteStackChildren(stack)
    81  			}
    82  		}
    83  	}()
    84  }
    85  
    86  func (r *StackReconciler) reconcileStack(key string) {
    87  	stack, err := r.stacks.get(key)
    88  	if err != nil {
    89  		log.Errorf("Cannot reconcile stack %s: %s", key, err)
    90  		return
    91  	}
    92  	if stack.DeletionTimestamp != nil {
    93  		// pending deletion
    94  		r.deleteStackChildren(stack)
    95  		return
    96  	}
    97  	updater, err := r.resourceUpdater.getUpdater(stack, convert.IsStackDirty(stack))
    98  	if err != nil {
    99  		log.Errorf("Updater resolution failed: %s", err)
   100  		return
   101  	}
   102  	err = r.reconcileStatus(stack, r.reconcileChildren(stack, updater), updater)
   103  	if err != nil {
   104  		log.Errorf("Status reconciliation failed: %s", err)
   105  	}
   106  }
   107  
   108  func (r *StackReconciler) deleteStackChildren(stack *latest.Stack) {
   109  	current, err := r.children.getCurrentStackState(stackresources.ObjKey(stack.Namespace, stack.Name))
   110  	if err != nil {
   111  		log.Errorf("Failed to resolve current state for %s/%s: %s", stack.Namespace, stack.Name, err)
   112  		return
   113  	}
   114  	updater, err := r.resourceUpdater.getUpdater(stack, false)
   115  	if err != nil {
   116  		log.Errorf("Updater resolution failed: %s", err)
   117  		return
   118  	}
   119  	diff := diff.ComputeDiff(current, stackresources.EmptyStackState)
   120  	if err := updater.applyStackDiff(diff); err != nil {
   121  		log.Errorf("Failed to remove stack children for %s/%s: %s", stack.Namespace, stack.Name, err)
   122  		return
   123  	}
   124  
   125  	// handle secrets and config maps
   126  	if err := updater.deleteSecretsAndConfigMaps(); err != nil {
   127  		log.Errorf("Failed to remove stack secrets and config maps for %s/%s: %s", stack.Namespace, stack.Name, err)
   128  		return
   129  	}
   130  
   131  	// remove from impersonation cache
   132  	r.ownerCache.remove(stackresources.ObjKey(stack.Namespace, stack.Name))
   133  }
   134  
   135  func (r *StackReconciler) reconcileChildren(stack *latest.Stack, resourceUpdater resourceUpdater) error {
   136  	objKey := stackresources.ObjKey(stack.Namespace, stack.Name)
   137  	current, err := r.children.getCurrentStackState(objKey)
   138  	if err != nil {
   139  		log.Errorf("Failed to resolve current state for %s", objKey)
   140  		return err
   141  	}
   142  	desired, err := convert.StackToStack(*stack, r.serviceStrategy, current)
   143  	if err != nil {
   144  		log.Warnf("Failed to compute desired state for %s: %s", objKey, err)
   145  		return err
   146  	}
   147  	setStackOwnership(desired, stack)
   148  	diff := diff.ComputeDiff(current, desired)
   149  	err = resourceUpdater.applyStackDiff(diff)
   150  	if kerrors.IsConflict(errors.Cause(err)) {
   151  		// some resources where not in sync on reconciliation. Backoff for 1sec and retry
   152  		log.Warnf("Conflict when updating %s or its children, retrying in 1 sec", objKey)
   153  		go func() {
   154  			time.Sleep(time.Second)
   155  			r.reconcileRetryQueue.In() <- objKey
   156  		}()
   157  	}
   158  	return err
   159  }
   160  
   161  func (r *StackReconciler) reconcileStatus(stack *latest.Stack, reconcileError error, resourceUpdater resourceUpdater) error {
   162  	objKey := stackresources.ObjKey(stack.Namespace, stack.Name)
   163  	current, err := r.children.getCurrentStackState(objKey)
   164  	if err != nil {
   165  		return err
   166  	}
   167  	var status latest.StackStatus
   168  	if reconcileError != nil {
   169  		status = statusFailure(reconcileError)
   170  	} else {
   171  		status = generateStatus(stack, current.FlattenResources())
   172  	}
   173  	_, err = resourceUpdater.updateStackStatus(status)
   174  	if kerrors.IsConflict(errors.Cause(err)) {
   175  		// some resources where not in sync on reconciliation. Backoff for 1sec and retry
   176  		log.Warnf("Conflict when updating %s or its children, retrying in 1 sec", objKey)
   177  		go func() {
   178  			time.Sleep(time.Second)
   179  			r.reconcileRetryQueue.In() <- objKey
   180  		}()
   181  	}
   182  	return err
   183  }