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