github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/storageprovisioner/volume_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/life"
    12  	"github.com/juju/juju/core/watcher"
    13  	"github.com/juju/juju/rpc/params"
    14  	"github.com/juju/juju/storage"
    15  	"github.com/juju/juju/storage/plans"
    16  	"github.com/juju/juju/wrench"
    17  )
    18  
    19  // volumesChanged is called when the lifecycle states of the volumes
    20  // with the provided IDs have been seen to have changed.
    21  func volumesChanged(ctx *context, changes []string) error {
    22  	tags := make([]names.Tag, len(changes))
    23  	for i, change := range changes {
    24  		tags[i] = names.NewVolumeTag(change)
    25  	}
    26  	alive, dying, dead, err := storageEntityLife(ctx, tags)
    27  	if err != nil {
    28  		return errors.Trace(err)
    29  	}
    30  	ctx.config.Logger.Debugf("volumes alive: %v, dying: %v, dead: %v", alive, dying, dead)
    31  	if err := processDyingVolumes(ctx, dying); err != nil {
    32  		return errors.Annotate(err, "processing dying volumes")
    33  	}
    34  	if len(alive)+len(dead) == 0 {
    35  		return nil
    36  	}
    37  
    38  	// Get volume information for alive and dead volumes, so
    39  	// we can provision/deprovision.
    40  	volumeTags := make([]names.VolumeTag, 0, len(alive)+len(dead))
    41  	for _, tag := range alive {
    42  		volumeTags = append(volumeTags, tag.(names.VolumeTag))
    43  	}
    44  	for _, tag := range dead {
    45  		volumeTags = append(volumeTags, tag.(names.VolumeTag))
    46  	}
    47  	volumeResults, err := ctx.config.Volumes.Volumes(volumeTags)
    48  	if err != nil {
    49  		return errors.Annotatef(err, "getting volume information")
    50  	}
    51  	if err := processDeadVolumes(ctx, volumeTags[len(alive):], volumeResults[len(alive):]); err != nil {
    52  		return errors.Annotate(err, "deprovisioning volumes")
    53  	}
    54  	if err := processAliveVolumes(ctx, alive, volumeResults[:len(alive)]); err != nil {
    55  		return errors.Annotate(err, "provisioning volumes")
    56  	}
    57  	return nil
    58  }
    59  
    60  func sortVolumeAttachmentPlans(ctx *context, ids []params.MachineStorageId) (
    61  	alive, dying, dead []params.VolumeAttachmentPlanResult, err error) {
    62  	plans, err := ctx.config.Volumes.VolumeAttachmentPlans(ids)
    63  	if err != nil {
    64  		return nil, nil, nil, errors.Trace(err)
    65  	}
    66  	ctx.config.Logger.Debugf("Found plans: %v", plans)
    67  	for _, plan := range plans {
    68  		switch plan.Result.Life {
    69  		case life.Alive:
    70  			alive = append(alive, plan)
    71  		case life.Dying:
    72  			dying = append(dying, plan)
    73  		case life.Dead:
    74  			dead = append(dead, plan)
    75  		}
    76  	}
    77  	return
    78  }
    79  
    80  func volumeAttachmentPlansChanged(ctx *context, watcherIds []watcher.MachineStorageId) error {
    81  	ctx.config.Logger.Debugf("Got machine storage ids: %v", watcherIds)
    82  	ids := copyMachineStorageIds(watcherIds)
    83  	alive, dying, dead, err := sortVolumeAttachmentPlans(ctx, ids)
    84  	if err != nil {
    85  		return errors.Trace(err)
    86  	}
    87  	ctx.config.Logger.Debugf("volume attachment plans alive: %v, dying: %v, dead: %v", alive, dying, dead)
    88  
    89  	if err := processAliveVolumePlans(ctx, alive); err != nil {
    90  		return err
    91  	}
    92  
    93  	if err := processDyingVolumePlans(ctx, dying); err != nil {
    94  		return err
    95  	}
    96  	return nil
    97  }
    98  
    99  func processAliveVolumePlans(ctx *context, volumePlans []params.VolumeAttachmentPlanResult) error {
   100  	volumeAttachmentPlans := make([]params.VolumeAttachmentPlan, len(volumePlans))
   101  	volumeTags := make([]names.VolumeTag, len(volumePlans))
   102  	for i, val := range volumePlans {
   103  		volumeAttachmentPlans[i] = val.Result
   104  		tag, err := names.ParseVolumeTag(val.Result.VolumeTag)
   105  		if err != nil {
   106  			return errors.Trace(err)
   107  		}
   108  		volumeTags[i] = tag
   109  	}
   110  
   111  	for idx, val := range volumeAttachmentPlans {
   112  		volPlan, err := plans.PlanByType(val.PlanInfo.DeviceType)
   113  		if err != nil {
   114  			if !errors.IsNotFound(err) {
   115  				return errors.Trace(err)
   116  			}
   117  			continue
   118  		}
   119  		if blockDeviceInfo, err := volPlan.AttachVolume(val.PlanInfo.DeviceAttributes); err != nil {
   120  			return errors.Trace(err)
   121  		} else {
   122  			volumeAttachmentPlans[idx].BlockDevice = blockDeviceInfo
   123  		}
   124  	}
   125  
   126  	results, err := ctx.config.Volumes.SetVolumeAttachmentPlanBlockInfo(volumeAttachmentPlans)
   127  	if err != nil {
   128  		return errors.Trace(err)
   129  	}
   130  	for _, result := range results {
   131  		if result.Error != nil {
   132  			return errors.Errorf("failed to publish block info to state: %s", result.Error)
   133  		}
   134  	}
   135  	_, err = refreshVolumeBlockDevices(ctx, volumeTags)
   136  	return err
   137  }
   138  
   139  func processDyingVolumePlans(ctx *context, volumePlans []params.VolumeAttachmentPlanResult) error {
   140  	ids := volumePlansToMachineIds(volumePlans)
   141  	for _, val := range volumePlans {
   142  		volPlan, err := plans.PlanByType(val.Result.PlanInfo.DeviceType)
   143  		if err != nil {
   144  			if !errors.IsNotFound(err) {
   145  				return errors.Trace(err)
   146  			}
   147  			continue
   148  		}
   149  		if err := volPlan.DetachVolume(val.Result.PlanInfo.DeviceAttributes); err != nil {
   150  			return errors.Trace(err)
   151  		}
   152  		if wrench.IsActive("storageprovisioner", "DetachVolume") {
   153  			return errors.New("wrench active")
   154  		}
   155  	}
   156  	results, err := ctx.config.Volumes.RemoveVolumeAttachmentPlan(ids)
   157  	if err != nil {
   158  		return err
   159  	}
   160  	for _, result := range results {
   161  		if result.Error != nil {
   162  			return errors.Annotate(result.Error, "removing volume plan")
   163  		}
   164  	}
   165  	return nil
   166  }
   167  
   168  func volumePlansToMachineIds(plans []params.VolumeAttachmentPlanResult) []params.MachineStorageId {
   169  	storageIds := make([]params.MachineStorageId, len(plans))
   170  	for i, plan := range plans {
   171  		storageIds[i] = params.MachineStorageId{
   172  			MachineTag:    plan.Result.MachineTag,
   173  			AttachmentTag: plan.Result.VolumeTag,
   174  		}
   175  	}
   176  	return storageIds
   177  }
   178  
   179  // volumeAttachmentsChanged is called when the lifecycle states of the volume
   180  // attachments with the provided IDs have been seen to have changed.
   181  func volumeAttachmentsChanged(ctx *context, watcherIds []watcher.MachineStorageId) error {
   182  	ids := copyMachineStorageIds(watcherIds)
   183  	alive, dying, dead, gone, err := attachmentLife(ctx, ids)
   184  	if err != nil {
   185  		return errors.Trace(err)
   186  	}
   187  	ctx.config.Logger.Debugf("volume attachments alive: %v, dying: %v, dead: %v", alive, dying, dead)
   188  	if len(dead) != 0 {
   189  		// We should not see dead volume attachments;
   190  		// attachments go directly from Dying to removed.
   191  		ctx.config.Logger.Warningf("unexpected dead volume attachments: %v", dead)
   192  	}
   193  	// Clean up any attachments which have been removed.
   194  	for _, id := range gone {
   195  		delete(ctx.volumeAttachments, id)
   196  	}
   197  	if len(alive)+len(dying) == 0 {
   198  		return nil
   199  	}
   200  
   201  	// Get volume information for alive and dying volume attachments, so
   202  	// we can attach/detach.
   203  	ids = append(alive, dying...)
   204  	volumeAttachmentResults, err := ctx.config.Volumes.VolumeAttachments(ids)
   205  	if err != nil {
   206  		return errors.Annotatef(err, "getting volume attachment information")
   207  	}
   208  
   209  	// Deprovision Dying volume attachments.
   210  	dyingVolumeAttachmentResults := volumeAttachmentResults[len(alive):]
   211  	if err := processDyingVolumeAttachments(ctx, dying, dyingVolumeAttachmentResults); err != nil {
   212  		return errors.Annotate(err, "deprovisioning volume attachments")
   213  	}
   214  
   215  	// Provision Alive volume attachments.
   216  	aliveVolumeAttachmentResults := volumeAttachmentResults[:len(alive)]
   217  	if err := processAliveVolumeAttachments(ctx, alive, aliveVolumeAttachmentResults); err != nil {
   218  		return errors.Annotate(err, "provisioning volumes")
   219  	}
   220  
   221  	return nil
   222  }
   223  
   224  // processDyingVolumes processes the VolumeResults for Dying volumes,
   225  // removing them from provisioning-pending as necessary.
   226  func processDyingVolumes(ctx *context, tags []names.Tag) error {
   227  	if ctx.isApplicationKind() {
   228  		// only care dead for application.
   229  		return nil
   230  	}
   231  	for _, tag := range tags {
   232  		removePendingVolume(ctx, tag.(names.VolumeTag))
   233  	}
   234  	return nil
   235  }
   236  
   237  // updateVolume updates the context with the given volume info.
   238  func updateVolume(ctx *context, info storage.Volume) {
   239  	ctx.volumes[info.Tag] = info
   240  	for id, params := range ctx.incompleteVolumeAttachmentParams {
   241  		if params.VolumeId == "" && id.AttachmentTag == info.Tag.String() {
   242  			params.VolumeId = info.VolumeId
   243  			updatePendingVolumeAttachment(ctx, id, params)
   244  		}
   245  	}
   246  }
   247  
   248  // updatePendingVolume adds the given volume params to either the incomplete
   249  // set or the schedule. If the params are incomplete due to a missing instance
   250  // ID, updatePendingVolume will request that the machine be watched so its
   251  // instance ID can be learned.
   252  func updatePendingVolume(ctx *context, params storage.VolumeParams) {
   253  	if params.Attachment == nil {
   254  		// NOTE(axw) this would only happen if the model is
   255  		// in an incoherent state; we should never have an
   256  		// alive, unprovisioned, and unattached volume.
   257  		ctx.config.Logger.Warningf(
   258  			"%s is in an incoherent state, ignoring",
   259  			names.ReadableString(params.Tag),
   260  		)
   261  		return
   262  	}
   263  	if params.Attachment.InstanceId == "" {
   264  		watchMachine(ctx, params.Attachment.Machine.(names.MachineTag))
   265  		ctx.incompleteVolumeParams[params.Tag] = params
   266  	} else {
   267  		delete(ctx.incompleteVolumeParams, params.Tag)
   268  		scheduleOperations(ctx, &createVolumeOp{args: params})
   269  	}
   270  }
   271  
   272  // removePendingVolume removes the specified pending volume from the
   273  // incomplete set and/or the schedule if it exists there.
   274  func removePendingVolume(ctx *context, tag names.VolumeTag) {
   275  	delete(ctx.incompleteVolumeParams, tag)
   276  	ctx.schedule.Remove(tag)
   277  }
   278  
   279  // updatePendingVolumeAttachment adds the given volume attachment params to
   280  // either the incomplete set or the schedule. If the params are incomplete
   281  // due to a missing instance ID, updatePendingVolumeAttachment will request
   282  // that the machine be watched so its instance ID can be learned.
   283  func updatePendingVolumeAttachment(
   284  	ctx *context,
   285  	id params.MachineStorageId,
   286  	params storage.VolumeAttachmentParams,
   287  ) {
   288  	if params.InstanceId == "" {
   289  		watchMachine(ctx, params.Machine.(names.MachineTag))
   290  	} else if params.VolumeId != "" {
   291  		delete(ctx.incompleteVolumeAttachmentParams, id)
   292  		scheduleOperations(ctx, &attachVolumeOp{args: params})
   293  		return
   294  	}
   295  	ctx.incompleteVolumeAttachmentParams[id] = params
   296  }
   297  
   298  // removePendingVolumeAttachment removes the specified pending volume
   299  // attachment from the incomplete set and/or the schedule if it exists
   300  // there.
   301  func removePendingVolumeAttachment(ctx *context, id params.MachineStorageId) {
   302  	delete(ctx.incompleteVolumeAttachmentParams, id)
   303  	ctx.schedule.Remove(id)
   304  }
   305  
   306  // processDeadVolumes processes the VolumeResults for Dead volumes,
   307  // deprovisioning volumes and removing from state as necessary.
   308  func processDeadVolumes(ctx *context, tags []names.VolumeTag, volumeResults []params.VolumeResult) error {
   309  	for _, tag := range tags {
   310  		removePendingVolume(ctx, tag)
   311  	}
   312  	var destroy []names.VolumeTag
   313  	var remove []names.Tag
   314  	for i, result := range volumeResults {
   315  		tag := tags[i]
   316  		if result.Error == nil {
   317  			ctx.config.Logger.Debugf("volume %s is provisioned, queuing for deprovisioning", tag.Id())
   318  			volume, err := volumeFromParams(result.Result)
   319  			if err != nil {
   320  				return errors.Annotate(err, "getting volume info")
   321  			}
   322  			updateVolume(ctx, volume)
   323  			destroy = append(destroy, tag)
   324  			continue
   325  		}
   326  		if params.IsCodeNotProvisioned(result.Error) {
   327  			ctx.config.Logger.Debugf("volume %s is not provisioned, queuing for removal", tag.Id())
   328  			remove = append(remove, tag)
   329  			continue
   330  		}
   331  		return errors.Annotatef(result.Error, "getting volume information for volume %s", tag.Id())
   332  	}
   333  	if len(destroy) > 0 {
   334  		ops := make([]scheduleOp, len(destroy))
   335  		for i, tag := range destroy {
   336  			ops[i] = &removeVolumeOp{tag: tag}
   337  		}
   338  		scheduleOperations(ctx, ops...)
   339  	}
   340  	if err := removeEntities(ctx, remove); err != nil {
   341  		return errors.Annotate(err, "removing volumes from state")
   342  	}
   343  	return nil
   344  }
   345  
   346  // processDyingVolumeAttachments processes the VolumeAttachmentResults for
   347  // Dying volume attachments, detaching volumes and updating state as necessary.
   348  func processDyingVolumeAttachments(
   349  	ctx *context,
   350  	ids []params.MachineStorageId,
   351  	volumeAttachmentResults []params.VolumeAttachmentResult,
   352  ) error {
   353  	for _, id := range ids {
   354  		removePendingVolumeAttachment(ctx, id)
   355  	}
   356  	detach := make([]params.MachineStorageId, 0, len(ids))
   357  	remove := make([]params.MachineStorageId, 0, len(ids))
   358  	for i, result := range volumeAttachmentResults {
   359  		id := ids[i]
   360  		if result.Error == nil {
   361  			detach = append(detach, id)
   362  			continue
   363  		}
   364  		if params.IsCodeNotProvisioned(result.Error) {
   365  			remove = append(remove, id)
   366  			continue
   367  		}
   368  		return errors.Annotatef(result.Error, "getting information for volume attachment %v", id)
   369  	}
   370  	if len(detach) > 0 {
   371  		attachmentParams, err := volumeAttachmentParams(ctx, detach)
   372  		if err != nil {
   373  			return errors.Trace(err)
   374  		}
   375  		ops := make([]scheduleOp, len(attachmentParams))
   376  		for i, p := range attachmentParams {
   377  			ops[i] = &detachVolumeOp{args: p}
   378  		}
   379  		scheduleOperations(ctx, ops...)
   380  	}
   381  	if err := removeAttachments(ctx, remove); err != nil {
   382  		return errors.Annotate(err, "removing attachments from state")
   383  	}
   384  	for _, id := range remove {
   385  		delete(ctx.volumeAttachments, id)
   386  	}
   387  	return nil
   388  }
   389  
   390  // processAliveVolumes processes the VolumeResults for Alive volumes,
   391  // provisioning volumes and setting the info in state as necessary.
   392  func processAliveVolumes(ctx *context, tags []names.Tag, volumeResults []params.VolumeResult) error {
   393  	if ctx.isApplicationKind() {
   394  		// only care dead for application kind.
   395  		return nil
   396  	}
   397  
   398  	// Filter out the already-provisioned volumes.
   399  	pending := make([]names.VolumeTag, 0, len(tags))
   400  	for i, result := range volumeResults {
   401  		volumeTag := tags[i].(names.VolumeTag)
   402  		if result.Error == nil {
   403  			// Volume is already provisioned: skip.
   404  			ctx.config.Logger.Debugf("volume %q is already provisioned, nothing to do", tags[i].Id())
   405  			volume, err := volumeFromParams(result.Result)
   406  			if err != nil {
   407  				return errors.Annotate(err, "getting volume info")
   408  			}
   409  			updateVolume(ctx, volume)
   410  			removePendingVolume(ctx, volumeTag)
   411  			continue
   412  		}
   413  		if !params.IsCodeNotProvisioned(result.Error) {
   414  			return errors.Annotatef(
   415  				result.Error, "getting volume information for volume %q", tags[i].Id(),
   416  			)
   417  		}
   418  		// The volume has not yet been provisioned, so record its tag
   419  		// to enquire about parameters below.
   420  		pending = append(pending, volumeTag)
   421  	}
   422  	if len(pending) == 0 {
   423  		return nil
   424  	}
   425  	volumeParams, err := volumeParams(ctx, pending)
   426  	if err != nil {
   427  		return errors.Annotate(err, "getting volume params")
   428  	}
   429  	for _, params := range volumeParams {
   430  		if params.Attachment != nil && params.Attachment.Machine.Kind() != names.MachineTagKind {
   431  			ctx.config.Logger.Debugf("not queuing volume for non-machine %v", params.Attachment.Machine)
   432  			continue
   433  		}
   434  		updatePendingVolume(ctx, params)
   435  	}
   436  	return nil
   437  }
   438  
   439  // processAliveVolumeAttachments processes the VolumeAttachmentResults
   440  // for Alive volume attachments, attaching volumes and setting the info
   441  // in state as necessary.
   442  func processAliveVolumeAttachments(
   443  	ctx *context,
   444  	ids []params.MachineStorageId,
   445  	volumeAttachmentResults []params.VolumeAttachmentResult,
   446  ) error {
   447  	// Filter out the already-attached.
   448  	pending := make([]params.MachineStorageId, 0, len(ids))
   449  	for i, result := range volumeAttachmentResults {
   450  		if result.Error == nil {
   451  			// Volume attachment is already provisioned: if we
   452  			// didn't (re)attach in this session, then we must
   453  			// do so now.
   454  			action := "nothing to do"
   455  			if _, ok := ctx.volumeAttachments[ids[i]]; !ok {
   456  				// Not yet (re)attached in this session.
   457  				pending = append(pending, ids[i])
   458  				action = "will reattach"
   459  			}
   460  			ctx.config.Logger.Debugf(
   461  				"%s is already attached to %s, %s",
   462  				ids[i].AttachmentTag, ids[i].MachineTag, action,
   463  			)
   464  			removePendingVolumeAttachment(ctx, ids[i])
   465  			continue
   466  		}
   467  		if !params.IsCodeNotProvisioned(result.Error) {
   468  			return errors.Annotatef(
   469  				result.Error, "getting information for attachment %v", ids[i],
   470  			)
   471  		}
   472  		// The volume has not yet been provisioned, so record its tag
   473  		// to enquire about parameters below.
   474  		pending = append(pending, ids[i])
   475  	}
   476  	if len(pending) == 0 {
   477  		return nil
   478  	}
   479  	params, err := volumeAttachmentParams(ctx, pending)
   480  	if err != nil {
   481  		return errors.Trace(err)
   482  	}
   483  	for i, params := range params {
   484  		if params.Machine.Kind() != names.MachineTagKind {
   485  			ctx.config.Logger.Debugf("not queuing volume attachment for non-machine %v", params.Machine)
   486  			continue
   487  		}
   488  		if volume, ok := ctx.volumes[params.Volume]; ok {
   489  			params.VolumeId = volume.VolumeId
   490  		}
   491  		updatePendingVolumeAttachment(ctx, pending[i], params)
   492  	}
   493  	return nil
   494  }
   495  
   496  // volumeAttachmentParams obtains the specified attachments' parameters.
   497  func volumeAttachmentParams(
   498  	ctx *context, ids []params.MachineStorageId,
   499  ) ([]storage.VolumeAttachmentParams, error) {
   500  	paramsResults, err := ctx.config.Volumes.VolumeAttachmentParams(ids)
   501  	if err != nil {
   502  		return nil, errors.Annotate(err, "getting volume attachment params")
   503  	}
   504  	attachmentParams := make([]storage.VolumeAttachmentParams, len(ids))
   505  	for i, result := range paramsResults {
   506  		if result.Error != nil {
   507  			return nil, errors.Annotate(result.Error, "getting volume attachment parameters")
   508  		}
   509  		params, err := volumeAttachmentParamsFromParams(result.Result)
   510  		if err != nil {
   511  			return nil, errors.Annotate(err, "getting volume attachment parameters")
   512  		}
   513  		attachmentParams[i] = params
   514  	}
   515  	return attachmentParams, nil
   516  }
   517  
   518  // volumeParams obtains the specified volumes' parameters.
   519  func volumeParams(ctx *context, tags []names.VolumeTag) ([]storage.VolumeParams, error) {
   520  	paramsResults, err := ctx.config.Volumes.VolumeParams(tags)
   521  	if err != nil {
   522  		return nil, errors.Annotate(err, "getting volume params")
   523  	}
   524  	allParams := make([]storage.VolumeParams, len(tags))
   525  	for i, result := range paramsResults {
   526  		if result.Error != nil {
   527  			return nil, errors.Annotate(result.Error, "getting volume parameters")
   528  		}
   529  		params, err := volumeParamsFromParams(result.Result)
   530  		if err != nil {
   531  			return nil, errors.Annotate(err, "getting volume parameters")
   532  		}
   533  		allParams[i] = params
   534  	}
   535  	return allParams, nil
   536  }
   537  
   538  // removeVolumeParams obtains the specified volumes' destruction parameters.
   539  func removeVolumeParams(ctx *context, tags []names.VolumeTag) ([]params.RemoveVolumeParams, error) {
   540  	paramsResults, err := ctx.config.Volumes.RemoveVolumeParams(tags)
   541  	if err != nil {
   542  		return nil, errors.Annotate(err, "getting volume params")
   543  	}
   544  	allParams := make([]params.RemoveVolumeParams, len(tags))
   545  	for i, result := range paramsResults {
   546  		if result.Error != nil {
   547  			return nil, errors.Annotate(result.Error, "getting volume removal parameters")
   548  		}
   549  		allParams[i] = result.Result
   550  	}
   551  	return allParams, nil
   552  }
   553  
   554  func volumesFromStorage(in []storage.Volume) []params.Volume {
   555  	out := make([]params.Volume, len(in))
   556  	for i, v := range in {
   557  		out[i] = params.Volume{
   558  			v.Tag.String(),
   559  			params.VolumeInfo{
   560  				v.VolumeId,
   561  				v.HardwareId,
   562  				v.WWN,
   563  				"", // pool
   564  				v.Size,
   565  				v.Persistent,
   566  			},
   567  		}
   568  	}
   569  	return out
   570  }
   571  
   572  func volumeAttachmentsFromStorage(in []storage.VolumeAttachment) []params.VolumeAttachment {
   573  	out := make([]params.VolumeAttachment, len(in))
   574  	for i, v := range in {
   575  		planInfo := &params.VolumeAttachmentPlanInfo{}
   576  		if v.PlanInfo != nil {
   577  			planInfo.DeviceType = v.PlanInfo.DeviceType
   578  			planInfo.DeviceAttributes = v.PlanInfo.DeviceAttributes
   579  		} else {
   580  			planInfo = nil
   581  		}
   582  		out[i] = params.VolumeAttachment{
   583  			v.Volume.String(),
   584  			v.Machine.String(),
   585  			params.VolumeAttachmentInfo{
   586  				v.DeviceName,
   587  				v.DeviceLink,
   588  				v.BusAddress,
   589  				v.ReadOnly,
   590  				planInfo,
   591  			},
   592  		}
   593  	}
   594  	return out
   595  }
   596  
   597  func volumeFromParams(in params.Volume) (storage.Volume, error) {
   598  	volumeTag, err := names.ParseVolumeTag(in.VolumeTag)
   599  	if err != nil {
   600  		return storage.Volume{}, errors.Trace(err)
   601  	}
   602  	return storage.Volume{
   603  		volumeTag,
   604  		storage.VolumeInfo{
   605  			in.Info.VolumeId,
   606  			in.Info.HardwareId,
   607  			in.Info.WWN,
   608  			in.Info.Size,
   609  			in.Info.Persistent,
   610  		},
   611  	}, nil
   612  }
   613  
   614  func volumeParamsFromParams(in params.VolumeParams) (storage.VolumeParams, error) {
   615  	volumeTag, err := names.ParseVolumeTag(in.VolumeTag)
   616  	if err != nil {
   617  		return storage.VolumeParams{}, errors.Trace(err)
   618  	}
   619  	providerType := storage.ProviderType(in.Provider)
   620  
   621  	var attachment *storage.VolumeAttachmentParams
   622  	if in.Attachment != nil {
   623  		if in.Attachment.Provider != in.Provider {
   624  			return storage.VolumeParams{}, errors.Errorf(
   625  				"storage provider mismatch: volume (%q), attachment (%q)",
   626  				in.Provider, in.Attachment.Provider,
   627  			)
   628  		}
   629  		if in.Attachment.VolumeTag != in.VolumeTag {
   630  			return storage.VolumeParams{}, errors.Errorf(
   631  				"volume tag mismatch: volume (%q), attachment (%q)",
   632  				in.VolumeTag, in.Attachment.VolumeTag,
   633  			)
   634  		}
   635  		hostTag, err := names.ParseTag(in.Attachment.MachineTag)
   636  		if err != nil {
   637  			return storage.VolumeParams{}, errors.Annotate(
   638  				err, "parsing attachment machine tag",
   639  			)
   640  		}
   641  		attachment = &storage.VolumeAttachmentParams{
   642  			AttachmentParams: storage.AttachmentParams{
   643  				Provider:   providerType,
   644  				Machine:    hostTag,
   645  				InstanceId: instance.Id(in.Attachment.InstanceId),
   646  				ReadOnly:   in.Attachment.ReadOnly,
   647  			},
   648  			Volume: volumeTag,
   649  		}
   650  	}
   651  	return storage.VolumeParams{
   652  		volumeTag,
   653  		in.Size,
   654  		providerType,
   655  		in.Attributes,
   656  		in.Tags,
   657  		attachment,
   658  	}, nil
   659  }
   660  
   661  func volumeAttachmentParamsFromParams(in params.VolumeAttachmentParams) (storage.VolumeAttachmentParams, error) {
   662  	hostTag, err := names.ParseTag(in.MachineTag)
   663  	if err != nil {
   664  		return storage.VolumeAttachmentParams{}, errors.Trace(err)
   665  	}
   666  	volumeTag, err := names.ParseVolumeTag(in.VolumeTag)
   667  	if err != nil {
   668  		return storage.VolumeAttachmentParams{}, errors.Trace(err)
   669  	}
   670  	return storage.VolumeAttachmentParams{
   671  		AttachmentParams: storage.AttachmentParams{
   672  			Provider:   storage.ProviderType(in.Provider),
   673  			Machine:    hostTag,
   674  			InstanceId: instance.Id(in.InstanceId),
   675  			ReadOnly:   in.ReadOnly,
   676  		},
   677  		Volume:   volumeTag,
   678  		VolumeId: in.VolumeId,
   679  	}, nil
   680  }