github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/controller/caasunitprovisioner/provisioner.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  	"sort"
     8  
     9  	"github.com/juju/clock"
    10  	"github.com/juju/collections/set"
    11  	"github.com/juju/errors"
    12  	"github.com/juju/loggo"
    13  	"gopkg.in/juju/names.v2"
    14  
    15  	"github.com/juju/juju/apiserver/common"
    16  	"github.com/juju/juju/apiserver/common/storagecommon"
    17  	"github.com/juju/juju/apiserver/facade"
    18  	"github.com/juju/juju/apiserver/params"
    19  	"github.com/juju/juju/caas"
    20  	"github.com/juju/juju/controller"
    21  	"github.com/juju/juju/core/status"
    22  	"github.com/juju/juju/environs/config"
    23  	"github.com/juju/juju/environs/tags"
    24  	"github.com/juju/juju/state"
    25  	"github.com/juju/juju/state/stateenvirons"
    26  	"github.com/juju/juju/state/watcher"
    27  	"github.com/juju/juju/storage"
    28  	"github.com/juju/juju/storage/poolmanager"
    29  )
    30  
    31  var logger = loggo.GetLogger("juju.apiserver.controller.caasunitprovisioner")
    32  
    33  type Facade struct {
    34  	*common.LifeGetter
    35  	resources               facade.Resources
    36  	state                   CAASUnitProvisionerState
    37  	storage                 StorageBackend
    38  	storageProviderRegistry storage.ProviderRegistry
    39  	storagePoolManager      poolmanager.PoolManager
    40  	devices                 DeviceBackend
    41  	clock                   clock.Clock
    42  }
    43  
    44  // NewStateFacade provides the signature required for facade registration.
    45  func NewStateFacade(ctx facade.Context) (*Facade, error) {
    46  	authorizer := ctx.Auth()
    47  	resources := ctx.Resources()
    48  	sb, err := state.NewStorageBackend(ctx.State())
    49  	if err != nil {
    50  		return nil, errors.Trace(err)
    51  	}
    52  	db, err := state.NewDeviceBackend(ctx.State())
    53  	if err != nil {
    54  		return nil, errors.Trace(err)
    55  	}
    56  
    57  	broker, err := stateenvirons.GetNewCAASBrokerFunc(caas.New)(ctx.State())
    58  	if err != nil {
    59  		return nil, errors.Annotate(err, "getting caas client")
    60  	}
    61  	registry := stateenvirons.NewStorageProviderRegistry(broker)
    62  	pm := poolmanager.New(state.NewStateSettings(ctx.State()), registry)
    63  
    64  	return NewFacade(
    65  		resources,
    66  		authorizer,
    67  		stateShim{ctx.State()},
    68  		sb,
    69  		db,
    70  		registry,
    71  		pm,
    72  		clock.WallClock,
    73  	)
    74  }
    75  
    76  // NewFacade returns a new CAAS unit provisioner Facade facade.
    77  func NewFacade(
    78  	resources facade.Resources,
    79  	authorizer facade.Authorizer,
    80  	st CAASUnitProvisionerState,
    81  	sb StorageBackend,
    82  	db DeviceBackend,
    83  	storageProviderRegistry storage.ProviderRegistry,
    84  	storagePoolManager poolmanager.PoolManager,
    85  	clock clock.Clock,
    86  ) (*Facade, error) {
    87  	if !authorizer.AuthController() {
    88  		return nil, common.ErrPerm
    89  	}
    90  	return &Facade{
    91  		LifeGetter: common.NewLifeGetter(
    92  			st, common.AuthAny(
    93  				common.AuthFuncForTagKind(names.ApplicationTagKind),
    94  				common.AuthFuncForTagKind(names.UnitTagKind),
    95  			),
    96  		),
    97  		resources:               resources,
    98  		state:                   st,
    99  		storage:                 sb,
   100  		devices:                 db,
   101  		storageProviderRegistry: storageProviderRegistry,
   102  		storagePoolManager:      storagePoolManager,
   103  		clock:                   clock,
   104  	}, nil
   105  }
   106  
   107  // WatchApplications starts a StringsWatcher to watch CAAS applications
   108  // deployed to this model.
   109  func (f *Facade) WatchApplications() (params.StringsWatchResult, error) {
   110  	watch := f.state.WatchApplications()
   111  	if changes, ok := <-watch.Changes(); ok {
   112  		return params.StringsWatchResult{
   113  			StringsWatcherId: f.resources.Register(watch),
   114  			Changes:          changes,
   115  		}, nil
   116  	}
   117  	return params.StringsWatchResult{}, watcher.EnsureErr(watch)
   118  }
   119  
   120  // WatchApplicationsScale starts a NotifyWatcher to watch changes
   121  // to the applications' scale.
   122  func (f *Facade) WatchApplicationsScale(args params.Entities) (params.NotifyWatchResults, error) {
   123  	results := params.NotifyWatchResults{
   124  		Results: make([]params.NotifyWatchResult, len(args.Entities)),
   125  	}
   126  	for i, arg := range args.Entities {
   127  		id, err := f.watchApplicationScale(arg.Tag)
   128  		if err != nil {
   129  			results.Results[i].Error = common.ServerError(err)
   130  			continue
   131  		}
   132  		results.Results[i].NotifyWatcherId = id
   133  	}
   134  	return results, nil
   135  }
   136  
   137  func (f *Facade) watchApplicationScale(tagString string) (string, error) {
   138  	tag, err := names.ParseApplicationTag(tagString)
   139  	if err != nil {
   140  		return "", errors.Trace(err)
   141  	}
   142  	app, err := f.state.Application(tag.Id())
   143  	if err != nil {
   144  		return "", errors.Trace(err)
   145  	}
   146  	w := app.WatchScale()
   147  	if _, ok := <-w.Changes(); ok {
   148  		return f.resources.Register(w), nil
   149  	}
   150  	return "", watcher.EnsureErr(w)
   151  }
   152  
   153  // WatchPodSpec starts a NotifyWatcher to watch changes to the
   154  // pod spec for specified units in this model.
   155  func (f *Facade) WatchPodSpec(args params.Entities) (params.NotifyWatchResults, error) {
   156  	model, err := f.state.Model()
   157  	if err != nil {
   158  		return params.NotifyWatchResults{}, errors.Trace(err)
   159  	}
   160  	results := params.NotifyWatchResults{
   161  		Results: make([]params.NotifyWatchResult, len(args.Entities)),
   162  	}
   163  	for i, arg := range args.Entities {
   164  		id, err := f.watchPodSpec(model, arg.Tag)
   165  		if err != nil {
   166  			results.Results[i].Error = common.ServerError(err)
   167  			continue
   168  		}
   169  		results.Results[i].NotifyWatcherId = id
   170  	}
   171  	return results, nil
   172  }
   173  
   174  func (f *Facade) watchPodSpec(model Model, tagString string) (string, error) {
   175  	tag, err := names.ParseApplicationTag(tagString)
   176  	if err != nil {
   177  		return "", errors.Trace(err)
   178  	}
   179  	w, err := model.WatchPodSpec(tag)
   180  	if err != nil {
   181  		return "", errors.Trace(err)
   182  	}
   183  	if _, ok := <-w.Changes(); ok {
   184  		return f.resources.Register(w), nil
   185  	}
   186  	return "", watcher.EnsureErr(w)
   187  }
   188  
   189  // ApplicationsScale returns the scaling info for specified applications in this model.
   190  func (f *Facade) ApplicationsScale(args params.Entities) (params.IntResults, error) {
   191  	results := params.IntResults{
   192  		Results: make([]params.IntResult, len(args.Entities)),
   193  	}
   194  	for i, arg := range args.Entities {
   195  		scale, err := f.applicationScale(arg.Tag)
   196  		if err != nil {
   197  			results.Results[i].Error = common.ServerError(err)
   198  			continue
   199  		}
   200  		results.Results[i].Result = scale
   201  	}
   202  	logger.Debugf("provisioning info result: %#v", results)
   203  	return results, nil
   204  }
   205  
   206  func (f *Facade) applicationScale(tagString string) (int, error) {
   207  	appTag, err := names.ParseApplicationTag(tagString)
   208  	if err != nil {
   209  		return 0, errors.Trace(err)
   210  	}
   211  	app, err := f.state.Application(appTag.Id())
   212  	if err != nil {
   213  		return 0, errors.Trace(err)
   214  	}
   215  	return app.GetScale(), nil
   216  }
   217  
   218  // ProvisioningInfo returns the provisioning info for specified applications in this model.
   219  func (f *Facade) ProvisioningInfo(args params.Entities) (params.KubernetesProvisioningInfoResults, error) {
   220  	model, err := f.state.Model()
   221  	if err != nil {
   222  		return params.KubernetesProvisioningInfoResults{}, errors.Trace(err)
   223  	}
   224  	results := params.KubernetesProvisioningInfoResults{
   225  		Results: make([]params.KubernetesProvisioningInfoResult, len(args.Entities)),
   226  	}
   227  	for i, arg := range args.Entities {
   228  		info, err := f.provisioningInfo(model, arg.Tag)
   229  		if err != nil {
   230  			results.Results[i].Error = common.ServerError(err)
   231  			continue
   232  		}
   233  		results.Results[i].Result = info
   234  	}
   235  	logger.Debugf("provisioning info result: %#v", results)
   236  	return results, nil
   237  }
   238  
   239  func (f *Facade) provisioningInfo(model Model, tagString string) (*params.KubernetesProvisioningInfo, error) {
   240  	appTag, err := names.ParseApplicationTag(tagString)
   241  	if err != nil {
   242  		return nil, errors.Trace(err)
   243  	}
   244  	// First the pod spec.
   245  	podSpec, err := model.PodSpec(appTag)
   246  	if err != nil {
   247  		return nil, errors.Trace(err)
   248  	}
   249  
   250  	// Now get any required storage. We need to provision storage
   251  	// at the same time as the pod as it can't be attached later.
   252  
   253  	// All units are currently homogeneous so we just
   254  	// need to get info for the first alive unit.
   255  	app, err := f.state.Application(appTag.Id())
   256  	if err != nil {
   257  		return nil, errors.Trace(err)
   258  	}
   259  	units, err := app.AllUnits()
   260  	if err != nil {
   261  		return nil, errors.Trace(err)
   262  	}
   263  	// Can happen if scale is set to 0 in k8s, outside of Juju.
   264  	// In this case, there is no provisioning info to return.
   265  	if len(units) == 0 {
   266  		logger.Debugf("cannot provision application %q with no units", appTag.Id())
   267  		return nil, nil
   268  	}
   269  	modelConfig, err := model.ModelConfig()
   270  	if err != nil {
   271  		return nil, errors.Trace(err)
   272  	}
   273  
   274  	// Find the first alive unit which will be used to get filesystem info.
   275  	var aliveUnit Unit
   276  	for _, u := range units {
   277  		if u.Life() == state.Alive {
   278  			aliveUnit = u
   279  			break
   280  		}
   281  	}
   282  
   283  	controllerCfg, err := f.state.ControllerConfig()
   284  	if err != nil {
   285  		return nil, errors.Trace(err)
   286  	}
   287  
   288  	var filesystemParams []params.KubernetesFilesystemParams
   289  	if aliveUnit != nil {
   290  		filesystemParams, err = f.applicationFilesystemParams(controllerCfg, modelConfig, aliveUnit.UnitTag())
   291  		if err != nil {
   292  			return nil, errors.Trace(err)
   293  		}
   294  
   295  		// The juju-storage-owner tag is set to the unit. We use it as a label on the CAAS volume.
   296  		// Since we used an arbitrary unit to get the info, reset the tag to the application.
   297  		for _, fsp := range filesystemParams {
   298  			fsp.Tags[tags.JujuStorageOwner] = appTag.Id()
   299  		}
   300  	}
   301  
   302  	devices, err := f.devicesParams(app)
   303  	if err != nil {
   304  		return nil, errors.Trace(err)
   305  	}
   306  	cons, err := app.Constraints()
   307  	if err != nil {
   308  		return nil, errors.Trace(err)
   309  	}
   310  	resourceTags := tags.ResourceTags(
   311  		names.NewModelTag(modelConfig.UUID()),
   312  		names.NewControllerTag(controllerCfg.ControllerUUID()),
   313  		modelConfig,
   314  	)
   315  
   316  	return &params.KubernetesProvisioningInfo{
   317  		PodSpec:     podSpec,
   318  		Filesystems: filesystemParams,
   319  		Devices:     devices,
   320  		Constraints: cons,
   321  		Placement:   app.GetPlacement(),
   322  		Tags:        resourceTags,
   323  	}, nil
   324  }
   325  
   326  func filesystemParams(
   327  	f state.Filesystem,
   328  	storageInstance state.StorageInstance,
   329  	modelUUID, controllerUUID string,
   330  	modelConfig *config.Config,
   331  	poolManager poolmanager.PoolManager,
   332  	registry storage.ProviderRegistry,
   333  ) (params.KubernetesFilesystemParams, error) {
   334  
   335  	var pool string
   336  	var size uint64
   337  	if stateFilesystemParams, ok := f.Params(); ok {
   338  		pool = stateFilesystemParams.Pool
   339  		size = stateFilesystemParams.Size
   340  	} else {
   341  		filesystemInfo, err := f.Info()
   342  		if err != nil {
   343  			return params.KubernetesFilesystemParams{}, errors.Trace(err)
   344  		}
   345  		pool = filesystemInfo.Pool
   346  		size = filesystemInfo.Size
   347  	}
   348  
   349  	filesystemTags, err := storagecommon.StorageTags(storageInstance, modelUUID, controllerUUID, modelConfig)
   350  	if err != nil {
   351  		return params.KubernetesFilesystemParams{}, errors.Annotate(err, "computing storage tags")
   352  	}
   353  
   354  	providerType, cfg, err := storagecommon.StoragePoolConfig(pool, poolManager, registry)
   355  	if err != nil {
   356  		return params.KubernetesFilesystemParams{}, errors.Trace(err)
   357  	}
   358  	result := params.KubernetesFilesystemParams{
   359  		Provider:    string(providerType),
   360  		Attributes:  cfg.Attrs(),
   361  		Tags:        filesystemTags,
   362  		Size:        size,
   363  		StorageName: storageInstance.StorageName(),
   364  	}
   365  	return result, nil
   366  }
   367  
   368  // applicationFilesystemParams retrieves FilesystemParams for the filesystems
   369  // that should be provisioned with, and attached to, pods of the application.
   370  func (f *Facade) applicationFilesystemParams(
   371  	controllerConfig controller.Config,
   372  	modelConfig *config.Config,
   373  	unitTag names.UnitTag,
   374  ) ([]params.KubernetesFilesystemParams, error) {
   375  	attachments, err := f.storage.UnitStorageAttachments(unitTag)
   376  	if err != nil {
   377  		return nil, errors.Trace(err)
   378  	}
   379  	if len(attachments) == 0 {
   380  		return nil, nil
   381  	}
   382  
   383  	allFilesystemParams := make([]params.KubernetesFilesystemParams, 0, len(attachments))
   384  	for _, attachment := range attachments {
   385  		si, err := f.storage.StorageInstance(attachment.StorageInstance())
   386  		if err != nil {
   387  			return nil, errors.Trace(err)
   388  		}
   389  		fs, err := f.storage.StorageInstanceFilesystem(si.StorageTag())
   390  		if err != nil {
   391  			return nil, errors.Trace(err)
   392  		}
   393  		filesystemParams, err := filesystemParams(
   394  			fs, si, modelConfig.UUID(), controllerConfig.ControllerUUID(),
   395  			modelConfig, f.storagePoolManager, f.storageProviderRegistry,
   396  		)
   397  		if err != nil {
   398  			return nil, errors.Annotatef(err, "getting filesystem %q parameters", fs.Tag().Id())
   399  		}
   400  		filesystemAttachment, err := f.storage.FilesystemAttachment(unitTag, fs.FilesystemTag())
   401  		if err != nil {
   402  			return nil, errors.Annotatef(err, "getting filesystem %q attachment info", fs.Tag().Id())
   403  		}
   404  		var location string
   405  		var readOnly bool
   406  		if filesystemAttachmentParams, ok := filesystemAttachment.Params(); ok {
   407  			location = filesystemAttachmentParams.Location
   408  			readOnly = filesystemAttachmentParams.ReadOnly
   409  		} else {
   410  			// All units are the same so even if the attachment exists
   411  			// for the unit used to gather info, we still need to read
   412  			// the relevant attachment params for the application as a whole.
   413  			filesystemAttachmentInfo, err := filesystemAttachment.Info()
   414  			if err != nil {
   415  				return nil, errors.Trace(err)
   416  			}
   417  			location = filesystemAttachmentInfo.MountPoint
   418  			readOnly = filesystemAttachmentInfo.ReadOnly
   419  		}
   420  		filesystemAttachmentParams := params.KubernetesFilesystemAttachmentParams{
   421  			Provider:   filesystemParams.Provider,
   422  			MountPoint: location,
   423  			ReadOnly:   readOnly,
   424  		}
   425  		filesystemParams.Attachment = &filesystemAttachmentParams
   426  		allFilesystemParams = append(allFilesystemParams, filesystemParams)
   427  	}
   428  	return allFilesystemParams, nil
   429  }
   430  
   431  func (f *Facade) devicesParams(app Application) ([]params.KubernetesDeviceParams, error) {
   432  	devices, err := app.DeviceConstraints()
   433  	if err != nil {
   434  		return nil, errors.Trace(err)
   435  	}
   436  	logger.Debugf("getting device constraints from state: %#v", devices)
   437  	var devicesParams []params.KubernetesDeviceParams
   438  	for _, d := range devices {
   439  		devicesParams = append(devicesParams, params.KubernetesDeviceParams{
   440  			Type:       params.DeviceType(d.Type),
   441  			Count:      d.Count,
   442  			Attributes: d.Attributes,
   443  		})
   444  	}
   445  	return devicesParams, nil
   446  }
   447  
   448  // ApplicationsConfig returns the config for the specified applications.
   449  func (f *Facade) ApplicationsConfig(args params.Entities) (params.ApplicationGetConfigResults, error) {
   450  	results := params.ApplicationGetConfigResults{
   451  		Results: make([]params.ConfigResult, len(args.Entities)),
   452  	}
   453  	for i, arg := range args.Entities {
   454  		result, err := f.getApplicationConfig(arg.Tag)
   455  		results.Results[i].Config = result
   456  		results.Results[i].Error = common.ServerError(err)
   457  	}
   458  	return results, nil
   459  }
   460  
   461  func (f *Facade) getApplicationConfig(tagString string) (map[string]interface{}, error) {
   462  	tag, err := names.ParseApplicationTag(tagString)
   463  	if err != nil {
   464  		return nil, errors.Trace(err)
   465  	}
   466  	app, err := f.state.Application(tag.Id())
   467  	if err != nil {
   468  		return nil, errors.Trace(err)
   469  	}
   470  	return app.ApplicationConfig()
   471  }
   472  
   473  // UpdateApplicationsUnits updates the Juju data model to reflect the given
   474  // units of the specified application.
   475  func (a *Facade) UpdateApplicationsUnits(args params.UpdateApplicationUnitArgs) (params.ErrorResults, error) {
   476  	result := params.ErrorResults{
   477  		Results: make([]params.ErrorResult, len(args.Args)),
   478  	}
   479  	if len(args.Args) == 0 {
   480  		return result, nil
   481  	}
   482  	for i, appUpdate := range args.Args {
   483  		appTag, err := names.ParseApplicationTag(appUpdate.ApplicationTag)
   484  		if err != nil {
   485  			result.Results[i].Error = common.ServerError(err)
   486  			continue
   487  		}
   488  		app, err := a.state.Application(appTag.Id())
   489  		if err != nil {
   490  			result.Results[i].Error = common.ServerError(err)
   491  			continue
   492  		}
   493  		err = a.updateUnitsFromCloud(app, appUpdate.Units)
   494  		if err != nil {
   495  			// Mask any not found errors as the worker (caller) treats them specially
   496  			// and they are not relevant here.
   497  			result.Results[i].Error = common.ServerError(errors.Mask(err))
   498  		}
   499  	}
   500  	return result, nil
   501  }
   502  
   503  // updateStatus constructs the agent and cloud container status values.
   504  func (a *Facade) updateStatus(params params.ApplicationUnitParams) (
   505  	agentStatus *status.StatusInfo,
   506  	cloudContainerStatus *status.StatusInfo,
   507  ) {
   508  	var containerStatus status.Status
   509  	switch status.Status(params.Status) {
   510  	case status.Unknown:
   511  		// The container runtime can spam us with unimportant
   512  		// status updates, so ignore any irrelevant ones.
   513  		return nil, nil
   514  	case status.Allocating:
   515  		// The container runtime has decided to restart the pod.
   516  		agentStatus = &status.StatusInfo{
   517  			Status:  status.Allocating,
   518  			Message: params.Info,
   519  		}
   520  		containerStatus = status.Waiting
   521  	case status.Running:
   522  		// A pod has finished starting so the workload is now active.
   523  		agentStatus = &status.StatusInfo{
   524  			Status: status.Idle,
   525  		}
   526  		containerStatus = status.Running
   527  	case status.Error:
   528  		agentStatus = &status.StatusInfo{
   529  			Status:  status.Error,
   530  			Message: params.Info,
   531  			Data:    params.Data,
   532  		}
   533  		containerStatus = status.Error
   534  	case status.Blocked:
   535  		containerStatus = status.Blocked
   536  		agentStatus = &status.StatusInfo{
   537  			Status: status.Idle,
   538  		}
   539  	}
   540  	cloudContainerStatus = &status.StatusInfo{
   541  		Status:  containerStatus,
   542  		Message: params.Info,
   543  		Data:    params.Data,
   544  	}
   545  	return agentStatus, cloudContainerStatus
   546  }
   547  
   548  // updateUnitsFromCloud takes a slice of unit information provided by an external
   549  // source (typically a cloud update event) and merges that with the existing unit
   550  // data model in state. The passed in units are the complete set for the cloud, so
   551  // any existing units in state with provider ids which aren't in the set will be removed.
   552  func (a *Facade) updateUnitsFromCloud(app Application, unitUpdates []params.ApplicationUnitParams) error {
   553  	logger.Debugf("unit updates: %#v", unitUpdates)
   554  	// Set up the initial data structures.
   555  	existingStateUnits, err := app.AllUnits()
   556  	if err != nil {
   557  		return errors.Trace(err)
   558  	}
   559  
   560  	stateUnitsById := make(map[string]Unit)
   561  	cloudPodsById := make(map[string]params.ApplicationUnitParams)
   562  
   563  	// Record all unit provider ids known to exist in the cloud.
   564  	for _, u := range unitUpdates {
   565  		cloudPodsById[u.ProviderId] = u
   566  	}
   567  
   568  	stateUnitExistsInCloud := func(providerId string) bool {
   569  		if providerId == "" {
   570  			return false
   571  		}
   572  		_, ok := cloudPodsById[providerId]
   573  		return ok
   574  	}
   575  
   576  	unitInfo := &updateStateUnitParams{
   577  		stateUnitsInCloud: make(map[string]Unit),
   578  		deletedRemoved:    true,
   579  	}
   580  	var (
   581  		// aliveStateIds holds the provider ids of alive units in state.
   582  		aliveStateIds = set.NewStrings()
   583  
   584  		// extraStateIds holds the provider ids of units in state which
   585  		// no longer exist in the cloud.
   586  		extraStateIds = set.NewStrings()
   587  	)
   588  
   589  	// Loop over any existing state units and record those which do not yet have
   590  	// provider ids, and those which have been removed or updated.
   591  	for _, u := range existingStateUnits {
   592  		var providerId string
   593  		info, err := u.ContainerInfo()
   594  		if err != nil && !errors.IsNotFound(err) {
   595  			return errors.Trace(err)
   596  		}
   597  		if err == nil {
   598  			providerId = info.ProviderId()
   599  		}
   600  
   601  		unitAlive := u.Life() == state.Alive
   602  		if !unitAlive {
   603  			continue
   604  		}
   605  
   606  		if providerId == "" {
   607  			logger.Debugf("unit %q is not associated with any pod", u.Name())
   608  			unitInfo.unassociatedUnits = append(unitInfo.unassociatedUnits, u)
   609  			continue
   610  		}
   611  		stateUnitsById[providerId] = u
   612  		stateUnitInCloud := stateUnitExistsInCloud(providerId)
   613  		aliveStateIds.Add(providerId)
   614  
   615  		if stateUnitInCloud {
   616  			logger.Debugf("unit %q (%v) has changed in the cloud", u.Name(), providerId)
   617  			unitInfo.stateUnitsInCloud[u.UnitTag().String()] = u
   618  		} else {
   619  			extraStateIds.Add(providerId)
   620  		}
   621  	}
   622  
   623  	// Do it in sorted order so it's deterministic for tests.
   624  	var ids []string
   625  	for id := range cloudPodsById {
   626  		ids = append(ids, id)
   627  	}
   628  	sort.Strings(ids)
   629  
   630  	// Sort extra ids also to guarantee order.
   631  	var extraIds []string
   632  	for id := range extraStateIds {
   633  		extraIds = append(extraIds, id)
   634  	}
   635  	sort.Strings(extraIds)
   636  	unassociatedUnitCount := len(unitInfo.unassociatedUnits)
   637  
   638  	for _, id := range ids {
   639  		u := cloudPodsById[id]
   640  		if aliveStateIds.Contains(id) {
   641  			u.UnitTag = stateUnitsById[id].UnitTag().String()
   642  			unitInfo.existingCloudPods = append(unitInfo.existingCloudPods, u)
   643  			continue
   644  		}
   645  
   646  		// First attempt to add any new cloud pod not yet represented in state
   647  		// to a unit which does not yet have a provider id.
   648  		if unassociatedUnitCount > 0 {
   649  			unassociatedUnitCount -= 1
   650  			unitInfo.addedCloudPods = append(unitInfo.addedCloudPods, u)
   651  			continue
   652  		}
   653  
   654  		// A new pod was added to the cloud but does not yet have a unit in state.
   655  		unitInfo.addedCloudPods = append(unitInfo.addedCloudPods, u)
   656  	}
   657  
   658  	// If there are any extra provider ids left over after allocating all the cloud pods,
   659  	// then consider those state units as terminated.
   660  	for _, providerId := range extraStateIds.Values() {
   661  		u := stateUnitsById[providerId]
   662  		logger.Debugf("unit %q (%v) has been removed from the cloud", u.Name(), providerId)
   663  		unitInfo.removedUnits = append(unitInfo.removedUnits, u)
   664  	}
   665  
   666  	return a.updateStateUnits(app, unitInfo)
   667  }
   668  
   669  type updateStateUnitParams struct {
   670  	stateUnitsInCloud map[string]Unit
   671  	addedCloudPods    []params.ApplicationUnitParams
   672  	existingCloudPods []params.ApplicationUnitParams
   673  	removedUnits      []Unit
   674  	unassociatedUnits []Unit
   675  	deletedRemoved    bool
   676  }
   677  
   678  type filesystemInfo struct {
   679  	unitTag      names.UnitTag
   680  	providerId   string
   681  	mountPoint   string
   682  	readOnly     bool
   683  	size         uint64
   684  	filesystemId string
   685  }
   686  
   687  type volumeInfo struct {
   688  	unitTag    names.UnitTag
   689  	providerId string
   690  	readOnly   bool
   691  	persistent bool
   692  	size       uint64
   693  	volumeId   string
   694  }
   695  
   696  func (a *Facade) updateStateUnits(app Application, unitInfo *updateStateUnitParams) error {
   697  
   698  	if app.Life() != state.Alive {
   699  		// We ignore any updates for dying applications.
   700  		logger.Debugf("ignoring unit updates for dying application: %v", app.Name())
   701  		return nil
   702  	}
   703  
   704  	logger.Tracef("added cloud units: %+v", unitInfo.addedCloudPods)
   705  	logger.Tracef("existing cloud units: %+v", unitInfo.existingCloudPods)
   706  	logger.Tracef("removed units: %+v", unitInfo.removedUnits)
   707  	logger.Tracef("unassociated units: %+v", unitInfo.unassociatedUnits)
   708  
   709  	// Now we have the added, removed, updated units all sorted,
   710  	// generate the state update operations.
   711  	var unitUpdate state.UpdateUnitsOperation
   712  
   713  	filesystemUpdates := make(map[string]filesystemInfo)
   714  	filesystemStatus := make(map[string]status.StatusInfo)
   715  	volumeUpdates := make(map[string]volumeInfo)
   716  	volumeStatus := make(map[string]status.StatusInfo)
   717  
   718  	for _, u := range unitInfo.removedUnits {
   719  		// If a unit is removed from the cloud, all filesystems are considered detached.
   720  		unitStorage, err := a.storage.UnitStorageAttachments(u.UnitTag())
   721  		if err != nil {
   722  			return errors.Trace(err)
   723  		}
   724  		for _, sa := range unitStorage {
   725  			fs, err := a.storage.StorageInstanceFilesystem(sa.StorageInstance())
   726  			if err != nil {
   727  				return errors.Trace(err)
   728  			}
   729  			filesystemStatus[fs.FilesystemTag().String()] = status.StatusInfo{Status: status.Detached}
   730  		}
   731  
   732  		if unitInfo.deletedRemoved {
   733  			unitUpdate.Deletes = append(unitUpdate.Deletes, u.DestroyOperation())
   734  		}
   735  		// We'll set the status as Terminated. This will either be transient, as will
   736  		// occur when a pod is restarted external to Juju, or permanent if the pod has
   737  		// been deleted external to Juju. In the latter case, juju remove-unit will be
   738  		// need to clean things up on the Juju side.
   739  		cloudContainerStatus := &status.StatusInfo{
   740  			Status:  status.Terminated,
   741  			Message: "unit stopped by the cloud",
   742  		}
   743  		agentStatus := &status.StatusInfo{
   744  			Status: status.Idle,
   745  		}
   746  		updateProps := state.UnitUpdateProperties{
   747  			CloudContainerStatus: cloudContainerStatus,
   748  			AgentStatus:          agentStatus,
   749  		}
   750  		unitUpdate.Updates = append(unitUpdate.Updates,
   751  			u.UpdateOperation(updateProps))
   752  	}
   753  
   754  	processUnitParams := func(unitParams params.ApplicationUnitParams) *state.UnitUpdateProperties {
   755  		agentStatus, cloudContainerStatus := a.updateStatus(unitParams)
   756  		return &state.UnitUpdateProperties{
   757  			ProviderId:           &unitParams.ProviderId,
   758  			Address:              &unitParams.Address,
   759  			Ports:                &unitParams.Ports,
   760  			AgentStatus:          agentStatus,
   761  			CloudContainerStatus: cloudContainerStatus,
   762  		}
   763  	}
   764  
   765  	processFilesystemParams := func(processedFilesystemIds set.Strings, unitTag names.UnitTag, unitParams params.ApplicationUnitParams) error {
   766  		// Once a unit is available in the cluster, we consider
   767  		// its filesystem(s) to be attached since the unit is
   768  		// not considered ready until this happens.
   769  		filesystemInfoByName := make(map[string][]params.KubernetesFilesystemInfo)
   770  		for _, fsInfo := range unitParams.FilesystemInfo {
   771  			infos := filesystemInfoByName[fsInfo.StorageName]
   772  			infos = append(infos, fsInfo)
   773  			filesystemInfoByName[fsInfo.StorageName] = infos
   774  		}
   775  
   776  		for storageName, infos := range filesystemInfoByName {
   777  			logger.Debugf("updating storage %v for %v", storageName, unitTag)
   778  			if len(infos) == 0 {
   779  				continue
   780  			}
   781  
   782  			unitStorage, err := a.storage.UnitStorageAttachments(unitTag)
   783  			if err != nil {
   784  				return errors.Trace(err)
   785  			}
   786  
   787  			// Loop over all the storage for the unit ans skip storage not
   788  			// relevant for storageName.
   789  			// TODO(caas) - Add storage bankend API to get all unit storage instances for a named storage.
   790  			for _, sa := range unitStorage {
   791  				si, err := a.storage.StorageInstance(sa.StorageInstance())
   792  				if errors.IsNotFound(err) {
   793  					logger.Warningf("ignoring non-existent storage instance %v for unit %v", sa.StorageInstance(), unitTag.Id())
   794  					continue
   795  				}
   796  				if err != nil {
   797  					return errors.Trace(err)
   798  				}
   799  				if si.StorageName() != storageName {
   800  					continue
   801  				}
   802  				fs, err := a.storage.StorageInstanceFilesystem(sa.StorageInstance())
   803  				if err != nil {
   804  					return errors.Trace(err)
   805  				}
   806  				fsInfo := infos[0]
   807  				processedFilesystemIds.Add(fsInfo.FilesystemId)
   808  
   809  				// k8s reports provisioned info even when the volume is not ready.
   810  				// Only update state when volume is created so Juju doesn't think
   811  				// the volume is active when it's not.
   812  				if fsInfo.Status != status.Pending.String() {
   813  					filesystemUpdates[fs.FilesystemTag().String()] = filesystemInfo{
   814  						unitTag:      unitTag,
   815  						providerId:   unitParams.ProviderId,
   816  						mountPoint:   fsInfo.MountPoint,
   817  						readOnly:     fsInfo.ReadOnly,
   818  						size:         fsInfo.Size,
   819  						filesystemId: fsInfo.FilesystemId,
   820  					}
   821  				}
   822  				filesystemStatus[fs.FilesystemTag().String()] = status.StatusInfo{
   823  					Status:  status.Status(fsInfo.Status),
   824  					Message: fsInfo.Info,
   825  					Data:    fsInfo.Data,
   826  				}
   827  
   828  				vol, err := a.storage.StorageInstanceVolume(sa.StorageInstance())
   829  				if err != nil {
   830  					return errors.Trace(err)
   831  				}
   832  				if fsInfo.Volume.Status != status.Pending.String() {
   833  					volumeUpdates[vol.VolumeTag().String()] = volumeInfo{
   834  						unitTag:    unitTag,
   835  						providerId: unitParams.ProviderId,
   836  						size:       fsInfo.Volume.Size,
   837  						volumeId:   fsInfo.Volume.VolumeId,
   838  						persistent: fsInfo.Volume.Persistent,
   839  						readOnly:   fsInfo.ReadOnly,
   840  					}
   841  				}
   842  				volumeStatus[vol.VolumeTag().String()] = status.StatusInfo{
   843  					Status:  status.Status(fsInfo.Volume.Status),
   844  					Message: fsInfo.Volume.Info,
   845  					Data:    fsInfo.Volume.Data,
   846  				}
   847  
   848  				infos = infos[1:]
   849  				if len(infos) == 0 {
   850  					break
   851  				}
   852  			}
   853  		}
   854  		return nil
   855  	}
   856  
   857  	var unitParamsWithFilesystemInfo []params.ApplicationUnitParams
   858  
   859  	for _, unitParams := range unitInfo.existingCloudPods {
   860  		u, ok := unitInfo.stateUnitsInCloud[unitParams.UnitTag]
   861  		if !ok {
   862  			logger.Warningf("unexpected unit parameters %+v not in state", unitParams)
   863  			continue
   864  		}
   865  		updateProps := processUnitParams(unitParams)
   866  		if len(unitParams.FilesystemInfo) > 0 {
   867  			unitParamsWithFilesystemInfo = append(unitParamsWithFilesystemInfo, unitParams)
   868  		}
   869  		unitUpdate.Updates = append(unitUpdate.Updates,
   870  			u.UpdateOperation(*updateProps))
   871  	}
   872  
   873  	// For newly added units in the cloud, either update state units which
   874  	// exist but which do not yet have provider ids (recording the provider
   875  	// id as well), or add a brand new unit.
   876  	idx := 0
   877  	for _, unitParams := range unitInfo.addedCloudPods {
   878  		if idx < len(unitInfo.unassociatedUnits) {
   879  			u := unitInfo.unassociatedUnits[idx]
   880  			updateProps := processUnitParams(unitParams)
   881  			unitUpdate.Updates = append(unitUpdate.Updates,
   882  				u.UpdateOperation(*updateProps))
   883  			idx += 1
   884  			if len(unitParams.FilesystemInfo) > 0 {
   885  				unitParamsWithFilesystemInfo = append(unitParamsWithFilesystemInfo, unitParams)
   886  			}
   887  			continue
   888  		}
   889  
   890  		// Process units added directly in the cloud instead of via Juju.
   891  		updateProps := processUnitParams(unitParams)
   892  		if len(unitParams.FilesystemInfo) > 0 {
   893  			unitParamsWithFilesystemInfo = append(unitParamsWithFilesystemInfo, unitParams)
   894  		}
   895  		unitUpdate.Adds = append(unitUpdate.Adds,
   896  			app.AddOperation(*updateProps))
   897  	}
   898  	err := app.UpdateUnits(&unitUpdate)
   899  	// We ignore any updates for dying applications.
   900  	if state.IsNotAlive(err) {
   901  		return nil
   902  	}
   903  
   904  	// Now update filesystem info - attachment data and status.
   905  	// For units added to the cloud directly, we first need to lookup the
   906  	// newly created unit tag from Juju using the cloud provider ids.
   907  	var providerIds []string
   908  	for _, unitParams := range unitParamsWithFilesystemInfo {
   909  		if unitParams.UnitTag == "" {
   910  			providerIds = append(providerIds, unitParams.ProviderId)
   911  		}
   912  	}
   913  	m, err := a.state.Model()
   914  	if err != nil {
   915  		return errors.Trace(err)
   916  	}
   917  	var providerIdToUnit = make(map[string]names.UnitTag)
   918  	containers, err := m.Containers(providerIds...)
   919  	if err != nil {
   920  		return errors.Trace(err)
   921  	}
   922  	for _, c := range containers {
   923  		providerIdToUnit[c.ProviderId()] = names.NewUnitTag(c.Unit())
   924  	}
   925  
   926  	processedFilesystemIds := set.NewStrings()
   927  	for _, unitParams := range unitParamsWithFilesystemInfo {
   928  		var (
   929  			unitTag names.UnitTag
   930  			ok      bool
   931  		)
   932  		// For units added to the cloud directly, we first need to lookup the
   933  		// newly created unit tag from Juju using the cloud provider ids.
   934  		if unitParams.UnitTag == "" {
   935  			unitTag, ok = providerIdToUnit[unitParams.ProviderId]
   936  			if !ok {
   937  				logger.Warningf("cannot update filesystem data for unknown pod %q", unitParams.ProviderId)
   938  				continue
   939  			}
   940  		} else {
   941  			unitTag, _ = names.ParseUnitTag(unitParams.UnitTag)
   942  		}
   943  		if err := processFilesystemParams(processedFilesystemIds, unitTag, unitParams); err != nil {
   944  			return errors.Annotatef(err, "processing filesystem info for unit %q", unitTag.Id())
   945  		}
   946  	}
   947  
   948  	// If pods are recreated on the Kubernetes side, new units are created on the Juju
   949  	// side and so any previously attached filesystems become orphaned and need to
   950  	// be cleaned up.
   951  	appName := app.Name()
   952  	if err := a.cleaupOrphanedFilesystems(processedFilesystemIds); err != nil {
   953  		return errors.Annotatef(err, "deleting orphaned filesystems for %v", appName)
   954  	}
   955  
   956  	// First do the volume updates as volumes need to be attached before the filesystem updates.
   957  	if err := a.updateVolumeInfo(volumeUpdates, volumeStatus); err != nil {
   958  		return errors.Annotatef(err, "updating volume information for %v", appName)
   959  	}
   960  
   961  	err = a.updateFilesystemInfo(filesystemUpdates, filesystemStatus)
   962  	return errors.Annotatef(err, "updating filesystem information for %v", appName)
   963  }
   964  
   965  func (a *Facade) cleaupOrphanedFilesystems(processedFilesystemIds set.Strings) error {
   966  	// TODO(caas) - record unit id on the filesystem so we can query by unit
   967  	allFilesystems, err := a.storage.AllFilesystems()
   968  	if err != nil {
   969  		return errors.Trace(err)
   970  	}
   971  	for _, fs := range allFilesystems {
   972  		fsInfo, err := fs.Info()
   973  		if errors.IsNotProvisioned(err) {
   974  			continue
   975  		}
   976  		if err != nil {
   977  			return errors.Trace(err)
   978  		}
   979  		if !processedFilesystemIds.Contains(fsInfo.FilesystemId) {
   980  			continue
   981  		}
   982  
   983  		storageTag, err := fs.Storage()
   984  		if err != nil && !errors.IsNotFound(err) {
   985  			return errors.Trace(err)
   986  		}
   987  		if err != nil {
   988  			continue
   989  		}
   990  
   991  		si, err := a.storage.StorageInstance(storageTag)
   992  		if err != nil && !errors.IsNotFound(err) {
   993  			return errors.Trace(err)
   994  		}
   995  		if err != nil {
   996  			continue
   997  		}
   998  		_, ok := si.Owner()
   999  		if ok {
  1000  			continue
  1001  		}
  1002  
  1003  		logger.Debugf("found orphaned filesystem %v", fs.FilesystemTag())
  1004  		err = a.storage.DestroyStorageInstance(storageTag, false)
  1005  		if err != nil && !errors.IsNotFound(err) {
  1006  			return errors.Trace(err)
  1007  		}
  1008  		err = a.storage.DestroyFilesystem(fs.FilesystemTag())
  1009  		if err != nil && !errors.IsNotFound(err) {
  1010  			return errors.Trace(err)
  1011  		}
  1012  	}
  1013  	return nil
  1014  }
  1015  
  1016  func (a *Facade) updateVolumeInfo(volumeUpdates map[string]volumeInfo, volumeStatus map[string]status.StatusInfo) error {
  1017  	// Do it in sorted order so it's deterministic for tests.
  1018  	var volTags []string
  1019  	for tag := range volumeUpdates {
  1020  		volTags = append(volTags, tag)
  1021  	}
  1022  	sort.Strings(volTags)
  1023  
  1024  	logger.Debugf("updating volume data: %+v", volumeUpdates)
  1025  	for _, tagString := range volTags {
  1026  		volTag, _ := names.ParseVolumeTag(tagString)
  1027  		volData := volumeUpdates[tagString]
  1028  
  1029  		vol, err := a.storage.Volume(volTag)
  1030  		if err != nil {
  1031  			return errors.Trace(err)
  1032  		}
  1033  		// If we have already recorded the provisioning info,
  1034  		// it's an error to try and do it again.
  1035  		_, err = vol.Info()
  1036  		if err != nil && !errors.IsNotProvisioned(err) {
  1037  			return errors.Trace(err)
  1038  		}
  1039  		if err != nil {
  1040  			// Provisioning info not set yet.
  1041  			err = a.storage.SetVolumeInfo(volTag, state.VolumeInfo{
  1042  				Size:       volData.size,
  1043  				VolumeId:   volData.volumeId,
  1044  				Persistent: volData.persistent,
  1045  			})
  1046  			if err != nil {
  1047  				return errors.Trace(err)
  1048  			}
  1049  		}
  1050  
  1051  		err = a.storage.SetVolumeAttachmentInfo(volData.unitTag, volTag, state.VolumeAttachmentInfo{
  1052  			ReadOnly: volData.readOnly,
  1053  		})
  1054  		if err != nil {
  1055  			return errors.Trace(err)
  1056  		}
  1057  	}
  1058  
  1059  	// Do it in sorted order so it's deterministic for tests.
  1060  	volTags = []string{}
  1061  	for tag := range volumeStatus {
  1062  		volTags = append(volTags, tag)
  1063  	}
  1064  	sort.Strings(volTags)
  1065  
  1066  	logger.Debugf("updating volume status: %+v", volumeStatus)
  1067  	for _, tagString := range volTags {
  1068  		volTag, _ := names.ParseVolumeTag(tagString)
  1069  		volStatus := volumeStatus[tagString]
  1070  		vol, err := a.storage.Volume(volTag)
  1071  		if err != nil {
  1072  			return errors.Trace(err)
  1073  		}
  1074  		now := a.clock.Now()
  1075  		err = vol.SetStatus(status.StatusInfo{
  1076  			Status:  volStatus.Status,
  1077  			Message: volStatus.Message,
  1078  			Data:    volStatus.Data,
  1079  			Since:   &now,
  1080  		})
  1081  		if err != nil {
  1082  			return errors.Trace(err)
  1083  		}
  1084  	}
  1085  
  1086  	return nil
  1087  }
  1088  
  1089  func (a *Facade) updateFilesystemInfo(filesystemUpdates map[string]filesystemInfo, filesystemStatus map[string]status.StatusInfo) error {
  1090  	// Do it in sorted order so it's deterministic for tests.
  1091  	var fsTags []string
  1092  	for tag := range filesystemUpdates {
  1093  		fsTags = append(fsTags, tag)
  1094  	}
  1095  	sort.Strings(fsTags)
  1096  
  1097  	logger.Debugf("updating filesystem data: %+v", filesystemUpdates)
  1098  	for _, tagString := range fsTags {
  1099  		fsTag, _ := names.ParseFilesystemTag(tagString)
  1100  		fsData := filesystemUpdates[tagString]
  1101  
  1102  		fs, err := a.storage.Filesystem(fsTag)
  1103  		if err != nil {
  1104  			return errors.Trace(err)
  1105  		}
  1106  		// If we have already recorded the provisioning info,
  1107  		// it's an error to try and do it again.
  1108  		_, err = fs.Info()
  1109  		if err != nil && !errors.IsNotProvisioned(err) {
  1110  			return errors.Trace(err)
  1111  		}
  1112  		if err != nil {
  1113  			// Provisioning info not set yet.
  1114  			err = a.storage.SetFilesystemInfo(fsTag, state.FilesystemInfo{
  1115  				Size:         fsData.size,
  1116  				FilesystemId: fsData.filesystemId,
  1117  			})
  1118  			if err != nil {
  1119  				return errors.Trace(err)
  1120  			}
  1121  		}
  1122  
  1123  		err = a.storage.SetFilesystemAttachmentInfo(fsData.unitTag, fsTag, state.FilesystemAttachmentInfo{
  1124  			MountPoint: fsData.mountPoint,
  1125  			ReadOnly:   fsData.readOnly,
  1126  		})
  1127  		if err != nil {
  1128  			return errors.Trace(err)
  1129  		}
  1130  	}
  1131  
  1132  	// Do it in sorted order so it's deterministic for tests.
  1133  	fsTags = []string{}
  1134  	for tag := range filesystemStatus {
  1135  		fsTags = append(fsTags, tag)
  1136  	}
  1137  	sort.Strings(fsTags)
  1138  
  1139  	logger.Debugf("updating filesystem status: %+v", filesystemStatus)
  1140  	for _, tagString := range fsTags {
  1141  		fsTag, _ := names.ParseFilesystemTag(tagString)
  1142  		fsStatus := filesystemStatus[tagString]
  1143  		fs, err := a.storage.Filesystem(fsTag)
  1144  		if err != nil {
  1145  			return errors.Trace(err)
  1146  		}
  1147  		now := a.clock.Now()
  1148  		err = fs.SetStatus(status.StatusInfo{
  1149  			Status:  fsStatus.Status,
  1150  			Message: fsStatus.Message,
  1151  			Data:    fsStatus.Data,
  1152  			Since:   &now,
  1153  		})
  1154  		if err != nil {
  1155  			return errors.Trace(err)
  1156  		}
  1157  	}
  1158  
  1159  	return nil
  1160  }
  1161  
  1162  // UpdateApplicationsService updates the Juju data model to reflect the given
  1163  // service details of the specified application.
  1164  func (a *Facade) UpdateApplicationsService(args params.UpdateApplicationServiceArgs) (params.ErrorResults, error) {
  1165  	result := params.ErrorResults{
  1166  		Results: make([]params.ErrorResult, len(args.Args)),
  1167  	}
  1168  	if len(args.Args) == 0 {
  1169  		return result, nil
  1170  	}
  1171  	for i, appUpdate := range args.Args {
  1172  		appTag, err := names.ParseApplicationTag(appUpdate.ApplicationTag)
  1173  		if err != nil {
  1174  			result.Results[i].Error = common.ServerError(err)
  1175  			continue
  1176  		}
  1177  		app, err := a.state.Application(appTag.Id())
  1178  		if err != nil {
  1179  			result.Results[i].Error = common.ServerError(err)
  1180  			continue
  1181  		}
  1182  		if err := app.UpdateCloudService(appUpdate.ProviderId, params.NetworkAddresses(appUpdate.Addresses...)); err != nil {
  1183  			result.Results[i].Error = common.ServerError(err)
  1184  		}
  1185  	}
  1186  	return result, nil
  1187  }
  1188  
  1189  // SetOperatorStatus updates the operator status for each given application.
  1190  func (a *Facade) SetOperatorStatus(args params.SetStatus) (params.ErrorResults, error) {
  1191  	result := params.ErrorResults{
  1192  		Results: make([]params.ErrorResult, len(args.Entities)),
  1193  	}
  1194  	for i, arg := range args.Entities {
  1195  		appTag, err := names.ParseApplicationTag(arg.Tag)
  1196  		if err != nil {
  1197  			result.Results[i].Error = common.ServerError(err)
  1198  			continue
  1199  		}
  1200  		app, err := a.state.Application(appTag.Id())
  1201  		if err != nil {
  1202  			result.Results[i].Error = common.ServerError(err)
  1203  			continue
  1204  		}
  1205  		now := a.clock.Now()
  1206  		s := status.StatusInfo{
  1207  			Status:  status.Status(arg.Status),
  1208  			Message: arg.Info,
  1209  			Data:    arg.Data,
  1210  			Since:   &now,
  1211  		}
  1212  		if err := app.SetOperatorStatus(s); err != nil {
  1213  			result.Results[i].Error = common.ServerError(err)
  1214  		}
  1215  	}
  1216  	return result, nil
  1217  }