func getVolumeBinding(st *State, volume Volume) (string, error) { // first filesystem fs, err := st.VolumeFilesystem(volume.VolumeTag()) if err == nil { return fs.FilesystemTag().String(), nil } else if !errors.IsNotFound(err) { return "", errors.Trace(err) } // then Volume.StorageInstance storageInstance, err := volume.StorageInstance() if err == nil { return storageInstance.String(), nil } else if !errors.IsNotAssigned(err) { return "", errors.Trace(err) } // then machine atts, err := st.VolumeAttachments(volume.VolumeTag()) if err != nil { return "", errors.Trace(err) } if len(atts) == 1 { return atts[0].Machine().String(), nil } return "", nil }
// ServerError returns an error suitable for returning to an API // client, with an error code suitable for various kinds of errors // generated in packages outside the API. func ServerError(err error) *params.Error { if err == nil { return nil } logger.Tracef("server RPC error %v", errors.Details(err)) msg := err.Error() // Skip past annotations when looking for the code. err = errors.Cause(err) code, ok := singletonCode(err) var info *params.ErrorInfo switch { case ok: case errors.IsUnauthorized(err): code = params.CodeUnauthorized case errors.IsNotFound(err): code = params.CodeNotFound case errors.IsUserNotFound(err): code = params.CodeUserNotFound case errors.IsAlreadyExists(err): code = params.CodeAlreadyExists case errors.IsNotAssigned(err): code = params.CodeNotAssigned case state.IsHasAssignedUnitsError(err): code = params.CodeHasAssignedUnits case state.IsHasHostedModelsError(err): code = params.CodeHasHostedModels case isNoAddressSetError(err): code = params.CodeNoAddressSet case errors.IsNotProvisioned(err): code = params.CodeNotProvisioned case IsUpgradeInProgressError(err): code = params.CodeUpgradeInProgress case state.IsHasAttachmentsError(err): code = params.CodeMachineHasAttachedStorage case isUnknownModelError(err): code = params.CodeModelNotFound case errors.IsNotSupported(err): code = params.CodeNotSupported case errors.IsBadRequest(err): code = params.CodeBadRequest case errors.IsMethodNotAllowed(err): code = params.CodeMethodNotAllowed default: if err, ok := err.(*DischargeRequiredError); ok { code = params.CodeDischargeRequired info = ¶ms.ErrorInfo{ Macaroon: err.Macaroon, // One macaroon fits all. MacaroonPath: "/", } break } code = params.ErrCode(err) } return ¶ms.Error{ Message: msg, Code: code, Info: info, } }
// ServiceInstances returns the instance IDs of provisioned // machines that are assigned units of the specified service. func ServiceInstances(st *State, service string) ([]instance.Id, error) { units, err := allUnits(st, service) if err != nil { return nil, err } instanceIds := make([]instance.Id, 0, len(units)) for _, unit := range units { machineId, err := unit.AssignedMachineId() if errors.IsNotAssigned(err) { continue } else if err != nil { return nil, err } machine, err := st.Machine(machineId) if err != nil { return nil, err } instanceId, err := machine.InstanceId() if err == nil { instanceIds = append(instanceIds, instanceId) } else if errors.IsNotProvisioned(err) { continue } else { return nil, err } } return instanceIds, nil }
// unitAssignedMachineStorageOps returns ops for creating volumes, filesystems // and their attachments to the machine that the specified unit is assigned to, // corresponding to the specified storage instance. // // If the unit is not assigned to a machine, then ops will be returned to assert // this, and no error will be returned. func unitAssignedMachineStorageOps( st *State, entity names.Tag, charmMeta *charm.Meta, cons map[string]StorageConstraints, series string, storage StorageInstance, ) (ops []txn.Op, err error) { tag, ok := entity.(names.UnitTag) if !ok { return nil, errors.NotSupportedf("dynamic creation of shared storage") } storageParams, err := machineStorageParamsForStorageInstance( st, charmMeta, tag, series, cons, storage, ) if err != nil { return nil, errors.Trace(err) } u, err := st.Unit(tag.Id()) if err != nil { return nil, errors.Trace(err) } m, err := u.machine() if err != nil { if errors.IsNotAssigned(err) { // The unit is not assigned to a machine; return // txn.Op that ensures that this remains the case // until the transaction is committed. return []txn.Op{{ C: unitsC, Id: u.doc.DocID, Assert: bson.D{{"machineid", ""}}, }}, nil } return nil, errors.Trace(err) } if err := validateDynamicMachineStorageParams(m, storageParams); err != nil { return nil, errors.Trace(err) } storageOps, volumeAttachments, filesystemAttachments, err := st.machineStorageOps( &m.doc, storageParams, ) if err != nil { return nil, errors.Trace(err) } attachmentOps, err := addMachineStorageAttachmentsOps( m, volumeAttachments, filesystemAttachments, ) if err != nil { return nil, errors.Trace(err) } storageOps = append(storageOps, attachmentOps...) return storageOps, nil }
// MaybeAssignedStorageInstance calls the provided function to get a // StorageTag, and returns the corresponding state.StorageInstance if // it didn't return an errors.IsNotAssigned error, or nil if it did. func MaybeAssignedStorageInstance( getTag func() (names.StorageTag, error), getStorageInstance func(names.StorageTag) (state.StorageInstance, error), ) (state.StorageInstance, error) { tag, err := getTag() if err == nil { return getStorageInstance(tag) } else if errors.IsNotAssigned(err) { return nil, nil } return nil, errors.Trace(err) }
func storageAttachmentInfo(st storageAccess, a state.StorageAttachment) (_ names.MachineTag, location string, _ error) { machineTag, err := st.UnitAssignedMachine(a.Unit()) if errors.IsNotAssigned(err) { return names.MachineTag{}, "", nil } else if err != nil { return names.MachineTag{}, "", errors.Trace(err) } info, err := storagecommon.StorageAttachmentInfo(st, a, machineTag) if errors.IsNotProvisioned(err) { return machineTag, "", nil } else if err != nil { return names.MachineTag{}, "", errors.Trace(err) } return machineTag, info.Location, nil }
func getFilesystemBinding(st *State, filesystem Filesystem) (string, error) { storage, err := filesystem.Storage() if err == nil { return storage.String(), nil } else if !errors.IsNotAssigned(err) { return "", errors.Trace(err) } atts, err := st.FilesystemAttachments(filesystem.FilesystemTag()) if err != nil { return "", errors.Trace(err) } if len(atts) == 1 { return atts[0].Machine().String(), nil } return "", nil }
// unitAssignedMachineStorageOps returns ops for creating volumes, filesystems // and their attachments to the machine that the specified unit is assigned to, // corresponding to the specified storage instance. // // If the unit is not assigned to a machine, then ops will be returned to assert // this, and no error will be returned. func unitAssignedMachineStorageOps( st *State, unitTag names.UnitTag, charmMeta *charm.Meta, cons map[string]StorageConstraints, series string, storage StorageInstance, machineAssignable machineAssignable, ) (ops []txn.Op, err error) { storageParams, err := machineStorageParamsForStorageInstance( st, charmMeta, unitTag, series, cons, storage, ) if err != nil { return nil, errors.Trace(err) } m, err := machineAssignable.machine() if err != nil { if errors.IsNotAssigned(err) { // The unit is not assigned to a machine; return // txn.Op that ensures that this remains the case // until the transaction is committed. return []txn.Op{machineAssignable.noAssignedMachineOp()}, nil } return nil, errors.Trace(err) } if err := validateDynamicMachineStorageParams(m, storageParams); err != nil { return nil, errors.Trace(err) } storageOps, volumeAttachments, filesystemAttachments, err := st.machineStorageOps( &m.doc, storageParams, ) if err != nil { return nil, errors.Trace(err) } attachmentOps, err := addMachineStorageAttachmentsOps( m, volumeAttachments, filesystemAttachments, ) if err != nil { return nil, errors.Trace(err) } storageOps = append(storageOps, attachmentOps...) return storageOps, nil }
// ServerError returns an error suitable for returning to an API // client, with an error code suitable for various kinds of errors // generated in packages outside the API. func ServerError(err error) *params.Error { if err == nil { return nil } msg := err.Error() // Skip past annotations when looking for the code. err = errors.Cause(err) code, ok := singletonCode(err) switch { case ok: case errors.IsUnauthorized(err): code = params.CodeUnauthorized case errors.IsNotFound(err): code = params.CodeNotFound case errors.IsAlreadyExists(err): code = params.CodeAlreadyExists case errors.IsNotAssigned(err): code = params.CodeNotAssigned case state.IsHasAssignedUnitsError(err): code = params.CodeHasAssignedUnits case IsNoAddressSetError(err): code = params.CodeNoAddressSet case errors.IsNotProvisioned(err): code = params.CodeNotProvisioned case state.IsUpgradeInProgressError(err): code = params.CodeUpgradeInProgress case state.IsHasAttachmentsError(err): code = params.CodeMachineHasAttachedStorage case IsUnknownEnviromentError(err): code = params.CodeNotFound case errors.IsNotSupported(err): code = params.CodeNotSupported default: code = params.ErrCode(err) } return ¶ms.Error{ Message: msg, Code: code, } }
func getUnitPortRangesAndPorts(st *State, unitName string) ([]network.PortRange, []network.Port, error) { // Get opened port ranges for the unit and convert them to ports, // as older clients/servers do not know about ranges). See bug // http://pad.lv/1418344 for more info. unit, err := st.Unit(unitName) if errors.IsNotFound(err) { // Empty slices ensure backwards compatibility with older clients. // See Bug #1425435. return []network.PortRange{}, []network.Port{}, nil } else if err != nil { return nil, nil, errors.Annotatef(err, "failed to get unit %q", unitName) } portRanges, err := unit.OpenedPorts() // Since the port ranges are associated with the unit's machine, // we need to check for NotAssignedError. if errors.IsNotAssigned(err) { // Not assigned, so there won't be any ports opened. // Empty slices ensure backwards compatibility with older clients. // See Bug #1425435. return []network.PortRange{}, []network.Port{}, nil } else if err != nil { return nil, nil, errors.Annotate(err, "failed to get unit port ranges") } // For backward compatibility, if there are no ports opened, return an // empty slice rather than a nil slice. Use a len(portRanges) capacity to // avoid unnecessary allocations, since most of the times only specific // ports are opened by charms. compatiblePorts := make([]network.Port, 0, len(portRanges)) for _, portRange := range portRanges { for j := portRange.FromPort; j <= portRange.ToPort; j++ { compatiblePorts = append(compatiblePorts, network.Port{ Number: j, Protocol: portRange.Protocol, }) } } return portRanges, compatiblePorts, nil }
// validateFilesystemMountPoints validates the mount points of filesystems // being attached to the specified machine. If there are any mount point // path conflicts, an error will be returned. func validateFilesystemMountPoints(m *Machine, newFilesystems []filesystemAttachmentTemplate) error { attachments, err := m.st.MachineFilesystemAttachments(m.MachineTag()) if err != nil { return errors.Trace(err) } existing := make(map[names.FilesystemTag]string) for _, a := range attachments { params, ok := a.Params() if ok { existing[a.Filesystem()] = params.Location continue } info, err := a.Info() if err != nil { return errors.Trace(err) } existing[a.Filesystem()] = info.MountPoint } storageName := func( filesystemTag names.FilesystemTag, storageTag names.StorageTag, ) string { if storageTag == (names.StorageTag{}) { return names.ReadableString(filesystemTag) } // We know the tag is valid, so ignore the error. storageName, _ := names.StorageName(storageTag.Id()) return fmt.Sprintf("%q storage", storageName) } containsPath := func(a, b string) bool { a = path.Clean(a) + "/" b = path.Clean(b) + "/" return strings.HasPrefix(b, a) } // These sets are expected to be small, so sorting and comparing // adjacent values is not worth the cost of creating a reverse // lookup from location to filesystem. for _, template := range newFilesystems { newMountPoint := template.params.Location for oldFilesystemTag, oldMountPoint := range existing { var conflicted, swapOrder bool if containsPath(oldMountPoint, newMountPoint) { conflicted = true } else if containsPath(newMountPoint, oldMountPoint) { conflicted = true swapOrder = true } if !conflicted { continue } // Get a helpful identifier for the new filesystem. If it // is being created for a storage instance, then use // the storage name; otherwise use the filesystem name. newStorageName := storageName(template.tag, template.storage) // Likewise for the old filesystem, but this time we'll // need to consult state. oldFilesystem, err := m.st.Filesystem(oldFilesystemTag) if err != nil { return errors.Trace(err) } storageTag, err := oldFilesystem.Storage() if errors.IsNotAssigned(err) { storageTag = names.StorageTag{} } else if err != nil { return errors.Trace(err) } oldStorageName := storageName(oldFilesystemTag, storageTag) lhs := fmt.Sprintf("mount point %q for %s", oldMountPoint, oldStorageName) rhs := fmt.Sprintf("mount point %q for %s", newMountPoint, newStorageName) if swapOrder { lhs, rhs = rhs, lhs } return errors.Errorf("%s contains %s", lhs, rhs) } } return nil }
// MigrateUnitPortsToOpenedPorts loops through all units stored in state and // migrates any ports into the openedPorts collection. func MigrateUnitPortsToOpenedPorts(st *State) error { err := st.ResumeTransactions() if err != nil { return errors.Trace(err) } var unitSlice []unitDoc units, closer := st.getRawCollection(unitsC) defer closer() // Get all units ordered by their service and name. err = units.Find(nil).Sort("service", "name").All(&unitSlice) if err != nil { return errors.Trace(err) } upgradesLogger.Infof("migrating legacy ports to port ranges for all %d units", len(unitSlice)) for _, uDoc := range unitSlice { unit := &Unit{st: st, doc: uDoc} upgradesLogger.Infof("migrating ports for unit %q", unit) upgradesLogger.Debugf("raw ports for unit %q: %v", unit, uDoc.Ports) skippedRanges, mergedRanges, validRanges := validateUnitPorts(st, unit) // Get the unit's assigned machine. machineId, err := unit.AssignedMachineId() if errors.IsNotAssigned(err) { upgradesLogger.Infof("unit %q has no assigned machine; skipping migration", unit) continue } else if err != nil { return errors.Annotatef(err, "cannot get the assigned machine for unit %q", unit) } upgradesLogger.Debugf("unit %q assigned to machine %q", unit, machineId) ops, machinePorts, err := beginUnitMigrationOps(st, unit, machineId) if err != nil { return errors.Trace(err) } // Get all existing port ranges on the machine. allMachineRanges := machinePorts.AllPortRanges() upgradesLogger.Debugf( "existing port ranges for unit %q's machine %q: %v", unit.Name(), machineId, allMachineRanges, ) rangesToMigrate, filteredRanges, err := filterUnitRangesToMigrate(unit, allMachineRanges, validRanges) if err != nil { return errors.Trace(err) } skippedRanges += filteredRanges migratedPorts, migratedRanges, ops := finishUnitMigrationOps( unit, rangesToMigrate, machinePorts.GlobalKey(), ops, ) if err = st.runRawTransaction(ops); err != nil { upgradesLogger.Warningf("migration failed for unit %q: %v", unit, err) } if len(uDoc.Ports) > 0 { totalPorts := len(uDoc.Ports) upgradesLogger.Infof( "unit %q's ports (ranges) migrated: total %d(%d); ok %d(%d); skipped %d(%d)", unit, totalPorts, len(mergedRanges), migratedPorts, migratedRanges, totalPorts-migratedPorts, skippedRanges, ) } else { upgradesLogger.Infof("no ports to migrate for unit %q", unit) } } upgradesLogger.Infof("legacy unit ports migrated to machine port ranges") return nil }
// createStorageOps returns txn.Ops for creating storage instances // and attachments for the newly created unit or service. // // The entity tag identifies the entity that owns the storage instance // either a unit or a service. Shared storage instances are owned by a // service, and non-shared storage instances are owned by a unit. // // The charm metadata corresponds to the charm that the owner (service/unit) // is or will be running, and is used to extract storage constraints, // default values, etc. // // The supplied storage constraints are constraints for the storage // instances to be created, keyed on the storage name. These constraints // will be correlated with the charm storage metadata for validation // and supplementing. func createStorageOps( st *State, entity names.Tag, charmMeta *charm.Meta, curl *charm.URL, cons map[string]StorageConstraints, series string, machineOpsNeeded bool, ) (ops []txn.Op, numStorageAttachments int, err error) { type template struct { storageName string meta charm.Storage cons StorageConstraints } createdShared := false switch entity := entity.(type) { case names.ServiceTag: createdShared = true case names.UnitTag: default: return nil, -1, errors.Errorf("expected service or unit tag, got %T", entity) } // Create storage instances in order of name, to simplify testing. storageNames := set.NewStrings() for name := range cons { storageNames.Add(name) } templates := make([]template, 0, len(cons)) for _, store := range storageNames.SortedValues() { cons := cons[store] charmStorage, ok := charmMeta.Storage[store] if !ok { return nil, -1, errors.NotFoundf("charm storage %q", store) } if createdShared != charmStorage.Shared { // services only get shared storage instances, // units only get non-shared storage instances. continue } templates = append(templates, template{ storageName: store, meta: charmStorage, cons: cons, }) } ops = make([]txn.Op, 0, len(templates)*2) for _, t := range templates { owner := entity.String() var kind StorageKind switch t.meta.Type { case charm.StorageBlock: kind = StorageKindBlock case charm.StorageFilesystem: kind = StorageKindFilesystem default: return nil, -1, errors.Errorf("unknown storage type %q", t.meta.Type) } for i := uint64(0); i < t.cons.Count; i++ { id, err := newStorageInstanceId(st, t.storageName) if err != nil { return nil, -1, errors.Annotate(err, "cannot generate storage instance name") } doc := &storageInstanceDoc{ Id: id, Kind: kind, Owner: owner, StorageName: t.storageName, CharmURL: curl, } if unit, ok := entity.(names.UnitTag); ok { doc.AttachmentCount = 1 storage := names.NewStorageTag(id) ops = append(ops, createStorageAttachmentOp(storage, unit)) numStorageAttachments++ } ops = append(ops, txn.Op{ C: storageInstancesC, Id: id, Assert: txn.DocMissing, Insert: doc, }) if machineOpsNeeded { machineOps, err := unitAssignedMachineStorageOps( st, entity, charmMeta, cons, series, &storageInstance{st, *doc}, ) if err == nil { ops = append(ops, machineOps...) } else if !errors.IsNotAssigned(err) { return nil, -1, errors.Annotatef( err, "creating machine storage for storage %s", id, ) } } } } // TODO(axw) create storage attachments for each shared storage // instance owned by the service. // // TODO(axw) prevent creation of shared storage after service // creation, because the only sane time to add storage attachments // is when units are added to said service. return ops, numStorageAttachments, nil }
func (e *exporter) addVolume(vol *volume, volAttachments []volumeAttachmentDoc) error { args := description.VolumeArgs{ Tag: vol.VolumeTag(), Binding: vol.LifeBinding(), } if tag, err := vol.StorageInstance(); err == nil { // only returns an error when no storage tag. args.Storage = tag } else { if !errors.IsNotAssigned(err) { // This is an unexpected error. return errors.Trace(err) } } logger.Debugf("addVolume: %#v", vol.doc) if info, err := vol.Info(); err == nil { logger.Debugf(" info %#v", info) args.Provisioned = true args.Size = info.Size args.Pool = info.Pool args.HardwareID = info.HardwareId args.VolumeID = info.VolumeId args.Persistent = info.Persistent } else { params, _ := vol.Params() logger.Debugf(" params %#v", params) args.Size = params.Size args.Pool = params.Pool } globalKey := vol.globalKey() statusArgs, err := e.statusArgs(globalKey) if err != nil { return errors.Annotatef(err, "status for volume %s", vol.doc.Name) } exVolume := e.model.AddVolume(args) exVolume.SetStatus(statusArgs) exVolume.SetStatusHistory(e.statusHistoryArgs(globalKey)) if count := len(volAttachments); count != vol.doc.AttachmentCount { return errors.Errorf("volume attachment count mismatch, have %d, expected %d", count, vol.doc.AttachmentCount) } for _, doc := range volAttachments { va := volumeAttachment{doc} logger.Debugf(" attachment %#v", doc) args := description.VolumeAttachmentArgs{ Machine: va.Machine(), } if info, err := va.Info(); err == nil { logger.Debugf(" info %#v", info) args.Provisioned = true args.ReadOnly = info.ReadOnly args.DeviceName = info.DeviceName args.DeviceLink = info.DeviceLink args.BusAddress = info.BusAddress } else { params, _ := va.Params() logger.Debugf(" params %#v", params) args.ReadOnly = params.ReadOnly } exVolume.AddAttachment(args) } return nil }