Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse and return BlockDevices for Machines. #40

Merged
merged 6 commits into from
Apr 15, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 175 additions & 0 deletions blockdevice.go
Original file line number Diff line number Diff line change
@@ -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["block_size"].(int),
usedSize: valid["used_size"].(int),
size: valid["size"].(int),

partitions: partitions,
}
return result, nil
}
99 changes: 99 additions & 0 deletions blockdevice_test.go
Original file line number Diff line number Diff line change
@@ -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"
]
}
]
`
63 changes: 63 additions & 0 deletions filesystem.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright 2016 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.

package gomaasapi

import "github.com/juju/schema"

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
}

// 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 filesystem2_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
}
38 changes: 38 additions & 0 deletions filesystem_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
Loading