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