github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/provider/maas/volumes.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package maas 5 6 import ( 7 "strconv" 8 "strings" 9 "unicode" 10 11 "github.com/dustin/go-humanize" 12 "github.com/juju/errors" 13 "github.com/juju/names" 14 "github.com/juju/schema" 15 "github.com/juju/utils/set" 16 17 "github.com/juju/juju/constraints" 18 "github.com/juju/juju/environs/config" 19 "github.com/juju/juju/provider/common" 20 "github.com/juju/juju/storage" 21 ) 22 23 const ( 24 // maasStorageProviderType is the name of the storage provider 25 // used to specify storage when acquiring MAAS nodes. 26 maasStorageProviderType = storage.ProviderType("maas") 27 28 // rootDiskLabel is the label recognised by MAAS as being for 29 // the root disk. 30 rootDiskLabel = "root" 31 32 // tagsAttribute is the name of the pool attribute used 33 // to specify tag values for requested volumes. 34 tagsAttribute = "tags" 35 ) 36 37 // maasStorageProvider allows volumes to be specified when a node is acquired. 38 type maasStorageProvider struct{} 39 40 var _ storage.Provider = (*maasStorageProvider)(nil) 41 42 var validConfigOptions = set.NewStrings( 43 tagsAttribute, 44 ) 45 46 var storageConfigFields = schema.Fields{ 47 tagsAttribute: schema.OneOf( 48 schema.List(schema.String()), 49 schema.String(), 50 ), 51 } 52 53 var storageConfigChecker = schema.FieldMap( 54 storageConfigFields, 55 schema.Defaults{ 56 tagsAttribute: schema.Omit, 57 }, 58 ) 59 60 type storageConfig struct { 61 tags []string 62 } 63 64 func newStorageConfig(attrs map[string]interface{}) (*storageConfig, error) { 65 out, err := storageConfigChecker.Coerce(attrs, nil) 66 if err != nil { 67 return nil, errors.Annotate(err, "validating MAAS storage config") 68 } 69 coerced := out.(map[string]interface{}) 70 var tags []string 71 switch v := coerced[tagsAttribute].(type) { 72 case []string: 73 tags = v 74 case string: 75 fields := strings.Split(v, ",") 76 for _, f := range fields { 77 f = strings.TrimSpace(f) 78 if len(f) == 0 { 79 continue 80 } 81 if i := strings.IndexFunc(f, unicode.IsSpace); i >= 0 { 82 return nil, errors.Errorf("tags may not contain whitespace: %q", f) 83 } 84 tags = append(tags, f) 85 } 86 } 87 return &storageConfig{tags: tags}, nil 88 } 89 90 // ValidateConfig is defined on the Provider interface. 91 func (e *maasStorageProvider) ValidateConfig(cfg *storage.Config) error { 92 _, err := newStorageConfig(cfg.Attrs()) 93 return errors.Trace(err) 94 } 95 96 // Supports is defined on the Provider interface. 97 func (e *maasStorageProvider) Supports(k storage.StorageKind) bool { 98 return k == storage.StorageKindBlock 99 } 100 101 // Scope is defined on the Provider interface. 102 func (e *maasStorageProvider) Scope() storage.Scope { 103 return storage.ScopeEnviron 104 } 105 106 // Dynamic is defined on the Provider interface. 107 func (e *maasStorageProvider) Dynamic() bool { 108 return false 109 } 110 111 // VolumeSource is defined on the Provider interface. 112 func (e *maasStorageProvider) VolumeSource(environConfig *config.Config, providerConfig *storage.Config) (storage.VolumeSource, error) { 113 // Dynamic volumes not supported. 114 return nil, errors.NotSupportedf("volumes") 115 } 116 117 // FilesystemSource is defined on the Provider interface. 118 func (e *maasStorageProvider) FilesystemSource(environConfig *config.Config, providerConfig *storage.Config) (storage.FilesystemSource, error) { 119 return nil, errors.NotSupportedf("filesystems") 120 } 121 122 type volumeInfo struct { 123 name string 124 sizeInGB uint64 125 tags []string 126 } 127 128 // mibToGB converts the value in MiB to GB. 129 // Juju works in MiB, MAAS expects GB. 130 func mibToGb(m uint64) uint64 { 131 return common.MiBToGiB(m) * (humanize.GiByte / humanize.GByte) 132 } 133 134 // buildMAASVolumeParameters creates the MAAS volume information to include 135 // in a request to acquire a MAAS node, based on the supplied storage parameters. 136 func buildMAASVolumeParameters(args []storage.VolumeParams, cons constraints.Value) ([]volumeInfo, error) { 137 if len(args) == 0 && cons.RootDisk == nil { 138 return nil, nil 139 } 140 volumes := make([]volumeInfo, len(args)+1) 141 rootVolume := volumeInfo{name: rootDiskLabel} 142 if cons.RootDisk != nil { 143 rootVolume.sizeInGB = mibToGb(*cons.RootDisk) 144 } 145 volumes[0] = rootVolume 146 for i, v := range args { 147 cfg, err := newStorageConfig(v.Attributes) 148 if err != nil { 149 return nil, errors.Trace(err) 150 } 151 info := volumeInfo{ 152 name: v.Tag.Id(), 153 sizeInGB: mibToGb(v.Size), 154 tags: cfg.tags, 155 } 156 volumes[i+1] = info 157 } 158 return volumes, nil 159 } 160 161 // volumes creates the storage volumes and attachments 162 // corresponding to the volume info associated with a MAAS node. 163 func (mi *maasInstance) volumes( 164 mTag names.MachineTag, requestedVolumes []names.VolumeTag, 165 ) ( 166 []storage.Volume, []storage.VolumeAttachment, error, 167 ) { 168 var volumes []storage.Volume 169 var attachments []storage.VolumeAttachment 170 171 deviceInfo, ok := mi.maasObject.GetMap()["physicalblockdevice_set"] 172 // Older MAAS servers don't support storage. 173 if !ok || deviceInfo.IsNil() { 174 return volumes, attachments, nil 175 } 176 177 labelsMap, ok := mi.maasObject.GetMap()["constraint_map"] 178 if !ok || labelsMap.IsNil() { 179 return nil, nil, errors.NotFoundf("constraint map field") 180 } 181 182 devices, err := deviceInfo.GetArray() 183 if err != nil { 184 return nil, nil, errors.Trace(err) 185 } 186 // deviceLabel is the volume label passed 187 // into the acquire node call as part 188 // of the storage constraints parameter. 189 deviceLabels, err := labelsMap.GetMap() 190 if err != nil { 191 return nil, nil, errors.Annotate(err, "invalid constraint map value") 192 } 193 194 // Set up a collection of volumes tags which 195 // we specifically asked for when the node was acquired. 196 validVolumes := set.NewStrings() 197 for _, v := range requestedVolumes { 198 validVolumes.Add(v.Id()) 199 } 200 201 for _, d := range devices { 202 deviceAttrs, err := d.GetMap() 203 if err != nil { 204 return nil, nil, errors.Trace(err) 205 } 206 // id in devices list is numeric 207 id, err := deviceAttrs["id"].GetFloat64() 208 if err != nil { 209 return nil, nil, errors.Annotate(err, "invalid device id") 210 } 211 // id in constraint_map field is a string 212 idKey := strconv.Itoa(int(id)) 213 214 // Device Label. 215 deviceLabelValue, ok := deviceLabels[idKey] 216 if !ok { 217 logger.Debugf("acquire maas node: missing volume label for id %q", idKey) 218 continue 219 } 220 deviceLabel, err := deviceLabelValue.GetString() 221 if err != nil { 222 return nil, nil, errors.Annotate(err, "invalid device label") 223 } 224 // We don't explicitly allow the root volume to be specified yet. 225 if deviceLabel == rootDiskLabel { 226 continue 227 } 228 // We only care about the volumes we specifically asked for. 229 if !validVolumes.Contains(deviceLabel) { 230 continue 231 } 232 233 // HardwareId and DeviceName. 234 // First try for id_path. 235 idPathPrefix := "/dev/disk/by-id/" 236 hardwareId, err := deviceAttrs["id_path"].GetString() 237 var deviceName string 238 if err == nil { 239 if !strings.HasPrefix(hardwareId, idPathPrefix) { 240 return nil, nil, errors.Errorf("invalid device id %q", hardwareId) 241 } 242 hardwareId = hardwareId[len(idPathPrefix):] 243 } else { 244 // On VMAAS, id_path not available so try for path instead. 245 deviceName, err = deviceAttrs["name"].GetString() 246 if err != nil { 247 return nil, nil, errors.Annotate(err, "invalid device name") 248 } 249 } 250 251 // Size. 252 sizeinBytes, err := deviceAttrs["size"].GetFloat64() 253 if err != nil { 254 return nil, nil, errors.Annotate(err, "invalid device size") 255 } 256 257 volumeTag := names.NewVolumeTag(deviceLabel) 258 vol := storage.Volume{ 259 volumeTag, 260 storage.VolumeInfo{ 261 VolumeId: volumeTag.String(), 262 HardwareId: hardwareId, 263 Size: uint64(sizeinBytes / humanize.MiByte), 264 Persistent: false, 265 }, 266 } 267 volumes = append(volumes, vol) 268 269 attachment := storage.VolumeAttachment{ 270 volumeTag, 271 mTag, 272 storage.VolumeAttachmentInfo{ 273 DeviceName: deviceName, 274 ReadOnly: false, 275 }, 276 } 277 attachments = append(attachments, attachment) 278 } 279 return volumes, attachments, nil 280 }