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