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