github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/core/description/volume.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package description 5 6 import ( 7 "github.com/juju/errors" 8 "github.com/juju/schema" 9 "gopkg.in/juju/names.v2" 10 ) 11 12 type volumes struct { 13 Version int `yaml:"version"` 14 Volumes_ []*volume `yaml:"volumes"` 15 } 16 17 type volume struct { 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"` 27 28 Status_ *status `yaml:"status"` 29 StatusHistory_ `yaml:"status-history"` 30 31 Attachments_ volumeAttachments `yaml:"attachments"` 32 } 33 34 type volumeAttachments struct { 35 Version int `yaml:"version"` 36 Attachments_ []*volumeAttachment `yaml:"attachments"` 37 } 38 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"` 46 } 47 48 // VolumeArgs is an argument struct used to add a volume to the Model. 49 type VolumeArgs struct { 50 Tag names.VolumeTag 51 Storage names.StorageTag 52 Binding names.Tag 53 Provisioned bool 54 Size uint64 55 Pool string 56 HardwareID string 57 VolumeID string 58 Persistent bool 59 } 60 61 func newVolume(args VolumeArgs) *volume { 62 v := &volume{ 63 ID_: args.Tag.Id(), 64 StorageID_: args.Storage.Id(), 65 Provisioned_: args.Provisioned, 66 Size_: args.Size, 67 Pool_: args.Pool, 68 HardwareID_: args.HardwareID, 69 VolumeID_: args.VolumeID, 70 Persistent_: args.Persistent, 71 StatusHistory_: newStatusHistory(), 72 } 73 if args.Binding != nil { 74 v.Binding_ = args.Binding.String() 75 } 76 v.setAttachments(nil) 77 return v 78 } 79 80 // Tag implements Volume. 81 func (v *volume) Tag() names.VolumeTag { 82 return names.NewVolumeTag(v.ID_) 83 } 84 85 // Storage implements Volume. 86 func (v *volume) Storage() names.StorageTag { 87 if v.StorageID_ == "" { 88 return names.StorageTag{} 89 } 90 return names.NewStorageTag(v.StorageID_) 91 } 92 93 // Binding implements Volume. 94 func (v *volume) Binding() (names.Tag, error) { 95 if v.Binding_ == "" { 96 return nil, nil 97 } 98 tag, err := names.ParseTag(v.Binding_) 99 if err != nil { 100 return nil, errors.Trace(err) 101 } 102 return tag, nil 103 } 104 105 // Provisioned implements Volume. 106 func (v *volume) Provisioned() bool { 107 return v.Provisioned_ 108 } 109 110 // Size implements Volume. 111 func (v *volume) Size() uint64 { 112 return v.Size_ 113 } 114 115 // Pool implements Volume. 116 func (v *volume) Pool() string { 117 return v.Pool_ 118 } 119 120 // HardwareID implements Volume. 121 func (v *volume) HardwareID() string { 122 return v.HardwareID_ 123 } 124 125 // VolumeID implements Volume. 126 func (v *volume) VolumeID() string { 127 return v.VolumeID_ 128 } 129 130 // Persistent implements Volume. 131 func (v *volume) Persistent() bool { 132 return v.Persistent_ 133 } 134 135 // Status implements Volume. 136 func (v *volume) Status() Status { 137 // To avoid typed nils check nil here. 138 if v.Status_ == nil { 139 return nil 140 } 141 return v.Status_ 142 } 143 144 // SetStatus implements Volume. 145 func (v *volume) SetStatus(args StatusArgs) { 146 v.Status_ = newStatus(args) 147 } 148 149 func (v *volume) setAttachments(attachments []*volumeAttachment) { 150 v.Attachments_ = volumeAttachments{ 151 Version: 1, 152 Attachments_: attachments, 153 } 154 } 155 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) 161 } 162 return result 163 } 164 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) 169 return a 170 } 171 172 // Validate implements Volume. 173 func (v *volume) Validate() error { 174 if v.ID_ == "" { 175 return errors.NotValidf("volume missing id") 176 } 177 if v.Size_ == 0 { 178 return errors.NotValidf("volume %q missing size", v.ID_) 179 } 180 if v.Status_ == nil { 181 return errors.NotValidf("volume %q missing status", v.ID_) 182 } 183 if _, err := v.Binding(); err != nil { 184 return errors.Wrap(err, errors.NotValidf("volume %q binding", v.ID_)) 185 } 186 return nil 187 } 188 189 func importVolumes(source map[string]interface{}) ([]*volume, error) { 190 checker := versionedChecker("volumes") 191 coerced, err := checker.Coerce(source, nil) 192 if err != nil { 193 return nil, errors.Annotatef(err, "volumes version schema check failed") 194 } 195 valid := coerced.(map[string]interface{}) 196 197 version := int(valid["version"].(int64)) 198 importFunc, ok := volumeDeserializationFuncs[version] 199 if !ok { 200 return nil, errors.NotValidf("version %d", version) 201 } 202 sourceList := valid["volumes"].([]interface{}) 203 return importVolumeList(sourceList, importFunc) 204 } 205 206 func importVolumeList(sourceList []interface{}, importFunc volumeDeserializationFunc) ([]*volume, error) { 207 result := make([]*volume, 0, len(sourceList)) 208 for i, value := range sourceList { 209 source, ok := value.(map[string]interface{}) 210 if !ok { 211 return nil, errors.Errorf("unexpected value for volume %d, %T", i, value) 212 } 213 volume, err := importFunc(source) 214 if err != nil { 215 return nil, errors.Annotatef(err, "volume %d", i) 216 } 217 result = append(result, volume) 218 } 219 return result, nil 220 } 221 222 type volumeDeserializationFunc func(map[string]interface{}) (*volume, error) 223 224 var volumeDeserializationFuncs = map[int]volumeDeserializationFunc{ 225 1: importVolumeV1, 226 } 227 228 func importVolumeV1(source map[string]interface{}) (*volume, error) { 229 fields := schema.Fields{ 230 "id": schema.String(), 231 "storage-id": schema.String(), 232 "binding": schema.String(), 233 "provisioned": schema.Bool(), 234 "size": schema.ForceUint(), 235 "pool": schema.String(), 236 "hardware-id": schema.String(), 237 "volume-id": schema.String(), 238 "persistent": schema.Bool(), 239 "status": schema.StringMap(schema.Any()), 240 "attachments": schema.StringMap(schema.Any()), 241 } 242 243 defaults := schema.Defaults{ 244 "storage-id": "", 245 "binding": "", 246 "pool": "", 247 "hardware-id": "", 248 "volume-id": "", 249 "attachments": schema.Omit, 250 } 251 addStatusHistorySchema(fields) 252 checker := schema.FieldMap(fields, defaults) 253 254 coerced, err := checker.Coerce(source, nil) 255 if err != nil { 256 return nil, errors.Annotatef(err, "volume v1 schema check failed") 257 } 258 valid := coerced.(map[string]interface{}) 259 // From here we know that the map returned from the schema coercion 260 // contains fields of the right type. 261 result := &volume{ 262 ID_: valid["id"].(string), 263 StorageID_: valid["storage-id"].(string), 264 Binding_: valid["binding"].(string), 265 Provisioned_: valid["provisioned"].(bool), 266 Size_: valid["size"].(uint64), 267 Pool_: valid["pool"].(string), 268 HardwareID_: valid["hardware-id"].(string), 269 VolumeID_: valid["volume-id"].(string), 270 Persistent_: valid["persistent"].(bool), 271 StatusHistory_: newStatusHistory(), 272 } 273 if err := result.importStatusHistory(valid); err != nil { 274 return nil, errors.Trace(err) 275 } 276 277 status, err := importStatus(valid["status"].(map[string]interface{})) 278 if err != nil { 279 return nil, errors.Trace(err) 280 } 281 result.Status_ = status 282 283 attachments, err := importVolumeAttachments(valid["attachments"].(map[string]interface{})) 284 if err != nil { 285 return nil, errors.Trace(err) 286 } 287 result.setAttachments(attachments) 288 289 return result, nil 290 } 291 292 // VolumeAttachmentArgs is an argument struct used to add information about the 293 // cloud instance to a Volume. 294 type VolumeAttachmentArgs struct { 295 Machine names.MachineTag 296 Provisioned bool 297 ReadOnly bool 298 DeviceName string 299 DeviceLink string 300 BusAddress string 301 } 302 303 func newVolumeAttachment(args VolumeAttachmentArgs) *volumeAttachment { 304 return &volumeAttachment{ 305 MachineID_: args.Machine.Id(), 306 Provisioned_: args.Provisioned, 307 ReadOnly_: args.ReadOnly, 308 DeviceName_: args.DeviceName, 309 DeviceLink_: args.DeviceLink, 310 BusAddress_: args.BusAddress, 311 } 312 } 313 314 // Machine implements VolumeAttachment 315 func (a *volumeAttachment) Machine() names.MachineTag { 316 return names.NewMachineTag(a.MachineID_) 317 } 318 319 // Provisioned implements VolumeAttachment 320 func (a *volumeAttachment) Provisioned() bool { 321 return a.Provisioned_ 322 } 323 324 // ReadOnly implements VolumeAttachment 325 func (a *volumeAttachment) ReadOnly() bool { 326 return a.ReadOnly_ 327 } 328 329 // DeviceName implements VolumeAttachment 330 func (a *volumeAttachment) DeviceName() string { 331 return a.DeviceName_ 332 } 333 334 // DeviceLink implements VolumeAttachment 335 func (a *volumeAttachment) DeviceLink() string { 336 return a.DeviceLink_ 337 } 338 339 // BusAddress implements VolumeAttachment 340 func (a *volumeAttachment) BusAddress() string { 341 return a.BusAddress_ 342 } 343 344 func importVolumeAttachments(source map[string]interface{}) ([]*volumeAttachment, error) { 345 checker := versionedChecker("attachments") 346 coerced, err := checker.Coerce(source, nil) 347 if err != nil { 348 return nil, errors.Annotatef(err, "volume attachments version schema check failed") 349 } 350 valid := coerced.(map[string]interface{}) 351 352 version := int(valid["version"].(int64)) 353 importFunc, ok := volumeAttachmentDeserializationFuncs[version] 354 if !ok { 355 return nil, errors.NotValidf("version %d", version) 356 } 357 sourceList := valid["attachments"].([]interface{}) 358 return importVolumeAttachmentList(sourceList, importFunc) 359 } 360 361 func importVolumeAttachmentList(sourceList []interface{}, importFunc volumeAttachmentDeserializationFunc) ([]*volumeAttachment, error) { 362 result := make([]*volumeAttachment, 0, len(sourceList)) 363 for i, value := range sourceList { 364 source, ok := value.(map[string]interface{}) 365 if !ok { 366 return nil, errors.Errorf("unexpected value for volumeAttachment %d, %T", i, value) 367 } 368 volumeAttachment, err := importFunc(source) 369 if err != nil { 370 return nil, errors.Annotatef(err, "volumeAttachment %d", i) 371 } 372 result = append(result, volumeAttachment) 373 } 374 return result, nil 375 } 376 377 type volumeAttachmentDeserializationFunc func(map[string]interface{}) (*volumeAttachment, error) 378 379 var volumeAttachmentDeserializationFuncs = map[int]volumeAttachmentDeserializationFunc{ 380 1: importVolumeAttachmentV1, 381 } 382 383 func importVolumeAttachmentV1(source map[string]interface{}) (*volumeAttachment, error) { 384 fields := schema.Fields{ 385 "machine-id": schema.String(), 386 "provisioned": schema.Bool(), 387 "read-only": schema.Bool(), 388 "device-name": schema.String(), 389 "device-link": schema.String(), 390 "bus-address": schema.String(), 391 } 392 checker := schema.FieldMap(fields, nil) // no defaults 393 394 coerced, err := checker.Coerce(source, nil) 395 if err != nil { 396 return nil, errors.Annotatef(err, "volumeAttachment v1 schema check failed") 397 } 398 valid := coerced.(map[string]interface{}) 399 // From here we know that the map returned from the schema coercion 400 // contains fields of the right type. 401 402 result := &volumeAttachment{ 403 MachineID_: valid["machine-id"].(string), 404 Provisioned_: valid["provisioned"].(bool), 405 ReadOnly_: valid["read-only"].(bool), 406 DeviceName_: valid["device-name"].(string), 407 DeviceLink_: valid["device-link"].(string), 408 BusAddress_: valid["bus-address"].(string), 409 } 410 return result, nil 411 }