github.com/AliyunContainerService/cli@v0.0.0-20181009023821-814ced4b30d0/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/cli/kubernetes/compose/v1beta1" 9 "github.com/docker/cli/kubernetes/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/runtime" 14 runtimeutil "k8s.io/apimachinery/pkg/util/runtime" 15 "k8s.io/apimachinery/pkg/watch" 16 cache "k8s.io/client-go/tools/cache" 17 podutils "k8s.io/kubernetes/pkg/api/v1/pod" 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 podutils.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) allReady() bool { 173 for _, status := range pw.services { 174 if status.podsReady == 0 { 175 return false 176 } 177 } 178 return true 179 } 180 181 func (pw *podWatcher) OnAdd(obj interface{}) { 182 pw.handlePod(obj) 183 } 184 185 func (pw *podWatcher) OnUpdate(oldObj, newObj interface{}) { 186 pw.handlePod(newObj) 187 } 188 189 func (pw *podWatcher) OnDelete(obj interface{}) { 190 pw.handlePod(obj) 191 } 192 193 func (w *deployWatcher) waitForPods(ctx context.Context, stackName string, serviceNames []string, e chan error, statusUpdates chan serviceStatus) { 194 informer := newPodInformer(w.pods, stackName, cache.Indexers{ 195 "byservice": func(obj interface{}) ([]string, error) { 196 pod, ok := obj.(*apiv1.Pod) 197 if !ok { 198 return nil, errors.Errorf("Pod has incorrect type in stack %s", stackName) 199 } 200 return []string{pod.Labels[labels.ForServiceName]}, nil 201 }}) 202 services := map[string]serviceStatus{} 203 for _, name := range serviceNames { 204 services[name] = serviceStatus{name: name} 205 } 206 pw := &podWatcher{ 207 stackName: stackName, 208 services: services, 209 resultChan: e, 210 starts: map[string]int32{}, 211 indexer: informer.GetIndexer(), 212 statusUpdates: statusUpdates, 213 } 214 informer.AddEventHandler(pw) 215 informer.Run(ctx.Done()) 216 } 217 218 func newPodInformer(podsClient podListWatch, stackName string, indexers cache.Indexers) cache.SharedIndexInformer { 219 return cache.NewSharedIndexInformer( 220 &cache.ListWatch{ 221 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 222 options.LabelSelector = labels.SelectorForStack(stackName) 223 options.IncludeUninitialized = true 224 return podsClient.List(options) 225 }, 226 227 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 228 options.LabelSelector = labels.SelectorForStack(stackName) 229 options.IncludeUninitialized = true 230 return podsClient.Watch(options) 231 }, 232 }, 233 &apiv1.Pod{}, 234 time.Second*5, 235 indexers, 236 ) 237 } 238 239 func newStackInformer(stacksClient stackListWatch, stackName string) cache.SharedInformer { 240 return cache.NewSharedInformer( 241 &cache.ListWatch{ 242 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 243 options.LabelSelector = labels.SelectorForStack(stackName) 244 return stacksClient.List(options) 245 }, 246 247 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 248 options.LabelSelector = labels.SelectorForStack(stackName) 249 return stacksClient.Watch(options) 250 }, 251 }, 252 &apiv1beta1.Stack{}, 253 time.Second*5, 254 ) 255 }