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  }