~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/provider/maas/volumes.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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 {
 
38
        return []storage.ProviderType{maasStorageProviderType}
 
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
}