github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/caasunitprovisioner/application_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  	"reflect"
     8  	"strings"
     9  
    10  	"github.com/juju/errors"
    11  	"gopkg.in/juju/names.v2"
    12  	"gopkg.in/juju/worker.v1"
    13  	"gopkg.in/juju/worker.v1/catacomb"
    14  
    15  	"github.com/juju/juju/apiserver/params"
    16  	"github.com/juju/juju/core/status"
    17  	"github.com/juju/juju/core/watcher"
    18  )
    19  
    20  type applicationWorker struct {
    21  	catacomb        catacomb.Catacomb
    22  	application     string
    23  	serviceBroker   ServiceBroker
    24  	containerBroker ContainerBroker
    25  
    26  	provisioningStatusSetter ProvisioningStatusSetter
    27  	provisioningInfoGetter   ProvisioningInfoGetter
    28  	applicationGetter        ApplicationGetter
    29  	applicationUpdater       ApplicationUpdater
    30  	unitUpdater              UnitUpdater
    31  }
    32  
    33  func newApplicationWorker(
    34  	application string,
    35  	serviceBroker ServiceBroker,
    36  	containerBroker ContainerBroker,
    37  	provisioningStatusSetter ProvisioningStatusSetter,
    38  	provisioningInfoGetter ProvisioningInfoGetter,
    39  	applicationGetter ApplicationGetter,
    40  	applicationUpdater ApplicationUpdater,
    41  	unitUpdater UnitUpdater,
    42  ) (*applicationWorker, error) {
    43  	w := &applicationWorker{
    44  		application:              application,
    45  		serviceBroker:            serviceBroker,
    46  		containerBroker:          containerBroker,
    47  		provisioningStatusSetter: provisioningStatusSetter,
    48  		provisioningInfoGetter:   provisioningInfoGetter,
    49  		applicationGetter:        applicationGetter,
    50  		applicationUpdater:       applicationUpdater,
    51  		unitUpdater:              unitUpdater,
    52  	}
    53  	if err := catacomb.Invoke(catacomb.Plan{
    54  		Site: &w.catacomb,
    55  		Work: w.loop,
    56  	}); err != nil {
    57  		return nil, errors.Trace(err)
    58  	}
    59  	return w, nil
    60  }
    61  
    62  // Kill is part of the worker.Worker interface.
    63  func (aw *applicationWorker) Kill() {
    64  	aw.catacomb.Kill(nil)
    65  }
    66  
    67  // Wait is part of the worker.Worker interface.
    68  func (aw *applicationWorker) Wait() error {
    69  	return aw.catacomb.Wait()
    70  }
    71  
    72  func (aw *applicationWorker) loop() error {
    73  	deploymentWorker, err := newDeploymentWorker(
    74  		aw.application,
    75  		aw.provisioningStatusSetter,
    76  		aw.serviceBroker,
    77  		aw.provisioningInfoGetter,
    78  		aw.applicationGetter,
    79  		aw.applicationUpdater,
    80  	)
    81  	if err != nil {
    82  		return errors.Trace(err)
    83  	}
    84  	aw.catacomb.Add(deploymentWorker)
    85  
    86  	var (
    87  		brokerUnitsWatcher watcher.NotifyWatcher
    88  		appOperatorWatcher watcher.NotifyWatcher
    89  	)
    90  	// The caas watcher can just die from underneath hence it needs to be
    91  	// restarted all the time. So we don't abuse the catacomb by adding new
    92  	// workers unbounded, use a defer to stop the running worker.
    93  	defer func() {
    94  		if brokerUnitsWatcher != nil {
    95  			worker.Stop(brokerUnitsWatcher)
    96  		}
    97  		if appOperatorWatcher != nil {
    98  			worker.Stop(appOperatorWatcher)
    99  		}
   100  	}()
   101  
   102  	// Cache the last reported status information
   103  	// so we only report true changes.
   104  	lastReportedStatus := make(map[string]status.StatusInfo)
   105  
   106  	for {
   107  		// The caas watcher can just die from underneath so recreate if needed.
   108  		if brokerUnitsWatcher == nil {
   109  			brokerUnitsWatcher, err = aw.containerBroker.WatchUnits(aw.application)
   110  			if err != nil {
   111  				if strings.Contains(err.Error(), "unexpected EOF") {
   112  					logger.Warningf("k8s cloud hosting %q has disappeared", aw.application)
   113  					return nil
   114  				}
   115  				return errors.Annotatef(err, "failed to start unit watcher for %q", aw.application)
   116  			}
   117  		}
   118  		if appOperatorWatcher == nil {
   119  			appOperatorWatcher, err = aw.containerBroker.WatchOperator(aw.application)
   120  			if err != nil {
   121  				if strings.Contains(err.Error(), "unexpected EOF") {
   122  					logger.Warningf("k8s cloud hosting %q has disappeared", aw.application)
   123  					return nil
   124  				}
   125  				return errors.Annotatef(err, "failed to start operator watcher for %q", aw.application)
   126  			}
   127  		}
   128  
   129  		select {
   130  		// We must handle any processing due to application being removed prior
   131  		// to shutdown so that we don't leave stuff running in the cloud.
   132  		case <-aw.catacomb.Dying():
   133  			return aw.catacomb.ErrDying()
   134  		case _, ok := <-brokerUnitsWatcher.Changes():
   135  			logger.Debugf("units changed: %#v", ok)
   136  			if !ok {
   137  				logger.Debugf("%v", brokerUnitsWatcher.Wait())
   138  				worker.Stop(brokerUnitsWatcher)
   139  				brokerUnitsWatcher = nil
   140  				continue
   141  			}
   142  			units, err := aw.containerBroker.Units(aw.application)
   143  			if err != nil {
   144  				return errors.Trace(err)
   145  			}
   146  			logger.Debugf("units for %v: %+v", aw.application, units)
   147  			args := params.UpdateApplicationUnits{
   148  				ApplicationTag: names.NewApplicationTag(aw.application).String(),
   149  			}
   150  			for _, u := range units {
   151  				// For pods managed by the substrate, any marked as dying
   152  				// are treated as non-existing.
   153  				if u.Dying {
   154  					continue
   155  				}
   156  				unitStatus := u.Status
   157  				lastStatus, ok := lastReportedStatus[u.Id]
   158  				lastReportedStatus[u.Id] = unitStatus
   159  				if ok {
   160  					// If we've seen the same status value previously,
   161  					// report as unknown as this value is ignored.
   162  					if reflect.DeepEqual(lastStatus, unitStatus) {
   163  						unitStatus = status.StatusInfo{
   164  							Status: status.Unknown,
   165  						}
   166  					}
   167  				}
   168  				unitParams := params.ApplicationUnitParams{
   169  					ProviderId: u.Id,
   170  					Address:    u.Address,
   171  					Ports:      u.Ports,
   172  					Status:     unitStatus.Status.String(),
   173  					Info:       unitStatus.Message,
   174  					Data:       unitStatus.Data,
   175  				}
   176  				// Fill in any filesystem info for volumes attached to the unit.
   177  				// A unit will not become active until all required volumes are
   178  				// provisioned, so it makes sense to send this information along
   179  				// with the units to which they are attached.
   180  				for _, info := range u.FilesystemInfo {
   181  					unitParams.FilesystemInfo = append(unitParams.FilesystemInfo, params.KubernetesFilesystemInfo{
   182  						StorageName:  info.StorageName,
   183  						FilesystemId: info.FilesystemId,
   184  						Size:         info.Size,
   185  						MountPoint:   info.MountPoint,
   186  						ReadOnly:     info.ReadOnly,
   187  						Status:       info.Status.Status.String(),
   188  						Info:         info.Status.Message,
   189  						Data:         info.Status.Data,
   190  						Volume: params.KubernetesVolumeInfo{
   191  							VolumeId:   info.Volume.VolumeId,
   192  							Size:       info.Volume.Size,
   193  							Persistent: info.Volume.Persistent,
   194  							Status:     info.Volume.Status.Status.String(),
   195  							Info:       info.Volume.Status.Message,
   196  							Data:       info.Volume.Status.Data,
   197  						},
   198  					})
   199  
   200  				}
   201  				args.Units = append(args.Units, unitParams)
   202  			}
   203  			if err := aw.unitUpdater.UpdateUnits(args); err != nil {
   204  				// We can ignore not found errors as the worker will get stopped anyway.
   205  				if !errors.IsNotFound(err) {
   206  					return errors.Trace(err)
   207  				}
   208  			}
   209  		case _, ok := <-appOperatorWatcher.Changes():
   210  			if !ok {
   211  				logger.Debugf("%v", appOperatorWatcher.Wait())
   212  				worker.Stop(appOperatorWatcher)
   213  				appOperatorWatcher = nil
   214  				continue
   215  			}
   216  			logger.Debugf("operator update for %v", aw.application)
   217  			operator, err := aw.containerBroker.Operator(aw.application)
   218  			if errors.IsNotFound(err) {
   219  				logger.Debugf("pod not found for application %q", aw.application)
   220  				if err := aw.provisioningStatusSetter.SetOperatorStatus(aw.application, status.Terminated, "", nil); err != nil {
   221  					return errors.Trace(err)
   222  				}
   223  			} else if err != nil {
   224  				return errors.Trace(err)
   225  			} else {
   226  				if err := aw.provisioningStatusSetter.SetOperatorStatus(aw.application, operator.Status.Status, operator.Status.Message, operator.Status.Data); err != nil {
   227  					return errors.Trace(err)
   228  				}
   229  			}
   230  		}
   231  
   232  	}
   233  }