github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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"
     9  
    10  	"github.com/juju/juju/apiserver/params"
    11  	"github.com/juju/juju/instance"
    12  	"github.com/juju/juju/storage"
    13  )
    14  
    15  // volumesChanged is called when the lifecycle states of the volumes
    16  // with the provided IDs have been seen to have changed.
    17  func volumesChanged(ctx *context, changes []string) error {
    18  	tags := make([]names.Tag, len(changes))
    19  	for i, change := range changes {
    20  		tags[i] = names.NewVolumeTag(change)
    21  	}
    22  	alive, dying, dead, err := storageEntityLife(ctx, tags)
    23  	if err != nil {
    24  		return errors.Trace(err)
    25  	}
    26  	logger.Debugf("volumes alive: %v, dying: %v, dead: %v", alive, dying, dead)
    27  	if err := processDyingVolumes(ctx, dying); err != nil {
    28  		return errors.Annotate(err, "processing dying volumes")
    29  	}
    30  	if len(alive)+len(dead) == 0 {
    31  		return nil
    32  	}
    33  
    34  	// Get volume information for alive and dead volumes, so
    35  	// we can provision/deprovision.
    36  	volumeTags := make([]names.VolumeTag, 0, len(alive)+len(dead))
    37  	for _, tag := range alive {
    38  		volumeTags = append(volumeTags, tag.(names.VolumeTag))
    39  	}
    40  	for _, tag := range dead {
    41  		volumeTags = append(volumeTags, tag.(names.VolumeTag))
    42  	}
    43  	volumeResults, err := ctx.volumeAccessor.Volumes(volumeTags)
    44  	if err != nil {
    45  		return errors.Annotatef(err, "getting volume information")
    46  	}
    47  	if err := processDeadVolumes(ctx, volumeTags[len(alive):], volumeResults[len(alive):]); err != nil {
    48  		return errors.Annotate(err, "deprovisioning volumes")
    49  	}
    50  	if err := processAliveVolumes(ctx, alive, volumeResults[:len(alive)]); err != nil {
    51  		return errors.Annotate(err, "provisioning volumes")
    52  	}
    53  	return nil
    54  }
    55  
    56  // volumeAttachmentsChanged is called when the lifecycle states of the volume
    57  // attachments with the provided IDs have been seen to have changed.
    58  func volumeAttachmentsChanged(ctx *context, ids []params.MachineStorageId) error {
    59  	alive, dying, dead, err := attachmentLife(ctx, ids)
    60  	if err != nil {
    61  		return errors.Trace(err)
    62  	}
    63  	logger.Debugf("volume attachments alive: %v, dying: %v, dead: %v", alive, dying, dead)
    64  	if len(dead) != 0 {
    65  		// We should not see dead volume attachments;
    66  		// attachments go directly from Dying to removed.
    67  		logger.Warningf("unexpected dead volume attachments: %v", dead)
    68  	}
    69  	if len(alive)+len(dying) == 0 {
    70  		return nil
    71  	}
    72  
    73  	// Get volume information for alive and dying volume attachments, so
    74  	// we can attach/detach.
    75  	ids = append(alive, dying...)
    76  	volumeAttachmentResults, err := ctx.volumeAccessor.VolumeAttachments(ids)
    77  	if err != nil {
    78  		return errors.Annotatef(err, "getting volume attachment information")
    79  	}
    80  
    81  	// Deprovision Dying volume attachments.
    82  	dyingVolumeAttachmentResults := volumeAttachmentResults[len(alive):]
    83  	if err := processDyingVolumeAttachments(ctx, dying, dyingVolumeAttachmentResults); err != nil {
    84  		return errors.Annotate(err, "deprovisioning volume attachments")
    85  	}
    86  
    87  	// Provision Alive volume attachments.
    88  	aliveVolumeAttachmentResults := volumeAttachmentResults[:len(alive)]
    89  	if err := processAliveVolumeAttachments(ctx, alive, aliveVolumeAttachmentResults); err != nil {
    90  		return errors.Annotate(err, "provisioning volumes")
    91  	}
    92  
    93  	return nil
    94  }
    95  
    96  // processDyingVolumes processes the VolumeResults for Dying volumes,
    97  // removing them from provisioning-pending as necessary.
    98  func processDyingVolumes(ctx *context, tags []names.Tag) error {
    99  	for _, tag := range tags {
   100  		removePendingVolume(ctx, tag.(names.VolumeTag))
   101  	}
   102  	return nil
   103  }
   104  
   105  // updateVolume updates the context with the given volume info.
   106  func updateVolume(ctx *context, info storage.Volume) {
   107  	ctx.volumes[info.Tag] = info
   108  	for id, params := range ctx.incompleteVolumeAttachmentParams {
   109  		if params.VolumeId == "" && id.AttachmentTag == info.Tag.String() {
   110  			params.VolumeId = info.VolumeId
   111  			updatePendingVolumeAttachment(ctx, id, params)
   112  		}
   113  	}
   114  }
   115  
   116  // updatePendingVolume adds the given volume params to either the incomplete
   117  // set or the schedule. If the params are incomplete due to a missing instance
   118  // ID, updatePendingVolume will request that the machine be watched so its
   119  // instance ID can be learned.
   120  func updatePendingVolume(ctx *context, params storage.VolumeParams) {
   121  	if params.Attachment.InstanceId == "" {
   122  		watchMachine(ctx, params.Attachment.Machine)
   123  		ctx.incompleteVolumeParams[params.Tag] = params
   124  	} else {
   125  		delete(ctx.incompleteVolumeParams, params.Tag)
   126  		scheduleOperations(ctx, &createVolumeOp{args: params})
   127  	}
   128  }
   129  
   130  // removePendingVolume removes the specified pending volume from the
   131  // incomplete set and/or the schedule if it exists there.
   132  func removePendingVolume(ctx *context, tag names.VolumeTag) {
   133  	delete(ctx.incompleteVolumeParams, tag)
   134  	ctx.schedule.Remove(tag)
   135  }
   136  
   137  // updatePendingVolumeAttachment adds the given volume attachment params to
   138  // either the incomplete set or the schedule. If the params are incomplete
   139  // due to a missing instance ID, updatePendingVolumeAttachment will request
   140  // that the machine be watched so its instance ID can be learned.
   141  func updatePendingVolumeAttachment(
   142  	ctx *context,
   143  	id params.MachineStorageId,
   144  	params storage.VolumeAttachmentParams,
   145  ) {
   146  	if params.InstanceId == "" {
   147  		watchMachine(ctx, params.Machine)
   148  	} else if params.VolumeId != "" {
   149  		delete(ctx.incompleteVolumeAttachmentParams, id)
   150  		scheduleOperations(ctx, &attachVolumeOp{args: params})
   151  		return
   152  	}
   153  	ctx.incompleteVolumeAttachmentParams[id] = params
   154  }
   155  
   156  // removePendingVolumeAttachment removes the specified pending volume
   157  // attachment from the incomplete set and/or the schedule if it exists
   158  // there.
   159  func removePendingVolumeAttachment(ctx *context, id params.MachineStorageId) {
   160  	delete(ctx.incompleteVolumeAttachmentParams, id)
   161  	ctx.schedule.Remove(id)
   162  }
   163  
   164  // processDeadVolumes processes the VolumeResults for Dead volumes,
   165  // deprovisioning volumes and removing from state as necessary.
   166  func processDeadVolumes(ctx *context, tags []names.VolumeTag, volumeResults []params.VolumeResult) error {
   167  	for _, tag := range tags {
   168  		removePendingVolume(ctx, tag)
   169  	}
   170  	var destroy []names.VolumeTag
   171  	var remove []names.Tag
   172  	for i, result := range volumeResults {
   173  		tag := tags[i]
   174  		if result.Error == nil {
   175  			logger.Debugf("volume %s is provisioned, queuing for deprovisioning", tag.Id())
   176  			volume, err := volumeFromParams(result.Result)
   177  			if err != nil {
   178  				return errors.Annotate(err, "getting volume info")
   179  			}
   180  			updateVolume(ctx, volume)
   181  			destroy = append(destroy, tag)
   182  			continue
   183  		}
   184  		if params.IsCodeNotProvisioned(result.Error) {
   185  			logger.Debugf("volume %s is not provisioned, queuing for removal", tag.Id())
   186  			remove = append(remove, tag)
   187  			continue
   188  		}
   189  		return errors.Annotatef(result.Error, "getting volume information for volume %s", tag.Id())
   190  	}
   191  	if len(destroy) > 0 {
   192  		ops := make([]scheduleOp, len(destroy))
   193  		for i, tag := range destroy {
   194  			ops[i] = &destroyVolumeOp{tag: tag}
   195  		}
   196  		scheduleOperations(ctx, ops...)
   197  	}
   198  	if err := removeEntities(ctx, remove); err != nil {
   199  		return errors.Annotate(err, "removing volumes from state")
   200  	}
   201  	return nil
   202  }
   203  
   204  // processDyingVolumeAttachments processes the VolumeAttachmentResults for
   205  // Dying volume attachments, detaching volumes and updating state as necessary.
   206  func processDyingVolumeAttachments(
   207  	ctx *context,
   208  	ids []params.MachineStorageId,
   209  	volumeAttachmentResults []params.VolumeAttachmentResult,
   210  ) error {
   211  	for _, id := range ids {
   212  		removePendingVolumeAttachment(ctx, id)
   213  	}
   214  	detach := make([]params.MachineStorageId, 0, len(ids))
   215  	remove := make([]params.MachineStorageId, 0, len(ids))
   216  	for i, result := range volumeAttachmentResults {
   217  		id := ids[i]
   218  		if result.Error == nil {
   219  			detach = append(detach, id)
   220  			continue
   221  		}
   222  		if params.IsCodeNotProvisioned(result.Error) {
   223  			remove = append(remove, id)
   224  			continue
   225  		}
   226  		return errors.Annotatef(result.Error, "getting information for volume attachment %v", id)
   227  	}
   228  	if len(detach) > 0 {
   229  		attachmentParams, err := volumeAttachmentParams(ctx, detach)
   230  		if err != nil {
   231  			return errors.Trace(err)
   232  		}
   233  		ops := make([]scheduleOp, len(attachmentParams))
   234  		for i, p := range attachmentParams {
   235  			ops[i] = &detachVolumeOp{args: p}
   236  		}
   237  		scheduleOperations(ctx, ops...)
   238  	}
   239  	if err := removeAttachments(ctx, remove); err != nil {
   240  		return errors.Annotate(err, "removing attachments from state")
   241  	}
   242  	for _, id := range remove {
   243  		delete(ctx.volumeAttachments, id)
   244  	}
   245  	return nil
   246  }
   247  
   248  // processAliveVolumes processes the VolumeResults for Alive volumes,
   249  // provisioning volumes and setting the info in state as necessary.
   250  func processAliveVolumes(ctx *context, tags []names.Tag, volumeResults []params.VolumeResult) error {
   251  	// Filter out the already-provisioned volumes.
   252  	pending := make([]names.VolumeTag, 0, len(tags))
   253  	for i, result := range volumeResults {
   254  		volumeTag := tags[i].(names.VolumeTag)
   255  		if result.Error == nil {
   256  			// Volume is already provisioned: skip.
   257  			logger.Debugf("volume %q is already provisioned, nothing to do", tags[i].Id())
   258  			volume, err := volumeFromParams(result.Result)
   259  			if err != nil {
   260  				return errors.Annotate(err, "getting volume info")
   261  			}
   262  			updateVolume(ctx, volume)
   263  			removePendingVolume(ctx, volumeTag)
   264  			continue
   265  		}
   266  		if !params.IsCodeNotProvisioned(result.Error) {
   267  			return errors.Annotatef(
   268  				result.Error, "getting volume information for volume %q", tags[i].Id(),
   269  			)
   270  		}
   271  		// The volume has not yet been provisioned, so record its tag
   272  		// to enquire about parameters below.
   273  		pending = append(pending, volumeTag)
   274  	}
   275  	if len(pending) == 0 {
   276  		return nil
   277  	}
   278  	volumeParams, err := volumeParams(ctx, pending)
   279  	if err != nil {
   280  		return errors.Annotate(err, "getting volume params")
   281  	}
   282  	for _, params := range volumeParams {
   283  		updatePendingVolume(ctx, params)
   284  	}
   285  	return nil
   286  }
   287  
   288  // processAliveVolumeAttachments processes the VolumeAttachmentResults
   289  // for Alive volume attachments, attaching volumes and setting the info
   290  // in state as necessary.
   291  func processAliveVolumeAttachments(
   292  	ctx *context,
   293  	ids []params.MachineStorageId,
   294  	volumeAttachmentResults []params.VolumeAttachmentResult,
   295  ) error {
   296  	// Filter out the already-attached.
   297  	pending := make([]params.MachineStorageId, 0, len(ids))
   298  	for i, result := range volumeAttachmentResults {
   299  		if result.Error == nil {
   300  			// Volume attachment is already provisioned: if we
   301  			// didn't (re)attach in this session, then we must
   302  			// do so now.
   303  			action := "nothing to do"
   304  			if _, ok := ctx.volumeAttachments[ids[i]]; !ok {
   305  				// Not yet (re)attached in this session.
   306  				pending = append(pending, ids[i])
   307  				action = "will reattach"
   308  			}
   309  			logger.Debugf(
   310  				"%s is already attached to %s, %s",
   311  				ids[i].AttachmentTag, ids[i].MachineTag, action,
   312  			)
   313  			removePendingVolumeAttachment(ctx, ids[i])
   314  			continue
   315  		}
   316  		if !params.IsCodeNotProvisioned(result.Error) {
   317  			return errors.Annotatef(
   318  				result.Error, "getting information for attachment %v", ids[i],
   319  			)
   320  		}
   321  		// The volume has not yet been provisioned, so record its tag
   322  		// to enquire about parameters below.
   323  		pending = append(pending, ids[i])
   324  	}
   325  	if len(pending) == 0 {
   326  		return nil
   327  	}
   328  	params, err := volumeAttachmentParams(ctx, pending)
   329  	if err != nil {
   330  		return errors.Trace(err)
   331  	}
   332  	for i, params := range params {
   333  		if volume, ok := ctx.volumes[params.Volume]; ok {
   334  			params.VolumeId = volume.VolumeId
   335  		}
   336  		updatePendingVolumeAttachment(ctx, pending[i], params)
   337  	}
   338  	return nil
   339  }
   340  
   341  // volumeAttachmentParams obtains the specified attachments' parameters.
   342  func volumeAttachmentParams(
   343  	ctx *context, ids []params.MachineStorageId,
   344  ) ([]storage.VolumeAttachmentParams, error) {
   345  	paramsResults, err := ctx.volumeAccessor.VolumeAttachmentParams(ids)
   346  	if err != nil {
   347  		return nil, errors.Annotate(err, "getting volume attachment params")
   348  	}
   349  	attachmentParams := make([]storage.VolumeAttachmentParams, len(ids))
   350  	for i, result := range paramsResults {
   351  		if result.Error != nil {
   352  			return nil, errors.Annotate(result.Error, "getting volume attachment parameters")
   353  		}
   354  		params, err := volumeAttachmentParamsFromParams(result.Result)
   355  		if err != nil {
   356  			return nil, errors.Annotate(err, "getting volume attachment parameters")
   357  		}
   358  		attachmentParams[i] = params
   359  	}
   360  	return attachmentParams, nil
   361  }
   362  
   363  // volumeParams obtains the specified volumes' parameters.
   364  func volumeParams(ctx *context, tags []names.VolumeTag) ([]storage.VolumeParams, error) {
   365  	paramsResults, err := ctx.volumeAccessor.VolumeParams(tags)
   366  	if err != nil {
   367  		return nil, errors.Annotate(err, "getting volume params")
   368  	}
   369  	allParams := make([]storage.VolumeParams, len(tags))
   370  	for i, result := range paramsResults {
   371  		if result.Error != nil {
   372  			return nil, errors.Annotate(result.Error, "getting volume parameters")
   373  		}
   374  		params, err := volumeParamsFromParams(result.Result)
   375  		if err != nil {
   376  			return nil, errors.Annotate(err, "getting volume parameters")
   377  		}
   378  		allParams[i] = params
   379  	}
   380  	return allParams, nil
   381  }
   382  
   383  func volumesFromStorage(in []storage.Volume) []params.Volume {
   384  	out := make([]params.Volume, len(in))
   385  	for i, v := range in {
   386  		out[i] = params.Volume{
   387  			v.Tag.String(),
   388  			params.VolumeInfo{
   389  				v.VolumeId,
   390  				v.HardwareId,
   391  				v.Size,
   392  				v.Persistent,
   393  			},
   394  		}
   395  	}
   396  	return out
   397  }
   398  
   399  func volumeAttachmentsFromStorage(in []storage.VolumeAttachment) []params.VolumeAttachment {
   400  	out := make([]params.VolumeAttachment, len(in))
   401  	for i, v := range in {
   402  		out[i] = params.VolumeAttachment{
   403  			v.Volume.String(),
   404  			v.Machine.String(),
   405  			params.VolumeAttachmentInfo{
   406  				v.DeviceName,
   407  				v.DeviceLink,
   408  				v.BusAddress,
   409  				v.ReadOnly,
   410  			},
   411  		}
   412  	}
   413  	return out
   414  }
   415  
   416  func volumeFromParams(in params.Volume) (storage.Volume, error) {
   417  	volumeTag, err := names.ParseVolumeTag(in.VolumeTag)
   418  	if err != nil {
   419  		return storage.Volume{}, errors.Trace(err)
   420  	}
   421  	return storage.Volume{
   422  		volumeTag,
   423  		storage.VolumeInfo{
   424  			in.Info.VolumeId,
   425  			in.Info.HardwareId,
   426  			in.Info.Size,
   427  			in.Info.Persistent,
   428  		},
   429  	}, nil
   430  }
   431  
   432  func volumeParamsFromParams(in params.VolumeParams) (storage.VolumeParams, error) {
   433  	volumeTag, err := names.ParseVolumeTag(in.VolumeTag)
   434  	if err != nil {
   435  		return storage.VolumeParams{}, errors.Trace(err)
   436  	}
   437  	providerType := storage.ProviderType(in.Provider)
   438  
   439  	var attachment *storage.VolumeAttachmentParams
   440  	if in.Attachment != nil {
   441  		if in.Attachment.Provider != in.Provider {
   442  			return storage.VolumeParams{}, errors.Errorf(
   443  				"storage provider mismatch: volume (%q), attachment (%q)",
   444  				in.Provider, in.Attachment.Provider,
   445  			)
   446  		}
   447  		if in.Attachment.VolumeTag != in.VolumeTag {
   448  			return storage.VolumeParams{}, errors.Errorf(
   449  				"volume tag mismatch: volume (%q), attachment (%q)",
   450  				in.VolumeTag, in.Attachment.VolumeTag,
   451  			)
   452  		}
   453  		machineTag, err := names.ParseMachineTag(in.Attachment.MachineTag)
   454  		if err != nil {
   455  			return storage.VolumeParams{}, errors.Annotate(
   456  				err, "parsing attachment machine tag",
   457  			)
   458  		}
   459  		attachment = &storage.VolumeAttachmentParams{
   460  			AttachmentParams: storage.AttachmentParams{
   461  				Provider:   providerType,
   462  				Machine:    machineTag,
   463  				InstanceId: instance.Id(in.Attachment.InstanceId),
   464  				ReadOnly:   in.Attachment.ReadOnly,
   465  			},
   466  			Volume: volumeTag,
   467  		}
   468  	}
   469  	return storage.VolumeParams{
   470  		volumeTag,
   471  		in.Size,
   472  		providerType,
   473  		in.Attributes,
   474  		in.Tags,
   475  		attachment,
   476  	}, nil
   477  }
   478  
   479  func volumeAttachmentParamsFromParams(in params.VolumeAttachmentParams) (storage.VolumeAttachmentParams, error) {
   480  	machineTag, err := names.ParseMachineTag(in.MachineTag)
   481  	if err != nil {
   482  		return storage.VolumeAttachmentParams{}, errors.Trace(err)
   483  	}
   484  	volumeTag, err := names.ParseVolumeTag(in.VolumeTag)
   485  	if err != nil {
   486  		return storage.VolumeAttachmentParams{}, errors.Trace(err)
   487  	}
   488  	return storage.VolumeAttachmentParams{
   489  		AttachmentParams: storage.AttachmentParams{
   490  			Provider:   storage.ProviderType(in.Provider),
   491  			Machine:    machineTag,
   492  			InstanceId: instance.Id(in.InstanceId),
   493  			ReadOnly:   in.ReadOnly,
   494  		},
   495  		Volume:   volumeTag,
   496  		VolumeId: in.VolumeId,
   497  	}, nil
   498  }