cord-776 create build / runtime containers for autmation uservices
Change-Id: I246973192adef56a250ffe93a5f65fff488840c1
diff --git a/automation/vendor/github.com/juju/gomaasapi/machine.go b/automation/vendor/github.com/juju/gomaasapi/machine.go
new file mode 100644
index 0000000..8518d94
--- /dev/null
+++ b/automation/vendor/github.com/juju/gomaasapi/machine.go
@@ -0,0 +1,584 @@
+// Copyright 2016 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package gomaasapi
+
+import (
+ "fmt"
+ "net/http"
+ "net/url"
+
+ "github.com/juju/errors"
+ "github.com/juju/schema"
+ "github.com/juju/version"
+)
+
+type machine struct {
+ controller *controller
+
+ resourceURI string
+
+ systemID string
+ hostname string
+ fqdn string
+ tags []string
+ ownerData map[string]string
+
+ operatingSystem string
+ distroSeries string
+ architecture string
+ memory int
+ cpuCount int
+
+ ipAddresses []string
+ powerState string
+
+ // NOTE: consider some form of status struct
+ statusName string
+ statusMessage string
+
+ bootInterface *interface_
+ interfaceSet []*interface_
+ zone *zone
+ // Don't really know the difference between these two lists:
+ physicalBlockDevices []*blockdevice
+ blockDevices []*blockdevice
+}
+
+func (m *machine) updateFrom(other *machine) {
+ m.resourceURI = other.resourceURI
+ m.systemID = other.systemID
+ m.hostname = other.hostname
+ m.fqdn = other.fqdn
+ m.operatingSystem = other.operatingSystem
+ m.distroSeries = other.distroSeries
+ m.architecture = other.architecture
+ m.memory = other.memory
+ m.cpuCount = other.cpuCount
+ m.ipAddresses = other.ipAddresses
+ m.powerState = other.powerState
+ m.statusName = other.statusName
+ m.statusMessage = other.statusMessage
+ m.zone = other.zone
+ m.tags = other.tags
+ m.ownerData = other.ownerData
+}
+
+// SystemID implements Machine.
+func (m *machine) SystemID() string {
+ return m.systemID
+}
+
+// Hostname implements Machine.
+func (m *machine) Hostname() string {
+ return m.hostname
+}
+
+// FQDN implements Machine.
+func (m *machine) FQDN() string {
+ return m.fqdn
+}
+
+// Tags implements Machine.
+func (m *machine) Tags() []string {
+ return m.tags
+}
+
+// IPAddresses implements Machine.
+func (m *machine) IPAddresses() []string {
+ return m.ipAddresses
+}
+
+// Memory implements Machine.
+func (m *machine) Memory() int {
+ return m.memory
+}
+
+// CPUCount implements Machine.
+func (m *machine) CPUCount() int {
+ return m.cpuCount
+}
+
+// PowerState implements Machine.
+func (m *machine) PowerState() string {
+ return m.powerState
+}
+
+// Zone implements Machine.
+func (m *machine) Zone() Zone {
+ if m.zone == nil {
+ return nil
+ }
+ return m.zone
+}
+
+// BootInterface implements Machine.
+func (m *machine) BootInterface() Interface {
+ if m.bootInterface == nil {
+ return nil
+ }
+ m.bootInterface.controller = m.controller
+ return m.bootInterface
+}
+
+// InterfaceSet implements Machine.
+func (m *machine) InterfaceSet() []Interface {
+ result := make([]Interface, len(m.interfaceSet))
+ for i, v := range m.interfaceSet {
+ v.controller = m.controller
+ result[i] = v
+ }
+ return result
+}
+
+// Interface implements Machine.
+func (m *machine) Interface(id int) Interface {
+ for _, iface := range m.interfaceSet {
+ if iface.ID() == id {
+ iface.controller = m.controller
+ return iface
+ }
+ }
+ return nil
+}
+
+// OperatingSystem implements Machine.
+func (m *machine) OperatingSystem() string {
+ return m.operatingSystem
+}
+
+// DistroSeries implements Machine.
+func (m *machine) DistroSeries() string {
+ return m.distroSeries
+}
+
+// Architecture implements Machine.
+func (m *machine) Architecture() string {
+ return m.architecture
+}
+
+// StatusName implements Machine.
+func (m *machine) StatusName() string {
+ return m.statusName
+}
+
+// StatusMessage implements Machine.
+func (m *machine) StatusMessage() string {
+ return m.statusMessage
+}
+
+// PhysicalBlockDevices implements Machine.
+func (m *machine) PhysicalBlockDevices() []BlockDevice {
+ result := make([]BlockDevice, len(m.physicalBlockDevices))
+ for i, v := range m.physicalBlockDevices {
+ result[i] = v
+ }
+ return result
+}
+
+// PhysicalBlockDevice implements Machine.
+func (m *machine) PhysicalBlockDevice(id int) BlockDevice {
+ for _, blockDevice := range m.physicalBlockDevices {
+ if blockDevice.ID() == id {
+ return blockDevice
+ }
+ }
+ return nil
+}
+
+// BlockDevices implements Machine.
+func (m *machine) BlockDevices() []BlockDevice {
+ result := make([]BlockDevice, len(m.blockDevices))
+ for i, v := range m.blockDevices {
+ result[i] = v
+ }
+ return result
+}
+
+// Devices implements Machine.
+func (m *machine) Devices(args DevicesArgs) ([]Device, error) {
+ // Perhaps in the future, MAAS will give us a way to query just for the
+ // devices for a particular parent.
+ devices, err := m.controller.Devices(args)
+ if err != nil {
+ return nil, errors.Trace(err)
+ }
+ var result []Device
+ for _, device := range devices {
+ if device.Parent() == m.SystemID() {
+ result = append(result, device)
+ }
+ }
+ return result, nil
+}
+
+// StartArgs is an argument struct for passing parameters to the Machine.Start
+// method.
+type StartArgs struct {
+ // UserData needs to be Base64 encoded user data for cloud-init.
+ UserData string
+ DistroSeries string
+ Kernel string
+ Comment string
+}
+
+// Start implements Machine.
+func (m *machine) Start(args StartArgs) error {
+ params := NewURLParams()
+ params.MaybeAdd("user_data", args.UserData)
+ params.MaybeAdd("distro_series", args.DistroSeries)
+ params.MaybeAdd("hwe_kernel", args.Kernel)
+ params.MaybeAdd("comment", args.Comment)
+ result, err := m.controller.post(m.resourceURI, "deploy", params.Values)
+ if err != nil {
+ if svrErr, ok := errors.Cause(err).(ServerError); ok {
+ switch svrErr.StatusCode {
+ case http.StatusNotFound, http.StatusConflict:
+ 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)
+ }
+
+ machine, err := readMachine(m.controller.apiVersion, result)
+ if err != nil {
+ return errors.Trace(err)
+ }
+ m.updateFrom(machine)
+ return nil
+}
+
+// CreateMachineDeviceArgs is an argument structure for Machine.CreateDevice.
+// Only InterfaceName and MACAddress fields are required, the others are only
+// used if set. If Subnet and VLAN are both set, Subnet.VLAN() must match the
+// given VLAN. On failure, returns an error satisfying errors.IsNotValid().
+type CreateMachineDeviceArgs struct {
+ Hostname string
+ InterfaceName string
+ MACAddress string
+ Subnet Subnet
+ VLAN VLAN
+}
+
+// Validate ensures that all required values are non-emtpy.
+func (a *CreateMachineDeviceArgs) Validate() error {
+ if a.InterfaceName == "" {
+ return errors.NotValidf("missing InterfaceName")
+ }
+
+ if a.MACAddress == "" {
+ return errors.NotValidf("missing MACAddress")
+ }
+
+ if a.Subnet != nil && a.VLAN != nil && a.Subnet.VLAN() != a.VLAN {
+ msg := fmt.Sprintf(
+ "given subnet %q on VLAN %d does not match given VLAN %d",
+ a.Subnet.CIDR(), a.Subnet.VLAN().ID(), a.VLAN.ID(),
+ )
+ return errors.NewNotValid(nil, msg)
+ }
+
+ return nil
+}
+
+// CreateDevice implements Machine
+func (m *machine) CreateDevice(args CreateMachineDeviceArgs) (_ Device, err error) {
+ if err := args.Validate(); err != nil {
+ return nil, errors.Trace(err)
+ }
+ device, err := m.controller.CreateDevice(CreateDeviceArgs{
+ Hostname: args.Hostname,
+ MACAddresses: []string{args.MACAddress},
+ Parent: m.SystemID(),
+ })
+ if err != nil {
+ return nil, errors.Trace(err)
+ }
+
+ defer func(err *error) {
+ // If there is an error return, at least try to delete the device we just created.
+ if *err != nil {
+ if innerErr := device.Delete(); innerErr != nil {
+ logger.Warningf("could not delete device %q", device.SystemID())
+ }
+ }
+ }(&err)
+
+ // Update the VLAN to use for the interface, if given.
+ vlanToUse := args.VLAN
+ if vlanToUse == nil && args.Subnet != nil {
+ vlanToUse = args.Subnet.VLAN()
+ }
+
+ // There should be one interface created for each MAC Address, and since we
+ // only specified one, there should just be one response.
+ interfaces := device.InterfaceSet()
+ if count := len(interfaces); count != 1 {
+ err := errors.Errorf("unexpected interface count for device: %d", count)
+ return nil, NewUnexpectedError(err)
+ }
+ iface := interfaces[0]
+ nameToUse := args.InterfaceName
+
+ if err := m.updateDeviceInterface(iface, nameToUse, vlanToUse); err != nil {
+ return nil, errors.Trace(err)
+ }
+
+ if args.Subnet == nil {
+ // Nothing further to update.
+ return device, nil
+ }
+
+ if err := m.linkDeviceInterfaceToSubnet(iface, args.Subnet); err != nil {
+ return nil, errors.Trace(err)
+ }
+
+ return device, nil
+}
+
+func (m *machine) updateDeviceInterface(iface Interface, nameToUse string, vlanToUse VLAN) error {
+ updateArgs := UpdateInterfaceArgs{}
+ updateArgs.Name = nameToUse
+
+ if vlanToUse != nil {
+ updateArgs.VLAN = vlanToUse
+ }
+
+ if err := iface.Update(updateArgs); err != nil {
+ return errors.Annotatef(err, "updating device interface %q failed", iface.Name())
+ }
+
+ return nil
+}
+
+func (m *machine) linkDeviceInterfaceToSubnet(iface Interface, subnetToUse Subnet) error {
+ err := iface.LinkSubnet(LinkSubnetArgs{
+ Mode: LinkModeStatic,
+ Subnet: subnetToUse,
+ })
+ if err != nil {
+ return errors.Annotatef(
+ err, "linking device interface %q to subnet %q failed",
+ iface.Name(), subnetToUse.CIDR())
+ }
+
+ return 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 {
+ return nil, errors.Trace(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 readMachines(controllerVersion version.Number, source interface{}) ([]*machine, error) {
+ readFunc, err := getMachineDeserializationFunc(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")
+ }
+ valid := coerced.([]interface{})
+ return readMachineList(valid, readFunc)
+}
+
+func getMachineDeserializationFunc(controllerVersion version.Number) (machineDeserializationFunc, error) {
+ var deserialisationVersion version.Number
+ for v := range machineDeserializationFuncs {
+ if v.Compare(deserialisationVersion) > 0 && v.Compare(controllerVersion) <= 0 {
+ deserialisationVersion = v
+ }
+ }
+ if deserialisationVersion == version.Zero {
+ return nil, NewUnsupportedVersionError("no machine read func for version %s", controllerVersion)
+ }
+ return machineDeserializationFuncs[deserialisationVersion], nil
+}
+
+func readMachineList(sourceList []interface{}, readFunc machineDeserializationFunc) ([]*machine, error) {
+ result := make([]*machine, 0, len(sourceList))
+ for i, value := range sourceList {
+ source, ok := value.(map[string]interface{})
+ if !ok {
+ return nil, NewDeserializationError("unexpected value for machine %d, %T", i, value)
+ }
+ machine, err := readFunc(source)
+ if err != nil {
+ return nil, errors.Annotatef(err, "machine %d", i)
+ }
+ result = append(result, machine)
+ }
+ return result, nil
+}
+
+type machineDeserializationFunc func(map[string]interface{}) (*machine, error)
+
+var machineDeserializationFuncs = map[version.Number]machineDeserializationFunc{
+ twoDotOh: machine_2_0,
+}
+
+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()),
+ "owner_data": schema.StringMap(schema.String()),
+
+ "osystem": schema.String(),
+ "distro_series": schema.String(),
+ "architecture": schema.OneOf(schema.Nil(""), schema.String()),
+ "memory": schema.ForceInt(),
+ "cpu_count": schema.ForceInt(),
+
+ "ip_addresses": schema.List(schema.String()),
+ "power_state": schema.String(),
+ "status_name": schema.String(),
+ "status_message": schema.OneOf(schema.Nil(""), schema.String()),
+
+ "boot_interface": schema.OneOf(schema.Nil(""), schema.StringMap(schema.Any())),
+ "interface_set": schema.List(schema.StringMap(schema.Any())),
+ "zone": schema.StringMap(schema.Any()),
+
+ "physicalblockdevice_set": schema.List(schema.StringMap(schema.Any())),
+ "blockdevice_set": schema.List(schema.StringMap(schema.Any())),
+ }
+ defaults := schema.Defaults{
+ "architecture": "",
+ }
+ checker := schema.FieldMap(fields, defaults)
+ coerced, err := checker.Coerce(source, nil)
+ if err != nil {
+ return nil, WrapWithDeserializationError(err, "machine 2.0 schema check failed")
+ }
+ valid := coerced.(map[string]interface{})
+ // From here we know that the map returned from the schema coercion
+ // contains fields of the right type.
+
+ var bootInterface *interface_
+ if ifaceMap, ok := valid["boot_interface"].(map[string]interface{}); ok {
+ bootInterface, err = interface_2_0(ifaceMap)
+ if err != nil {
+ return nil, errors.Trace(err)
+ }
+ }
+
+ interfaceSet, err := readInterfaceList(valid["interface_set"].([]interface{}), interface_2_0)
+ if err != nil {
+ return nil, errors.Trace(err)
+ }
+ zone, err := zone_2_0(valid["zone"].(map[string]interface{}))
+ if err != nil {
+ return nil, errors.Trace(err)
+ }
+ physicalBlockDevices, err := readBlockDeviceList(valid["physicalblockdevice_set"].([]interface{}), blockdevice_2_0)
+ if err != nil {
+ return nil, errors.Trace(err)
+ }
+ blockDevices, err := readBlockDeviceList(valid["blockdevice_set"].([]interface{}), blockdevice_2_0)
+ if err != nil {
+ return nil, errors.Trace(err)
+ }
+ architecture, _ := valid["architecture"].(string)
+ statusMessage, _ := valid["status_message"].(string)
+ 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"]),
+ ownerData: convertToStringMap(valid["owner_data"]),
+
+ operatingSystem: valid["osystem"].(string),
+ distroSeries: valid["distro_series"].(string),
+ architecture: architecture,
+ memory: valid["memory"].(int),
+ cpuCount: valid["cpu_count"].(int),
+
+ ipAddresses: convertToStringSlice(valid["ip_addresses"]),
+ powerState: valid["power_state"].(string),
+ statusName: valid["status_name"].(string),
+ statusMessage: statusMessage,
+
+ bootInterface: bootInterface,
+ interfaceSet: interfaceSet,
+ zone: zone,
+ physicalBlockDevices: physicalBlockDevices,
+ blockDevices: blockDevices,
+ }
+
+ return result, nil
+}
+
+func convertToStringSlice(field interface{}) []string {
+ if field == nil {
+ return nil
+ }
+ fieldSlice := field.([]interface{})
+ result := make([]string, len(fieldSlice))
+ for i, value := range fieldSlice {
+ result[i] = value.(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
+}