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  }