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