-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request juju#12 from howbazaar/controller-interface
Controller interface Introduce the concepts of interfaces to gomaasapi. Add Controller, Zone and Machine interfaces. The machine listing from the controller doesn't yet honour the params, but instead just lists all of them.
- Loading branch information
Showing
10 changed files
with
1,569 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
// Copyright 2016 Canonical Ltd. | ||
// Licensed under the LGPLv3, see LICENCE file for details. | ||
|
||
package gomaasapi | ||
|
||
import ( | ||
"encoding/json" | ||
"net/url" | ||
"sync/atomic" | ||
|
||
"github.com/juju/errors" | ||
"github.com/juju/loggo" | ||
"github.com/juju/schema" | ||
"github.com/juju/utils/set" | ||
"github.com/juju/version" | ||
) | ||
|
||
var ( | ||
logger = loggo.GetLogger("maas") | ||
|
||
// The supported versions should be ordered from most desirable version to | ||
// least as they will be tried in order. | ||
supportedAPIVersions = []string{"2.0"} | ||
|
||
// Each of the api versions that change the request or response structure | ||
// for any given call should have a value defined for easy definition of | ||
// the deserialization functions. | ||
twoDotOh = version.Number{Major: 2, Minor: 0} | ||
|
||
// Current request number. Informational only for logging. | ||
requestNumber int64 | ||
) | ||
|
||
// ControllerArgs is an argument struct for passing the required parameters | ||
// to the NewController method. | ||
type ControllerArgs struct { | ||
BaseURL string | ||
APIKey string | ||
} | ||
|
||
// NewController creates an authenticated client to the MAAS API, and checks | ||
// the capabilities of the server. | ||
func NewController(args ControllerArgs) (Controller, error) { | ||
// For now we don't need to test multiple versions. It is expected that at | ||
// some time in the future, we will try the most up to date version and then | ||
// work our way backwards. | ||
var outerErr error | ||
for _, apiVersion := range supportedAPIVersions { | ||
major, minor, err := version.ParseMajorMinor(apiVersion) | ||
// We should not get an error here. See the test. | ||
if err != nil { | ||
return nil, errors.Errorf("bad version defined in supported versions: %q", apiVersion) | ||
} | ||
client, err := NewAuthenticatedClient(args.BaseURL, args.APIKey, apiVersion) | ||
if err != nil { | ||
outerErr = err | ||
continue | ||
} | ||
controllerVersion := version.Number{ | ||
Major: major, | ||
Minor: minor, | ||
} | ||
controller := &controller{client: client} | ||
// The controllerVersion returned from the function will include any patch version. | ||
controller.capabilities, controller.apiVersion, err = controller.readAPIVersion(controllerVersion) | ||
if err != nil { | ||
logger.Debugf("read version failed: %v", err) | ||
outerErr = err | ||
continue | ||
} | ||
return controller, nil | ||
} | ||
|
||
return nil, errors.Wrap(outerErr, errors.New("unable to create authenticated client")) | ||
} | ||
|
||
type controller struct { | ||
client *Client | ||
apiVersion version.Number | ||
capabilities set.Strings | ||
} | ||
|
||
// Capabilities implements Controller. | ||
func (c *controller) Capabilities() set.Strings { | ||
return c.capabilities | ||
} | ||
|
||
// Zones implements Controller. | ||
func (c *controller) Zones() ([]Zone, error) { | ||
source, err := c.get("zones") | ||
if err != nil { | ||
return nil, errors.Trace(err) | ||
} | ||
zones, err := readZones(c.apiVersion, source) | ||
if err != nil { | ||
return nil, errors.Trace(err) | ||
} | ||
var result []Zone | ||
for _, z := range zones { | ||
result = append(result, z) | ||
} | ||
return result, nil | ||
} | ||
|
||
// Machines implements Controller. | ||
func (c *controller) Machines(params MachinesArgs) ([]Machine, error) { | ||
// ignore params for now | ||
source, err := c.get("machines") | ||
if err != nil { | ||
return nil, errors.Trace(err) | ||
} | ||
machines, err := readMachines(c.apiVersion, source) | ||
if err != nil { | ||
return nil, errors.Trace(err) | ||
} | ||
var result []Machine | ||
for _, m := range machines { | ||
result = append(result, m) | ||
} | ||
return result, nil | ||
} | ||
|
||
func (c *controller) get(path string) (interface{}, error) { | ||
path = EnsureTrailingSlash(path) | ||
requestID := nextrequestID() | ||
logger.Tracef("request %x: GET %s%s", requestID, c.client.APIURL, path) | ||
bytes, err := c.client.Get(&url.URL{Path: path}, "", nil) | ||
if err != nil { | ||
logger.Tracef("response %x: error: %q", requestID, err.Error()) | ||
return nil, errors.Trace(err) | ||
} | ||
logger.Tracef("response %x: %s", requestID, string(bytes)) | ||
|
||
var parsed interface{} | ||
err = json.Unmarshal(bytes, &parsed) | ||
if err != nil { | ||
return nil, errors.Trace(err) | ||
} | ||
return parsed, nil | ||
} | ||
|
||
func nextrequestID() int64 { | ||
return atomic.AddInt64(&requestNumber, 1) | ||
} | ||
|
||
func (c *controller) readAPIVersion(apiVersion version.Number) (set.Strings, version.Number, error) { | ||
parsed, err := c.get("version") | ||
if err != nil { | ||
return nil, apiVersion, errors.Trace(err) | ||
} | ||
|
||
// As we care about other fields, add them. | ||
fields := schema.Fields{ | ||
"capabilities": schema.List(schema.String()), | ||
} | ||
checker := schema.FieldMap(fields, nil) // no defaults | ||
coerced, err := checker.Coerce(parsed, nil) | ||
if err != nil { | ||
return nil, apiVersion, errors.Trace(err) | ||
} | ||
// For now, we don't append any subversion, but as it becomes used, we | ||
// should parse and check. | ||
|
||
valid := coerced.(map[string]interface{}) | ||
// From here we know that the map returned from the schema coercion | ||
// contains fields of the right type. | ||
capabilities := set.NewStrings() | ||
capabilityValues := valid["capabilities"].([]interface{}) | ||
for _, value := range capabilityValues { | ||
capabilities.Add(value.(string)) | ||
} | ||
|
||
return capabilities, apiVersion, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
// Copyright 2016 Canonical Ltd. | ||
// Licensed under the LGPLv3, see LICENCE file for details. | ||
|
||
package gomaasapi | ||
|
||
import ( | ||
"net/http" | ||
|
||
"github.com/juju/testing" | ||
jc "github.com/juju/testing/checkers" | ||
"github.com/juju/utils/set" | ||
"github.com/juju/version" | ||
gc "gopkg.in/check.v1" | ||
) | ||
|
||
type versionSuite struct { | ||
} | ||
|
||
var _ = gc.Suite(&versionSuite{}) | ||
|
||
func (*versionSuite) TestSupportedVersions(c *gc.C) { | ||
for _, apiVersion := range supportedAPIVersions { | ||
_, _, err := version.ParseMajorMinor(apiVersion) | ||
c.Check(err, jc.ErrorIsNil) | ||
} | ||
} | ||
|
||
type controllerSuite struct { | ||
testing.CleanupSuite | ||
server *SimpleTestServer | ||
} | ||
|
||
var _ = gc.Suite(&controllerSuite{}) | ||
|
||
func (s *controllerSuite) SetUpTest(c *gc.C) { | ||
s.CleanupSuite.SetUpTest(c) | ||
|
||
server := NewSimpleServer() | ||
server.AddResponse("/api/2.0/version/", http.StatusOK, versionResponse) | ||
server.AddResponse("/api/2.0/zones/", http.StatusOK, zoneResponse) | ||
server.AddResponse("/api/2.0/machines/", http.StatusOK, machinesResponse) | ||
server.Start() | ||
s.AddCleanup(func(*gc.C) { server.Close() }) | ||
s.server = server | ||
} | ||
|
||
func (s *controllerSuite) getController(c *gc.C) Controller { | ||
controller, err := NewController(ControllerArgs{ | ||
BaseURL: s.server.URL, | ||
APIKey: "fake:as:key", | ||
}) | ||
c.Assert(err, jc.ErrorIsNil) | ||
return controller | ||
} | ||
|
||
func (s *controllerSuite) TestNewController(c *gc.C) { | ||
controller := s.getController(c) | ||
|
||
expectedCapabilities := set.NewStrings( | ||
NetworksManagement, | ||
StaticIPAddresses, | ||
IPv6DeploymentUbuntu, | ||
DevicesManagement, | ||
StorageDeploymentUbuntu, | ||
NetworkDeploymentUbuntu, | ||
) | ||
|
||
capabilities := controller.Capabilities() | ||
c.Assert(capabilities.Difference(expectedCapabilities), gc.HasLen, 0) | ||
c.Assert(expectedCapabilities.Difference(capabilities), gc.HasLen, 0) | ||
} | ||
|
||
func (s *controllerSuite) TestZones(c *gc.C) { | ||
controller := s.getController(c) | ||
zones, err := controller.Zones() | ||
c.Assert(err, jc.ErrorIsNil) | ||
c.Assert(zones, gc.HasLen, 2) | ||
} | ||
|
||
func (s *controllerSuite) TestMachines(c *gc.C) { | ||
controller := s.getController(c) | ||
machines, err := controller.Machines(MachinesArgs{}) | ||
c.Assert(err, jc.ErrorIsNil) | ||
c.Assert(machines, gc.HasLen, 3) | ||
} | ||
|
||
var versionResponse = `{"version": "unknown", "subversion": "", "capabilities": ["networks-management", "static-ipaddresses", "ipv6-deployment-ubuntu", "devices-management", "storage-deployment-ubuntu", "network-deployment-ubuntu"]}` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
github.com/juju/errors git 1b5e39b83d1835fa480e0c2ddefb040ee82d58b3 2015-09-16T12:56:42Z | ||
github.com/juju/loggo git 8477fc936adf0e382d680310047ca27e128a309a 2015-05-27T03:58:39Z | ||
github.com/juju/names git ef19de31613af3735aa69ba3b40accce2faf7316 2016-03-01T22:07:10Z | ||
github.com/juju/schema git 1e25943f8c6fd6815282d6f1ac87091d21e14e19 2016-03-01T11:16:46Z | ||
github.com/juju/testing git 45f216f8ef2a6fbed3f38cbcd57f68741d295053 2016-03-17T08:39:30Z | ||
github.com/juju/utils git af32cfaf28093965fdf63373d8447c489efb2875 2016-03-09T18:28:39Z | ||
github.com/juju/version git ef897ad7f130870348ce306f61332f5335355063 2015-11-27T20:34:00Z | ||
golang.org/x/crypto git aedad9a179ec1ea11b7064c57cbc6dc30d7724ec 2015-08-30T18:06:42Z | ||
gopkg.in/check.v1 git 4f90aeace3a26ad7021961c297b22c42160c7b25 2016-01-05T16:49:36Z | ||
gopkg.in/mgo.v2 git 4d04138ffef2791c479c0c8bbffc30b34081b8d9 2015-10-26T16:34:53Z | ||
gopkg.in/yaml.v2 git a83829b6f1293c91addabc89d0571c246397bbf4 2016-03-01T20:40:22Z |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
// Copyright 2016 Canonical Ltd. | ||
// Licensed under the LGPLv3, see LICENCE file for details. | ||
|
||
package gomaasapi | ||
|
||
import "github.com/juju/utils/set" | ||
|
||
const ( | ||
// Capability constants. | ||
NetworksManagement = "networks-management" | ||
StaticIPAddresses = "static-ipaddresses" | ||
IPv6DeploymentUbuntu = "ipv6-deployment-ubuntu" | ||
DevicesManagement = "devices-management" | ||
StorageDeploymentUbuntu = "storage-deployment-ubuntu" | ||
NetworkDeploymentUbuntu = "network-deployment-ubuntu" | ||
) | ||
|
||
// Controller represents an API connection to a MAAS Controller. Since the API | ||
// is restful, there is no long held connection to the API server, but instead | ||
// HTTP calls are made and JSON response structures parsed. | ||
type Controller interface { | ||
|
||
// Capabilities returns a set of capabilities as defined by the string | ||
// constants. | ||
Capabilities() set.Strings | ||
|
||
// Zones lists all the zones known to the MAAS controller. | ||
Zones() ([]Zone, error) | ||
|
||
// Machines returns a list of machines that match the params. | ||
Machines(MachinesArgs) ([]Machine, error) | ||
} | ||
|
||
// Zone represents a physical zone that a Machine is in. The meaning of a | ||
// physical zone is up to you: it could identify e.g. a server rack, a network, | ||
// or a data centre. Users can then allocate nodes from specific physical zones, | ||
// to suit their redundancy or performance requirements. | ||
type Zone interface { | ||
Name() string | ||
Description() string | ||
} | ||
|
||
// Machine represents a physical machine. | ||
type Machine interface { | ||
SystemId() string | ||
Hostname() string | ||
FQDN() string | ||
|
||
OperatingSystem() string | ||
DistroSeries() string | ||
Architecture() string | ||
Memory() int | ||
CpuCount() int | ||
|
||
IPAddresses() []string | ||
PowerState() string | ||
|
||
// Consider bundling the status values into a single struct. | ||
// but need to check for consistent representation if exposed on other | ||
// entities. | ||
|
||
StatusName() string | ||
StatusMessage() string | ||
|
||
Zone() Zone | ||
} | ||
|
||
type MachinesArgs struct { | ||
SystemIds []string | ||
} |
Oops, something went wrong.