github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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 storageConfigFields = schema.Fields{ 41 tagsAttribute: schema.OneOf( 42 schema.List(schema.String()), 43 schema.String(), 44 ), 45 } 46 47 var storageConfigChecker = schema.FieldMap( 48 storageConfigFields, 49 schema.Defaults{ 50 tagsAttribute: schema.Omit, 51 }, 52 ) 53 54 type storageConfig struct { 55 tags []string 56 } 57 58 func newStorageConfig(attrs map[string]interface{}) (*storageConfig, error) { 59 out, err := storageConfigChecker.Coerce(attrs, nil) 60 if err != nil { 61 return nil, errors.Annotate(err, "validating MAAS storage config") 62 } 63 coerced := out.(map[string]interface{}) 64 var tags []string 65 switch v := coerced[tagsAttribute].(type) { 66 case []string: 67 tags = v 68 case string: 69 fields := strings.Split(v, ",") 70 for _, f := range fields { 71 f = strings.TrimSpace(f) 72 if len(f) == 0 { 73 continue 74 } 75 if i := strings.IndexFunc(f, unicode.IsSpace); i >= 0 { 76 return nil, errors.Errorf("tags may not contain whitespace: %q", f) 77 } 78 tags = append(tags, f) 79 } 80 } 81 return &storageConfig{tags: tags}, nil 82 } 83 84 // ValidateConfig is defined on the Provider interface. 85 func (e *maasStorageProvider) ValidateConfig(cfg *storage.Config) error { 86 _, err := newStorageConfig(cfg.Attrs()) 87 return errors.Trace(err) 88 } 89 90 // Supports is defined on the Provider interface. 91 func (e *maasStorageProvider) Supports(k storage.StorageKind) bool { 92 return k == storage.StorageKindBlock 93 } 94 95 // Scope is defined on the Provider interface. 96 func (e *maasStorageProvider) Scope() storage.Scope { 97 return storage.ScopeEnviron 98 } 99 100 // Dynamic is defined on the Provider interface. 101 func (e *maasStorageProvider) Dynamic() bool { 102 return false 103 } 104 105 // VolumeSource is defined on the Provider interface. 106 func (e *maasStorageProvider) VolumeSource(environConfig *config.Config, providerConfig *storage.Config) (storage.VolumeSource, error) { 107 // Dynamic volumes not supported. 108 return nil, errors.NotSupportedf("volumes") 109 } 110 111 // FilesystemSource is defined on the Provider interface. 112 func (e *maasStorageProvider) FilesystemSource(environConfig *config.Config, providerConfig *storage.Config) (storage.FilesystemSource, error) { 113 return nil, errors.NotSupportedf("filesystems") 114 } 115 116 type volumeInfo struct { 117 name string 118 sizeInGB uint64 119 tags []string 120 } 121 122 // mibToGB converts the value in MiB to GB. 123 // Juju works in MiB, MAAS expects GB. 124 func mibToGb(m uint64) uint64 { 125 return common.MiBToGiB(m) * (humanize.GiByte / humanize.GByte) 126 } 127 128 // buildMAASVolumeParameters creates the MAAS volume information to include 129 // in a request to acquire a MAAS node, based on the supplied storage parameters. 130 func buildMAASVolumeParameters(args []storage.VolumeParams, cons constraints.Value) ([]volumeInfo, error) { 131 if len(args) == 0 && cons.RootDisk == nil { 132 return nil, nil 133 } 134 volumes := make([]volumeInfo, len(args)+1) 135 rootVolume := volumeInfo{name: rootDiskLabel} 136 if cons.RootDisk != nil { 137 rootVolume.sizeInGB = mibToGb(*cons.RootDisk) 138 } 139 volumes[0] = rootVolume 140 for i, v := range args { 141 cfg, err := newStorageConfig(v.Attributes) 142 if err != nil { 143 return nil, errors.Trace(err) 144 } 145 info := volumeInfo{ 146 name: v.Tag.Id(), 147 sizeInGB: mibToGb(v.Size), 148 tags: cfg.tags, 149 } 150 volumes[i+1] = info 151 } 152 return volumes, nil 153 } 154 155 // volumes creates the storage volumes and attachments 156 // corresponding to the volume info associated with a MAAS node. 157 func (mi *maas1Instance) volumes( 158 mTag names.MachineTag, requestedVolumes []names.VolumeTag, 159 ) ( 160 []storage.Volume, []storage.VolumeAttachment, error, 161 ) { 162 var volumes []storage.Volume 163 var attachments []storage.VolumeAttachment 164 165 deviceInfo, ok := mi.maasObject.GetMap()["physicalblockdevice_set"] 166 // Older MAAS servers don't support storage. 167 if !ok || deviceInfo.IsNil() { 168 return volumes, attachments, nil 169 } 170 171 labelsMap, ok := mi.maasObject.GetMap()["constraint_map"] 172 if !ok || labelsMap.IsNil() { 173 return nil, nil, errors.NotFoundf("constraint map field") 174 } 175 176 devices, err := deviceInfo.GetArray() 177 if err != nil { 178 return nil, nil, errors.Trace(err) 179 } 180 // deviceLabel is the volume label passed 181 // into the acquire node call as part 182 // of the storage constraints parameter. 183 deviceLabels, err := labelsMap.GetMap() 184 if err != nil { 185 return nil, nil, errors.Annotate(err, "invalid constraint map value") 186 } 187 188 // Set up a collection of volumes tags which 189 // we specifically asked for when the node was acquired. 190 validVolumes := set.NewStrings() 191 for _, v := range requestedVolumes { 192 validVolumes.Add(v.Id()) 193 } 194 195 for _, d := range devices { 196 deviceAttrs, err := d.GetMap() 197 if err != nil { 198 return nil, nil, errors.Trace(err) 199 } 200 // id in devices list is numeric 201 id, err := deviceAttrs["id"].GetFloat64() 202 if err != nil { 203 return nil, nil, errors.Annotate(err, "invalid device id") 204 } 205 // id in constraint_map field is a string 206 idKey := strconv.Itoa(int(id)) 207 208 // Device Label. 209 deviceLabelValue, ok := deviceLabels[idKey] 210 if !ok { 211 logger.Debugf("acquire maas node: missing volume label for id %q", idKey) 212 continue 213 } 214 deviceLabel, err := deviceLabelValue.GetString() 215 if err != nil { 216 return nil, nil, errors.Annotate(err, "invalid device label") 217 } 218 // We don't explicitly allow the root volume to be specified yet. 219 if deviceLabel == rootDiskLabel { 220 continue 221 } 222 // We only care about the volumes we specifically asked for. 223 if !validVolumes.Contains(deviceLabel) { 224 continue 225 } 226 227 // HardwareId and DeviceName. 228 // First try for id_path. 229 idPathPrefix := "/dev/disk/by-id/" 230 hardwareId, err := deviceAttrs["id_path"].GetString() 231 var deviceName string 232 if err == nil { 233 if !strings.HasPrefix(hardwareId, idPathPrefix) { 234 return nil, nil, errors.Errorf("invalid device id %q", hardwareId) 235 } 236 hardwareId = hardwareId[len(idPathPrefix):] 237 } else { 238 // On VMAAS, id_path not available so try for path instead. 239 deviceName, err = deviceAttrs["name"].GetString() 240 if err != nil { 241 return nil, nil, errors.Annotate(err, "invalid device name") 242 } 243 } 244 245 // Size. 246 sizeinBytes, err := deviceAttrs["size"].GetFloat64() 247 if err != nil { 248 return nil, nil, errors.Annotate(err, "invalid device size") 249 } 250 251 volumeTag := names.NewVolumeTag(deviceLabel) 252 vol := storage.Volume{ 253 volumeTag, 254 storage.VolumeInfo{ 255 VolumeId: volumeTag.String(), 256 HardwareId: hardwareId, 257 Size: uint64(sizeinBytes / humanize.MiByte), 258 Persistent: false, 259 }, 260 } 261 volumes = append(volumes, vol) 262 263 attachment := storage.VolumeAttachment{ 264 volumeTag, 265 mTag, 266 storage.VolumeAttachmentInfo{ 267 DeviceName: deviceName, 268 ReadOnly: false, 269 }, 270 } 271 attachments = append(attachments, attachment) 272 } 273 return volumes, attachments, nil 274 } 275 276 func (mi *maas2Instance) volumes( 277 mTag names.MachineTag, requestedVolumes []names.VolumeTag, 278 ) ( 279 []storage.Volume, []storage.VolumeAttachment, error, 280 ) { 281 if mi.constraintMatches.Storage == nil { 282 return nil, nil, errors.NotFoundf("constraint storage mapping") 283 } 284 285 var volumes []storage.Volume 286 var attachments []storage.VolumeAttachment 287 288 // Set up a collection of volumes tags which 289 // we specifically asked for when the node was acquired. 290 validVolumes := set.NewStrings() 291 for _, v := range requestedVolumes { 292 validVolumes.Add(v.Id()) 293 } 294 295 for label, device := range mi.constraintMatches.Storage { 296 // We don't explicitly allow the root volume to be specified yet. 297 if label == rootDiskLabel { 298 continue 299 } 300 // We only care about the volumes we specifically asked for. 301 if !validVolumes.Contains(label) { 302 continue 303 } 304 305 volumeTag := names.NewVolumeTag(label) 306 vol := storage.Volume{ 307 volumeTag, 308 storage.VolumeInfo{ 309 VolumeId: volumeTag.String(), 310 Size: uint64(device.Size() / humanize.MiByte), 311 Persistent: false, 312 }, 313 } 314 volumes = append(volumes, vol) 315 316 attachment := storage.VolumeAttachment{ 317 volumeTag, 318 mTag, 319 storage.VolumeAttachmentInfo{ 320 DeviceLink: device.Path(), 321 ReadOnly: false, 322 }, 323 } 324 attachments = append(attachments, attachment) 325 } 326 return volumes, attachments, nil 327 }