github.com/xeptore/docker-cli@v20.10.14+incompatible/cli/command/stack/kubernetes/watcher.go (about) 1 package kubernetes 2 3 import ( 4 "context" 5 "sync" 6 "time" 7 8 apiv1beta1 "github.com/docker/compose-on-kubernetes/api/compose/v1beta1" 9 "github.com/docker/compose-on-kubernetes/api/labels" 10 "github.com/pkg/errors" 11 apiv1 "k8s.io/api/core/v1" 12 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 "k8s.io/apimachinery/pkg/fields" 14 "k8s.io/apimachinery/pkg/runtime" 15 runtimeutil "k8s.io/apimachinery/pkg/util/runtime" 16 "k8s.io/apimachinery/pkg/watch" 17 cache "k8s.io/client-go/tools/cache" 18 ) 19 20 type stackListWatch interface { 21 List(opts metav1.ListOptions) (*apiv1beta1.StackList, error) 22 Watch(opts metav1.ListOptions) (watch.Interface, error) 23 } 24 25 type podListWatch interface { 26 List(opts metav1.ListOptions) (*apiv1.PodList, error) 27 Watch(opts metav1.ListOptions) (watch.Interface, error) 28 } 29 30 // DeployWatcher watches a stack deployement 31 type deployWatcher struct { 32 pods podListWatch 33 stacks stackListWatch 34 } 35 36 // Watch watches a stuck deployement and return a chan that will holds the state of the stack 37 func (w *deployWatcher) Watch(name string, serviceNames []string, statusUpdates chan serviceStatus) error { 38 errC := make(chan error, 1) 39 defer close(errC) 40 41 handlers := runtimeutil.ErrorHandlers 42 43 // informer errors are reported using global error handlers 44 runtimeutil.ErrorHandlers = append(handlers, func(err error) { 45 errC <- err 46 }) 47 defer func() { 48 runtimeutil.ErrorHandlers = handlers 49 }() 50 51 ctx, cancel := context.WithCancel(context.Background()) 52 wg := sync.WaitGroup{} 53 defer func() { 54 cancel() 55 wg.Wait() 56 }() 57 wg.Add(2) 58 go func() { 59 defer wg.Done() 60 w.watchStackStatus(ctx, name, errC) 61 }() 62 go func() { 63 defer wg.Done() 64 w.waitForPods(ctx, name, serviceNames, errC, statusUpdates) 65 }() 66 67 return <-errC 68 } 69 70 type stackWatcher struct { 71 resultChan chan error 72 stackName string 73 } 74 75 var _ cache.ResourceEventHandler = &stackWatcher{} 76 77 func (sw *stackWatcher) OnAdd(obj interface{}) { 78 stack, ok := obj.(*apiv1beta1.Stack) 79 switch { 80 case !ok: 81 sw.resultChan <- errors.Errorf("stack %s has incorrect type", sw.stackName) 82 case stack.Status.Phase == apiv1beta1.StackFailure: 83 sw.resultChan <- errors.Errorf("stack %s failed with status %s: %s", sw.stackName, stack.Status.Phase, stack.Status.Message) 84 } 85 } 86 87 func (sw *stackWatcher) OnUpdate(oldObj, newObj interface{}) { 88 sw.OnAdd(newObj) 89 } 90 91 func (sw *stackWatcher) OnDelete(obj interface{}) { 92 } 93 94 func (w *deployWatcher) watchStackStatus(ctx context.Context, stackname string, e chan error) { 95 informer := newStackInformer(w.stacks, stackname) 96 sw := &stackWatcher{ 97 resultChan: e, 98 } 99 informer.AddEventHandler(sw) 100 informer.Run(ctx.Done()) 101 } 102 103 type serviceStatus struct { 104 name string 105 podsPending int 106 podsRunning int 107 podsSucceeded int 108 podsFailed int 109 podsUnknown int 110 podsReady int 111 podsTotal int 112 } 113 114 type podWatcher struct { 115 stackName string 116 services map[string]serviceStatus 117 resultChan chan error 118 starts map[string]int32 119 indexer cache.Indexer 120 statusUpdates chan serviceStatus 121 } 122 123 var _ cache.ResourceEventHandler = &podWatcher{} 124 125 func (pw *podWatcher) handlePod(obj interface{}) { 126 pod, ok := obj.(*apiv1.Pod) 127 if !ok { 128 pw.resultChan <- errors.Errorf("Pod has incorrect type in stack %s", pw.stackName) 129 return 130 } 131 serviceName := pod.Labels[labels.ForServiceName] 132 pw.updateServiceStatus(serviceName) 133 if pw.allReady() { 134 select { 135 case pw.resultChan <- nil: 136 default: 137 // result has already been reported, just don't block 138 } 139 } 140 } 141 142 func (pw *podWatcher) updateServiceStatus(serviceName string) { 143 pods, _ := pw.indexer.ByIndex("byservice", serviceName) 144 status := serviceStatus{name: serviceName} 145 for _, obj := range pods { 146 if pod, ok := obj.(*apiv1.Pod); ok { 147 switch pod.Status.Phase { 148 case apiv1.PodPending: 149 status.podsPending++ 150 case apiv1.PodRunning: 151 status.podsRunning++ 152 case apiv1.PodSucceeded: 153 status.podsSucceeded++ 154 case apiv1.PodFailed: 155 status.podsFailed++ 156 case apiv1.PodUnknown: 157 status.podsUnknown++ 158 } 159 if pw.isPodReady(pod) { 160 status.podsReady++ 161 } 162 } 163 } 164 status.podsTotal = len(pods) 165 oldStatus := pw.services[serviceName] 166 if oldStatus != status { 167 pw.statusUpdates <- status 168 } 169 pw.services[serviceName] = status 170 } 171 172 func (pw *podWatcher) isPodReady(pod *apiv1.Pod) bool { 173 for _, condition := range pod.Status.Conditions { 174 if condition.Status == apiv1.ConditionTrue && condition.Type == apiv1.PodReady { 175 return true 176 } 177 } 178 return false 179 } 180 181 func (pw *podWatcher) allReady() bool { 182 for _, status := range pw.services { 183 if status.podsReady == 0 { 184 return false 185 } 186 } 187 return true 188 } 189 190 func (pw *podWatcher) OnAdd(obj interface{}) { 191 pw.handlePod(obj) 192 } 193 194 func (pw *podWatcher) OnUpdate(oldObj, newObj interface{}) { 195 pw.handlePod(newObj) 196 } 197 198 func (pw *podWatcher) OnDelete(obj interface{}) { 199 pw.handlePod(obj) 200 } 201 202 func (w *deployWatcher) waitForPods(ctx context.Context, stackName string, serviceNames []string, e chan error, statusUpdates chan serviceStatus) { 203 informer := newPodInformer(w.pods, stackName, cache.Indexers{ 204 "byservice": func(obj interface{}) ([]string, error) { 205 pod, ok := obj.(*apiv1.Pod) 206 if !ok { 207 return nil, errors.Errorf("Pod has incorrect type in stack %s", stackName) 208 } 209 return []string{pod.Labels[labels.ForServiceName]}, nil 210 }}) 211 services := map[string]serviceStatus{} 212 for _, name := range serviceNames { 213 services[name] = serviceStatus{name: name} 214 } 215 pw := &podWatcher{ 216 stackName: stackName, 217 services: services, 218 resultChan: e, 219 starts: map[string]int32{}, 220 indexer: informer.GetIndexer(), 221 statusUpdates: statusUpdates, 222 } 223 informer.AddEventHandler(pw) 224 informer.Run(ctx.Done()) 225 } 226 227 func newPodInformer(podsClient podListWatch, stackName string, indexers cache.Indexers) cache.SharedIndexInformer { 228 return cache.NewSharedIndexInformer( 229 &cache.ListWatch{ 230 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 231 options.LabelSelector = labels.SelectorForStack(stackName) 232 return podsClient.List(options) 233 }, 234 235 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 236 options.LabelSelector = labels.SelectorForStack(stackName) 237 return podsClient.Watch(options) 238 }, 239 }, 240 &apiv1.Pod{}, 241 time.Second*5, 242 indexers, 243 ) 244 } 245 246 func newStackInformer(stacksClient stackListWatch, stackName string) cache.SharedInformer { 247 return cache.NewSharedInformer( 248 &cache.ListWatch{ 249 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 250 options.FieldSelector = fields.OneTermEqualSelector("metadata.name", stackName).String() 251 return stacksClient.List(options) 252 }, 253 254 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 255 options.FieldSelector = fields.OneTermEqualSelector("metadata.name", stackName).String() 256 return stacksClient.Watch(options) 257 }, 258 }, 259 &apiv1beta1.Stack{}, 260 time.Second*5, 261 ) 262 }