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

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // Package storageprovisioner provides a worker that manages the provisioning
     5  // and deprovisioning of storage volumes and filesystems, and attaching them
     6  // to and detaching them from machines.
     7  //
     8  // A storage provisioner worker is run at each model manager, which
     9  // manages model-scoped storage such as virtual disk services of the
    10  // cloud provider. In addition to this, each machine agent runs a machine-
    11  // storage provisioner worker that manages storage scoped to that machine,
    12  // such as loop devices, temporary filesystems (tmpfs), and rootfs.
    13  //
    14  // The storage provisioner worker is comprised of the following major
    15  // components:
    16  //  - a set of watchers for provisioning and attachment events
    17  //  - a schedule of pending operations
    18  //  - event-handling code fed by the watcher, that identifies
    19  //    interesting changes (unprovisioned -> provisioned, etc.),
    20  //    ensures prerequisites are met (e.g. volume and machine are both
    21  //    provisioned before attachment is attempted), and populates
    22  //    operations into the schedule
    23  //  - operation execution code fed by the schedule, that groups
    24  //    operations to make bulk calls to storage providers; updates
    25  //    status; and reschedules operations upon failure
    26  //
    27  package storageprovisioner
    28  
    29  import (
    30  	"github.com/juju/errors"
    31  	"github.com/juju/loggo"
    32  	"gopkg.in/juju/names.v2"
    33  	"gopkg.in/juju/worker.v1"
    34  	"gopkg.in/juju/worker.v1/catacomb"
    35  
    36  	"github.com/juju/juju/apiserver/params"
    37  	"github.com/juju/juju/core/watcher"
    38  	"github.com/juju/juju/storage"
    39  	"github.com/juju/juju/storage/provider"
    40  	"github.com/juju/juju/worker/storageprovisioner/internal/schedule"
    41  )
    42  
    43  var logger = loggo.GetLogger("juju.worker.storageprovisioner")
    44  
    45  var newManagedFilesystemSource = provider.NewManagedFilesystemSource
    46  
    47  // VolumeAccessor defines an interface used to allow a storage provisioner
    48  // worker to perform volume related operations.
    49  type VolumeAccessor interface {
    50  	// WatchBlockDevices watches for changes to the block devices of the
    51  	// specified machine.
    52  	WatchBlockDevices(names.MachineTag) (watcher.NotifyWatcher, error)
    53  
    54  	// WatchVolumes watches for changes to volumes that this storage
    55  	// provisioner is responsible for.
    56  	WatchVolumes(scope names.Tag) (watcher.StringsWatcher, error)
    57  
    58  	// WatchVolumeAttachments watches for changes to volume attachments
    59  	// that this storage provisioner is responsible for.
    60  	WatchVolumeAttachments(scope names.Tag) (watcher.MachineStorageIdsWatcher, error)
    61  
    62  	// WatchVolumeAttachmentPlans watches for changes to volume attachments
    63  	// destined for this machine. It allows the machine agent to do any extra
    64  	// initialization of the attachment, such as logging into the iSCSI target
    65  	WatchVolumeAttachmentPlans(scope names.Tag) (watcher.MachineStorageIdsWatcher, error)
    66  
    67  	// Volumes returns details of volumes with the specified tags.
    68  	Volumes([]names.VolumeTag) ([]params.VolumeResult, error)
    69  
    70  	// VolumeBlockDevices returns details of block devices corresponding to
    71  	// the specified volume attachment IDs.
    72  	VolumeBlockDevices([]params.MachineStorageId) ([]params.BlockDeviceResult, error)
    73  
    74  	// VolumeAttachments returns details of volume attachments with
    75  	// the specified tags.
    76  	VolumeAttachments([]params.MachineStorageId) ([]params.VolumeAttachmentResult, error)
    77  
    78  	VolumeAttachmentPlans([]params.MachineStorageId) ([]params.VolumeAttachmentPlanResult, error)
    79  
    80  	// VolumeParams returns the parameters for creating the volumes
    81  	// with the specified tags.
    82  	VolumeParams([]names.VolumeTag) ([]params.VolumeParamsResult, error)
    83  
    84  	// RemoveVolumeParams returns the parameters for destroying or
    85  	// releasing the volumes with the specified tags.
    86  	RemoveVolumeParams([]names.VolumeTag) ([]params.RemoveVolumeParamsResult, error)
    87  
    88  	// VolumeAttachmentParams returns the parameters for creating the
    89  	// volume attachments with the specified tags.
    90  	VolumeAttachmentParams([]params.MachineStorageId) ([]params.VolumeAttachmentParamsResult, error)
    91  
    92  	// SetVolumeInfo records the details of newly provisioned volumes.
    93  	SetVolumeInfo([]params.Volume) ([]params.ErrorResult, error)
    94  
    95  	// SetVolumeAttachmentInfo records the details of newly provisioned
    96  	// volume attachments.
    97  	SetVolumeAttachmentInfo([]params.VolumeAttachment) ([]params.ErrorResult, error)
    98  
    99  	CreateVolumeAttachmentPlans(volumeAttachmentPlans []params.VolumeAttachmentPlan) ([]params.ErrorResult, error)
   100  	RemoveVolumeAttachmentPlan([]params.MachineStorageId) ([]params.ErrorResult, error)
   101  	SetVolumeAttachmentPlanBlockInfo(volumeAttachmentPlans []params.VolumeAttachmentPlan) ([]params.ErrorResult, error)
   102  }
   103  
   104  // FilesystemAccessor defines an interface used to allow a storage provisioner
   105  // worker to perform filesystem related operations.
   106  type FilesystemAccessor interface {
   107  	// WatchFilesystems watches for changes to filesystems that this
   108  	// storage provisioner is responsible for.
   109  	WatchFilesystems(scope names.Tag) (watcher.StringsWatcher, error)
   110  
   111  	// WatchFilesystemAttachments watches for changes to filesystem attachments
   112  	// that this storage provisioner is responsible for.
   113  	WatchFilesystemAttachments(scope names.Tag) (watcher.MachineStorageIdsWatcher, error)
   114  
   115  	// Filesystems returns details of filesystems with the specified tags.
   116  	Filesystems([]names.FilesystemTag) ([]params.FilesystemResult, error)
   117  
   118  	// FilesystemAttachments returns details of filesystem attachments with
   119  	// the specified tags.
   120  	FilesystemAttachments([]params.MachineStorageId) ([]params.FilesystemAttachmentResult, error)
   121  
   122  	// FilesystemParams returns the parameters for creating the filesystems
   123  	// with the specified tags.
   124  	FilesystemParams([]names.FilesystemTag) ([]params.FilesystemParamsResult, error)
   125  
   126  	// RemoveFilesystemParams returns the parameters for destroying or
   127  	// releasing the filesystems with the specified tags.
   128  	RemoveFilesystemParams([]names.FilesystemTag) ([]params.RemoveFilesystemParamsResult, error)
   129  
   130  	// FilesystemAttachmentParams returns the parameters for creating the
   131  	// filesystem attachments with the specified tags.
   132  	FilesystemAttachmentParams([]params.MachineStorageId) ([]params.FilesystemAttachmentParamsResult, error)
   133  
   134  	// SetFilesystemInfo records the details of newly provisioned filesystems.
   135  	SetFilesystemInfo([]params.Filesystem) ([]params.ErrorResult, error)
   136  
   137  	// SetFilesystemAttachmentInfo records the details of newly provisioned
   138  	// filesystem attachments.
   139  	SetFilesystemAttachmentInfo([]params.FilesystemAttachment) ([]params.ErrorResult, error)
   140  }
   141  
   142  // MachineAccessor defines an interface used to allow a storage provisioner
   143  // worker to perform machine related operations.
   144  type MachineAccessor interface {
   145  	// WatchMachine watches for changes to the specified machine.
   146  	WatchMachine(names.MachineTag) (watcher.NotifyWatcher, error)
   147  
   148  	// InstanceIds returns the instance IDs of each machine.
   149  	InstanceIds([]names.MachineTag) ([]params.StringResult, error)
   150  }
   151  
   152  // LifecycleManager defines an interface used to enable a storage provisioner
   153  // worker to perform lifcycle-related operations on storage entities and
   154  // attachments.
   155  type LifecycleManager interface {
   156  	// Life returns the lifecycle state of the specified entities.
   157  	Life([]names.Tag) ([]params.LifeResult, error)
   158  
   159  	// Remove removes the specified entities from state.
   160  	Remove([]names.Tag) ([]params.ErrorResult, error)
   161  
   162  	// AttachmentLife returns the lifecycle state of the specified
   163  	// machine/entity attachments.
   164  	AttachmentLife([]params.MachineStorageId) ([]params.LifeResult, error)
   165  
   166  	// RemoveAttachments removes the specified machine/entity attachments
   167  	// from state.
   168  	RemoveAttachments([]params.MachineStorageId) ([]params.ErrorResult, error)
   169  }
   170  
   171  // StatusSetter defines an interface used to set the status of entities.
   172  type StatusSetter interface {
   173  	SetStatus([]params.EntityStatusArgs) error
   174  }
   175  
   176  // NewStorageProvisioner returns a Worker which manages
   177  // provisioning (deprovisioning), and attachment (detachment)
   178  // of first-class volumes and filesystems.
   179  //
   180  // Machine-scoped storage workers will be provided with
   181  // a storage directory, while model-scoped workers
   182  // will not. If the directory path is non-empty, then it
   183  // will be passed to the storage source via its config.
   184  var NewStorageProvisioner = func(config Config) (worker.Worker, error) {
   185  	if err := config.Validate(); err != nil {
   186  		return nil, errors.Trace(err)
   187  	}
   188  	w := &storageProvisioner{
   189  		config: config,
   190  	}
   191  	err := catacomb.Invoke(catacomb.Plan{
   192  		Site: &w.catacomb,
   193  		Work: w.loop,
   194  	})
   195  	if err != nil {
   196  		return nil, errors.Trace(err)
   197  	}
   198  	return w, nil
   199  }
   200  
   201  type storageProvisioner struct {
   202  	catacomb catacomb.Catacomb
   203  	config   Config
   204  }
   205  
   206  // Kill implements Worker.Kill().
   207  func (w *storageProvisioner) Kill() {
   208  	w.catacomb.Kill(nil)
   209  }
   210  
   211  // Wait implements Worker.Wait().
   212  func (w *storageProvisioner) Wait() error {
   213  	err := w.catacomb.Wait()
   214  	return err
   215  }
   216  
   217  func (w *storageProvisioner) loop() error {
   218  	var (
   219  		volumesChanges               watcher.StringsChannel
   220  		filesystemsChanges           watcher.StringsChannel
   221  		volumeAttachmentsChanges     watcher.MachineStorageIdsChannel
   222  		volumeAttachmentPlansChanges watcher.MachineStorageIdsChannel
   223  		filesystemAttachmentsChanges watcher.MachineStorageIdsChannel
   224  		machineBlockDevicesChanges   <-chan struct{}
   225  	)
   226  	machineChanges := make(chan names.MachineTag)
   227  
   228  	// Machine-scoped provisioners need to watch block devices, to create
   229  	// volume-backed filesystems.
   230  	if machineTag, ok := w.config.Scope.(names.MachineTag); ok {
   231  		machineBlockDevicesWatcher, err := w.config.Volumes.WatchBlockDevices(machineTag)
   232  		if err != nil {
   233  			return errors.Annotate(err, "watching block devices")
   234  		}
   235  		if err := w.catacomb.Add(machineBlockDevicesWatcher); err != nil {
   236  			return errors.Trace(err)
   237  		}
   238  		machineBlockDevicesChanges = machineBlockDevicesWatcher.Changes()
   239  
   240  		volumeAttachmentPlansWatcher, err := w.config.Volumes.WatchVolumeAttachmentPlans(machineTag)
   241  		if err != nil {
   242  			return errors.Annotate(err, "watching volume attachment plans")
   243  		}
   244  		if err := w.catacomb.Add(volumeAttachmentPlansWatcher); err != nil {
   245  			return errors.Trace(err)
   246  		}
   247  
   248  		volumeAttachmentPlansChanges = volumeAttachmentPlansWatcher.Changes()
   249  	}
   250  
   251  	ctx := context{
   252  		kill:                                 w.catacomb.Kill,
   253  		addWorker:                            w.catacomb.Add,
   254  		config:                               w.config,
   255  		volumes:                              make(map[names.VolumeTag]storage.Volume),
   256  		volumeAttachments:                    make(map[params.MachineStorageId]storage.VolumeAttachment),
   257  		volumeBlockDevices:                   make(map[names.VolumeTag]storage.BlockDevice),
   258  		filesystems:                          make(map[names.FilesystemTag]storage.Filesystem),
   259  		filesystemAttachments:                make(map[params.MachineStorageId]storage.FilesystemAttachment),
   260  		machines:                             make(map[names.MachineTag]*machineWatcher),
   261  		machineChanges:                       machineChanges,
   262  		schedule:                             schedule.NewSchedule(w.config.Clock),
   263  		incompleteVolumeParams:               make(map[names.VolumeTag]storage.VolumeParams),
   264  		incompleteVolumeAttachmentParams:     make(map[params.MachineStorageId]storage.VolumeAttachmentParams),
   265  		incompleteFilesystemParams:           make(map[names.FilesystemTag]storage.FilesystemParams),
   266  		incompleteFilesystemAttachmentParams: make(map[params.MachineStorageId]storage.FilesystemAttachmentParams),
   267  		pendingVolumeBlockDevices:            names.NewSet(),
   268  	}
   269  	ctx.managedFilesystemSource = newManagedFilesystemSource(
   270  		ctx.volumeBlockDevices, ctx.filesystems,
   271  	)
   272  	// Units don't use managed volume backed filesystems.
   273  	if ctx.isApplicationKind() {
   274  		ctx.managedFilesystemSource = &noopFilesystemSource{}
   275  	}
   276  
   277  	// Units don't have unit-scoped volumes - all volumes are
   278  	// associated with the model (namespace).
   279  	if !ctx.isApplicationKind() {
   280  		volumesWatcher, err := w.config.Volumes.WatchVolumes(w.config.Scope)
   281  		if err != nil {
   282  			return errors.Annotate(err, "watching volumes")
   283  		}
   284  		if err := w.catacomb.Add(volumesWatcher); err != nil {
   285  			return errors.Trace(err)
   286  		}
   287  		volumesChanges = volumesWatcher.Changes()
   288  	}
   289  
   290  	filesystemsWatcher, err := w.config.Filesystems.WatchFilesystems(w.config.Scope)
   291  	if err != nil {
   292  		return errors.Annotate(err, "watching filesystems")
   293  	}
   294  	if err := w.catacomb.Add(filesystemsWatcher); err != nil {
   295  		return errors.Trace(err)
   296  	}
   297  	filesystemsChanges = filesystemsWatcher.Changes()
   298  
   299  	volumeAttachmentsWatcher, err := w.config.Volumes.WatchVolumeAttachments(w.config.Scope)
   300  	if err != nil {
   301  		return errors.Annotate(err, "watching volume attachments")
   302  	}
   303  	if err := w.catacomb.Add(volumeAttachmentsWatcher); err != nil {
   304  		return errors.Trace(err)
   305  	}
   306  	volumeAttachmentsChanges = volumeAttachmentsWatcher.Changes()
   307  
   308  	filesystemAttachmentsWatcher, err := w.config.Filesystems.WatchFilesystemAttachments(w.config.Scope)
   309  	if err != nil {
   310  		return errors.Annotate(err, "watching filesystem attachments")
   311  	}
   312  	if err := w.catacomb.Add(filesystemAttachmentsWatcher); err != nil {
   313  		return errors.Trace(err)
   314  	}
   315  	filesystemAttachmentsChanges = filesystemAttachmentsWatcher.Changes()
   316  
   317  	for {
   318  
   319  		// Check if block devices need to be refreshed.
   320  		if err := processPendingVolumeBlockDevices(&ctx); err != nil {
   321  			return errors.Annotate(err, "processing pending block devices")
   322  		}
   323  
   324  		select {
   325  		case <-w.catacomb.Dying():
   326  			return w.catacomb.ErrDying()
   327  		case changes, ok := <-volumesChanges:
   328  			if !ok {
   329  				return errors.New("volumes watcher closed")
   330  			}
   331  			if err := volumesChanged(&ctx, changes); err != nil {
   332  				return errors.Trace(err)
   333  			}
   334  		case changes, ok := <-volumeAttachmentsChanges:
   335  			if !ok {
   336  				return errors.New("volume attachments watcher closed")
   337  			}
   338  			if err := volumeAttachmentsChanged(&ctx, changes); err != nil {
   339  				return errors.Trace(err)
   340  			}
   341  		case changes, ok := <-volumeAttachmentPlansChanges:
   342  			if !ok {
   343  				return errors.New("volume attachment plans watcher closed")
   344  			}
   345  			if err := volumeAttachmentPlansChanged(&ctx, changes); err != nil {
   346  				return errors.Trace(err)
   347  			}
   348  		case changes, ok := <-filesystemsChanges:
   349  			if !ok {
   350  				return errors.New("filesystems watcher closed")
   351  			}
   352  			if err := filesystemsChanged(&ctx, changes); err != nil {
   353  				return errors.Trace(err)
   354  			}
   355  		case changes, ok := <-filesystemAttachmentsChanges:
   356  			if !ok {
   357  				return errors.New("filesystem attachments watcher closed")
   358  			}
   359  			if err := filesystemAttachmentsChanged(&ctx, changes); err != nil {
   360  				return errors.Trace(err)
   361  			}
   362  		case _, ok := <-machineBlockDevicesChanges:
   363  			if !ok {
   364  				return errors.New("machine block devices watcher closed")
   365  			}
   366  			if err := machineBlockDevicesChanged(&ctx); err != nil {
   367  				return errors.Trace(err)
   368  			}
   369  		case machineTag := <-machineChanges:
   370  			if err := refreshMachine(&ctx, machineTag); err != nil {
   371  				return errors.Trace(err)
   372  			}
   373  		case <-ctx.schedule.Next():
   374  			// Ready to pick something(s) off the pending queue.
   375  			if err := processSchedule(&ctx); err != nil {
   376  				return errors.Trace(err)
   377  			}
   378  		}
   379  	}
   380  }
   381  
   382  // processSchedule executes scheduled operations.
   383  func processSchedule(ctx *context) error {
   384  	ready := ctx.schedule.Ready(ctx.config.Clock.Now())
   385  	createVolumeOps := make(map[names.VolumeTag]*createVolumeOp)
   386  	removeVolumeOps := make(map[names.VolumeTag]*removeVolumeOp)
   387  	attachVolumeOps := make(map[params.MachineStorageId]*attachVolumeOp)
   388  	detachVolumeOps := make(map[params.MachineStorageId]*detachVolumeOp)
   389  	createFilesystemOps := make(map[names.FilesystemTag]*createFilesystemOp)
   390  	removeFilesystemOps := make(map[names.FilesystemTag]*removeFilesystemOp)
   391  	attachFilesystemOps := make(map[params.MachineStorageId]*attachFilesystemOp)
   392  	detachFilesystemOps := make(map[params.MachineStorageId]*detachFilesystemOp)
   393  	for _, item := range ready {
   394  		op := item.(scheduleOp)
   395  		key := op.key()
   396  		switch op := op.(type) {
   397  		case *createVolumeOp:
   398  			createVolumeOps[key.(names.VolumeTag)] = op
   399  		case *removeVolumeOp:
   400  			removeVolumeOps[key.(names.VolumeTag)] = op
   401  		case *attachVolumeOp:
   402  			attachVolumeOps[key.(params.MachineStorageId)] = op
   403  		case *detachVolumeOp:
   404  			detachVolumeOps[key.(params.MachineStorageId)] = op
   405  		case *createFilesystemOp:
   406  			createFilesystemOps[key.(names.FilesystemTag)] = op
   407  		case *removeFilesystemOp:
   408  			removeFilesystemOps[key.(names.FilesystemTag)] = op
   409  		case *attachFilesystemOp:
   410  			attachFilesystemOps[key.(params.MachineStorageId)] = op
   411  		case *detachFilesystemOp:
   412  			detachFilesystemOps[key.(params.MachineStorageId)] = op
   413  		}
   414  	}
   415  	if len(removeVolumeOps) > 0 {
   416  		if err := removeVolumes(ctx, removeVolumeOps); err != nil {
   417  			return errors.Annotate(err, "removing volumes")
   418  		}
   419  	}
   420  	if len(createVolumeOps) > 0 {
   421  		if err := createVolumes(ctx, createVolumeOps); err != nil {
   422  			return errors.Annotate(err, "creating volumes")
   423  		}
   424  	}
   425  	if len(detachVolumeOps) > 0 {
   426  		if err := detachVolumes(ctx, detachVolumeOps); err != nil {
   427  			return errors.Annotate(err, "detaching volumes")
   428  		}
   429  	}
   430  	if len(attachVolumeOps) > 0 {
   431  		if err := attachVolumes(ctx, attachVolumeOps); err != nil {
   432  			return errors.Annotate(err, "attaching volumes")
   433  		}
   434  	}
   435  	if len(removeFilesystemOps) > 0 {
   436  		if err := removeFilesystems(ctx, removeFilesystemOps); err != nil {
   437  			return errors.Annotate(err, "removing filesystems")
   438  		}
   439  	}
   440  	if len(createFilesystemOps) > 0 {
   441  		if err := createFilesystems(ctx, createFilesystemOps); err != nil {
   442  			return errors.Annotate(err, "creating filesystems")
   443  		}
   444  	}
   445  	if len(detachFilesystemOps) > 0 {
   446  		if err := detachFilesystems(ctx, detachFilesystemOps); err != nil {
   447  			return errors.Annotate(err, "detaching filesystems")
   448  		}
   449  	}
   450  	if len(attachFilesystemOps) > 0 {
   451  		if err := attachFilesystems(ctx, attachFilesystemOps); err != nil {
   452  			return errors.Annotate(err, "attaching filesystems")
   453  		}
   454  	}
   455  	return nil
   456  }
   457  
   458  type context struct {
   459  	kill      func(error)
   460  	addWorker func(worker.Worker) error
   461  	config    Config
   462  
   463  	// volumes contains information about provisioned volumes.
   464  	volumes map[names.VolumeTag]storage.Volume
   465  
   466  	// volumeAttachments contains information about attached volumes.
   467  	volumeAttachments map[params.MachineStorageId]storage.VolumeAttachment
   468  
   469  	// volumeBlockDevices contains information about block devices
   470  	// corresponding to volumes attached to the scope-machine. This
   471  	// is only used by the machine-scoped storage provisioner.
   472  	volumeBlockDevices map[names.VolumeTag]storage.BlockDevice
   473  
   474  	// filesystems contains information about provisioned filesystems.
   475  	filesystems map[names.FilesystemTag]storage.Filesystem
   476  
   477  	// filesystemAttachments contains information about attached filesystems.
   478  	filesystemAttachments map[params.MachineStorageId]storage.FilesystemAttachment
   479  
   480  	// machines contains information about machines in the worker's scope.
   481  	machines map[names.MachineTag]*machineWatcher
   482  
   483  	// machineChanges is a channel that machine watchers will send to once
   484  	// their machine is known to have been provisioned.
   485  	machineChanges chan<- names.MachineTag
   486  
   487  	// schedule is the schedule of storage operations.
   488  	schedule *schedule.Schedule
   489  
   490  	// incompleteVolumeParams contains incomplete parameters for volumes.
   491  	//
   492  	// Volume parameters are incomplete when they lack information about
   493  	// the initial attachment. Once the initial attachment information is
   494  	// available, the parameters are removed from this map and a volume
   495  	// creation operation is scheduled.
   496  	incompleteVolumeParams map[names.VolumeTag]storage.VolumeParams
   497  
   498  	// incompleteVolumeAttachmentParams contains incomplete parameters
   499  	// for volume attachments
   500  	//
   501  	// Volume attachment parameters are incomplete when they lack
   502  	// information about the associated volume or machine. Once this
   503  	// information is available, the parameters are removed from this
   504  	// map and a volume attachment operation is scheduled.
   505  	incompleteVolumeAttachmentParams map[params.MachineStorageId]storage.VolumeAttachmentParams
   506  
   507  	// incompleteFilesystemParams contains incomplete parameters for
   508  	// filesystems.
   509  	//
   510  	// Filesystem parameters are incomplete when they lack information
   511  	// about the initial attachment. Once the initial attachment
   512  	// information is available, the parameters are removed from this
   513  	// map and a filesystem creation operation is scheduled.
   514  	incompleteFilesystemParams map[names.FilesystemTag]storage.FilesystemParams
   515  
   516  	// incompleteFilesystemAttachmentParams contains incomplete parameters
   517  	// for filesystem attachments
   518  	//
   519  	// Filesystem attachment parameters are incomplete when they lack
   520  	// information about the associated filesystem or machine. Once this
   521  	// information is available, the parameters are removed from this
   522  	// map and a filesystem attachment operation is scheduled.
   523  	incompleteFilesystemAttachmentParams map[params.MachineStorageId]storage.FilesystemAttachmentParams
   524  
   525  	// pendingVolumeBlockDevices contains the tags of volumes about whose
   526  	// block devices we wish to enquire.
   527  	pendingVolumeBlockDevices names.Set
   528  
   529  	// managedFilesystemSource is a storage.FilesystemSource that
   530  	// manages filesystems backed by volumes attached to the host
   531  	// machine.
   532  	managedFilesystemSource storage.FilesystemSource
   533  }
   534  
   535  func (c *context) isApplicationKind() bool {
   536  	return c.config.Scope.Kind() == names.ApplicationTagKind
   537  }