Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| // Copyright 2015 Canonical Ltd. | |
| // Licensed under the AGPLv3, see LICENCE file for details. | |
| package maas | |
| import ( | |
| "strconv" | |
| "strings" | |
| "unicode" | |
| "github.com/dustin/go-humanize" | |
| "github.com/juju/errors" | |
| "github.com/juju/schema" | |
| "github.com/juju/utils/set" | |
| "gopkg.in/juju/names.v2" | |
| "github.com/juju/juju/constraints" | |
| "github.com/juju/juju/provider/common" | |
| "github.com/juju/juju/storage" | |
| ) | |
| const ( | |
| // maasStorageProviderType is the name of the storage provider | |
| // used to specify storage when acquiring MAAS nodes. | |
| maasStorageProviderType = storage.ProviderType("maas") | |
| // rootDiskLabel is the label recognised by MAAS as being for | |
| // the root disk. | |
| rootDiskLabel = "root" | |
| // tagsAttribute is the name of the pool attribute used | |
| // to specify tag values for requested volumes. | |
| tagsAttribute = "tags" | |
| ) | |
| // StorageProviderTypes implements storage.ProviderRegistry. | |
| func (*maasEnviron) StorageProviderTypes() ([]storage.ProviderType, error) { | |
| return []storage.ProviderType{maasStorageProviderType}, nil | |
| } | |
| // StorageProvider implements storage.ProviderRegistry. | |
| func (*maasEnviron) StorageProvider(t storage.ProviderType) (storage.Provider, error) { | |
| if t == maasStorageProviderType { | |
| return maasStorageProvider{}, nil | |
| } | |
| return nil, errors.NotFoundf("storage provider %q", t) | |
| } | |
| // maasStorageProvider allows volumes to be specified when a node is acquired. | |
| type maasStorageProvider struct{} | |
| var storageConfigFields = schema.Fields{ | |
| tagsAttribute: schema.OneOf( | |
| schema.List(schema.String()), | |
| schema.String(), | |
| ), | |
| } | |
| var storageConfigChecker = schema.FieldMap( | |
| storageConfigFields, | |
| schema.Defaults{ | |
| tagsAttribute: schema.Omit, | |
| }, | |
| ) | |
| type storageConfig struct { | |
| tags []string | |
| } | |
| func newStorageConfig(attrs map[string]interface{}) (*storageConfig, error) { | |
| out, err := storageConfigChecker.Coerce(attrs, nil) | |
| if err != nil { | |
| return nil, errors.Annotate(err, "validating MAAS storage config") | |
| } | |
| coerced := out.(map[string]interface{}) | |
| var tags []string | |
| switch v := coerced[tagsAttribute].(type) { | |
| case []string: | |
| tags = v | |
| case string: | |
| fields := strings.Split(v, ",") | |
| for _, f := range fields { | |
| f = strings.TrimSpace(f) | |
| if len(f) == 0 { | |
| continue | |
| } | |
| if i := strings.IndexFunc(f, unicode.IsSpace); i >= 0 { | |
| return nil, errors.Errorf("tags may not contain whitespace: %q", f) | |
| } | |
| tags = append(tags, f) | |
| } | |
| } | |
| return &storageConfig{tags: tags}, nil | |
| } | |
| // ValidateConfig is defined on the Provider interface. | |
| func (maasStorageProvider) ValidateConfig(cfg *storage.Config) error { | |
| _, err := newStorageConfig(cfg.Attrs()) | |
| return errors.Trace(err) | |
| } | |
| // Supports is defined on the Provider interface. | |
| func (maasStorageProvider) Supports(k storage.StorageKind) bool { | |
| return k == storage.StorageKindBlock | |
| } | |
| // Scope is defined on the Provider interface. | |
| func (maasStorageProvider) Scope() storage.Scope { | |
| return storage.ScopeEnviron | |
| } | |
| // Dynamic is defined on the Provider interface. | |
| func (maasStorageProvider) Dynamic() bool { | |
| return false | |
| } | |
| // DefaultPools is defined on the Provider interface. | |
| func (maasStorageProvider) DefaultPools() []*storage.Config { | |
| return nil | |
| } | |
| // VolumeSource is defined on the Provider interface. | |
| func (maasStorageProvider) VolumeSource(providerConfig *storage.Config) (storage.VolumeSource, error) { | |
| // Dynamic volumes not supported. | |
| return nil, errors.NotSupportedf("volumes") | |
| } | |
| // FilesystemSource is defined on the Provider interface. | |
| func (maasStorageProvider) FilesystemSource(providerConfig *storage.Config) (storage.FilesystemSource, error) { | |
| return nil, errors.NotSupportedf("filesystems") | |
| } | |
| type volumeInfo struct { | |
| name string | |
| sizeInGB uint64 | |
| tags []string | |
| } | |
| // mibToGB converts the value in MiB to GB. | |
| // Juju works in MiB, MAAS expects GB. | |
| func mibToGb(m uint64) uint64 { | |
| return common.MiBToGiB(m) * (humanize.GiByte / humanize.GByte) | |
| } | |
| // buildMAASVolumeParameters creates the MAAS volume information to include | |
| // in a request to acquire a MAAS node, based on the supplied storage parameters. | |
| func buildMAASVolumeParameters(args []storage.VolumeParams, cons constraints.Value) ([]volumeInfo, error) { | |
| if len(args) == 0 && cons.RootDisk == nil { | |
| return nil, nil | |
| } | |
| volumes := make([]volumeInfo, len(args)+1) | |
| rootVolume := volumeInfo{name: rootDiskLabel} | |
| if cons.RootDisk != nil { | |
| rootVolume.sizeInGB = mibToGb(*cons.RootDisk) | |
| } | |
| volumes[0] = rootVolume | |
| for i, v := range args { | |
| cfg, err := newStorageConfig(v.Attributes) | |
| if err != nil { | |
| return nil, errors.Trace(err) | |
| } | |
| info := volumeInfo{ | |
| name: v.Tag.Id(), | |
| sizeInGB: mibToGb(v.Size), | |
| tags: cfg.tags, | |
| } | |
| volumes[i+1] = info | |
| } | |
| return volumes, nil | |
| } | |
| // volumes creates the storage volumes and attachments | |
| // corresponding to the volume info associated with a MAAS node. | |
| func (mi *maas1Instance) volumes( | |
| mTag names.MachineTag, requestedVolumes []names.VolumeTag, | |
| ) ( | |
| []storage.Volume, []storage.VolumeAttachment, error, | |
| ) { | |
| var volumes []storage.Volume | |
| var attachments []storage.VolumeAttachment | |
| deviceInfo, ok := mi.maasObject.GetMap()["physicalblockdevice_set"] | |
| // Older MAAS servers don't support storage. | |
| if !ok || deviceInfo.IsNil() { | |
| return volumes, attachments, nil | |
| } | |
| labelsMap, ok := mi.maasObject.GetMap()["constraint_map"] | |
| if !ok || labelsMap.IsNil() { | |
| return nil, nil, errors.NotFoundf("constraint map field") | |
| } | |
| devices, err := deviceInfo.GetArray() | |
| if err != nil { | |
| return nil, nil, errors.Trace(err) | |
| } | |
| // deviceLabel is the volume label passed | |
| // into the acquire node call as part | |
| // of the storage constraints parameter. | |
| deviceLabels, err := labelsMap.GetMap() | |
| if err != nil { | |
| return nil, nil, errors.Annotate(err, "invalid constraint map value") | |
| } | |
| // Set up a collection of volumes tags which | |
| // we specifically asked for when the node was acquired. | |
| validVolumes := set.NewStrings() | |
| for _, v := range requestedVolumes { | |
| validVolumes.Add(v.Id()) | |
| } | |
| for _, d := range devices { | |
| deviceAttrs, err := d.GetMap() | |
| if err != nil { | |
| return nil, nil, errors.Trace(err) | |
| } | |
| // id in devices list is numeric | |
| id, err := deviceAttrs["id"].GetFloat64() | |
| if err != nil { | |
| return nil, nil, errors.Annotate(err, "invalid device id") | |
| } | |
| // id in constraint_map field is a string | |
| idKey := strconv.Itoa(int(id)) | |
| // Device Label. | |
| deviceLabelValue, ok := deviceLabels[idKey] | |
| if !ok { | |
| logger.Debugf("acquire maas node: missing volume label for id %q", idKey) | |
| continue | |
| } | |
| deviceLabel, err := deviceLabelValue.GetString() | |
| if err != nil { | |
| return nil, nil, errors.Annotate(err, "invalid device label") | |
| } | |
| // We don't explicitly allow the root volume to be specified yet. | |
| if deviceLabel == rootDiskLabel { | |
| continue | |
| } | |
| // We only care about the volumes we specifically asked for. | |
| if !validVolumes.Contains(deviceLabel) { | |
| continue | |
| } | |
| // HardwareId and DeviceName. | |
| // First try for id_path. | |
| idPathPrefix := "/dev/disk/by-id/" | |
| hardwareId, err := deviceAttrs["id_path"].GetString() | |
| var deviceName string | |
| if err == nil { | |
| if !strings.HasPrefix(hardwareId, idPathPrefix) { | |
| return nil, nil, errors.Errorf("invalid device id %q", hardwareId) | |
| } | |
| hardwareId = hardwareId[len(idPathPrefix):] | |
| } else { | |
| // On VMAAS, id_path not available so try for path instead. | |
| deviceName, err = deviceAttrs["name"].GetString() | |
| if err != nil { | |
| return nil, nil, errors.Annotate(err, "invalid device name") | |
| } | |
| } | |
| // Size. | |
| sizeinBytes, err := deviceAttrs["size"].GetFloat64() | |
| if err != nil { | |
| return nil, nil, errors.Annotate(err, "invalid device size") | |
| } | |
| volumeTag := names.NewVolumeTag(deviceLabel) | |
| vol := storage.Volume{ | |
| volumeTag, | |
| storage.VolumeInfo{ | |
| VolumeId: volumeTag.String(), | |
| HardwareId: hardwareId, | |
| Size: uint64(sizeinBytes / humanize.MiByte), | |
| Persistent: false, | |
| }, | |
| } | |
| volumes = append(volumes, vol) | |
| attachment := storage.VolumeAttachment{ | |
| volumeTag, | |
| mTag, | |
| storage.VolumeAttachmentInfo{ | |
| DeviceName: deviceName, | |
| ReadOnly: false, | |
| }, | |
| } | |
| attachments = append(attachments, attachment) | |
| } | |
| return volumes, attachments, nil | |
| } | |
| func (mi *maas2Instance) volumes( | |
| mTag names.MachineTag, requestedVolumes []names.VolumeTag, | |
| ) ( | |
| []storage.Volume, []storage.VolumeAttachment, error, | |
| ) { | |
| if mi.constraintMatches.Storage == nil { | |
| return nil, nil, errors.NotFoundf("constraint storage mapping") | |
| } | |
| var volumes []storage.Volume | |
| var attachments []storage.VolumeAttachment | |
| // Set up a collection of volumes tags which | |
| // we specifically asked for when the node was acquired. | |
| validVolumes := set.NewStrings() | |
| for _, v := range requestedVolumes { | |
| validVolumes.Add(v.Id()) | |
| } | |
| for label, devices := range mi.constraintMatches.Storage { | |
| // We don't explicitly allow the root volume to be specified yet. | |
| if label == rootDiskLabel { | |
| continue | |
| } | |
| // We only care about the volumes we specifically asked for. | |
| if !validVolumes.Contains(label) { | |
| continue | |
| } | |
| for _, device := range devices { | |
| volumeTag := names.NewVolumeTag(label) | |
| vol := storage.Volume{ | |
| volumeTag, | |
| storage.VolumeInfo{ | |
| VolumeId: volumeTag.String(), | |
| Size: uint64(device.Size() / humanize.MiByte), | |
| Persistent: false, | |
| }, | |
| } | |
| volumes = append(volumes, vol) | |
| attachment := storage.VolumeAttachment{ | |
| volumeTag, | |
| mTag, | |
| storage.VolumeAttachmentInfo{ | |
| DeviceLink: device.Path(), | |
| ReadOnly: false, | |
| }, | |
| } | |
| attachments = append(attachments, attachment) | |
| } | |
| } | |
| return volumes, attachments, nil | |
| } |