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

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package storageprovisioner
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"gopkg.in/juju/names.v2"
     9  
    10  	"github.com/juju/juju/apiserver/params"
    11  	"github.com/juju/juju/core/instance"
    12  	"github.com/juju/juju/core/watcher"
    13  	"github.com/juju/juju/storage"
    14  )
    15  
    16  // filesystemsChanged is called when the lifecycle states of the filesystems
    17  // with the provided IDs have been seen to have changed.
    18  func filesystemsChanged(ctx *context, changes []string) error {
    19  	tags := make([]names.Tag, len(changes))
    20  	for i, change := range changes {
    21  		tags[i] = names.NewFilesystemTag(change)
    22  	}
    23  	alive, dying, dead, err := storageEntityLife(ctx, tags)
    24  	if err != nil {
    25  		return errors.Trace(err)
    26  	}
    27  	logger.Debugf("filesystems alive: %v, dying: %v, dead: %v", alive, dying, dead)
    28  	if len(alive)+len(dying)+len(dead) == 0 {
    29  		return nil
    30  	}
    31  
    32  	// Get filesystem information for filesystems, so we can provision,
    33  	// deprovision, attach and detach.
    34  	filesystemTags := make([]names.FilesystemTag, 0, len(alive)+len(dying)+len(dead))
    35  	for _, tag := range alive {
    36  		filesystemTags = append(filesystemTags, tag.(names.FilesystemTag))
    37  	}
    38  	for _, tag := range dying {
    39  		filesystemTags = append(filesystemTags, tag.(names.FilesystemTag))
    40  	}
    41  	for _, tag := range dead {
    42  		filesystemTags = append(filesystemTags, tag.(names.FilesystemTag))
    43  	}
    44  	filesystemResults, err := ctx.config.Filesystems.Filesystems(filesystemTags)
    45  	if err != nil {
    46  		return errors.Annotatef(err, "getting filesystem information")
    47  	}
    48  
    49  	aliveFilesystemTags := filesystemTags[:len(alive)]
    50  	dyingFilesystemTags := filesystemTags[len(alive) : len(alive)+len(dying)]
    51  	deadFilesystemTags := filesystemTags[len(alive)+len(dying):]
    52  	aliveFilesystemResults := filesystemResults[:len(alive)]
    53  	dyingFilesystemResults := filesystemResults[len(alive) : len(alive)+len(dying)]
    54  	deadFilesystemResults := filesystemResults[len(alive)+len(dying):]
    55  
    56  	if err := processDeadFilesystems(ctx, deadFilesystemTags, deadFilesystemResults); err != nil {
    57  		return errors.Annotate(err, "deprovisioning filesystems")
    58  	}
    59  	if err := processDyingFilesystems(ctx, dyingFilesystemTags, dyingFilesystemResults); err != nil {
    60  		return errors.Annotate(err, "processing dying filesystems")
    61  	}
    62  	if err := processAliveFilesystems(ctx, aliveFilesystemTags, aliveFilesystemResults); err != nil {
    63  		return errors.Annotate(err, "provisioning filesystems")
    64  	}
    65  	return nil
    66  }
    67  
    68  // filesystemAttachmentsChanged is called when the lifecycle states of the filesystem
    69  // attachments with the provided IDs have been seen to have changed.
    70  func filesystemAttachmentsChanged(ctx *context, watcherIds []watcher.MachineStorageId) error {
    71  	ids := copyMachineStorageIds(watcherIds)
    72  	alive, dying, dead, err := attachmentLife(ctx, ids)
    73  	if err != nil {
    74  		return errors.Trace(err)
    75  	}
    76  	logger.Debugf("filesystem attachment alive: %v, dying: %v, dead: %v", alive, dying, dead)
    77  	if len(dead) != 0 {
    78  		// We should not see dead filesystem attachments;
    79  		// attachments go directly from Dying to removed.
    80  		logger.Warningf("unexpected dead filesystem attachments: %v", dead)
    81  	}
    82  	if len(alive)+len(dying) == 0 {
    83  		return nil
    84  	}
    85  
    86  	// Get filesystem information for alive and dying filesystem attachments, so
    87  	// we can attach/detach.
    88  	ids = append(alive, dying...)
    89  	filesystemAttachmentResults, err := ctx.config.Filesystems.FilesystemAttachments(ids)
    90  	if err != nil {
    91  		return errors.Annotatef(err, "getting filesystem attachment information")
    92  	}
    93  
    94  	// Deprovision Dying filesystem attachments.
    95  	dyingFilesystemAttachmentResults := filesystemAttachmentResults[len(alive):]
    96  	if err := processDyingFilesystemAttachments(ctx, dying, dyingFilesystemAttachmentResults); err != nil {
    97  		return errors.Annotate(err, "destroying filesystem attachments")
    98  	}
    99  
   100  	// Provision Alive filesystem attachments.
   101  	aliveFilesystemAttachmentResults := filesystemAttachmentResults[:len(alive)]
   102  	if err := processAliveFilesystemAttachments(ctx, alive, aliveFilesystemAttachmentResults); err != nil {
   103  		return errors.Annotate(err, "creating filesystem attachments")
   104  	}
   105  
   106  	return nil
   107  }
   108  
   109  // processDyingFilesystems processes the FilesystemResults for Dying filesystems,
   110  // removing them from provisioning-pending as necessary.
   111  func processDyingFilesystems(ctx *context, tags []names.FilesystemTag, filesystemResults []params.FilesystemResult) error {
   112  	for _, tag := range tags {
   113  		removePendingFilesystem(ctx, tag)
   114  	}
   115  	return nil
   116  }
   117  
   118  func updateFilesystem(ctx *context, info storage.Filesystem) {
   119  	ctx.filesystems[info.Tag] = info
   120  	for id, params := range ctx.incompleteFilesystemAttachmentParams {
   121  		if params.FilesystemId == "" && id.AttachmentTag == info.Tag.String() {
   122  			updatePendingFilesystemAttachment(ctx, id, params)
   123  		}
   124  	}
   125  }
   126  
   127  func updatePendingFilesystem(ctx *context, params storage.FilesystemParams) {
   128  	if params.Volume != (names.VolumeTag{}) {
   129  		// The filesystem is volume-backed: we must watch for
   130  		// the corresponding block device. This will trigger a
   131  		// one-time (for the volume) forced update of block
   132  		// devices. If the block device is not immediately
   133  		// available, then we rely on the watcher. The forced
   134  		// update is necessary in case the block device was
   135  		// added to state already, and we didn't observe it.
   136  		if _, ok := ctx.volumeBlockDevices[params.Volume]; !ok {
   137  			ctx.pendingVolumeBlockDevices.Add(params.Volume)
   138  			ctx.incompleteFilesystemParams[params.Tag] = params
   139  			return
   140  		}
   141  	}
   142  	delete(ctx.incompleteFilesystemParams, params.Tag)
   143  	scheduleOperations(ctx, &createFilesystemOp{args: params})
   144  }
   145  
   146  func removePendingFilesystem(ctx *context, tag names.FilesystemTag) {
   147  	delete(ctx.incompleteFilesystemParams, tag)
   148  	ctx.schedule.Remove(tag)
   149  }
   150  
   151  // updatePendingFilesystemAttachment adds the given filesystem attachment params to
   152  // either the incomplete set or the schedule. If the params are incomplete
   153  // due to a missing instance ID, updatePendingFilesystemAttachment will request
   154  // that the machine be watched so its instance ID can be learned.
   155  func updatePendingFilesystemAttachment(
   156  	ctx *context,
   157  	id params.MachineStorageId,
   158  	params storage.FilesystemAttachmentParams,
   159  ) {
   160  	var incomplete bool
   161  	filesystem, ok := ctx.filesystems[params.Filesystem]
   162  	if !ok {
   163  		incomplete = true
   164  	} else {
   165  		params.FilesystemId = filesystem.FilesystemId
   166  		if filesystem.Volume != (names.VolumeTag{}) {
   167  			// The filesystem is volume-backed: if the filesystem
   168  			// was created in another session, then the block device
   169  			// may not have been seen yet. We must wait for the block
   170  			// device watcher to trigger.
   171  			if _, ok := ctx.volumeBlockDevices[filesystem.Volume]; !ok {
   172  				incomplete = true
   173  			}
   174  		}
   175  	}
   176  	if params.InstanceId == "" {
   177  		watchMachine(ctx, params.Machine.(names.MachineTag))
   178  		incomplete = true
   179  	}
   180  	if params.FilesystemId == "" {
   181  		incomplete = true
   182  	}
   183  	if incomplete {
   184  		ctx.incompleteFilesystemAttachmentParams[id] = params
   185  		return
   186  	}
   187  	delete(ctx.incompleteFilesystemAttachmentParams, id)
   188  	scheduleOperations(ctx, &attachFilesystemOp{args: params})
   189  }
   190  
   191  // removePendingFilesystemAttachment removes the specified pending filesystem
   192  // attachment from the incomplete set and/or the schedule if it exists
   193  // there.
   194  func removePendingFilesystemAttachment(ctx *context, id params.MachineStorageId) {
   195  	delete(ctx.incompleteFilesystemAttachmentParams, id)
   196  	ctx.schedule.Remove(id)
   197  }
   198  
   199  // processDeadFilesystems processes the FilesystemResults for Dead filesystems,
   200  // deprovisioning filesystems and removing from state as necessary.
   201  func processDeadFilesystems(ctx *context, tags []names.FilesystemTag, filesystemResults []params.FilesystemResult) error {
   202  	for _, tag := range tags {
   203  		removePendingFilesystem(ctx, tag)
   204  	}
   205  	var destroy []names.FilesystemTag
   206  	var remove []names.Tag
   207  	for i, result := range filesystemResults {
   208  		tag := tags[i]
   209  		if result.Error == nil {
   210  			logger.Debugf("filesystem %s is provisioned, queuing for deprovisioning", tag.Id())
   211  			filesystem, err := filesystemFromParams(result.Result)
   212  			if err != nil {
   213  				return errors.Annotate(err, "getting filesystem info")
   214  			}
   215  			updateFilesystem(ctx, filesystem)
   216  			destroy = append(destroy, tag)
   217  			continue
   218  		}
   219  		if params.IsCodeNotProvisioned(result.Error) {
   220  			logger.Debugf("filesystem %s is not provisioned, queuing for removal", tag.Id())
   221  			remove = append(remove, tag)
   222  			continue
   223  		}
   224  		return errors.Annotatef(result.Error, "getting filesystem information for filesystem %s", tag.Id())
   225  	}
   226  	if len(destroy) > 0 {
   227  		ops := make([]scheduleOp, len(destroy))
   228  		for i, tag := range destroy {
   229  			ops[i] = &removeFilesystemOp{tag: tag}
   230  		}
   231  		scheduleOperations(ctx, ops...)
   232  	}
   233  	if err := removeEntities(ctx, remove); err != nil {
   234  		return errors.Annotate(err, "removing filesystems from state")
   235  	}
   236  	return nil
   237  }
   238  
   239  // processDyingFilesystemAttachments processes the FilesystemAttachmentResults for
   240  // Dying filesystem attachments, detaching filesystems and updating state as necessary.
   241  func processDyingFilesystemAttachments(
   242  	ctx *context,
   243  	ids []params.MachineStorageId,
   244  	filesystemAttachmentResults []params.FilesystemAttachmentResult,
   245  ) error {
   246  	for _, id := range ids {
   247  		removePendingFilesystemAttachment(ctx, id)
   248  	}
   249  	detach := make([]params.MachineStorageId, 0, len(ids))
   250  	remove := make([]params.MachineStorageId, 0, len(ids))
   251  	for i, result := range filesystemAttachmentResults {
   252  		id := ids[i]
   253  		if result.Error == nil {
   254  			detach = append(detach, id)
   255  			continue
   256  		}
   257  		if params.IsCodeNotProvisioned(result.Error) {
   258  			remove = append(remove, id)
   259  			continue
   260  		}
   261  		return errors.Annotatef(result.Error, "getting information for filesystem attachment %v", id)
   262  	}
   263  	if len(detach) > 0 {
   264  		attachmentParams, err := filesystemAttachmentParams(ctx, detach)
   265  		if err != nil {
   266  			return errors.Trace(err)
   267  		}
   268  		ops := make([]scheduleOp, len(attachmentParams))
   269  		for i, p := range attachmentParams {
   270  			ops[i] = &detachFilesystemOp{args: p}
   271  		}
   272  		scheduleOperations(ctx, ops...)
   273  	}
   274  	if err := removeAttachments(ctx, remove); err != nil {
   275  		return errors.Annotate(err, "removing attachments from state")
   276  	}
   277  	return nil
   278  }
   279  
   280  // processAliveFilesystems processes the FilesystemResults for Alive filesystems,
   281  // provisioning filesystems and setting the info in state as necessary.
   282  func processAliveFilesystems(ctx *context, tags []names.FilesystemTag, filesystemResults []params.FilesystemResult) error {
   283  	// Filter out the already-provisioned filesystems.
   284  	pending := make([]names.FilesystemTag, 0, len(tags))
   285  	for i, result := range filesystemResults {
   286  		tag := tags[i]
   287  		if result.Error == nil {
   288  			// Filesystem is already provisioned: skip.
   289  			logger.Debugf("filesystem %q is already provisioned, nothing to do", tag.Id())
   290  			filesystem, err := filesystemFromParams(result.Result)
   291  			if err != nil {
   292  				return errors.Annotate(err, "getting filesystem info")
   293  			}
   294  			updateFilesystem(ctx, filesystem)
   295  			if !ctx.isApplicationKind() {
   296  				if filesystem.Volume != (names.VolumeTag{}) {
   297  					// Ensure that volume-backed filesystems' block
   298  					// devices are present even after creating the
   299  					// filesystem, so that attachments can be made.
   300  					maybeAddPendingVolumeBlockDevice(ctx, filesystem.Volume)
   301  				}
   302  			}
   303  			continue
   304  		}
   305  		if !params.IsCodeNotProvisioned(result.Error) {
   306  			return errors.Annotatef(
   307  				result.Error, "getting filesystem information for filesystem %q", tag.Id(),
   308  			)
   309  		}
   310  		// The filesystem has not yet been provisioned, so record its tag
   311  		// to enquire about parameters below.
   312  		pending = append(pending, tag)
   313  	}
   314  	if len(pending) == 0 {
   315  		return nil
   316  	}
   317  	params, err := filesystemParams(ctx, pending)
   318  	if err != nil {
   319  		return errors.Annotate(err, "getting filesystem params")
   320  	}
   321  	for _, params := range params {
   322  		if ctx.isApplicationKind() {
   323  			logger.Debugf("not queuing filesystem for %v unit", ctx.config.Scope.Id())
   324  			continue
   325  		}
   326  		updatePendingFilesystem(ctx, params)
   327  	}
   328  	return nil
   329  }
   330  
   331  func maybeAddPendingVolumeBlockDevice(ctx *context, v names.VolumeTag) {
   332  	if _, ok := ctx.volumeBlockDevices[v]; !ok {
   333  		ctx.pendingVolumeBlockDevices.Add(v)
   334  	}
   335  }
   336  
   337  // processAliveFilesystemAttachments processes the FilesystemAttachmentResults
   338  // for Alive filesystem attachments, attaching filesystems and setting the info
   339  // in state as necessary.
   340  func processAliveFilesystemAttachments(
   341  	ctx *context,
   342  	ids []params.MachineStorageId,
   343  	filesystemAttachmentResults []params.FilesystemAttachmentResult,
   344  ) error {
   345  	// Filter out the already-attached.
   346  	pending := make([]params.MachineStorageId, 0, len(ids))
   347  	for i, result := range filesystemAttachmentResults {
   348  		if result.Error == nil {
   349  			// Filesystem attachment is already provisioned: if we
   350  			// didn't (re)attach in this session, then we must do
   351  			// so now.
   352  			action := "nothing to do"
   353  			if _, ok := ctx.filesystemAttachments[ids[i]]; !ok {
   354  				// Not yet (re)attached in this session.
   355  				pending = append(pending, ids[i])
   356  				action = "will reattach"
   357  			}
   358  			logger.Debugf(
   359  				"%s is already attached to %s, %s",
   360  				ids[i].AttachmentTag, ids[i].MachineTag, action,
   361  			)
   362  			removePendingFilesystemAttachment(ctx, ids[i])
   363  			continue
   364  		}
   365  		if !params.IsCodeNotProvisioned(result.Error) {
   366  			return errors.Annotatef(
   367  				result.Error, "getting information for attachment %v", ids[i],
   368  			)
   369  		}
   370  		// The filesystem has not yet been attached, so
   371  		// record its tag to enquire about parameters below.
   372  		pending = append(pending, ids[i])
   373  	}
   374  	if len(pending) == 0 {
   375  		return nil
   376  	}
   377  	params, err := filesystemAttachmentParams(ctx, pending)
   378  	if err != nil {
   379  		return errors.Trace(err)
   380  	}
   381  	for i, params := range params {
   382  		if params.Machine != nil && params.Machine.Kind() != names.MachineTagKind {
   383  			logger.Debugf("not queuing filesystem attachment for non-machine %v", params.Machine)
   384  			continue
   385  		}
   386  		updatePendingFilesystemAttachment(ctx, pending[i], params)
   387  	}
   388  	return nil
   389  }
   390  
   391  // filesystemAttachmentParams obtains the specified attachments' parameters.
   392  func filesystemAttachmentParams(
   393  	ctx *context, ids []params.MachineStorageId,
   394  ) ([]storage.FilesystemAttachmentParams, error) {
   395  	paramsResults, err := ctx.config.Filesystems.FilesystemAttachmentParams(ids)
   396  	if err != nil {
   397  		return nil, errors.Annotate(err, "getting filesystem attachment params")
   398  	}
   399  	attachmentParams := make([]storage.FilesystemAttachmentParams, len(ids))
   400  	for i, result := range paramsResults {
   401  		if result.Error != nil {
   402  			return nil, errors.Annotate(result.Error, "getting filesystem attachment parameters")
   403  		}
   404  		params, err := filesystemAttachmentParamsFromParams(result.Result)
   405  		if err != nil {
   406  			return nil, errors.Annotate(err, "getting filesystem attachment parameters")
   407  		}
   408  		attachmentParams[i] = params
   409  	}
   410  	return attachmentParams, nil
   411  }
   412  
   413  // filesystemParams obtains the specified filesystems' parameters.
   414  func filesystemParams(ctx *context, tags []names.FilesystemTag) ([]storage.FilesystemParams, error) {
   415  	paramsResults, err := ctx.config.Filesystems.FilesystemParams(tags)
   416  	if err != nil {
   417  		return nil, errors.Annotate(err, "getting filesystem params")
   418  	}
   419  	allParams := make([]storage.FilesystemParams, len(tags))
   420  	for i, result := range paramsResults {
   421  		if result.Error != nil {
   422  			return nil, errors.Annotate(result.Error, "getting filesystem parameters")
   423  		}
   424  		params, err := filesystemParamsFromParams(result.Result)
   425  		if err != nil {
   426  			return nil, errors.Annotate(err, "getting filesystem parameters")
   427  		}
   428  		allParams[i] = params
   429  	}
   430  	return allParams, nil
   431  }
   432  
   433  // removeFilesystemParams obtains the specified filesystems' destruction parameters.
   434  func removeFilesystemParams(ctx *context, tags []names.FilesystemTag) ([]params.RemoveFilesystemParams, error) {
   435  	paramsResults, err := ctx.config.Filesystems.RemoveFilesystemParams(tags)
   436  	if err != nil {
   437  		return nil, errors.Annotate(err, "getting filesystem params")
   438  	}
   439  	allParams := make([]params.RemoveFilesystemParams, len(tags))
   440  	for i, result := range paramsResults {
   441  		if result.Error != nil {
   442  			return nil, errors.Annotate(result.Error, "getting filesystem removal parameters")
   443  		}
   444  		allParams[i] = result.Result
   445  	}
   446  	return allParams, nil
   447  }
   448  
   449  func filesystemFromParams(in params.Filesystem) (storage.Filesystem, error) {
   450  	filesystemTag, err := names.ParseFilesystemTag(in.FilesystemTag)
   451  	if err != nil {
   452  		return storage.Filesystem{}, errors.Trace(err)
   453  	}
   454  	var volumeTag names.VolumeTag
   455  	if in.VolumeTag != "" {
   456  		volumeTag, err = names.ParseVolumeTag(in.VolumeTag)
   457  		if err != nil {
   458  			return storage.Filesystem{}, errors.Trace(err)
   459  		}
   460  	}
   461  	return storage.Filesystem{
   462  		filesystemTag,
   463  		volumeTag,
   464  		storage.FilesystemInfo{
   465  			in.Info.FilesystemId,
   466  			in.Info.Size,
   467  		},
   468  	}, nil
   469  }
   470  
   471  func filesystemParamsFromParams(in params.FilesystemParams) (storage.FilesystemParams, error) {
   472  	filesystemTag, err := names.ParseFilesystemTag(in.FilesystemTag)
   473  	if err != nil {
   474  		return storage.FilesystemParams{}, errors.Trace(err)
   475  	}
   476  	var volumeTag names.VolumeTag
   477  	if in.VolumeTag != "" {
   478  		volumeTag, err = names.ParseVolumeTag(in.VolumeTag)
   479  		if err != nil {
   480  			return storage.FilesystemParams{}, errors.Trace(err)
   481  		}
   482  	}
   483  	providerType := storage.ProviderType(in.Provider)
   484  	return storage.FilesystemParams{
   485  		Tag:          filesystemTag,
   486  		Volume:       volumeTag,
   487  		Size:         in.Size,
   488  		Provider:     providerType,
   489  		Attributes:   in.Attributes,
   490  		ResourceTags: in.Tags,
   491  	}, nil
   492  }
   493  
   494  func filesystemAttachmentParamsFromParams(in params.FilesystemAttachmentParams) (storage.FilesystemAttachmentParams, error) {
   495  	hostTag, err := names.ParseTag(in.MachineTag)
   496  	if err != nil {
   497  		return storage.FilesystemAttachmentParams{}, errors.Trace(err)
   498  	}
   499  	filesystemTag, err := names.ParseFilesystemTag(in.FilesystemTag)
   500  	if err != nil {
   501  		return storage.FilesystemAttachmentParams{}, errors.Trace(err)
   502  	}
   503  	return storage.FilesystemAttachmentParams{
   504  		AttachmentParams: storage.AttachmentParams{
   505  			Provider:   storage.ProviderType(in.Provider),
   506  			Machine:    hostTag,
   507  			InstanceId: instance.Id(in.InstanceId),
   508  			ReadOnly:   in.ReadOnly,
   509  		},
   510  		Filesystem:   filesystemTag,
   511  		FilesystemId: in.FilesystemId,
   512  		Path:         in.MountPoint,
   513  	}, nil
   514  }