1
// Copyright 2016 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
7
"github.com/juju/errors"
8
"github.com/juju/schema"
9
"gopkg.in/juju/names.v2"
13
Version int `yaml:"version"`
14
Volumes_ []*volume `yaml:"volumes"`
18
ID_ string `yaml:"id"`
19
Binding_ string `yaml:"binding,omitempty"`
20
StorageID_ string `yaml:"storage-id,omitempty"`
21
Provisioned_ bool `yaml:"provisioned"`
22
Size_ uint64 `yaml:"size"`
23
Pool_ string `yaml:"pool,omitempty"`
24
HardwareID_ string `yaml:"hardware-id,omitempty"`
25
VolumeID_ string `yaml:"volume-id,omitempty"`
26
Persistent_ bool `yaml:"persistent"`
28
Status_ *status `yaml:"status"`
29
StatusHistory_ `yaml:"status-history"`
31
Attachments_ volumeAttachments `yaml:"attachments"`
34
type volumeAttachments struct {
35
Version int `yaml:"version"`
36
Attachments_ []*volumeAttachment `yaml:"attachments"`
39
type volumeAttachment struct {
40
MachineID_ string `yaml:"machine-id"`
41
Provisioned_ bool `yaml:"provisioned"`
42
ReadOnly_ bool `yaml:"read-only"`
43
DeviceName_ string `yaml:"device-name"`
44
DeviceLink_ string `yaml:"device-link"`
45
BusAddress_ string `yaml:"bus-address"`
48
// VolumeArgs is an argument struct used to add a volume to the Model.
49
type VolumeArgs struct {
51
Storage names.StorageTag
61
func newVolume(args VolumeArgs) *volume {
64
StorageID_: args.Storage.Id(),
65
Provisioned_: args.Provisioned,
68
HardwareID_: args.HardwareID,
69
VolumeID_: args.VolumeID,
70
Persistent_: args.Persistent,
71
StatusHistory_: newStatusHistory(),
73
if args.Binding != nil {
74
v.Binding_ = args.Binding.String()
80
// Tag implements Volume.
81
func (v *volume) Tag() names.VolumeTag {
82
return names.NewVolumeTag(v.ID_)
85
// Storage implements Volume.
86
func (v *volume) Storage() names.StorageTag {
87
if v.StorageID_ == "" {
88
return names.StorageTag{}
90
return names.NewStorageTag(v.StorageID_)
93
// Binding implements Volume.
94
func (v *volume) Binding() (names.Tag, error) {
98
tag, err := names.ParseTag(v.Binding_)
100
return nil, errors.Trace(err)
105
// Provisioned implements Volume.
106
func (v *volume) Provisioned() bool {
107
return v.Provisioned_
110
// Size implements Volume.
111
func (v *volume) Size() uint64 {
115
// Pool implements Volume.
116
func (v *volume) Pool() string {
120
// HardwareID implements Volume.
121
func (v *volume) HardwareID() string {
125
// VolumeID implements Volume.
126
func (v *volume) VolumeID() string {
130
// Persistent implements Volume.
131
func (v *volume) Persistent() bool {
135
// Status implements Volume.
136
func (v *volume) Status() Status {
137
// To avoid typed nils check nil here.
138
if v.Status_ == nil {
144
// SetStatus implements Volume.
145
func (v *volume) SetStatus(args StatusArgs) {
146
v.Status_ = newStatus(args)
149
func (v *volume) setAttachments(attachments []*volumeAttachment) {
150
v.Attachments_ = volumeAttachments{
152
Attachments_: attachments,
156
// Attachments implements Volume.
157
func (v *volume) Attachments() []VolumeAttachment {
158
var result []VolumeAttachment
159
for _, attachment := range v.Attachments_.Attachments_ {
160
result = append(result, attachment)
165
// AddAttachment implements Volume.
166
func (v *volume) AddAttachment(args VolumeAttachmentArgs) VolumeAttachment {
167
a := newVolumeAttachment(args)
168
v.Attachments_.Attachments_ = append(v.Attachments_.Attachments_, a)
172
// Validate implements Volume.
173
func (v *volume) Validate() error {
175
return errors.NotValidf("volume missing id")
178
return errors.NotValidf("volume %q missing size", v.ID_)
180
if v.Status_ == nil {
181
return errors.NotValidf("volume %q missing status", v.ID_)
186
func importVolumes(source map[string]interface{}) ([]*volume, error) {
187
checker := versionedChecker("volumes")
188
coerced, err := checker.Coerce(source, nil)
190
return nil, errors.Annotatef(err, "volumes version schema check failed")
192
valid := coerced.(map[string]interface{})
194
version := int(valid["version"].(int64))
195
importFunc, ok := volumeDeserializationFuncs[version]
197
return nil, errors.NotValidf("version %d", version)
199
sourceList := valid["volumes"].([]interface{})
200
return importVolumeList(sourceList, importFunc)
203
func importVolumeList(sourceList []interface{}, importFunc volumeDeserializationFunc) ([]*volume, error) {
204
result := make([]*volume, 0, len(sourceList))
205
for i, value := range sourceList {
206
source, ok := value.(map[string]interface{})
208
return nil, errors.Errorf("unexpected value for volume %d, %T", i, value)
210
volume, err := importFunc(source)
212
return nil, errors.Annotatef(err, "volume %d", i)
214
result = append(result, volume)
219
type volumeDeserializationFunc func(map[string]interface{}) (*volume, error)
221
var volumeDeserializationFuncs = map[int]volumeDeserializationFunc{
225
func importVolumeV1(source map[string]interface{}) (*volume, error) {
226
fields := schema.Fields{
227
"id": schema.String(),
228
"storage-id": schema.String(),
229
"binding": schema.String(),
230
"provisioned": schema.Bool(),
231
"size": schema.ForceUint(),
232
"pool": schema.String(),
233
"hardware-id": schema.String(),
234
"volume-id": schema.String(),
235
"persistent": schema.Bool(),
236
"status": schema.StringMap(schema.Any()),
237
"attachments": schema.StringMap(schema.Any()),
240
defaults := schema.Defaults{
246
"attachments": schema.Omit,
248
addStatusHistorySchema(fields)
249
checker := schema.FieldMap(fields, defaults)
251
coerced, err := checker.Coerce(source, nil)
253
return nil, errors.Annotatef(err, "volume v1 schema check failed")
255
valid := coerced.(map[string]interface{})
256
// From here we know that the map returned from the schema coercion
257
// contains fields of the right type.
259
ID_: valid["id"].(string),
260
StorageID_: valid["storage-id"].(string),
261
Binding_: valid["binding"].(string),
262
Provisioned_: valid["provisioned"].(bool),
263
Size_: valid["size"].(uint64),
264
Pool_: valid["pool"].(string),
265
HardwareID_: valid["hardware-id"].(string),
266
VolumeID_: valid["volume-id"].(string),
267
Persistent_: valid["persistent"].(bool),
268
StatusHistory_: newStatusHistory(),
270
if err := result.importStatusHistory(valid); err != nil {
271
return nil, errors.Trace(err)
274
status, err := importStatus(valid["status"].(map[string]interface{}))
276
return nil, errors.Trace(err)
278
result.Status_ = status
280
attachments, err := importVolumeAttachments(valid["attachments"].(map[string]interface{}))
282
return nil, errors.Trace(err)
284
result.setAttachments(attachments)
289
// VolumeAttachmentArgs is an argument struct used to add information about the
290
// cloud instance to a Volume.
291
type VolumeAttachmentArgs struct {
292
Machine names.MachineTag
300
func newVolumeAttachment(args VolumeAttachmentArgs) *volumeAttachment {
301
return &volumeAttachment{
302
MachineID_: args.Machine.Id(),
303
Provisioned_: args.Provisioned,
304
ReadOnly_: args.ReadOnly,
305
DeviceName_: args.DeviceName,
306
DeviceLink_: args.DeviceLink,
307
BusAddress_: args.BusAddress,
311
// Machine implements VolumeAttachment
312
func (a *volumeAttachment) Machine() names.MachineTag {
313
return names.NewMachineTag(a.MachineID_)
316
// Provisioned implements VolumeAttachment
317
func (a *volumeAttachment) Provisioned() bool {
318
return a.Provisioned_
321
// ReadOnly implements VolumeAttachment
322
func (a *volumeAttachment) ReadOnly() bool {
326
// DeviceName implements VolumeAttachment
327
func (a *volumeAttachment) DeviceName() string {
331
// DeviceLink implements VolumeAttachment
332
func (a *volumeAttachment) DeviceLink() string {
336
// BusAddress implements VolumeAttachment
337
func (a *volumeAttachment) BusAddress() string {
341
func importVolumeAttachments(source map[string]interface{}) ([]*volumeAttachment, error) {
342
checker := versionedChecker("attachments")
343
coerced, err := checker.Coerce(source, nil)
345
return nil, errors.Annotatef(err, "volume attachments version schema check failed")
347
valid := coerced.(map[string]interface{})
349
version := int(valid["version"].(int64))
350
importFunc, ok := volumeAttachmentDeserializationFuncs[version]
352
return nil, errors.NotValidf("version %d", version)
354
sourceList := valid["attachments"].([]interface{})
355
return importVolumeAttachmentList(sourceList, importFunc)
358
func importVolumeAttachmentList(sourceList []interface{}, importFunc volumeAttachmentDeserializationFunc) ([]*volumeAttachment, error) {
359
result := make([]*volumeAttachment, 0, len(sourceList))
360
for i, value := range sourceList {
361
source, ok := value.(map[string]interface{})
363
return nil, errors.Errorf("unexpected value for volumeAttachment %d, %T", i, value)
365
volumeAttachment, err := importFunc(source)
367
return nil, errors.Annotatef(err, "volumeAttachment %d", i)
369
result = append(result, volumeAttachment)
374
type volumeAttachmentDeserializationFunc func(map[string]interface{}) (*volumeAttachment, error)
376
var volumeAttachmentDeserializationFuncs = map[int]volumeAttachmentDeserializationFunc{
377
1: importVolumeAttachmentV1,
380
func importVolumeAttachmentV1(source map[string]interface{}) (*volumeAttachment, error) {
381
fields := schema.Fields{
382
"machine-id": schema.String(),
383
"provisioned": schema.Bool(),
384
"read-only": schema.Bool(),
385
"device-name": schema.String(),
386
"device-link": schema.String(),
387
"bus-address": schema.String(),
389
checker := schema.FieldMap(fields, nil) // no defaults
391
coerced, err := checker.Coerce(source, nil)
393
return nil, errors.Annotatef(err, "volumeAttachment v1 schema check failed")
395
valid := coerced.(map[string]interface{})
396
// From here we know that the map returned from the schema coercion
397
// contains fields of the right type.
399
result := &volumeAttachment{
400
MachineID_: valid["machine-id"].(string),
401
Provisioned_: valid["provisioned"].(bool),
402
ReadOnly_: valid["read-only"].(bool),
403
DeviceName_: valid["device-name"].(string),
404
DeviceLink_: valid["device-link"].(string),
405
BusAddress_: valid["bus-address"].(string),