Skip to content

Commit

Permalink
Merge pull request #57 from babbageclunk/tags
Browse files Browse the repository at this point in the history
Enable reading and setting machine owner data

Expose owner data from the API - this is key/value storage that can be set by the owner of the machine and is cleared when the machine is released.

It's needed so Juju can to store controller/model information for instances without using the Storage interface.
  • Loading branch information
jujubot authored Sep 8, 2016
2 parents 0d72ac7 + 275c57d commit 5235db1
Show file tree
Hide file tree
Showing 6 changed files with 253 additions and 74 deletions.
16 changes: 15 additions & 1 deletion controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ type MachinesArgs struct {
Domain string
Zone string
AgentName string
OwnerData map[string]string
}

// Machines implements Controller.
Expand All @@ -263,6 +264,8 @@ func (c *controller) Machines(args MachinesArgs) ([]Machine, error) {
params.MaybeAdd("domain", args.Domain)
params.MaybeAdd("zone", args.Zone)
params.MaybeAdd("agent_name", args.AgentName)
// 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)
if err != nil {
return nil, NewUnexpectedError(err)
Expand All @@ -274,11 +277,22 @@ func (c *controller) Machines(args MachinesArgs) ([]Machine, error) {
var result []Machine
for _, m := range machines {
m.controller = c
result = append(result, m)
if ownerDataMatches(m.ownerData, args.OwnerData) {
result = append(result, m)
}
}
return result, nil
}

func ownerDataMatches(ownerData, filter map[string]string) bool {
for key, value := range filter {
if ownerData[key] != value {
return false
}
}
return true
}

// StorageSpec represents one element of storage constraints necessary
// to be satisfied to allocate a machine.
type StorageSpec struct {
Expand Down
38 changes: 38 additions & 0 deletions controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,44 @@ func (s *controllerSuite) TestMachinesFilter(c *gc.C) {
c.Assert(machines[0].Hostname(), gc.Equals, "untasted-markita")
}

func (s *controllerSuite) TestMachinesFilterWithOwnerData(c *gc.C) {
controller := s.getController(c)
machines, err := controller.Machines(MachinesArgs{
Hostnames: []string{"untasted-markita"},
OwnerData: map[string]string{
"fez": "jim crawford",
},
})
c.Assert(err, jc.ErrorIsNil)
c.Assert(machines, gc.HasLen, 0)
}

func (s *controllerSuite) TestMachinesFilterWithOwnerData_MultipleMatches(c *gc.C) {
controller := s.getController(c)
machines, err := controller.Machines(MachinesArgs{
OwnerData: map[string]string{
"braid": "jonathan blow",
},
})
c.Assert(err, jc.ErrorIsNil)
c.Assert(machines, gc.HasLen, 2)
c.Assert(machines[0].Hostname(), gc.Equals, "lowlier-glady")
c.Assert(machines[1].Hostname(), gc.Equals, "icier-nina")
}

func (s *controllerSuite) TestMachinesFilterWithOwnerData_RequiresAllMatch(c *gc.C) {
controller := s.getController(c)
machines, err := controller.Machines(MachinesArgs{
OwnerData: map[string]string{
"braid": "jonathan blow",
"frog-fractions": "jim crawford",
},
})
c.Assert(err, jc.ErrorIsNil)
c.Assert(machines, gc.HasLen, 1)
c.Assert(machines[0].Hostname(), gc.Equals, "lowlier-glady")
}

func (s *controllerSuite) TestMachinesArgs(c *gc.C) {
controller := s.getController(c)
// This will fail with a 404 due to the test server not having something at
Expand Down
17 changes: 17 additions & 0 deletions interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ type Device interface {

// Machine represents a physical machine.
type Machine interface {
OwnerDataHolder

SystemID() string
Hostname() string
FQDN() string
Expand Down Expand Up @@ -343,3 +345,18 @@ type BlockDevice interface {
// There are some other attributes for block devices, but we can
// expose them on an as needed basis.
}

// OwnerDataHolder represents any MAAS object that can store key/value
// data.
type OwnerDataHolder interface {
// OwnerData returns a copy of the key/value data stored for this
// object.
OwnerData() map[string]string

// SetOwnerData updates the key/value data stored for this object
// with the values passed in. Existing keys that aren't specified
// in the map passed in will be left in place; to clear a key set
// its value to "". All owner data is cleared when the object is
// released.
SetOwnerData(map[string]string) error
}
71 changes: 59 additions & 12 deletions machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,19 @@ import (
"github.com/juju/errors"
"github.com/juju/schema"
"github.com/juju/version"
"net/url"
)

type machine struct {
controller *controller

resourceURI string

systemID string
hostname string
fqdn string
tags []string
systemID string
hostname string
fqdn string
tags []string
ownerData map[string]string

operatingSystem string
distroSeries string
Expand Down Expand Up @@ -57,6 +59,8 @@ func (m *machine) updateFrom(other *machine) {
m.statusName = other.statusName
m.statusMessage = other.statusMessage
m.zone = other.zone
m.tags = other.tags
m.ownerData = other.ownerData
}

// SystemID implements Machine.
Expand Down Expand Up @@ -326,6 +330,33 @@ func (m *machine) CreateDevice(args CreateMachineDeviceArgs) (_ Device, err erro
return device, nil
}

// OwnerData implements OwnerDataHolder.
func (m *machine) OwnerData() map[string]string {
result := make(map[string]string)
for key, value := range m.ownerData {
result[key] = value
}
return result
}

// SetOwnerData implements OwnerDataHolder.
func (m *machine) SetOwnerData(ownerData map[string]string) error {
params := make(url.Values)
for key, value := range ownerData {
params.Add(key, value)
}
result, err := m.controller.post(m.resourceURI, "set-owner-data", params)
if err != nil {
return errors.Trace(err)
}
machine, err := readMachine(m.controller.apiVersion, result)
if err != nil {
return errors.Trace(err)
}
m.updateFrom(machine)
return nil
}

func readMachine(controllerVersion version.Number, source interface{}) (*machine, error) {
readFunc, err := getMachineDeserializationFunc(controllerVersion)
if err != nil {
Expand Down Expand Up @@ -395,10 +426,11 @@ func machine_2_0(source map[string]interface{}) (*machine, error) {
fields := schema.Fields{
"resource_uri": schema.String(),

"system_id": schema.String(),
"hostname": schema.String(),
"fqdn": schema.String(),
"tag_names": schema.List(schema.String()),
"system_id": schema.String(),
"hostname": schema.String(),
"fqdn": schema.String(),
"tag_names": schema.List(schema.String()),
"owner_data": schema.StringMap(schema.String()),

"osystem": schema.String(),
"distro_series": schema.String(),
Expand Down Expand Up @@ -459,10 +491,11 @@ func machine_2_0(source map[string]interface{}) (*machine, error) {
result := &machine{
resourceURI: valid["resource_uri"].(string),

systemID: valid["system_id"].(string),
hostname: valid["hostname"].(string),
fqdn: valid["fqdn"].(string),
tags: convertToStringSlice(valid["tag_names"]),
systemID: valid["system_id"].(string),
hostname: valid["hostname"].(string),
fqdn: valid["fqdn"].(string),
tags: convertToStringSlice(valid["tag_names"]),
ownerData: convertToStringMap(valid["owner_data"]),

operatingSystem: valid["osystem"].(string),
distroSeries: valid["distro_series"].(string),
Expand Down Expand Up @@ -496,3 +529,17 @@ func convertToStringSlice(field interface{}) []string {
}
return result
}

func convertToStringMap(field interface{}) map[string]string {
if field == nil {
return nil
}
// This function is only called after a schema Coerce, so it's
// safe.
fieldMap := field.(map[string]interface{})
result := make(map[string]string)
for key, value := range fieldMap {
result[key] = value.(string)
}
return result
}
Loading

0 comments on commit 5235db1

Please sign in to comment.