// 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 }
// 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 }
// 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 }
// 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 }
// 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(), }, } }
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) }
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 }
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 }
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 }
// 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 }
// 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) }
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 }
func (st *mockState) StorageAttachment( storageTag names.StorageTag, unitTag names.UnitTag, ) (params.StorageAttachment, error) { if unitTag != st.unit.tag { return params.StorageAttachment{}, ¶ms.Error{Code: params.CodeNotFound} } attachment, ok := st.storageAttachment[params.StorageAttachmentId{ UnitTag: unitTag.String(), StorageTag: storageTag.String(), }] if !ok { return params.StorageAttachment{}, ¶ms.Error{Code: params.CodeNotFound} } if attachment.Kind == params.StorageKindUnknown { return params.StorageAttachment{}, ¶ms.Error{Code: params.CodeNotProvisioned} } return attachment, nil }
// 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) }
// 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 }
// 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 }
// 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 }
// 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 }
// 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) }
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) }
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()), ) }
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 }
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) }