From c736cd8e509424b988d8fec9f1cc6630ccbf03ad Mon Sep 17 00:00:00 2001 From: Tim Penhey Date: Fri, 15 Apr 2016 12:47:11 +1200 Subject: [PATCH 1/5] Initial block device parsing. --- blockdevice.go | 175 +++++++++++++++++++++++++++++++++++++++++++++++++ filesystem.go | 108 ++++++++++++++++++++++++++++++ interfaces.go | 46 +++++++++++++ machine.go | 41 ++++++++++-- partition.go | 139 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 505 insertions(+), 4 deletions(-) create mode 100644 blockdevice.go create mode 100644 filesystem.go create mode 100644 partition.go diff --git a/blockdevice.go b/blockdevice.go new file mode 100644 index 0000000..b1df76a --- /dev/null +++ b/blockdevice.go @@ -0,0 +1,175 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package gomaasapi + +import ( + "github.com/juju/errors" + "github.com/juju/schema" + "github.com/juju/version" +) + +type blockdevice struct { + resourceURI string + + id int + name string + model string + path string + usedFor string + tags []string + + blockSize int + usedSize int + size int + + partitions []*partition +} + +// ID implements BlockDevice. +func (b *blockdevice) ID() int { + return b.id +} + +// Name implements BlockDevice. +func (b *blockdevice) Name() string { + return b.name +} + +// Model implements BlockDevice. +func (b *blockdevice) Model() string { + return b.model +} + +// Path implements BlockDevice. +func (b *blockdevice) Path() string { + return b.path +} + +// UsedFor implements BlockDevice. +func (b *blockdevice) UsedFor() string { + return b.usedFor +} + +// Tags implements BlockDevice. +func (b *blockdevice) Tags() []string { + return b.tags +} + +// BlockSize implements BlockDevice. +func (b *blockdevice) BlockSize() int { + return b.blockSize +} + +// UsedSize implements BlockDevice. +func (b *blockdevice) UsedSize() int { + return b.usedSize +} + +// Size implements BlockDevice. +func (b *blockdevice) Size() int { + return b.size +} + +// Partitions implements BlockDevice. +func (b *blockdevice) Partitions() []Partition { + result := make([]Partition, len(b.partitions)) + for i, v := range b.partitions { + result[i] = v + } + return result +} + +func readBlockDevices(controllerVersion version.Number, source interface{}) ([]*blockdevice, error) { + checker := schema.List(schema.StringMap(schema.Any())) + coerced, err := checker.Coerce(source, nil) + if err != nil { + return nil, WrapWithDeserializationError(err, "blockdevice base schema check failed") + } + valid := coerced.([]interface{}) + + var deserialisationVersion version.Number + for v := range blockdeviceDeserializationFuncs { + if v.Compare(deserialisationVersion) > 0 && v.Compare(controllerVersion) <= 0 { + deserialisationVersion = v + } + } + if deserialisationVersion == version.Zero { + return nil, NewUnsupportedVersionError("no blockdevice read func for version %s", controllerVersion) + } + readFunc := blockdeviceDeserializationFuncs[deserialisationVersion] + return readBlockDeviceList(valid, readFunc) +} + +// readBlockDeviceList expects the values of the sourceList to be string maps. +func readBlockDeviceList(sourceList []interface{}, readFunc blockdeviceDeserializationFunc) ([]*blockdevice, error) { + result := make([]*blockdevice, 0, len(sourceList)) + for i, value := range sourceList { + source, ok := value.(map[string]interface{}) + if !ok { + return nil, NewDeserializationError("unexpected value for blockdevice %d, %T", i, value) + } + blockdevice, err := readFunc(source) + if err != nil { + return nil, errors.Annotatef(err, "blockdevice %d", i) + } + result = append(result, blockdevice) + } + return result, nil +} + +type blockdeviceDeserializationFunc func(map[string]interface{}) (*blockdevice, error) + +var blockdeviceDeserializationFuncs = map[version.Number]blockdeviceDeserializationFunc{ + twoDotOh: blockdevice_2_0, +} + +func blockdevice_2_0(source map[string]interface{}) (*blockdevice, error) { + fields := schema.Fields{ + "resource_uri": schema.String(), + + "id": schema.ForceInt(), + "name": schema.String(), + "model": schema.String(), + "path": schema.String(), + "used_for": schema.String(), + "tags": schema.List(schema.String()), + + "block_size": schema.ForceInt(), + "used_size": schema.ForceInt(), + "size": schema.ForceInt(), + + "partitions": schema.List(schema.StringMap(schema.Any())), + } + checker := schema.FieldMap(fields, nil) + coerced, err := checker.Coerce(source, nil) + if err != nil { + return nil, WrapWithDeserializationError(err, "blockdevice 2.0 schema check failed") + } + valid := coerced.(map[string]interface{}) + // From here we know that the map returned from the schema coercion + // contains fields of the right type. + + partitions, err := readPartitionList(valid["partitions"].([]interface{}), partition_2_0) + if err != nil { + return nil, errors.Trace(err) + } + + result := &blockdevice{ + resourceURI: valid["resource_uri"].(string), + + id: valid["id"].(int), + name: valid["name"].(string), + model: valid["model"].(string), + path: valid["path"].(string), + usedFor: valid["used_for"].(string), + tags: convertToStringSlice(valid["tags"]), + + blockSize: valid["size"].(int), + usedSize: valid["size"].(int), + size: valid["size"].(int), + + partitions: partitions, + } + return result, nil +} diff --git a/filesystem.go b/filesystem.go new file mode 100644 index 0000000..79a2cf2 --- /dev/null +++ b/filesystem.go @@ -0,0 +1,108 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package gomaasapi + +import ( + "github.com/juju/errors" + "github.com/juju/schema" + "github.com/juju/version" +) + +type filesystem struct { + fstype string + mountPoint string + label string + uuid string + // no idea what the mount_options are as a value type, so ignoring for now. +} + +// Type implements FileSystem. +func (f *filesystem) Type() string { + return f.fstype +} + +// MountPoint implements FileSystem. +func (f *filesystem) MountPoint() string { + return f.mountPoint +} + +// Label implements FileSystem. +func (f *filesystem) Label() string { + return f.label +} + +// UUID implements FileSystem. +func (f *filesystem) UUID() string { + return f.uuid +} + +func readFileSystems(controllerVersion version.Number, source interface{}) ([]*filesystem, error) { + checker := schema.List(schema.StringMap(schema.Any())) + coerced, err := checker.Coerce(source, nil) + if err != nil { + return nil, WrapWithDeserializationError(err, "filesystem base schema check failed") + } + valid := coerced.([]interface{}) + + var deserialisationVersion version.Number + for v := range filesystemDeserializationFuncs { + if v.Compare(deserialisationVersion) > 0 && v.Compare(controllerVersion) <= 0 { + deserialisationVersion = v + } + } + if deserialisationVersion == version.Zero { + return nil, NewUnsupportedVersionError("no filesystem read func for version %s", controllerVersion) + } + readFunc := filesystemDeserializationFuncs[deserialisationVersion] + return readFileSystemList(valid, readFunc) +} + +// readFileSystemList expects the values of the sourceList to be string maps. +func readFileSystemList(sourceList []interface{}, readFunc filesystemDeserializationFunc) ([]*filesystem, error) { + result := make([]*filesystem, 0, len(sourceList)) + for i, value := range sourceList { + source, ok := value.(map[string]interface{}) + if !ok { + return nil, NewDeserializationError("unexpected value for filesystem %d, %T", i, value) + } + filesystem, err := readFunc(source) + if err != nil { + return nil, errors.Annotatef(err, "filesystem %d", i) + } + result = append(result, filesystem) + } + return result, nil +} + +type filesystemDeserializationFunc func(map[string]interface{}) (*filesystem, error) + +var filesystemDeserializationFuncs = map[version.Number]filesystemDeserializationFunc{ + twoDotOh: filesystem_2_0, +} + +func filesystem_2_0(source map[string]interface{}) (*filesystem, error) { + fields := schema.Fields{ + "fstype": schema.String(), + "mount_point": schema.String(), + "label": schema.String(), + "uuid": schema.String(), + // TODO: mount_options when we know the type. + } + checker := schema.FieldMap(fields, nil) + coerced, err := checker.Coerce(source, nil) + if err != nil { + return nil, WrapWithDeserializationError(err, "filesystem 2.0 schema check failed") + } + valid := coerced.(map[string]interface{}) + // From here we know that the map returned from the schema coercion + // contains fields of the right type. + + result := &filesystem{ + fstype: valid["fstype"].(string), + mountPoint: valid["mount_point"].(string), + label: valid["label"].(string), + uuid: valid["uuid"].(string), + } + return result, nil +} diff --git a/interfaces.go b/interfaces.go index ccc2646..7eafd7e 100644 --- a/interfaces.go +++ b/interfaces.go @@ -210,6 +210,9 @@ type Machine interface { // specified. If there is no match, nil is returned. Interface(id int) Interface + PhysicalBlockDevices() []BlockDevice + BlockDevices() []BlockDevice + Zone() Zone // Start the machine and install the operating system specified in the args. @@ -287,3 +290,46 @@ type Link interface { // If unavailble, the address will be empty. IPAddress() string } + +// FileSystem represents a formatted filesystem mounted at a location. +type FileSystem interface { + // Type is the format type, e.g. "ext4". + Type() string + + MountPoint() string + Label() string + UUID() string +} + +// Partition represents a partition of a block device. It may be mounted +// as a filesystem. +type Partition interface { + ID() int + Path() string + // FileSystem may be nil if not mounted. + FileSystem() FileSystem + UUID() string + // UsedFor is a human readable string. + UsedFor() string + // Size is the number of bytes in the partition. + Size() int +} + +// BlockDevice represents an entire block device on the machine. +type BlockDevice interface { + ID() int + Name() string + Model() string + Path() string + UsedFor() string + Tags() []string + + BlockSize() int + UsedSize() int + Size() int + + Partitions() []Partition + + // There are some other attributes for block devices, but we can + // expose them on an as needed basis. +} diff --git a/machine.go b/machine.go index 5e03dee..ba181b4 100644 --- a/machine.go +++ b/machine.go @@ -38,6 +38,9 @@ type machine struct { bootInterface *interface_ interfaceSet []*interface_ zone *zone + // Don't really know the difference between these two lists: + physicalBlockDevices []*blockdevice + blockDevices []*blockdevice } func (m *machine) updateFrom(other *machine) { @@ -154,6 +157,24 @@ func (m *machine) StatusMessage() string { return m.statusMessage } +// PhysicalBlockDevices implements Machine. +func (m *machine) PhysicalBlockDevices() []BlockDevice { + result := make([]BlockDevice, len(m.physicalBlockDevices)) + for i, v := range m.physicalBlockDevices { + result[i] = v + } + return result +} + +// BlockDevices implements Machine. +func (m *machine) BlockDevices() []BlockDevice { + result := make([]BlockDevice, len(m.blockDevices)) + for i, v := range m.blockDevices { + result[i] = v + } + return result +} + // Devices implements Machine. func (m *machine) Devices(args DevicesArgs) ([]Device, error) { // Perhaps in the future, MAAS will give us a way to query just for the @@ -301,8 +322,10 @@ func machine_2_0(source map[string]interface{}) (*machine, error) { "boot_interface": schema.StringMap(schema.Any()), "interface_set": schema.List(schema.StringMap(schema.Any())), + "zone": schema.StringMap(schema.Any()), - "zone": schema.StringMap(schema.Any()), + "physicalblockdevice_set": schema.List(schema.StringMap(schema.Any())), + "blockdevice_set": schema.List(schema.StringMap(schema.Any())), } checker := schema.FieldMap(fields, nil) // no defaults coerced, err := checker.Coerce(source, nil) @@ -325,6 +348,14 @@ func machine_2_0(source map[string]interface{}) (*machine, error) { if err != nil { return nil, errors.Trace(err) } + physicalBlockDevices, err := readBlockDeviceList(valid["physicalblockdevice_set"].([]interface{}), blockdevice_2_0) + if err != nil { + return nil, errors.Trace(err) + } + blockDevices, err := readBlockDeviceList(valid["blockdevice_set"].([]interface{}), blockdevice_2_0) + if err != nil { + return nil, errors.Trace(err) + } result := &machine{ resourceURI: valid["resource_uri"].(string), @@ -345,9 +376,11 @@ func machine_2_0(source map[string]interface{}) (*machine, error) { statusName: valid["status_name"].(string), statusMessage: valid["status_message"].(string), - bootInterface: bootInterface, - interfaceSet: interfaceSet, - zone: zone, + bootInterface: bootInterface, + interfaceSet: interfaceSet, + zone: zone, + physicalBlockDevices: physicalBlockDevices, + blockDevices: blockDevices, } return result, nil diff --git a/partition.go b/partition.go new file mode 100644 index 0000000..ba9895c --- /dev/null +++ b/partition.go @@ -0,0 +1,139 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package gomaasapi + +import ( + "github.com/juju/errors" + "github.com/juju/schema" + "github.com/juju/version" +) + +type partition struct { + resourceURI string + + id int + path string + uuid string + + usedFor string + size int + + filesystem *filesystem +} + +// ID implements Partition. +func (p *partition) ID() int { + return p.id +} + +// Path implements Partition. +func (p *partition) Path() string { + return p.path +} + +// FileSystem implements Partition. +func (p *partition) FileSystem() FileSystem { + return p.filesystem +} + +// UUID implements Partition. +func (p *partition) UUID() string { + return p.uuid +} + +// UsedFor implements Partition. +func (p *partition) UsedFor() string { + return p.usedFor +} + +// Size implements Partition. +func (p *partition) Size() int { + return p.size +} + +func readPartitions(controllerVersion version.Number, source interface{}) ([]*partition, error) { + checker := schema.List(schema.StringMap(schema.Any())) + coerced, err := checker.Coerce(source, nil) + if err != nil { + return nil, WrapWithDeserializationError(err, "partition base schema check failed") + } + valid := coerced.([]interface{}) + + var deserialisationVersion version.Number + for v := range partitionDeserializationFuncs { + if v.Compare(deserialisationVersion) > 0 && v.Compare(controllerVersion) <= 0 { + deserialisationVersion = v + } + } + if deserialisationVersion == version.Zero { + return nil, NewUnsupportedVersionError("no partition read func for version %s", controllerVersion) + } + readFunc := partitionDeserializationFuncs[deserialisationVersion] + return readPartitionList(valid, readFunc) +} + +// readPartitionList expects the values of the sourceList to be string maps. +func readPartitionList(sourceList []interface{}, readFunc partitionDeserializationFunc) ([]*partition, error) { + result := make([]*partition, 0, len(sourceList)) + for i, value := range sourceList { + source, ok := value.(map[string]interface{}) + if !ok { + return nil, NewDeserializationError("unexpected value for partition %d, %T", i, value) + } + partition, err := readFunc(source) + if err != nil { + return nil, errors.Annotatef(err, "partition %d", i) + } + result = append(result, partition) + } + return result, nil +} + +type partitionDeserializationFunc func(map[string]interface{}) (*partition, error) + +var partitionDeserializationFuncs = map[version.Number]partitionDeserializationFunc{ + twoDotOh: partition_2_0, +} + +func partition_2_0(source map[string]interface{}) (*partition, error) { + fields := schema.Fields{ + "resource_uri": schema.String(), + + "id": schema.ForceInt(), + "path": schema.String(), + "uuid": schema.String(), + + "used_for": schema.String(), + "size": schema.ForceInt(), + + "filesystem": schema.OneOf(schema.Nil(""), schema.StringMap(schema.Any())), + } + checker := schema.FieldMap(fields, nil) + coerced, err := checker.Coerce(source, nil) + if err != nil { + return nil, WrapWithDeserializationError(err, "partition 2.0 schema check failed") + } + valid := coerced.(map[string]interface{}) + // From here we know that the map returned from the schema coercion + // contains fields of the right type. + + var filesystem *filesystem + if fsSource := valid["filesystem"]; fsSource != nil { + filesystem, err = filesystem_2_0(fsSource.(map[string]interface{})) + if err != nil { + return nil, errors.Trace(err) + } + } + + result := &partition{ + resourceURI: valid["resource_uri"].(string), + id: valid["id"].(int), + path: valid["path"].(string), + uuid: valid["uuid"].(string), + usedFor: valid["used_for"].(string), + size: valid["size"].(int), + filesystem: filesystem, + } + return result, nil +} From 608c4170f14ac83f057e5d82f1d2c7bd9cf4485f Mon Sep 17 00:00:00 2001 From: Tim Penhey Date: Fri, 15 Apr 2016 14:21:33 +1200 Subject: [PATCH 2/5] Tests for the filesystem. --- filesystem.go | 53 ++++------------------------------------------ filesystem_test.go | 38 +++++++++++++++++++++++++++++++++ partition.go | 2 +- 3 files changed, 43 insertions(+), 50 deletions(-) create mode 100644 filesystem_test.go diff --git a/filesystem.go b/filesystem.go index 79a2cf2..b1896f8 100644 --- a/filesystem.go +++ b/filesystem.go @@ -3,11 +3,7 @@ package gomaasapi -import ( - "github.com/juju/errors" - "github.com/juju/schema" - "github.com/juju/version" -) +import "github.com/juju/schema" type filesystem struct { fstype string @@ -37,51 +33,10 @@ func (f *filesystem) UUID() string { return f.uuid } -func readFileSystems(controllerVersion version.Number, source interface{}) ([]*filesystem, error) { - checker := schema.List(schema.StringMap(schema.Any())) - coerced, err := checker.Coerce(source, nil) - if err != nil { - return nil, WrapWithDeserializationError(err, "filesystem base schema check failed") - } - valid := coerced.([]interface{}) - - var deserialisationVersion version.Number - for v := range filesystemDeserializationFuncs { - if v.Compare(deserialisationVersion) > 0 && v.Compare(controllerVersion) <= 0 { - deserialisationVersion = v - } - } - if deserialisationVersion == version.Zero { - return nil, NewUnsupportedVersionError("no filesystem read func for version %s", controllerVersion) - } - readFunc := filesystemDeserializationFuncs[deserialisationVersion] - return readFileSystemList(valid, readFunc) -} - -// readFileSystemList expects the values of the sourceList to be string maps. -func readFileSystemList(sourceList []interface{}, readFunc filesystemDeserializationFunc) ([]*filesystem, error) { - result := make([]*filesystem, 0, len(sourceList)) - for i, value := range sourceList { - source, ok := value.(map[string]interface{}) - if !ok { - return nil, NewDeserializationError("unexpected value for filesystem %d, %T", i, value) - } - filesystem, err := readFunc(source) - if err != nil { - return nil, errors.Annotatef(err, "filesystem %d", i) - } - result = append(result, filesystem) - } - return result, nil -} - -type filesystemDeserializationFunc func(map[string]interface{}) (*filesystem, error) - -var filesystemDeserializationFuncs = map[version.Number]filesystemDeserializationFunc{ - twoDotOh: filesystem_2_0, -} +// There is no need for controller based parsing of filesystems until we need it. +// Currently the filesystem reading is only called by the Partition parsing. -func filesystem_2_0(source map[string]interface{}) (*filesystem, error) { +func filesystem2_0(source map[string]interface{}) (*filesystem, error) { fields := schema.Fields{ "fstype": schema.String(), "mount_point": schema.String(), diff --git a/filesystem_test.go b/filesystem_test.go new file mode 100644 index 0000000..6959e6c --- /dev/null +++ b/filesystem_test.go @@ -0,0 +1,38 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package gomaasapi + +import ( + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" +) + +type filesystemSuite struct{} + +var _ = gc.Suite(&filesystemSuite{}) + +func (*filesystemSuite) TestParse2_0(c *gc.C) { + source := map[string]interface{}{ + "fstype": "ext4", + "mount_point": "/", + "label": "root", + "uuid": "fake-uuid", + } + fs, err := filesystem2_0(source) + c.Assert(err, jc.ErrorIsNil) + c.Check(fs.Type(), gc.Equals, "ext4") + c.Check(fs.MountPoint(), gc.Equals, "/") + c.Check(fs.Label(), gc.Equals, "root") + c.Check(fs.UUID(), gc.Equals, "fake-uuid") +} + +func (*filesystemSuite) TestParse2_0BadSchema(c *gc.C) { + source := map[string]interface{}{ + "mount_point": "/", + "label": "root", + "uuid": "fake-uuid", + } + _, err := filesystem2_0(source) + c.Assert(err, jc.Satisfies, IsDeserializationError) +} diff --git a/partition.go b/partition.go index ba9895c..9ee3891 100644 --- a/partition.go +++ b/partition.go @@ -120,7 +120,7 @@ func partition_2_0(source map[string]interface{}) (*partition, error) { var filesystem *filesystem if fsSource := valid["filesystem"]; fsSource != nil { - filesystem, err = filesystem_2_0(fsSource.(map[string]interface{})) + filesystem, err = filesystem2_0(fsSource.(map[string]interface{})) if err != nil { return nil, errors.Trace(err) } From e145ecc612d71fab5491637344bcdc90605ccb11 Mon Sep 17 00:00:00 2001 From: Tim Penhey Date: Fri, 15 Apr 2016 14:32:16 +1200 Subject: [PATCH 3/5] Test for partitions. --- partition_test.go | 71 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 partition_test.go diff --git a/partition_test.go b/partition_test.go new file mode 100644 index 0000000..bc96796 --- /dev/null +++ b/partition_test.go @@ -0,0 +1,71 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package gomaasapi + +import ( + jc "github.com/juju/testing/checkers" + "github.com/juju/version" + gc "gopkg.in/check.v1" +) + +type partitionSuite struct{} + +var _ = gc.Suite(&partitionSuite{}) + +func (*partitionSuite) TestReadPartitionsBadSchema(c *gc.C) { + _, err := readPartitions(twoDotOh, "wat?") + c.Check(err, jc.Satisfies, IsDeserializationError) + c.Assert(err.Error(), gc.Equals, `partition base schema check failed: expected list, got string("wat?")`) +} + +func (*partitionSuite) TestReadPartitions(c *gc.C) { + partitions, err := readPartitions(twoDotOh, parseJSON(c, partitionsResponse)) + c.Assert(err, jc.ErrorIsNil) + c.Assert(partitions, gc.HasLen, 1) + partition := partitions[0] + + c.Check(partition.ID(), gc.Equals, 1) + c.Check(partition.Path(), gc.Equals, "/dev/disk/by-dname/sda-part1") + c.Check(partition.UUID(), gc.Equals, "6199b7c9-b66f-40f6-a238-a938a58a0adf") + c.Check(partition.UsedFor(), gc.Equals, "ext4 formatted filesystem mounted at /") + c.Check(partition.Size(), gc.Equals, 8581545984) + + fs := partition.FileSystem() + c.Assert(fs, gc.NotNil) + c.Assert(fs.Type(), gc.Equals, "ext4") + c.Assert(fs.MountPoint(), gc.Equals, "/") +} + +func (*partitionSuite) TestLowVersion(c *gc.C) { + _, err := readPartitions(version.MustParse("1.9.0"), parseJSON(c, partitionsResponse)) + c.Assert(err, jc.Satisfies, IsUnsupportedVersionError) +} + +func (*partitionSuite) TestHighVersion(c *gc.C) { + partitions, err := readPartitions(version.MustParse("2.1.9"), parseJSON(c, partitionsResponse)) + c.Assert(err, jc.ErrorIsNil) + c.Assert(partitions, gc.HasLen, 1) +} + +var partitionsResponse = ` +[ + { + "bootable": false, + "id": 1, + "path": "/dev/disk/by-dname/sda-part1", + "filesystem": { + "fstype": "ext4", + "mount_point": "/", + "label": "root", + "mount_options": null, + "uuid": "fcd7745e-f1b5-4f5d-9575-9b0bb796b752" + }, + "type": "partition", + "resource_uri": "/MAAS/api/2.0/nodes/4y3ha3/blockdevices/34/partition/1", + "uuid": "6199b7c9-b66f-40f6-a238-a938a58a0adf", + "used_for": "ext4 formatted filesystem mounted at /", + "size": 8581545984 + } +] +` From c24f6589e564ce1cba1c5541620a772d61964524 Mon Sep 17 00:00:00 2001 From: Tim Penhey Date: Fri, 15 Apr 2016 15:06:12 +1200 Subject: [PATCH 4/5] Tests for block device. --- blockdevice.go | 4 +- blockdevice_test.go | 99 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 blockdevice_test.go diff --git a/blockdevice.go b/blockdevice.go index b1df76a..99f7a91 100644 --- a/blockdevice.go +++ b/blockdevice.go @@ -165,8 +165,8 @@ func blockdevice_2_0(source map[string]interface{}) (*blockdevice, error) { usedFor: valid["used_for"].(string), tags: convertToStringSlice(valid["tags"]), - blockSize: valid["size"].(int), - usedSize: valid["size"].(int), + blockSize: valid["block_size"].(int), + usedSize: valid["used_size"].(int), size: valid["size"].(int), partitions: partitions, diff --git a/blockdevice_test.go b/blockdevice_test.go new file mode 100644 index 0000000..f7e423e --- /dev/null +++ b/blockdevice_test.go @@ -0,0 +1,99 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package gomaasapi + +import ( + jc "github.com/juju/testing/checkers" + "github.com/juju/version" + gc "gopkg.in/check.v1" +) + +type blockdeviceSuite struct{} + +var _ = gc.Suite(&blockdeviceSuite{}) + +func (*blockdeviceSuite) TestReadBlockDevicesBadSchema(c *gc.C) { + _, err := readBlockDevices(twoDotOh, "wat?") + c.Check(err, jc.Satisfies, IsDeserializationError) + c.Assert(err.Error(), gc.Equals, `blockdevice base schema check failed: expected list, got string("wat?")`) +} + +func (*blockdeviceSuite) TestReadBlockDevices(c *gc.C) { + blockdevices, err := readBlockDevices(twoDotOh, parseJSON(c, blockdevicesResponse)) + c.Assert(err, jc.ErrorIsNil) + c.Assert(blockdevices, gc.HasLen, 1) + blockdevice := blockdevices[0] + + c.Check(blockdevice.ID(), gc.Equals, 34) + c.Check(blockdevice.Name(), gc.Equals, "sda") + c.Check(blockdevice.Model(), gc.Equals, "QEMU HARDDISK") + c.Check(blockdevice.Path(), gc.Equals, "/dev/disk/by-dname/sda") + c.Check(blockdevice.UsedFor(), gc.Equals, "MBR partitioned with 1 partition") + c.Check(blockdevice.Tags(), jc.DeepEquals, []string{"rotary"}) + c.Check(blockdevice.BlockSize(), gc.Equals, 4096) + c.Check(blockdevice.UsedSize(), gc.Equals, 8586788864) + c.Check(blockdevice.Size(), gc.Equals, 8589934592) + + partitions := blockdevice.Partitions() + c.Assert(partitions, gc.HasLen, 1) + partition := partitions[0] + c.Check(partition.ID(), gc.Equals, 1) + c.Check(partition.UsedFor(), gc.Equals, "ext4 formatted filesystem mounted at /") +} + +func (*blockdeviceSuite) TestLowVersion(c *gc.C) { + _, err := readBlockDevices(version.MustParse("1.9.0"), parseJSON(c, blockdevicesResponse)) + c.Assert(err, jc.Satisfies, IsUnsupportedVersionError) +} + +func (*blockdeviceSuite) TestHighVersion(c *gc.C) { + blockdevices, err := readBlockDevices(version.MustParse("2.1.9"), parseJSON(c, blockdevicesResponse)) + c.Assert(err, jc.ErrorIsNil) + c.Assert(blockdevices, gc.HasLen, 1) +} + +var blockdevicesResponse = ` +[ + { + "path": "/dev/disk/by-dname/sda", + "name": "sda", + "used_for": "MBR partitioned with 1 partition", + "partitions": [ + { + "bootable": false, + "id": 1, + "path": "/dev/disk/by-dname/sda-part1", + "filesystem": { + "fstype": "ext4", + "mount_point": "/", + "label": "root", + "mount_options": null, + "uuid": "fcd7745e-f1b5-4f5d-9575-9b0bb796b752" + }, + "type": "partition", + "resource_uri": "/MAAS/api/2.0/nodes/4y3ha3/blockdevices/34/partition/1", + "uuid": "6199b7c9-b66f-40f6-a238-a938a58a0adf", + "used_for": "ext4 formatted filesystem mounted at /", + "size": 8581545984 + } + ], + "filesystem": null, + "id_path": "/dev/disk/by-id/ata-QEMU_HARDDISK_QM00001", + "resource_uri": "/MAAS/api/2.0/nodes/4y3ha3/blockdevices/34/", + "id": 34, + "serial": "QM00001", + "type": "physical", + "block_size": 4096, + "used_size": 8586788864, + "available_size": 0, + "partition_table_type": "MBR", + "uuid": null, + "size": 8589934592, + "model": "QEMU HARDDISK", + "tags": [ + "rotary" + ] + } +] +` From 5c030bbdf0a0bcaa41474c81c227ecb3b6d5eb79 Mon Sep 17 00:00:00 2001 From: Tim Penhey Date: Fri, 15 Apr 2016 15:13:05 +1200 Subject: [PATCH 5/5] Tests for machine block device functions. --- interfaces.go | 6 ++++++ machine.go | 10 ++++++++++ machine_test.go | 12 ++++++++++++ 3 files changed, 28 insertions(+) diff --git a/interfaces.go b/interfaces.go index 8d58756..adb6a66 100644 --- a/interfaces.go +++ b/interfaces.go @@ -210,7 +210,13 @@ type Machine interface { // specified. If there is no match, nil is returned. Interface(id int) Interface + // PhysicalBlockDevices returns all the physical block devices on the machine. PhysicalBlockDevices() []BlockDevice + // PhysicalBlockDevice returns the physical block device for the machine + // that matches the id specified. If there is no match, nil is returned. + PhysicalBlockDevice(id int) BlockDevice + + // BlockDevices returns all the physical and virtual block devices on the machine. BlockDevices() []BlockDevice Zone() Zone diff --git a/machine.go b/machine.go index 5e61470..a3ca829 100644 --- a/machine.go +++ b/machine.go @@ -165,6 +165,16 @@ func (m *machine) PhysicalBlockDevices() []BlockDevice { return result } +// PhysicalBlockDevice implements Machine. +func (m *machine) PhysicalBlockDevice(id int) BlockDevice { + for _, blockDevice := range m.physicalBlockDevices { + if blockDevice.ID() == id { + return blockDevice + } + } + return nil +} + // BlockDevices implements Machine. func (m *machine) BlockDevices() []BlockDevice { result := make([]BlockDevice, len(m.blockDevices)) diff --git a/machine_test.go b/machine_test.go index c5e3821..f05bb78 100644 --- a/machine_test.go +++ b/machine_test.go @@ -65,6 +65,18 @@ func (*machineSuite) TestReadMachines(c *gc.C) { id := interfaceSet[0].ID() c.Assert(machine.Interface(id), jc.DeepEquals, interfaceSet[0]) c.Assert(machine.Interface(id+5), gc.IsNil) + + blockDevices := machine.BlockDevices() + c.Assert(blockDevices, gc.HasLen, 1) + c.Assert(blockDevices[0].Name(), gc.Equals, "sda") + + blockDevices = machine.PhysicalBlockDevices() + c.Assert(blockDevices, gc.HasLen, 1) + c.Assert(blockDevices[0].Name(), gc.Equals, "sda") + + id = blockDevices[0].ID() + c.Assert(machine.PhysicalBlockDevice(id), jc.DeepEquals, blockDevices[0]) + c.Assert(machine.PhysicalBlockDevice(id+5), gc.IsNil) } func (*machineSuite) TestLowVersion(c *gc.C) {