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  }