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

Expand API with new Machine, Interface, and Tags interfaces #85

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
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
210 changes: 193 additions & 17 deletions blockdevice.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@
package gomaasapi

import (
"fmt"
"net/http"

"github.com/juju/errors"
"github.com/juju/schema"
"github.com/juju/version"
)

type blockdevice struct {
resourceURI string
controller *controller

id int
uuid string
Expand All @@ -29,6 +33,24 @@ type blockdevice struct {
partitions []*partition
}

func (b *blockdevice) updateFrom(other *blockdevice) {
b.resourceURI = other.resourceURI
b.controller = other.controller
b.id = other.id
b.uuid = other.uuid
b.name = other.name
b.model = other.model
b.idPath = other.idPath
b.path = other.path
b.usedFor = other.usedFor
b.tags = other.tags
b.blockSize = other.blockSize
b.usedSize = other.usedSize
b.size = other.size
b.filesystem = other.filesystem
b.partitions = other.partitions
}

// Type implements BlockDevice
func (b *blockdevice) Type() string {
return "blockdevice"
Expand Down Expand Up @@ -98,19 +120,135 @@ func (b *blockdevice) FileSystem() FileSystem {
func (b *blockdevice) Partitions() []Partition {
result := make([]Partition, len(b.partitions))
for i, v := range b.partitions {
v.controller = b.controller
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)
// FormatStorageDeviceArgs are options for formatting BlockDevices and Partitions
type FormatStorageDeviceArgs struct {
FSType string // Required. Type of filesystem.
UUID string // Optional. The UUID for the filesystem.
Label string // Optional. The label for the filesystem, only applies to partitions.
}

// Validate ensures correct args
func (a *FormatStorageDeviceArgs) Validate() error {
if a.FSType == "" {
return fmt.Errorf("A filesystem type must be specified")
}

return nil
}

func (b *blockdevice) Format(args FormatStorageDeviceArgs) error {
if err := args.Validate(); err != nil {
return errors.Trace(err)
}

params := NewURLParams()
params.MaybeAdd("fs_type", args.FSType)
params.MaybeAdd("uuid", args.UUID)

result, err := b.controller.post(b.resourceURI, "format", params.Values)
if err != nil {
return nil, WrapWithDeserializationError(err, "blockdevice base schema check failed")
if svrErr, ok := errors.Cause(err).(ServerError); ok {
switch svrErr.StatusCode {
case http.StatusNotFound:
return errors.Wrap(err, NewBadRequestError(svrErr.BodyMessage))
case http.StatusForbidden:
return errors.Wrap(err, NewPermissionError(svrErr.BodyMessage))
case http.StatusServiceUnavailable:
return errors.Wrap(err, NewCannotCompleteError(svrErr.BodyMessage))
}
}
return NewUnexpectedError(err)
}
valid := coerced.([]interface{})

blockDevice, err := readBlockDevice(b.controller.apiVersion, result)
if err != nil {
return errors.Trace(err)
}
b.updateFrom(blockDevice)
return nil
}

// CreatePartitionArgs options for creating partitions
type CreatePartitionArgs struct {
Size int // Optional. The size of the partition. If not specified, all available space will be used.
UUID string // Optional. UUID for the partition. Only used if the partition table type for the block device is GPT.
Bootable bool // Optional. If the partition should be marked bootable.
}

func (a *CreatePartitionArgs) toParams() *URLParams {
params := NewURLParams()
params.MaybeAddInt("size", a.Size)
params.MaybeAdd("uuid", a.UUID)
params.MaybeAddBool("bootable", a.Bootable)
return params
}

func (b *blockdevice) CreatePartition(args CreatePartitionArgs) (Partition, error) {
params := args.toParams()
source, err := b.controller.post(b.resourceURI+"partitions/", "", params.Values)
if err != nil {
if svrErr, ok := errors.Cause(err).(ServerError); ok {
switch svrErr.StatusCode {
case http.StatusNotFound:
return nil, errors.Wrap(err, NewNoMatchError(svrErr.BodyMessage))
case http.StatusForbidden:
return nil, errors.Wrap(err, NewPermissionError(svrErr.BodyMessage))
}
}
return nil, NewUnexpectedError(err)
}

response, err := readPartition(b.controller.apiVersion, source)
if err != nil {
return nil, errors.Trace(err)
}
response.controller = b.controller
return response, nil
}

// MountStorageDeviceArgs options for creating partitions
type MountStorageDeviceArgs struct {
MountPoint string // Required. Path on the filesystem to mount.
MountOptions string // Optional. Options to pass to mount(8).
}

func (a *MountStorageDeviceArgs) toParams() *URLParams {
params := NewURLParams()
params.MaybeAdd("mount_point", a.MountPoint)
params.MaybeAdd("mount_options", a.MountOptions)
return params
}

func (b *blockdevice) Mount(args MountStorageDeviceArgs) error {
params := args.toParams()
source, err := b.controller.post(b.resourceURI, "mount", params.Values)
if err != nil {
if svrErr, ok := errors.Cause(err).(ServerError); ok {
switch svrErr.StatusCode {
case http.StatusNotFound:
return errors.Wrap(err, NewNoMatchError(svrErr.BodyMessage))
case http.StatusForbidden:
return errors.Wrap(err, NewPermissionError(svrErr.BodyMessage))
}
}
return NewUnexpectedError(err)
}

response, err := readBlockDevice(b.controller.apiVersion, source)
if err != nil {
return errors.Trace(err)
}
b.updateFrom(response)
return nil
}

func getBlockDeviceDeserializationFunc(controllerVersion version.Number) (blockdeviceDeserializationFunc, error) {
var deserialisationVersion version.Number
for v := range blockdeviceDeserializationFuncs {
if v.Compare(deserialisationVersion) > 0 && v.Compare(controllerVersion) <= 0 {
Expand All @@ -120,7 +258,37 @@ func readBlockDevices(controllerVersion version.Number, source interface{}) ([]*
if deserialisationVersion == version.Zero {
return nil, NewUnsupportedVersionError("no blockdevice read func for version %s", controllerVersion)
}
readFunc := blockdeviceDeserializationFuncs[deserialisationVersion]
return blockdeviceDeserializationFuncs[deserialisationVersion], nil
}

func readBlockDevice(controllerVersion version.Number, source interface{}) (*blockdevice, error) {
readFunc, err := getBlockDeviceDeserializationFunc(controllerVersion)
if err != nil {
return nil, err
}

checker := schema.StringMap(schema.Any())
coerced, err := checker.Coerce(source, nil)
if err != nil {
return nil, WrapWithDeserializationError(err, "machine base schema check failed")
}
valid := coerced.(map[string]interface{})

return readFunc(valid)
}

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{})

readFunc, err := getBlockDeviceDeserializationFunc(controllerVersion)
if err != nil {
return nil, err
}
return readBlockDeviceList(valid, readFunc)
}

Expand Down Expand Up @@ -153,19 +321,19 @@ func blockdevice_2_0(source map[string]interface{}) (*blockdevice, error) {

"id": schema.ForceInt(),
"uuid": schema.OneOf(schema.Nil(""), schema.String()),
"name": schema.String(),
"name": schema.OneOf(schema.Nil(""), schema.String()),
"model": schema.OneOf(schema.Nil(""), schema.String()),
"id_path": schema.OneOf(schema.Nil(""), schema.String()),
"path": schema.String(),
"used_for": schema.String(),
"tags": schema.List(schema.String()),
"tags": schema.OneOf(schema.Nil(""), schema.List(schema.String())),

"block_size": schema.ForceUint(),
"used_size": schema.ForceUint(),
"block_size": schema.OneOf(schema.Nil(""), schema.ForceUint()),
"used_size": schema.OneOf(schema.Nil(""), schema.ForceUint()),
"size": schema.ForceUint(),

"filesystem": schema.OneOf(schema.Nil(""), schema.StringMap(schema.Any())),
"partitions": schema.List(schema.StringMap(schema.Any())),
"partitions": schema.OneOf(schema.Nil(""), schema.List(schema.StringMap(schema.Any()))),
}
checker := schema.FieldMap(fields, nil)
coerced, err := checker.Coerce(source, nil)
Expand All @@ -182,28 +350,36 @@ func blockdevice_2_0(source map[string]interface{}) (*blockdevice, error) {
return nil, errors.Trace(err)
}
}
partitions, err := readPartitionList(valid["partitions"].([]interface{}), partition_2_0)
if err != nil {
return nil, errors.Trace(err)

partitions := []*partition{}
if valid["partitions"] != nil {
var err error
partitions, err = readPartitionList(valid["partitions"].([]interface{}), partition_2_0)
if err != nil {
return nil, errors.Trace(err)
}
}

uuid, _ := valid["uuid"].(string)
model, _ := valid["model"].(string)
idPath, _ := valid["id_path"].(string)
name, _ := valid["name"].(string)
blockSize, _ := valid["block_size"].(uint64)
usedSize, _ := valid["used_size"].(uint64)
result := &blockdevice{
resourceURI: valid["resource_uri"].(string),

id: valid["id"].(int),
uuid: uuid,
name: valid["name"].(string),
name: name,
model: model,
idPath: idPath,
path: valid["path"].(string),
usedFor: valid["used_for"].(string),
tags: convertToStringSlice(valid["tags"]),

blockSize: valid["block_size"].(uint64),
usedSize: valid["used_size"].(uint64),
blockSize: blockSize,
usedSize: usedSize,
size: valid["size"].(uint64),

filesystem: filesystem,
Expand Down
Loading