Esempio n. 1
0
// filesystemMountPoint returns a mount point to use for the given charm
// storage. For stores with potentially multiple instances, the instance
// name is appended to the location.
func filesystemMountPoint(
	meta charm.Storage,
	tag names.StorageTag,
	series string,
) (string, error) {
	storageDir, err := paths.StorageDir(series)
	if err != nil {
		return "", errors.Trace(err)
	}
	if strings.HasPrefix(meta.Location, storageDir) {
		return "", errors.Errorf(
			"invalid location %q: must not fall within %q",
			meta.Location, storageDir,
		)
	}
	if meta.Location != "" && meta.CountMax == 1 {
		// The location is specified and it's a singleton
		// store, so just use the location as-is.
		return meta.Location, nil
	}
	// If the location is unspecified then we use
	// <storage-dir>/<storage-id> as the location.
	// Otherwise, we use <location>/<storage-id>.
	if meta.Location != "" {
		storageDir = meta.Location
	}
	return path.Join(storageDir, tag.Id()), nil
}
Esempio n. 2
0
// add creates a new storager for the specified storage tag.
func (a *Attachments) add(storageTag names.StorageTag, stateFile *stateFile) error {
	a.storageAttachments[storageTag] = storageAttachment{
		stateFile: stateFile,
	}
	logger.Debugf("adding storage %q", storageTag.Id())
	return nil
}
Esempio n. 3
0
// StorageAttachments returns the StorageAttachments for the specified storage
// instance.
func (st *State) StorageAttachments(storage names.StorageTag) ([]StorageAttachment, error) {
	query := bson.D{{"storageid", storage.Id()}}
	attachments, err := st.storageAttachments(query)
	if err != nil {
		return nil, errors.Annotatef(err, "cannot get storage attachments for storage %s", storage.Id())
	}
	return attachments, nil
}
Esempio n. 4
0
// add creates a new storager for the specified storage tag.
func (a *Attachments) add(storageTag names.StorageTag, stateFile *stateFile) error {
	s, err := newStorager(a.st, a.unitTag, storageTag, stateFile, a.hooks)
	if err != nil {
		return errors.Annotatef(err, "watching storage %q", storageTag.Id())
	}
	a.storagers[storageTag] = s
	logger.Debugf("watching storage %q", storageTag.Id())
	return nil
}
Esempio n. 5
0
// createStorageAttachmentOps returns a txn.Op for creating a storage attachment.
// The caller is responsible for updating the attachmentcount field of the storage
// instance.
func createStorageAttachmentOp(storage names.StorageTag, unit names.UnitTag) txn.Op {
	return txn.Op{
		C:      storageAttachmentsC,
		Id:     storageAttachmentId(unit.Id(), storage.Id()),
		Assert: txn.DocMissing,
		Insert: &storageAttachmentDoc{
			Unit:            unit.Id(),
			StorageInstance: storage.Id(),
		},
	}
}
Esempio n. 6
0
func (s *storageResolver) nextHookOp(
	tag names.StorageTag,
	snap remotestate.StorageSnapshot,
	opFactory operation.Factory,
) (operation.Operation, error) {
	logger.Debugf("next hook op for %v: %+v", tag, snap)
	if !snap.Attached {
		return nil, resolver.ErrNoOperation
	}
	storageAttachment, ok := s.storage.storageAttachments[tag]
	if !ok {
		return nil, resolver.ErrNoOperation
	}
	switch snap.Life {
	case params.Alive:
		if storageAttachment.attached {
			// Storage attachments currently do not change
			// (apart from lifecycle) after being provisioned.
			// We don't process unprovisioned storage here,
			// so there's nothing to do.
			return nil, resolver.ErrNoOperation
		}
	case params.Dying:
		if !storageAttachment.attached {
			// Nothing to do: attachment is dying, but
			// the storage-attached hook has not been
			// consumed.
			return nil, resolver.ErrNoOperation
		}
	case params.Dead:
		// Storage must have been Dying to become Dead;
		// no further action is required.
		return nil, resolver.ErrNoOperation
	}

	hookInfo := hook.Info{
		StorageId: tag.Id(),
	}
	if snap.Life == params.Alive {
		hookInfo.Kind = hooks.StorageAttached
	} else {
		hookInfo.Kind = hooks.StorageDetaching
	}
	context := &contextStorage{
		tag:      tag,
		kind:     storage.StorageKind(snap.Kind),
		location: snap.Location,
	}
	storageAttachment.ContextStorageAttachment = context
	s.storage.storageAttachments[tag] = storageAttachment

	logger.Debugf("queued hook: %v", hookInfo)
	return opFactory.NewRunHook(hookInfo)
}
Esempio n. 7
0
func (s *StorageStateSuite) storageInstanceExists(c *gc.C, tag names.StorageTag) bool {
	_, err := state.TxnRevno(
		s.State,
		state.StorageInstancesC,
		state.DocID(s.State, tag.Id()),
	)
	if err != nil {
		c.Assert(err, gc.Equals, mgo.ErrNotFound)
		return false
	}
	return true
}
Esempio n. 8
0
func (st *State) storageAttachment(storage names.StorageTag, unit names.UnitTag) (*storageAttachment, error) {
	coll, closer := st.getCollection(storageAttachmentsC)
	defer closer()
	var s storageAttachment
	err := coll.FindId(storageAttachmentId(unit.Id(), storage.Id())).One(&s.doc)
	if err == mgo.ErrNotFound {
		return nil, errors.NotFoundf("storage attachment %s:%s", storage.Id(), unit.Id())
	} else if err != nil {
		return nil, errors.Annotatef(err, "cannot get storage attachment %s:%s", storage.Id(), unit.Id())
	}
	return &s, nil
}
Esempio n. 9
0
func (st *State) storageInstance(tag names.StorageTag) (*storageInstance, error) {
	storageInstances, cleanup := st.getCollection(storageInstancesC)
	defer cleanup()

	s := storageInstance{st: st}
	err := storageInstances.FindId(tag.Id()).One(&s.doc)
	if err == mgo.ErrNotFound {
		return nil, errors.NotFoundf("storage instance %q", tag.Id())
	} else if err != nil {
		return nil, errors.Annotate(err, "cannot get storage instance details")
	}
	return &s, nil
}
Esempio n. 10
0
// removeStorageInstanceOps removes the storage instance with the given
// tag from state, if the specified assertions hold true.
func removeStorageInstanceOps(
	st *State,
	tag names.StorageTag,
	assert bson.D,
) ([]txn.Op, error) {
	ops := []txn.Op{{
		C:      storageInstancesC,
		Id:     tag.Id(),
		Assert: assert,
		Remove: true,
	}}

	machineStorageOp := func(c string, id string) txn.Op {
		return txn.Op{
			C:      c,
			Id:     id,
			Assert: bson.D{{"storageid", tag.Id()}},
			Update: bson.D{{"$set", bson.D{{"storageid", ""}}}},
		}
	}

	// If the storage instance has an assigned volume and/or filesystem,
	// unassign them. Any volumes and filesystems bound to the storage
	// will be destroyed.
	volume, err := st.storageInstanceVolume(tag)
	if err == nil {
		ops = append(ops, machineStorageOp(
			volumesC, volume.Tag().Id(),
		))
		if volume.LifeBinding() == tag {
			ops = append(ops, destroyVolumeOps(st, volume)...)
		}
	} else if !errors.IsNotFound(err) {
		return nil, errors.Trace(err)
	}
	filesystem, err := st.storageInstanceFilesystem(tag)
	if err == nil {
		ops = append(ops, machineStorageOp(
			filesystemsC, filesystem.Tag().Id(),
		))
		if filesystem.LifeBinding() == tag {
			ops = append(ops, destroyFilesystemOps(st, filesystem)...)
		}
	} else if !errors.IsNotFound(err) {
		return nil, errors.Trace(err)
	}
	return ops, nil
}
Esempio n. 11
0
// Remove removes the storage attachment from state, and may remove its storage
// instance as well, if the storage instance is Dying and no other references to
// it exist. It will fail if the storage attachment is not Dead.
func (st *State) RemoveStorageAttachment(storage names.StorageTag, unit names.UnitTag) (err error) {
	defer errors.DeferredAnnotatef(&err, "cannot remove storage attachment %s:%s", storage.Id(), unit.Id())
	buildTxn := func(attempt int) ([]txn.Op, error) {
		s, err := st.storageAttachment(storage, unit)
		if errors.IsNotFound(err) {
			return nil, jujutxn.ErrNoOperations
		} else if err != nil {
			return nil, errors.Trace(err)
		}
		inst, err := st.storageInstance(storage)
		if errors.IsNotFound(err) {
			// This implies that the attachment was removed
			// after the call to st.storageAttachment.
			return nil, jujutxn.ErrNoOperations
		} else if err != nil {
			return nil, errors.Trace(err)
		}
		ops, err := removeStorageAttachmentOps(st, s, inst)
		if err != nil {
			return nil, errors.Trace(err)
		}
		return ops, nil
	}
	return st.run(buildTxn)
}
Esempio n. 12
0
func destroyStorageAttachmentOps(storage names.StorageTag, unit names.UnitTag) []txn.Op {
	ops := []txn.Op{{
		C:      storageAttachmentsC,
		Id:     storageAttachmentId(unit.Id(), storage.Id()),
		Assert: isAliveDoc,
		Update: bson.D{{"$set", bson.D{{"life", Dying}}}},
	}}
	return ops
}
Esempio n. 13
0
func (st *mockState) StorageAttachment(
	storageTag names.StorageTag, unitTag names.UnitTag,
) (params.StorageAttachment, error) {
	if unitTag != st.unit.tag {
		return params.StorageAttachment{}, &params.Error{Code: params.CodeNotFound}
	}
	attachment, ok := st.storageAttachment[params.StorageAttachmentId{
		UnitTag:    unitTag.String(),
		StorageTag: storageTag.String(),
	}]
	if !ok {
		return params.StorageAttachment{}, &params.Error{Code: params.CodeNotFound}
	}
	if attachment.Kind == params.StorageKindUnknown {
		return params.StorageAttachment{}, &params.Error{Code: params.CodeNotProvisioned}
	}
	return attachment, nil
}
Esempio n. 14
0
// DestroyStorageInstance ensures that the storage instance and all its
// attachments will be removed at some point; if the storage instance has
// no attachments, it will be removed immediately.
func (st *State) DestroyStorageInstance(tag names.StorageTag) (err error) {
	defer errors.DeferredAnnotatef(&err, "cannot destroy storage %q", tag.Id())
	buildTxn := func(attempt int) ([]txn.Op, error) {
		s, err := st.storageInstance(tag)
		if errors.IsNotFound(err) {
			return nil, jujutxn.ErrNoOperations
		} else if err != nil {
			return nil, errors.Trace(err)
		}
		switch ops, err := st.destroyStorageInstanceOps(s); err {
		case errAlreadyDying:
			return nil, jujutxn.ErrNoOperations
		case nil:
			return ops, nil
		default:
			return nil, errors.Trace(err)
		}
	}
	return st.run(buildTxn)
}
Esempio n. 15
0
// RemoveStorageAttachment removes the storage attachment with the
// specified unit and storage tags from state. This method is only
// expected to succeed if the storage attachment is Dead.
func (sa *StorageAccessor) RemoveStorageAttachment(storageTag names.StorageTag, unitTag names.UnitTag) error {
	var results params.ErrorResults
	args := params.StorageAttachmentIds{
		Ids: []params.StorageAttachmentId{{
			StorageTag: storageTag.String(),
			UnitTag:    unitTag.String(),
		}},
	}
	err := sa.facade.FacadeCall("RemoveStorageAttachments", args, &results)
	if err != nil {
		return err
	}
	if len(results.Results) != 1 {
		return errors.Errorf("expected 1 result, got %d", len(results.Results))
	}
	result := results.Results[0]
	if result.Error != nil {
		return result.Error
	}
	return nil
}
Esempio n. 16
0
// WatchStorageAttachments starts a watcher for changes to the info
// of the storage attachment with the specified unit and storage tags.
func (sa *StorageAccessor) WatchStorageAttachment(storageTag names.StorageTag, unitTag names.UnitTag) (watcher.NotifyWatcher, error) {
	var results params.NotifyWatchResults
	args := params.StorageAttachmentIds{
		Ids: []params.StorageAttachmentId{{
			StorageTag: storageTag.String(),
			UnitTag:    unitTag.String(),
		}},
	}
	err := sa.facade.FacadeCall("WatchStorageAttachments", args, &results)
	if err != nil {
		return nil, err
	}
	if len(results.Results) != 1 {
		return nil, errors.Errorf("expected 1 result, got %d", len(results.Results))
	}
	result := results.Results[0]
	if result.Error != nil {
		return nil, result.Error
	}
	w := apiwatcher.NewNotifyWatcher(sa.facade.RawAPICaller(), result)
	return w, nil
}
Esempio n. 17
0
// readStateFile loads a stateFile from the subdirectory of dirPath named
// for the supplied storage tag. If the directory does not exist, no error
// is returned.
func readStateFile(dirPath string, tag names.StorageTag) (d *stateFile, err error) {
	filename := strings.Replace(tag.Id(), "/", "-", -1)
	d = &stateFile{
		filepath.Join(dirPath, filename),
		state{storage: tag},
	}
	defer errors.DeferredAnnotatef(&err, "cannot load storage %q state from %q", tag.Id(), d.path)
	if _, err := os.Stat(d.path); os.IsNotExist(err) {
		return d, nil
	} else if err != nil {
		return nil, err
	}
	var info diskInfo
	if err := utils.ReadYaml(d.path, &info); err != nil {
		return nil, errors.Errorf("invalid storage state file %q: %v", d.path, err)
	}
	if info.Attached == nil {
		return nil, errors.Errorf("invalid storage state file %q: missing 'attached'", d.path)
	}
	d.state.attached = *info.Attached
	return d, nil
}
Esempio n. 18
0
// StorageAttachment returns the storage attachment with the specified
// unit and storage tags.
func (sa *StorageAccessor) StorageAttachment(storageTag names.StorageTag, unitTag names.UnitTag) (params.StorageAttachment, error) {
	if sa.facade.BestAPIVersion() < 2 {
		return params.StorageAttachment{}, errors.NotImplementedf("StorageAttachment() (need V2+)")
	}
	args := params.StorageAttachmentIds{
		Ids: []params.StorageAttachmentId{{
			StorageTag: storageTag.String(),
			UnitTag:    unitTag.String(),
		}},
	}
	var results params.StorageAttachmentResults
	err := sa.facade.FacadeCall("StorageAttachments", args, &results)
	if err != nil {
		return params.StorageAttachment{}, errors.Trace(err)
	}
	if len(results.Results) != 1 {
		panic(errors.Errorf("expected 1 result, got %d", len(results.Results)))
	}
	result := results.Results[0]
	if result.Error != nil {
		return params.StorageAttachment{}, result.Error
	}
	return result.Result, nil
}
Esempio n. 19
0
// DestroyStorageAttachment ensures that the storage attachment will be
// removed at some point.
func (st *State) DestroyStorageAttachment(storage names.StorageTag, unit names.UnitTag) (err error) {
	defer errors.DeferredAnnotatef(&err, "cannot destroy storage attachment %s:%s", storage.Id(), unit.Id())
	buildTxn := func(attempt int) ([]txn.Op, error) {
		s, err := st.storageAttachment(storage, unit)
		if errors.IsNotFound(err) {
			return nil, jujutxn.ErrNoOperations
		} else if err != nil {
			return nil, errors.Trace(err)
		}
		if s.doc.Life == Dying {
			return nil, jujutxn.ErrNoOperations
		}
		return destroyStorageAttachmentOps(storage, unit), nil
	}
	return st.run(buildTxn)
}
Esempio n. 20
0
func (s *storageResolver) nextHookOp(
	tag names.StorageTag,
	snap remotestate.StorageSnapshot,
	opFactory operation.Factory,
) (operation.Operation, error) {

	logger.Debugf("next hook op for %v: %+v", tag, snap)

	if snap.Life == params.Dead {
		// Storage must have been Dying to become Dead;
		// no further action is required.
		return nil, resolver.ErrNoOperation
	}

	hookInfo := hook.Info{StorageId: tag.Id()}
	switch snap.Life {
	case params.Alive:
		storageAttachment, ok := s.storage.storageAttachments[tag]
		if ok && storageAttachment.attached {
			// Once the storage is attached, we only care about
			// lifecycle state changes.
			return nil, resolver.ErrNoOperation
		}
		// The storage-attached hook has not been committed, so add the
		// storage to the pending set.
		s.storage.pending.Add(tag)
		if !snap.Attached {
			// The storage attachment has not been provisioned yet,
			// so just ignore it for now. We'll be notified again
			// when it has been provisioned.
			return nil, resolver.ErrNoOperation
		}
		// The storage is alive, but we haven't previously run the
		// "storage-attached" hook. Do so now.
		hookInfo.Kind = hooks.StorageAttached
	case params.Dying:
		storageAttachment, ok := s.storage.storageAttachments[tag]
		if !ok || !storageAttachment.attached {
			// Nothing to do: attachment is dying, but
			// the storage-attached hook has not been
			// issued.
			return nil, resolver.ErrNoOperation
		}
		// The storage is dying, but we haven't previously run the
		// "storage-detached" hook. Do so now.
		hookInfo.Kind = hooks.StorageDetaching
	}

	// Update the local state to reflect what we're about to report
	// to a hook.
	stateFile, err := readStateFile(s.storage.storageStateDir, tag)
	if err != nil {
		return nil, errors.Trace(err)
	}
	s.storage.storageAttachments[tag] = storageAttachment{
		stateFile, &contextStorage{
			tag:      tag,
			kind:     storage.StorageKind(snap.Kind),
			location: snap.Location,
		},
	}

	return opFactory.NewRunHook(hookInfo)
}
Esempio n. 21
0
func (st *State) storageInstanceVolume(tag names.StorageTag) (*volume, error) {
	return st.volume(
		bson.D{{"storageid", tag.Id()}},
		fmt.Sprintf("volume for storage instance %q", tag.Id()),
	)
}
Esempio n. 22
0
func (api *API) getStorageAttachments(
	storageTag names.StorageTag,
	instance params.StorageDetails,
) ([]params.StorageDetails, *params.Error) {
	serverError := func(err error) *params.Error {
		return common.ServerError(errors.Annotatef(err, "getting attachments for storage %v", storageTag.Id()))
	}
	stateAttachments, err := api.storage.StorageAttachments(storageTag)
	if err != nil {
		return nil, serverError(err)
	}
	result := make([]params.StorageDetails, len(stateAttachments))
	for i, one := range stateAttachments {
		paramsStorageAttachment, err := api.createParamsStorageAttachment(instance, one)
		if err != nil {
			return nil, serverError(err)
		}
		result[i] = paramsStorageAttachment
	}
	return result, nil
}
Esempio n. 23
0
func (st *State) storageInstanceFilesystem(tag names.StorageTag) (*filesystem, error) {
	query := bson.D{{"storageid", tag.Id()}}
	description := fmt.Sprintf("filesystem for storage instance %q", tag.Id())
	return st.filesystem(query, description)
}