diff --git a/blockdevice.go b/blockdevice.go index 51319a4..e6d79fa 100644 --- a/blockdevice.go +++ b/blockdevice.go @@ -15,6 +15,7 @@ type BlockDevice interface { Label() string UUID() string HardwareID() string + SerialID() string WWN() string BusAddress() string Size() uint64 @@ -40,6 +41,7 @@ type blockdevice struct { Label_ string `yaml:"label,omitempty"` UUID_ string `yaml:"uuid,omitempty"` HardwareID_ string `yaml:"hardware-id,omitempty"` + SerialID_ string `yaml:"serial-id,omitempty"` WWN_ string `yaml:"wwn,omitempty"` BusAddress_ string `yaml:"bus-address,omitempty"` Size_ uint64 `yaml:"size"` @@ -57,6 +59,7 @@ type BlockDeviceArgs struct { HardwareID string WWN string BusAddress string + SerialID string Size uint64 FilesystemType string InUse bool @@ -70,6 +73,7 @@ func newBlockDevice(args BlockDeviceArgs) *blockdevice { Label_: args.Label, UUID_: args.UUID, HardwareID_: args.HardwareID, + SerialID_: args.SerialID, WWN_: args.WWN, BusAddress_: args.BusAddress, Size_: args.Size, @@ -106,6 +110,11 @@ func (b *blockdevice) HardwareID() string { return b.HardwareID_ } +// SerialID implements BlockDevice. +func (b *blockdevice) SerialID() string { + return b.SerialID_ +} + // WWN implements BlockDevice. func (b *blockdevice) WWN() string { return b.WWN_ @@ -173,9 +182,10 @@ type blockdeviceDeserializationFunc func(map[string]interface{}) (*blockdevice, var blockdeviceDeserializationFuncs = map[int]blockdeviceDeserializationFunc{ 1: importBlockDeviceV1, + 2: importBlockDeviceV2, } -func importBlockDeviceV1(source map[string]interface{}) (*blockdevice, error) { +func blockDeviceV1Fields() (schema.Fields, schema.Defaults) { fields := schema.Fields{ "name": schema.String(), "links": schema.List(schema.String()), @@ -200,6 +210,27 @@ func importBlockDeviceV1(source map[string]interface{}) (*blockdevice, error) { "fs-type": "", "mount-point": "", } + return fields, defaults +} + +func blockDeviceV2Fields() (schema.Fields, schema.Defaults) { + fields, defaults := blockDeviceV1Fields() + fields["serial-id"] = schema.String() + defaults["serial-id"] = "" + return fields, defaults +} + +func importBlockDeviceV1(source map[string]interface{}) (*blockdevice, error) { + fields, defaults := blockDeviceV1Fields() + return importBlockDevice(fields, defaults, 1, source) +} + +func importBlockDeviceV2(source map[string]interface{}) (*blockdevice, error) { + fields, defaults := blockDeviceV2Fields() + return importBlockDevice(fields, defaults, 2, source) +} + +func importBlockDevice(fields schema.Fields, defaults schema.Defaults, importVersion int, source map[string]interface{}) (*blockdevice, error) { checker := schema.FieldMap(fields, defaults) coerced, err := checker.Coerce(source, nil) @@ -223,5 +254,9 @@ func importBlockDeviceV1(source map[string]interface{}) (*blockdevice, error) { MountPoint_: valid["mount-point"].(string), } + if importVersion >= 2 { + result.SerialID_ = valid["serial-id"].(string) + } + return result, nil } diff --git a/blockdevice_test.go b/blockdevice_test.go index 96fd25d..db4e3a6 100644 --- a/blockdevice_test.go +++ b/blockdevice_test.go @@ -34,6 +34,7 @@ func allBlockDeviceArgs() BlockDeviceArgs { Label: "sda", UUID: "some-uuid", HardwareID: "magic", + SerialID: "coco-pops", WWN: "drbr", BusAddress: "bus stop", Size: 16 * 1024 * 1024 * 1024, @@ -50,6 +51,7 @@ func (s *BlockDeviceSerializationSuite) TestNewBlockDevice(c *gc.C) { c.Check(d.Label(), gc.Equals, "sda") c.Check(d.UUID(), gc.Equals, "some-uuid") c.Check(d.HardwareID(), gc.Equals, "magic") + c.Check(d.SerialID(), gc.Equals, "coco-pops") c.Check(d.WWN(), gc.Equals, "drbr") c.Check(d.BusAddress(), gc.Equals, "bus stop") c.Check(d.Size(), gc.Equals, uint64(16*1024*1024*1024)) @@ -58,9 +60,9 @@ func (s *BlockDeviceSerializationSuite) TestNewBlockDevice(c *gc.C) { c.Check(d.MountPoint(), gc.Equals, "/") } -func (s *BlockDeviceSerializationSuite) exportImport(c *gc.C, dev *blockdevice) *blockdevice { +func (s *BlockDeviceSerializationSuite) exportImport(c *gc.C, dev *blockdevice, version int) *blockdevice { initial := blockdevices{ - Version: 1, + Version: version, BlockDevices_: []*blockdevice{dev}, } @@ -77,9 +79,20 @@ func (s *BlockDeviceSerializationSuite) exportImport(c *gc.C, dev *blockdevice) return devices[0] } +func (s *BlockDeviceSerializationSuite) exportImportLatest(c *gc.C, dev *blockdevice) *blockdevice { + return s.exportImport(c, dev, 2) +} + func (s *BlockDeviceSerializationSuite) TestParsingSerializedData(c *gc.C) { initial := newBlockDevice(allBlockDeviceArgs()) - imported := s.exportImport(c, initial) + imported := s.exportImportLatest(c, initial) + c.Assert(imported, jc.DeepEquals, initial) +} + +func (s *BlockDeviceSerializationSuite) TestV1ParsingReturnsLatest(c *gc.C) { + initial := newBlockDevice(allBlockDeviceArgs()) + imported := s.exportImport(c, initial, 1) + initial.SerialID_ = "" c.Assert(imported, jc.DeepEquals, initial) } @@ -91,7 +104,7 @@ func (s *BlockDeviceSerializationSuite) TestImportEmpty(c *gc.C) { func emptyBlockDeviceMap() map[interface{}]interface{} { return map[interface{}]interface{}{ - "version": 1, + "version": 2, "block-devices": []interface{}{}, } } diff --git a/machine.go b/machine.go index 94e3457..b25a36d 100644 --- a/machine.go +++ b/machine.go @@ -318,7 +318,7 @@ func (m *machine) AddBlockDevice(args BlockDeviceArgs) BlockDevice { func (m *machine) setBlockDevices(devices []*blockdevice) { m.BlockDevices_ = blockdevices{ - Version: 1, + Version: 2, BlockDevices_: devices, } } diff --git a/model.go b/model.go index d5e50d1..a772f23 100644 --- a/model.go +++ b/model.go @@ -4,6 +4,7 @@ package description import ( + "fmt" "net" "sort" "strings" @@ -136,6 +137,8 @@ type Model interface { MeterStatus() MeterStatus PasswordHash() string + + AddBlockDevice(string, BlockDeviceArgs) error } // ModelArgs represent the bare minimum information that is needed @@ -431,6 +434,18 @@ func (m *model) setMachines(machineList []*machine) { } } +// AddBlockDevice adds a block device for the specified machine. +func (m *model) AddBlockDevice(machineId string, bdArgs BlockDeviceArgs) error { + for i := range m.Machines_.Machines_ { + if m.Machines_.Machines_[i].Id_ != machineId { + continue + } + m.Machines_.Machines_[i].AddBlockDevice(bdArgs) + return nil + } + return fmt.Errorf("machine %q %w", machineId, errors.NotFound) +} + // Applications implements Model. func (m *model) Applications() []Application { var result []Application diff --git a/model_test.go b/model_test.go index b640554..241b827 100644 --- a/model_test.go +++ b/model_test.go @@ -324,6 +324,51 @@ func (s *ModelSerializationSuite) addMachineToModel(model Model, id string) Mach return machine } +func (s *ModelSerializationSuite) TestAddBlockDevices(c *gc.C) { + model := s.newModel(ModelArgs{Owner: names.NewUserTag("owner"), CloudRegion: "some-region"}) + model.AddMachine(MachineArgs{Id: names.NewMachineTag("666")}) + err := model.AddBlockDevice("666", BlockDeviceArgs{ + Name: "foo", + Links: []string{"a-link"}, + Label: "label", + UUID: "device-uuid", + HardwareID: "hardware-id", + WWN: "wwn", + BusAddress: "bus-address", + SerialID: "serial-id", + Size: 100, + FilesystemType: "ext4", + InUse: true, + MountPoint: "/path/to/here", + }) + c.Assert(err, jc.ErrorIsNil) + m := model.Machines() + c.Assert(m, gc.HasLen, 1) + blockDevices := m[0].BlockDevices() + c.Assert(blockDevices, gc.HasLen, 1) + bd := blockDevices[0] + c.Assert(bd.Name(), gc.Equals, "foo") + c.Assert(bd.Links(), jc.DeepEquals, []string{"a-link"}) + c.Assert(bd.Label(), gc.Equals, "label") + c.Assert(bd.UUID(), gc.Equals, "device-uuid") + c.Assert(bd.HardwareID(), gc.Equals, "hardware-id") + c.Assert(bd.WWN(), gc.Equals, "wwn") + c.Assert(bd.BusAddress(), gc.Equals, "bus-address") + c.Assert(bd.SerialID(), gc.Equals, "serial-id") + c.Assert(bd.Size(), gc.Equals, uint64(100)) + c.Assert(bd.FilesystemType(), gc.Equals, "ext4") + c.Assert(bd.InUse(), jc.IsTrue) + c.Assert(bd.MountPoint(), gc.Equals, "/path/to/here") +} + +func (s *ModelSerializationSuite) TestAddBlockDevicesMachineNotFound(c *gc.C) { + model := s.newModel(ModelArgs{Owner: names.NewUserTag("owner"), CloudRegion: "some-region"}) + err := model.AddBlockDevice("666", BlockDeviceArgs{ + Name: "foo", + }) + c.Assert(err, jc.ErrorIs, errors.NotFound) +} + func (s *ModelSerializationSuite) TestModelValidationChecksMachinesGood(c *gc.C) { model := s.newModel(ModelArgs{Owner: names.NewUserTag("owner"), CloudRegion: "some-region"}) s.addMachineToModel(model, "0")