Skip to content

Commit

Permalink
Merge pull request #96 from markylaing/tags-hardware-info
Browse files Browse the repository at this point in the history
#96

build build.properties go-path go1.17.11.linux-amd64.tar.gz goversion pr_props Adds a new interface `Tag` (implemented by `tag` in `tags.go`) for interacting with MAAS tags.
* Adds a method `Tags() ([]Tag, error)` to the `Controller` for retrieving a slice of MAAS tags.
* Adds `Tags` to `MachineArgs` to allow filtering machines by tag.
* Adds a method `HardwareInfo() map[string]string` to the `Machine` interface and implements it. Hardware info is added to `machine` when deserializing the API response.

I have tried to follow conventions that I have found elsewhere in the codebase but let me know if I have missed something, thanks 😄
  • Loading branch information
jujubot authored Jun 21, 2022
2 parents 1ee6817 + 16207a0 commit 7268ed0
Show file tree
Hide file tree
Showing 6 changed files with 299 additions and 0 deletions.
22 changes: 22 additions & 0 deletions controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ type MachinesArgs struct {
Zone string
Pool string
AgentName string
Tags []string
OwnerData map[string]string
}

Expand All @@ -361,6 +362,7 @@ func (c *controller) Machines(args MachinesArgs) ([]Machine, error) {
params.MaybeAdd("zone", args.Zone)
params.MaybeAdd("pool", args.Pool)
params.MaybeAdd("agent_name", args.AgentName)
params.MaybeAddMany("tags", args.Tags)
// At the moment the MAAS API doesn't support filtering by owner
// data so we do that ourselves below.
source, err := c.getQuery("machines", params.Values)
Expand Down Expand Up @@ -1059,3 +1061,23 @@ func convertConstraintMatchesAny(source interface{}) map[string][]interface{} {
}
return result
}

// Tags implements Controller.
func (c *controller) Tags() ([]Tag, error) {
source, err := c.getQuery("tags", nil)
if err != nil {
return nil, NewUnexpectedError(err)
}

tags, err := readTags(c.apiVersion, source)
if err != nil {
return nil, errors.Trace(err)
}

result := make([]Tag, len(tags))
for i, tag := range tags {
result[i] = tag
}

return result, nil
}
12 changes: 12 additions & 0 deletions interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ type Controller interface {

// Returns the DNS Domain Managed By MAAS
Domains() ([]Domain, error)

// Returns the list of MAAS tags
Tags() ([]Tag, error)
}

