Skip to content

Commit

Permalink
Merge pull request #51 from babbageclunk/maas2-constraint-matches
Browse files Browse the repository at this point in the history
Make MAAS2 constraint matches lists

In APIv2 the constraint matches are actually label -> [id, id...]. This makes sense because in v1 they were id -> label, which meant that label could appear more than once; there could be multiple interfaces or storage devices that fulfil a specific constraint.

Change ConstraintMatches and the parsing to handle this.
  • Loading branch information
jujubot committed Apr 28, 2016
2 parents b5d5d8e + 61b3b97 commit 3b0458b
Show file tree
Hide file tree
Showing 3 changed files with 201 additions and 38 deletions.
66 changes: 44 additions & 22 deletions controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -442,13 +442,13 @@ func (a *AllocateMachineArgs) notNetworks() string {
//.how the allocated machine matched the storage and interfaces constraints specified.
// The labels that were used in the constraints are the keys in the maps.
type ConstraintMatches struct {
// Interface is a mapping of the constraint label specified to the Interface
// that matches that constraint.
Interfaces map[string]Interface
// Interface is a mapping of the constraint label specified to the Interfaces
// that match that constraint.
Interfaces map[string][]Interface

// Storage is a mapping of the constraint label specified to the BlockDevice
// that matches that constraint.
Storage map[string]BlockDevice
// Storage is a mapping of the constraint label specified to the BlockDevices
// that match that constraint.
Storage map[string][]BlockDevice
}

// AllocateMachine implements Controller.
Expand Down Expand Up @@ -807,8 +807,8 @@ func (c *controller) readAPIVersion(apiVersion version.Number) (set.Strings, ver
func parseAllocateConstraintsResponse(source interface{}, machine *machine) (ConstraintMatches, error) {
var empty ConstraintMatches
matchFields := schema.Fields{
"storage": schema.StringMap(schema.ForceInt()),
"interfaces": schema.StringMap(schema.ForceInt()),
"storage": schema.StringMap(schema.List(schema.ForceInt())),
"interfaces": schema.StringMap(schema.List(schema.ForceInt())),
}
matchDefaults := schema.Defaults{
"storage": schema.Omit,
Expand All @@ -825,30 +825,52 @@ func parseAllocateConstraintsResponse(source interface{}, machine *machine) (Con
valid := coerced.(map[string]interface{})
constraintsMap := valid["constraints_by_type"].(map[string]interface{})
result := ConstraintMatches{
Interfaces: make(map[string]Interface),
Storage: make(map[string]BlockDevice),
Interfaces: make(map[string][]Interface),
Storage: make(map[string][]BlockDevice),
}

if interfaceMatches, found := constraintsMap["interfaces"]; found {
for label, value := range interfaceMatches.(map[string]interface{}) {
id := value.(int)
iface := machine.Interface(id)
if iface == nil {
return empty, NewDeserializationError("constraint match interface %q: %d does not match an interface for the machine", label, id)
matches := convertConstraintMatches(interfaceMatches)
for label, ids := range matches {
interfaces := make([]Interface, len(ids))
for index, id := range ids {
iface := machine.Interface(id)
if iface == nil {
return empty, NewDeserializationError("constraint match interface %q: %d does not match an interface for the machine", label, id)
}
interfaces[index] = iface
}
result.Interfaces[label] = iface
result.Interfaces[label] = interfaces
}
}

if storageMatches, found := constraintsMap["storage"]; found {
for label, value := range storageMatches.(map[string]interface{}) {
id := value.(int)
blockDevice := machine.PhysicalBlockDevice(id)
if blockDevice == nil {
return empty, NewDeserializationError("constraint match storage %q: %d does not match a physical block device for the machine", label, id)
matches := convertConstraintMatches(storageMatches)
for label, ids := range matches {
blockDevices := make([]BlockDevice, len(ids))
for index, id := range ids {
blockDevice := machine.PhysicalBlockDevice(id)
if blockDevice == nil {
return empty, NewDeserializationError("constraint match storage %q: %d does not match a physical block device for the machine", label, id)
}
blockDevices[index] = blockDevice
}
result.Storage[label] = blockDevice
result.Storage[label] = blockDevices
}
}
return result, nil
}

func convertConstraintMatches(source interface{}) map[string][]int {
// These casts are all safe because of the schema check.
result := make(map[string][]int)
matchMap := source.(map[string]interface{})
for label, values := range matchMap {
items := values.([]interface{})
result[label] = make([]int, len(items))
for index, value := range items {
result[label][index] = value.(int)
}
}
return result
}
32 changes: 19 additions & 13 deletions controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,9 @@ func (s *controllerSuite) TestAllocateMachineArgs(c *gc.C) {
}
}

func (s *controllerSuite) addAllocateResponse(c *gc.C, status int, interfaceMatches, storageMatches map[string]int) {
type constraintMatchInfo map[string][]int

func (s *controllerSuite) addAllocateResponse(c *gc.C, status int, interfaceMatches, storageMatches constraintMatchInfo) {
constraints := make(map[string]interface{})
if interfaceMatches != nil {
constraints["interfaces"] = interfaceMatches
Expand All @@ -426,8 +428,8 @@ func (s *controllerSuite) TestAllocateMachine(c *gc.C) {
}

func (s *controllerSuite) TestAllocateMachineInterfacesMatch(c *gc.C) {
s.addAllocateResponse(c, http.StatusOK, map[string]int{
"database": 35,
s.addAllocateResponse(c, http.StatusOK, constraintMatchInfo{
"database": []int{35, 99},
}, nil)
controller := s.getController(c)
_, match, err := controller.AllocateMachine(AllocateMachineArgs{
Expand All @@ -439,15 +441,17 @@ func (s *controllerSuite) TestAllocateMachineInterfacesMatch(c *gc.C) {
})
c.Assert(err, jc.ErrorIsNil)
c.Assert(match.Interfaces, gc.HasLen, 1)
iface := match.Interfaces["database"]
c.Assert(iface.ID(), gc.Equals, 35)
ifaces := match.Interfaces["database"]
c.Assert(ifaces, gc.HasLen, 2)
c.Assert(ifaces[0].ID(), gc.Equals, 35)
c.Assert(ifaces[1].ID(), gc.Equals, 99)
}

func (s *controllerSuite) TestAllocateMachineInterfacesMatchMissing(c *gc.C) {
// This should never happen, but if it does it is a clear indication of a
// bug somewhere.
s.addAllocateResponse(c, http.StatusOK, map[string]int{
"database": 40,
s.addAllocateResponse(c, http.StatusOK, constraintMatchInfo{
"database": []int{40},
}, nil)
controller := s.getController(c)
_, _, err := controller.AllocateMachine(AllocateMachineArgs{
Expand All @@ -460,8 +464,8 @@ func (s *controllerSuite) TestAllocateMachineInterfacesMatchMissing(c *gc.C) {
}

func (s *controllerSuite) TestAllocateMachineStorageMatches(c *gc.C) {
s.addAllocateResponse(c, http.StatusOK, nil, map[string]int{
"root": 34,
s.addAllocateResponse(c, http.StatusOK, nil, constraintMatchInfo{
"root": []int{34, 98},
})
controller := s.getController(c)
_, match, err := controller.AllocateMachine(AllocateMachineArgs{
Expand All @@ -473,15 +477,17 @@ func (s *controllerSuite) TestAllocateMachineStorageMatches(c *gc.C) {
})
c.Assert(err, jc.ErrorIsNil)
c.Assert(match.Storage, gc.HasLen, 1)
storage := match.Storage["root"]
c.Assert(storage.ID(), gc.Equals, 34)
storages := match.Storage["root"]
c.Assert(storages, gc.HasLen, 2)
c.Assert(storages[0].ID(), gc.Equals, 34)
c.Assert(storages[1].ID(), gc.Equals, 98)
}

func (s *controllerSuite) TestAllocateMachineStorageMatchMissing(c *gc.C) {
// This should never happen, but if it does it is a clear indication of a
// bug somewhere.
s.addAllocateResponse(c, http.StatusOK, nil, map[string]int{
"root": 50,
s.addAllocateResponse(c, http.StatusOK, nil, constraintMatchInfo{
"root": []int{50},
})
controller := s.getController(c)
_, _, err := controller.AllocateMachine(AllocateMachineArgs{
Expand Down
141 changes: 138 additions & 3 deletions machine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,18 +69,20 @@ func (*machineSuite) TestReadMachines(c *gc.C) {
c.Check(bootInterface.Name(), gc.Equals, "eth0")

interfaceSet := machine.InterfaceSet()
c.Assert(interfaceSet, gc.HasLen, 1)
c.Assert(interfaceSet, gc.HasLen, 2)
id := interfaceSet[0].ID()
c.Assert(machine.Interface(id), jc.DeepEquals, interfaceSet[0])
c.Assert(machine.Interface(id+5), gc.IsNil)

blockDevices := machine.BlockDevices()
c.Assert(blockDevices, gc.HasLen, 1)
c.Assert(blockDevices, gc.HasLen, 2)
c.Assert(blockDevices[0].Name(), gc.Equals, "sda")
c.Assert(blockDevices[1].Name(), gc.Equals, "sdb")

blockDevices = machine.PhysicalBlockDevices()
c.Assert(blockDevices, gc.HasLen, 1)
c.Assert(blockDevices, gc.HasLen, 2)
c.Assert(blockDevices[0].Name(), gc.Equals, "sda")
c.Assert(blockDevices[1].Name(), gc.Equals, "sdb")

id = blockDevices[0].ID()
c.Assert(machine.PhysicalBlockDevice(id), jc.DeepEquals, blockDevices[0])
Expand Down Expand Up @@ -360,6 +362,46 @@ const (
"tags": [
"rotary"
]
},
{
"path": "/dev/disk/by-dname/sdb",
"name": "sdb",
"used_for": "MBR partitioned with 1 partition",
"partitions": [
{
"bootable": false,
"id": 101,
"path": "/dev/disk/by-dname/sdb-part1",
"filesystem": {
"fstype": "ext4",
"mount_point": "/home",
"label": "home",
"mount_options": null,
"uuid": "fcd7745e-f1b5-4f5d-9575-9b0bb796b753"
},
"type": "partition",
"resource_uri": "/MAAS/api/2.0/nodes/4y3ha3/blockdevices/98/partition/101",
"uuid": "6199b7c9-b66f-40f6-a238-a938a58a0ae0",
"used_for": "ext4 formatted filesystem mounted at /home",
"size": 8581545984
}
],
"filesystem": null,
"id_path": "/dev/disk/by-id/ata-QEMU_HARDDISK_QM00002",
"resource_uri": "/MAAS/api/2.0/nodes/4y3ha3/blockdevices/98/",
"id": 98,
"serial": "QM00002",
"type": "physical",
"block_size": 4096,
"used_size": 8586788864,
"available_size": 0,
"partition_table_type": "MBR",
"uuid": null,
"size": 8589934592,
"model": "QEMU HARDDISK",
"tags": [
"rotary"
]
}
],
"interface_set": [
Expand Down Expand Up @@ -415,6 +457,59 @@ const (
"mode": "auto"
}
]
},
{
"effective_mtu": 1500,
"mac_address": "52:54:00:55:b6:81",
"children": [],
"discovered": [],
"params": "",
"vlan": {
"resource_uri": "/MAAS/api/2.0/vlans/1/",
"id": 1,
"secondary_rack": null,
"mtu": 1500,
"primary_rack": "4y3h7n",
"name": "untagged",
"fabric": "fabric-0",
"dhcp_on": true,
"vid": 0
},
"name": "eth0",
"enabled": true,
"parents": [],
"id": 99,
"type": "physical",
"resource_uri": "/MAAS/api/2.0/nodes/4y3ha3/interfaces/99/",
"tags": [],
"links": [
{
"id": 83,
"ip_address": "192.168.100.5",
"subnet": {
"resource_uri": "/MAAS/api/2.0/subnets/1/",
"id": 1,
"rdns_mode": 2,
"vlan": {
"resource_uri": "/MAAS/api/2.0/vlans/1/",
"id": 1,
"secondary_rack": null,
"mtu": 1500,
"primary_rack": "4y3h7n",
"name": "untagged",
"fabric": "fabric-0",
"dhcp_on": true,
"vid": 0
},
"dns_servers": [],
"space": "space-0",
"name": "192.168.100.0/24",
"gateway_ip": "192.168.100.1",
"cidr": "192.168.100.0/24"
},
"mode": "auto"
}
]
}
],
"resource_uri": "/MAAS/api/2.0/machines/4y3ha3/",
Expand Down Expand Up @@ -525,6 +620,46 @@ const (
"uuid": null,
"size": 8589934592,
"model": "QEMU HARDDISK"
},
{
"path": "/dev/disk/by-dname/sdb",
"name": "sdb",
"used_for": "MBR partitioned with 1 partition",
"partitions": [
{
"bootable": false,
"id": 101,
"path": "/dev/disk/by-dname/sdb-part1",
"filesystem": {
"fstype": "ext4",
"mount_point": "/home",
"label": "home",
"mount_options": null,
"uuid": "fcd7745e-f1b5-4f5d-9575-9b0bb796b753"
},
"type": "partition",
"resource_uri": "/MAAS/api/2.0/nodes/4y3ha3/blockdevices/98/partition/101",
"uuid": "6199b7c9-b66f-40f6-a238-a938a58a0ae0",
"used_for": "ext4 formatted filesystem mounted at /home",
"size": 8581545984
}
],
"filesystem": null,
"id_path": "/dev/disk/by-id/ata-QEMU_HARDDISK_QM00002",
"resource_uri": "/MAAS/api/2.0/nodes/4y3ha3/blockdevices/98/",
"id": 98,
"serial": "QM00002",
"type": "physical",
"block_size": 4096,
"used_size": 8586788864,
"available_size": 0,
"partition_table_type": "MBR",
"uuid": null,
"size": 8589934592,
"model": "QEMU HARDDISK",
"tags": [
"rotary"
]
}
],
"zone": {
Expand Down

0 comments on commit 3b0458b

Please sign in to comment.