github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/caasunitprovisioner/deployment_worker.go (about)

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package caasunitprovisioner
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"gopkg.in/juju/names.v2"
     9  	"gopkg.in/juju/worker.v1"
    10  	"gopkg.in/juju/worker.v1/catacomb"
    11  
    12  	"github.com/juju/juju/api/caasunitprovisioner"
    13  	"github.com/juju/juju/apiserver/params"
    14  	"github.com/juju/juju/caas"
    15  	"github.com/juju/juju/core/watcher"
    16  )
    17  
    18  // deploymentWorker informs the CAAS broker of how many pods to run and their spec, and
    19  // lets the broker figure out how to make that all happen.
    20  type deploymentWorker struct {
    21  	catacomb                 catacomb.Catacomb
    22  	application              string
    23  	provisioningStatusSetter ProvisioningStatusSetter
    24  	broker                   ServiceBroker
    25  	applicationGetter        ApplicationGetter
    26  	applicationUpdater       ApplicationUpdater
    27  	provisioningInfoGetter   ProvisioningInfoGetter
    28  }
    29  
    30  func newDeploymentWorker(
    31  	application string,
    32  	provisioningStatusSetter ProvisioningStatusSetter,
    33  	broker ServiceBroker,
    34  	provisioningInfoGetter ProvisioningInfoGetter,
    35  	applicationGetter ApplicationGetter,
    36  	applicationUpdater ApplicationUpdater,
    37  ) (worker.Worker, error) {
    38  	w := &deploymentWorker{
    39  		application:              application,
    40  		provisioningStatusSetter: provisioningStatusSetter,
    41  		broker:                   broker,
    42  		provisioningInfoGetter:   provisioningInfoGetter,
    43  		applicationGetter:        applicationGetter,
    44  		applicationUpdater:       applicationUpdater,
    45  	}
    46  	if err := catacomb.Invoke(catacomb.Plan{
    47  		Site: &w.catacomb,
    48  		Work: w.loop,
    49  	}); err != nil {
    50  		return nil, errors.Trace(err)
    51  	}
    52  	return w, nil
    53  }
    54  
    55  // Kill is part of the worker.Worker interface.
    56  func (w *deploymentWorker) Kill() {
    57  	w.catacomb.Kill(nil)
    58  }
    59  
    60  // Wait is part of the worker.Worker interface.
    61  func (w *deploymentWorker) Wait() error {
    62  	return w.catacomb.Wait()
    63  }
    64  
    65  func (w *deploymentWorker) loop() error {
    66  	appScaleWatcher, err := w.applicationGetter.WatchApplicationScale(w.application)
    67  	if err != nil {
    68  		return errors.Trace(err)
    69  	}
    70  	w.catacomb.Add(appScaleWatcher)
    71  
    72  	var (
    73  		cw       watcher.NotifyWatcher
    74  		specChan watcher.NotifyChannel
    75  
    76  		currentScale int
    77  		currentSpec  string
    78  	)
    79  
    80  	gotSpecNotify := false
    81  	serviceUpdated := false
    82  	scale := 0
    83  	for {
    84  		select {
    85  		case <-w.catacomb.Dying():
    86  			return w.catacomb.ErrDying()
    87  		case _, ok := <-appScaleWatcher.Changes():
    88  			if !ok {
    89  				return errors.New("watcher closed channel")
    90  			}
    91  			var err error
    92  			scale, err = w.applicationGetter.ApplicationScale(w.application)
    93  			if err != nil {
    94  				return errors.Trace(err)
    95  			}
    96  			if scale > 0 && specChan == nil {
    97  				var err error
    98  				cw, err = w.provisioningInfoGetter.WatchPodSpec(w.application)
    99  				if err != nil {
   100  					return errors.Trace(err)
   101  				}
   102  				w.catacomb.Add(cw)
   103  				specChan = cw.Changes()
   104  			}
   105  		case _, ok := <-specChan:
   106  			if !ok {
   107  				return errors.New("watcher closed channel")
   108  			}
   109  			gotSpecNotify = true
   110  		}
   111  		if scale > 0 && !gotSpecNotify {
   112  			continue
   113  		}
   114  		info, err := w.provisioningInfoGetter.ProvisioningInfo(w.application)
   115  		noUnits := errors.Cause(err) == caasunitprovisioner.ErrNoUnits
   116  		if errors.IsNotFound(err) {
   117  			// No pod spec defined for a unit yet;
   118  			// wait for one to be set.
   119  			continue
   120  		} else if err != nil && !noUnits {
   121  			return errors.Trace(err)
   122  		}
   123  		if scale == 0 || noUnits {
   124  			if cw != nil {
   125  				worker.Stop(cw)
   126  				specChan = nil
   127  			}
   128  			logger.Debugf("no units for %v", w.application)
   129  			err = w.broker.EnsureService(w.application, w.provisioningStatusSetter.SetOperatorStatus, &caas.ServiceParams{}, 0, nil)
   130  			if err != nil {
   131  				return errors.Trace(err)
   132  			}
   133  			currentScale = 0
   134  			continue
   135  		}
   136  		specStr := info.PodSpec
   137  
   138  		if scale == currentScale && specStr == currentSpec {
   139  			continue
   140  		}
   141  
   142  		currentScale = scale
   143  		currentSpec = specStr
   144  
   145  		appConfig, err := w.applicationGetter.ApplicationConfig(w.application)
   146  		if err != nil {
   147  			return errors.Trace(err)
   148  		}
   149  		spec, err := w.broker.Provider().ParsePodSpec(specStr)
   150  		if err != nil {
   151  			return errors.Annotate(err, "cannot parse pod spec")
   152  		}
   153  		if len(spec.CustomResourceDefinitions) > 0 {
   154  			err = w.broker.EnsureCustomResourceDefinition(w.application, spec)
   155  			if err != nil {
   156  				return errors.Trace(err)
   157  			}
   158  			logger.Debugf("created/updated custom resource definition for %q.", w.application)
   159  		}
   160  		serviceParams := &caas.ServiceParams{
   161  			PodSpec:      spec,
   162  			Constraints:  info.Constraints,
   163  			Placement:    info.Placement,
   164  			ResourceTags: info.Tags,
   165  			Filesystems:  info.Filesystems,
   166  			Devices:      info.Devices,
   167  		}
   168  		err = w.broker.EnsureService(w.application, w.provisioningStatusSetter.SetOperatorStatus, serviceParams, currentScale, appConfig)
   169  		if err != nil {
   170  			return errors.Trace(err)
   171  		}
   172  		logger.Debugf("created/updated deployment for %s for %v units", w.application, currentScale)
   173  		if !serviceUpdated && !spec.OmitServiceFrontend {
   174  			// TODO(caas) - add a service watcher
   175  			service, err := w.broker.Service(w.application)
   176  			if err != nil && !errors.IsNotFound(err) {
   177  				return errors.Annotate(err, "cannot get new service details")
   178  			}
   179  			err = w.applicationUpdater.UpdateApplicationService(params.UpdateApplicationServiceArg{
   180  				ApplicationTag: names.NewApplicationTag(w.application).String(),
   181  				ProviderId:     service.Id,
   182  				Addresses:      params.FromNetworkAddresses(service.Addresses...),
   183  			})
   184  			if err != nil {
   185  				return errors.Trace(err)
   186  			}
   187  			serviceUpdated = true
   188  		}
   189  	}
   190  }