// File represents a file stored in the MAAS controller.
Expand Down Expand Up @@ -214,6 +217,7 @@ type Machine interface {
Architecture() string
Memory() int
CPUCount() int
HardwareInfo() map[string]string

IPAddresses() []string
PowerState() string
Expand Down Expand Up @@ -420,3 +424,11 @@ type OwnerDataHolder interface {
// released.
SetOwnerData(map[string]string) error
}

// Tag represents a MAAS tag.
type Tag interface {
Name() string
Comment() string
Definition() string
KernelOpts() string
}
30 changes: 30 additions & 0 deletions machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type machine struct {
architecture string
memory int
cpuCount int
hardwareInfo map[string]string

ipAddresses []string
powerState string
Expand Down Expand Up @@ -56,6 +57,7 @@ func (m *machine) updateFrom(other *machine) {
m.architecture = other.architecture
m.memory = other.memory
m.cpuCount = other.cpuCount
m.hardwareInfo = other.hardwareInfo
m.ipAddresses = other.ipAddresses
m.powerState = other.powerState
m.statusName = other.statusName
Expand Down Expand Up @@ -109,6 +111,16 @@ func (m *machine) CPUCount() int {
return m.cpuCount
}

// HardwareInfo implements Machine.
func (m *machine) HardwareInfo() map[string]string {
info := make(map[string]string, len(m.hardwareInfo))
for k, v := range m.hardwareInfo {
info[k] = v
}

return info
}

// PowerState implements Machine.
func (m *machine) PowerState() string {
return m.powerState
Expand Down Expand Up @@ -513,6 +525,7 @@ func machine_2_0(source map[string]interface{}) (*machine, error) {
"architecture": schema.OneOf(schema.Nil(""), schema.String()),
"memory": schema.ForceInt(),
"cpu_count": schema.ForceInt(),
"hardware_info": schema.StringMap(schema.String()),

"ip_addresses": schema.List(schema.String()),
"power_state": schema.String(),
Expand Down Expand Up @@ -574,6 +587,22 @@ func machine_2_0(source map[string]interface{}) (*machine, error) {
if err != nil {
return nil, errors.Trace(err)
}

validHardwareInfo, ok := valid["hardware_info"].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("field \"hardware_info\" is not valid")
}

hardwareInfo := make(map[string]string, len(validHardwareInfo))
for key, value := range validHardwareInfo {
v, ok := value.(string)
if !ok {
return nil, fmt.Errorf("invalid field %q in \"hardware_info\"", key)
}

hardwareInfo[key] = v
}

architecture, _ := valid["architecture"].(string)
statusMessage, _ := valid["status_message"].(string)
result := &machine{
Expand All @@ -590,6 +619,7 @@ func machine_2_0(source map[string]interface{}) (*machine, error) {
architecture: architecture,
memory: valid["memory"].(int),
cpuCount: valid["cpu_count"].(int),
hardwareInfo: hardwareInfo,

ipAddresses: convertToStringSlice(valid["ip_addresses"]),
powerState: valid["power_state"].(string),
Expand Down
60 changes: 60 additions & 0 deletions machine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -861,6 +861,26 @@ const (
"name": "default"
},
"fqdn": "untasted-markita.maas",
"hardware_info": {
"chassis_serial": "Unknown",
"chassis_type": "Unknown",
"chassis_vendor": "Unknown",
"chassis_version": "Unknown",
"cpu_model": "Unknown",
"mainboard_firmware_date": "Unknown",
"mainboard_firmware_vendor": "Unknown",
"mainboard_firmware_version": "Unknown",
"mainboard_product": "Unknown",
"mainboard_serial": "Unknown",
"mainboard_vendor": "Unknown",
"mainboard_version": "Unknown",
"system_family": "Unknown",
"system_product": "Unknown",
"system_serial": "Unknown",
"system_sku": "Unknown",
"system_vendor": "Unknown",
"system_version": "Unknown"
},
"storage": 8589.934592,
"node_type": 0,
"boot_disk": null,
Expand Down Expand Up @@ -1183,6 +1203,26 @@ var (
"name": "default"
},
"fqdn": "lowlier-glady.maas",
"hardware_info": {
"chassis_serial": "Unknown",
"chassis_type": "Unknown",
"chassis_vendor": "Unknown",
"chassis_version": "Unknown",
"cpu_model": "Unknown",
"mainboard_firmware_date": "Unknown",
"mainboard_firmware_vendor": "Unknown",
"mainboard_firmware_version": "Unknown",
"mainboard_product": "Unknown",
"mainboard_serial": "Unknown",
"mainboard_vendor": "Unknown",
"mainboard_version": "Unknown",
"system_family": "Unknown",
"system_product": "Unknown",
"system_serial": "Unknown",
"system_sku": "Unknown",
"system_vendor": "Unknown",
"system_version": "Unknown"
},
"storage": 8589.934592,
"node_type": 0,
"boot_disk": null,
Expand Down Expand Up @@ -1434,6 +1474,26 @@ var (
"name": "default"
},
"fqdn": "icier-nina.maas",
"hardware_info": {
"chassis_serial": "Unknown",
"chassis_type": "Unknown",
"chassis_vendor": "Unknown",
"chassis_version": "Unknown",
"cpu_model": "Unknown",
"mainboard_firmware_date": "Unknown",
"mainboard_firmware_vendor": "Unknown",
"mainboard_firmware_version": "Unknown",
"mainboard_product": "Unknown",
"mainboard_serial": "Unknown",
"mainboard_vendor": "Unknown",
"mainboard_version": "Unknown",
"system_family": "Unknown",
"system_product": "Unknown",
"system_serial": "Unknown",
"system_sku": "Unknown",
"system_vendor": "Unknown",
"system_version": "Unknown"
},
"storage": 8589.934592,
"node_type": 0,
"boot_disk": null,
Expand Down
125 changes: 125 additions & 0 deletions tags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Copyright 2022 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 tag struct {
resourceURI string

name string
comment string
definition string
kernelOpts string
}

func (tag tag) Name() string {
return tag.name
}

func (tag tag) Comment() string {
return tag.comment
}

func (tag tag) Definition() string {
return tag.definition
}

func (tag tag) KernelOpts() string {
return tag.kernelOpts
}

func readTags(controllerVersion version.Number, source interface{}) ([]*tag, error) {
readFunc, err := getTagDeserializationFunc(controllerVersion)
if err != nil {
return nil, errors.Trace(err)
}

checker := schema.List(schema.StringMap(schema.Any()))
coerced, err := checker.Coerce(source, nil)
if err != nil {
return nil, WrapWithDeserializationError(err, "machine base schema check failed")
}

// OK to do a direct cast here because we just coerced the interface.
valid := coerced.([]interface{})
return readTagList(valid, readFunc)
}

func readTagList(sourceList []interface{}, readFunc tagDeserializationFunc) ([]*tag, error) {
result := make([]*tag, 0, len(sourceList))
for i, value := range sourceList {
source, ok := value.(map[string]interface{})
if !ok {
return nil, NewDeserializationError("unexpected value for tag %d, %T", i, value)
}

machine, err := readFunc(source)
if err != nil {
return nil, errors.Annotatef(err, "tag %d", i)
}

result = append(result, machine)
}

return result, nil
}

func getTagDeserializationFunc(controllerVersion version.Number) (tagDeserializationFunc, error) {
var deserialisationVersion version.Number
for v := range tagDeserializationFuncs {
if v.Compare(deserialisationVersion) > 0 && v.Compare(controllerVersion) <= 0 {
deserialisationVersion = v
}
}

if deserialisationVersion == version.Zero {
return nil, NewUnsupportedVersionError("no tag read func for version %s", controllerVersion)
}

return tagDeserializationFuncs[deserialisationVersion], nil
}

type tagDeserializationFunc func(map[string]interface{}) (*tag, error)

var tagDeserializationFuncs = map[version.Number]tagDeserializationFunc{
twoDotOh: tag_2_0,
}

func tag_2_0(source map[string]interface{}) (*tag, error) {
fields := schema.Fields{
"resource_uri": schema.String(),
"name": schema.String(),
"comment": schema.String(),
"definition": schema.String(),
"kernel_opts": schema.String(),
}

defaults := schema.Defaults{
"comment": "",
"definition": "",
"kernel_opts": "",
}

checker := schema.FieldMap(fields, defaults)

coerced, err := checker.Coerce(source, nil)
if err != nil {
return nil, WrapWithDeserializationError(err, "tag 2.0 schema check failed")
}

valid := coerced.(map[string]interface{})

return &tag{
resourceURI: valid["resource_uri"].(string),
name: valid["name"].(string),
comment: valid["comment"].(string),
definition: valid["definition"].(string),
kernelOpts: valid["kernel_opts"].(string),
}, nil
}
50 changes: 50 additions & 0 deletions tags_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package gomaasapi

import (
"github.com/juju/testing"
jc "github.com/juju/testing/checkers"
gc "gopkg.in/check.v1"
)

type tagSuite struct {
testing.LoggingCleanupSuite
}

var _ = gc.Suite(&tagSuite{})

func (*tagSuite) TestReadTags(c *gc.C) {
tags, err := readTags(twoDotOh, parseJSON(c, tagsResponse))
c.Assert(err, jc.ErrorIsNil)
c.Assert(tags, gc.HasLen, 3)

tag := tags[0]

c.Check(tag.Name(), gc.Equals, "virtual")
c.Check(tag.Comment(), gc.Equals, "virtual machines")
c.Check(tag.Definition(), gc.Equals, "tag for machines that are virtual")
c.Check(tag.KernelOpts(), gc.Equals, "nvme_core")
}

var tagsResponse = `[
{
"resource_uri": "/2.0/tags/virtual",
"name": "virtual",
"comment": "virtual machines",
"definition": "tag for machines that are virtual",
"kernel_opts": "nvme_core"
},
{
"resource_uri": "/2.0/tags/physical",
"name": "physical",
"comment": "physical machines",
"definition": "tag for machines that are physical",
"kernel_opts": ""
},
{
"resource_uri": "/2.0/tags/r-pi",
"name": "r-pi",
"comment": "raspberry pis'",
"definition": "tag for machines that are raspberry pis",
"kernel_opts": ""
}
]`

0 comments on commit 7268ed0

Please sign in to comment.