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 }