1
// Copyright 2015 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
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"
17
"github.com/juju/juju/constraints"
18
"github.com/juju/juju/provider/common"
19
"github.com/juju/juju/storage"
23
// maasStorageProviderType is the name of the storage provider
24
// used to specify storage when acquiring MAAS nodes.
25
maasStorageProviderType = storage.ProviderType("maas")
27
// rootDiskLabel is the label recognised by MAAS as being for
29
rootDiskLabel = "root"
31
// tagsAttribute is the name of the pool attribute used
32
// to specify tag values for requested volumes.
33
tagsAttribute = "tags"
36
// StorageProviderTypes implements storage.ProviderRegistry.
37
func (*maasEnviron) StorageProviderTypes() []storage.ProviderType {
38
return []storage.ProviderType{maasStorageProviderType}
41
// StorageProvider implements storage.ProviderRegistry.
42
func (*maasEnviron) StorageProvider(t storage.ProviderType) (storage.Provider, error) {
43
if t == maasStorageProviderType {
44
return maasStorageProvider{}, nil
46
return nil, errors.NotFoundf("storage provider %q", t)
49
// maasStorageProvider allows volumes to be specified when a node is acquired.
50
type maasStorageProvider struct{}
52
var storageConfigFields = schema.Fields{
53
tagsAttribute: schema.OneOf(
54
schema.List(schema.String()),
59
var storageConfigChecker = schema.FieldMap(
62
tagsAttribute: schema.Omit,
66
type storageConfig struct {
70
func newStorageConfig(attrs map[string]interface{}) (*storageConfig, error) {
71
out, err := storageConfigChecker.Coerce(attrs, nil)
73
return nil, errors.Annotate(err, "validating MAAS storage config")
75
coerced := out.(map[string]interface{})
77
switch v := coerced[tagsAttribute].(type) {
81
fields := strings.Split(v, ",")
82
for _, f := range fields {
83
f = strings.TrimSpace(f)
87
if i := strings.IndexFunc(f, unicode.IsSpace); i >= 0 {
88
return nil, errors.Errorf("tags may not contain whitespace: %q", f)
90
tags = append(tags, f)
93
return &storageConfig{tags: tags}, nil
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)
102
// Supports is defined on the Provider interface.
103
func (maasStorageProvider) Supports(k storage.StorageKind) bool {
104
return k == storage.StorageKindBlock
107
// Scope is defined on the Provider interface.
108
func (maasStorageProvider) Scope() storage.Scope {
109
return storage.ScopeEnviron
112
// Dynamic is defined on the Provider interface.
113
func (maasStorageProvider) Dynamic() bool {
117
// DefaultPools is defined on the Provider interface.
118
func (maasStorageProvider) DefaultPools() []*storage.Config {
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")
128
// FilesystemSource is defined on the Provider interface.
129
func (maasStorageProvider) FilesystemSource(providerConfig *storage.Config) (storage.FilesystemSource, error) {
130
return nil, errors.NotSupportedf("filesystems")
133
type volumeInfo struct {
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)
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 {
151
volumes := make([]volumeInfo, len(args)+1)
152
rootVolume := volumeInfo{name: rootDiskLabel}
153
if cons.RootDisk != nil {
154
rootVolume.sizeInGB = mibToGb(*cons.RootDisk)
156
volumes[0] = rootVolume
157
for i, v := range args {
158
cfg, err := newStorageConfig(v.Attributes)
160
return nil, errors.Trace(err)
164
sizeInGB: mibToGb(v.Size),
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,
177
[]storage.Volume, []storage.VolumeAttachment, error,
179
var volumes []storage.Volume
180
var attachments []storage.VolumeAttachment
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
188
labelsMap, ok := mi.maasObject.GetMap()["constraint_map"]
189
if !ok || labelsMap.IsNil() {
190
return nil, nil, errors.NotFoundf("constraint map field")
193
devices, err := deviceInfo.GetArray()
195
return nil, nil, errors.Trace(err)
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()
202
return nil, nil, errors.Annotate(err, "invalid constraint map value")
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())
212
for _, d := range devices {
213
deviceAttrs, err := d.GetMap()
215
return nil, nil, errors.Trace(err)
217
// id in devices list is numeric
218
id, err := deviceAttrs["id"].GetFloat64()
220
return nil, nil, errors.Annotate(err, "invalid device id")
222
// id in constraint_map field is a string
223
idKey := strconv.Itoa(int(id))
226
deviceLabelValue, ok := deviceLabels[idKey]
228
logger.Debugf("acquire maas node: missing volume label for id %q", idKey)
231
deviceLabel, err := deviceLabelValue.GetString()
233
return nil, nil, errors.Annotate(err, "invalid device label")
235
// We don't explicitly allow the root volume to be specified yet.
236
if deviceLabel == rootDiskLabel {
239
// We only care about the volumes we specifically asked for.
240
if !validVolumes.Contains(deviceLabel) {
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
250
if !strings.HasPrefix(hardwareId, idPathPrefix) {
251
return nil, nil, errors.Errorf("invalid device id %q", hardwareId)
253
hardwareId = hardwareId[len(idPathPrefix):]
255
// On VMAAS, id_path not available so try for path instead.
256
deviceName, err = deviceAttrs["name"].GetString()
258
return nil, nil, errors.Annotate(err, "invalid device name")
263
sizeinBytes, err := deviceAttrs["size"].GetFloat64()
265
return nil, nil, errors.Annotate(err, "invalid device size")
268
volumeTag := names.NewVolumeTag(deviceLabel)
269
vol := storage.Volume{
272
VolumeId: volumeTag.String(),
273
HardwareId: hardwareId,
274
Size: uint64(sizeinBytes / humanize.MiByte),
278
volumes = append(volumes, vol)
280
attachment := storage.VolumeAttachment{
283
storage.VolumeAttachmentInfo{
284
DeviceName: deviceName,
288
attachments = append(attachments, attachment)
290
return volumes, attachments, nil
293
func (mi *maas2Instance) volumes(
294
mTag names.MachineTag, requestedVolumes []names.VolumeTag,
296
[]storage.Volume, []storage.VolumeAttachment, error,
298
if mi.constraintMatches.Storage == nil {
299
return nil, nil, errors.NotFoundf("constraint storage mapping")
302
var volumes []storage.Volume
303
var attachments []storage.VolumeAttachment
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())
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 {
317
// We only care about the volumes we specifically asked for.
318
if !validVolumes.Contains(label) {
322
for _, device := range devices {
323
volumeTag := names.NewVolumeTag(label)
324
vol := storage.Volume{
327
VolumeId: volumeTag.String(),
328
Size: uint64(device.Size() / humanize.MiByte),
332
volumes = append(volumes, vol)
334
attachment := storage.VolumeAttachment{
337
storage.VolumeAttachmentInfo{
338
DeviceLink: device.Path(),
342
attachments = append(attachments, attachment)
345
return volumes, attachments, nil