github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/provider/maas/instance.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package maas
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/gomaasapi"
    12  	"gopkg.in/juju/names.v2"
    13  
    14  	"github.com/juju/juju/instance"
    15  	"github.com/juju/juju/network"
    16  	"github.com/juju/juju/status"
    17  	"github.com/juju/juju/storage"
    18  )
    19  
    20  type maasInstance interface {
    21  	instance.Instance
    22  	zone() (string, error)
    23  	hostname() (string, error)
    24  	hardwareCharacteristics() (*instance.HardwareCharacteristics, error)
    25  	volumes(names.MachineTag, []names.VolumeTag) ([]storage.Volume, []storage.VolumeAttachment, error)
    26  }
    27  
    28  type maas1Instance struct {
    29  	maasObject   *gomaasapi.MAASObject
    30  	environ      *maasEnviron
    31  	statusGetter func(instance.Id) (string, string)
    32  }
    33  
    34  var _ maasInstance = (*maas1Instance)(nil)
    35  
    36  func (mi *maas1Instance) String() string {
    37  	hostname, err := mi.hostname()
    38  	if err != nil {
    39  		// This is meant to be impossible, but be paranoid.
    40  		hostname = fmt.Sprintf("<DNSName failed: %q>", err)
    41  	}
    42  	return fmt.Sprintf("%s:%s", hostname, mi.Id())
    43  }
    44  
    45  func (mi *maas1Instance) Id() instance.Id {
    46  	return maasObjectId(mi.maasObject)
    47  }
    48  
    49  func maasObjectId(maasObject *gomaasapi.MAASObject) instance.Id {
    50  	// Use the node's 'resource_uri' value.
    51  	return instance.Id(maasObject.URI().String())
    52  }
    53  
    54  func normalizeStatus(statusMsg string) string {
    55  	return strings.ToLower(strings.TrimSpace(statusMsg))
    56  }
    57  
    58  func convertInstanceStatus(statusMsg, substatus string, id instance.Id) instance.InstanceStatus {
    59  	maasInstanceStatus := status.Empty
    60  	switch normalizeStatus(statusMsg) {
    61  	case "":
    62  		logger.Debugf("unable to obtain status of instance %s", id)
    63  		statusMsg = "error in getting status"
    64  	case "deployed":
    65  		maasInstanceStatus = status.Running
    66  	case "deploying":
    67  		maasInstanceStatus = status.Allocating
    68  		if substatus != "" {
    69  			statusMsg = fmt.Sprintf("%s: %s", statusMsg, substatus)
    70  		}
    71  	case "failed deployment":
    72  		maasInstanceStatus = status.ProvisioningError
    73  		if substatus != "" {
    74  			statusMsg = fmt.Sprintf("%s: %s", statusMsg, substatus)
    75  		}
    76  	default:
    77  		maasInstanceStatus = status.Empty
    78  		statusMsg = fmt.Sprintf("%s: %s", statusMsg, substatus)
    79  	}
    80  	return instance.InstanceStatus{
    81  		Status:  maasInstanceStatus,
    82  		Message: statusMsg,
    83  	}
    84  }
    85  
    86  // Status returns a juju status based on the maas instance returned
    87  // status message.
    88  func (mi *maas1Instance) Status() instance.InstanceStatus {
    89  	statusMsg, substatus := mi.statusGetter(mi.Id())
    90  	return convertInstanceStatus(statusMsg, substatus, mi.Id())
    91  }
    92  
    93  func (mi *maas1Instance) Addresses() ([]network.Address, error) {
    94  	interfaceAddresses, err := mi.interfaceAddresses()
    95  	if err != nil {
    96  		return nil, errors.Annotate(err, "getting node interfaces")
    97  	}
    98  
    99  	logger.Debugf("instance %q has interface addresses: %+v", mi.Id(), interfaceAddresses)
   100  	return interfaceAddresses, nil
   101  }
   102  
   103  var refreshMAASObject = func(maasObject *gomaasapi.MAASObject) (gomaasapi.MAASObject, error) {
   104  	// Defined like this to allow patching in tests to overcome limitations of
   105  	// gomaasapi's test server.
   106  	return maasObject.Get()
   107  }
   108  
   109  // interfaceAddresses fetches a fresh copy of the node details from MAAS and
   110  // extracts all addresses from the node's interfaces. Returns an error
   111  // satisfying errors.IsNotSupported() if MAAS API does not report interfaces
   112  // information.
   113  func (mi *maas1Instance) interfaceAddresses() ([]network.Address, error) {
   114  	// Fetch a fresh copy of the instance JSON first.
   115  	obj, err := refreshMAASObject(mi.maasObject)
   116  	if err != nil {
   117  		return nil, errors.Annotate(err, "getting instance details")
   118  	}
   119  
   120  	subnetsMap, err := mi.environ.subnetToSpaceIds()
   121  	if err != nil {
   122  		return nil, errors.Trace(err)
   123  	}
   124  	// Get all the interface details and extract the addresses.
   125  	interfaces, err := maasObjectNetworkInterfaces(&obj, subnetsMap)
   126  	if err != nil {
   127  		return nil, errors.Trace(err)
   128  	}
   129  
   130  	var addresses []network.Address
   131  	for _, iface := range interfaces {
   132  		if iface.Address.Value != "" {
   133  			logger.Debugf("found address %q on interface %q", iface.Address, iface.InterfaceName)
   134  			addresses = append(addresses, iface.Address)
   135  		} else {
   136  			logger.Infof("no address found on interface %q", iface.InterfaceName)
   137  		}
   138  	}
   139  	return addresses, nil
   140  }
   141  
   142  func (mi *maas1Instance) architecture() (arch, subarch string, err error) {
   143  	// MAAS may return an architecture of the form, for example,
   144  	// "amd64/generic"; we only care about the major part.
   145  	arch, err = mi.maasObject.GetField("architecture")
   146  	if err != nil {
   147  		return "", "", err
   148  	}
   149  	parts := strings.SplitN(arch, "/", 2)
   150  	arch = parts[0]
   151  	if len(parts) == 2 {
   152  		subarch = parts[1]
   153  	}
   154  	return arch, subarch, nil
   155  }
   156  
   157  func (mi *maas1Instance) zone() (string, error) {
   158  	// TODO (anastasiamac 2016-03-31)
   159  	// This code is needed until gomaasapi testing code is
   160  	// updated to align with MAAS.
   161  	// Currently, "zone" property is still treated as field
   162  	// by gomaasi infrastructure and is searched for
   163  	// using matchField(node, "zone", zoneName) instead of
   164  	// getMap.
   165  	// @see gomaasapi/testservice.go#findFreeNode
   166  	// bug https://bugs.launchpad.net/gomaasapi/+bug/1563631
   167  	zone, fieldErr := mi.maasObject.GetField("zone")
   168  	if fieldErr == nil && zone != "" {
   169  		return zone, nil
   170  	}
   171  
   172  	obj := mi.maasObject.GetMap()["zone"]
   173  	if obj.IsNil() {
   174  		return "", errors.New("zone property not set on maas")
   175  	}
   176  	zoneMap, err := obj.GetMap()
   177  	if err != nil {
   178  		return "", errors.New("zone property is not an expected type")
   179  	}
   180  	nameObj, ok := zoneMap["name"]
   181  	if !ok {
   182  		return "", errors.New("zone property is not set correctly: name is missing")
   183  	}
   184  	str, err := nameObj.GetString()
   185  	if err != nil {
   186  		return "", err
   187  	}
   188  	return str, nil
   189  }
   190  
   191  func (mi *maas1Instance) cpuCount() (uint64, error) {
   192  	count, err := mi.maasObject.GetMap()["cpu_count"].GetFloat64()
   193  	if err != nil {
   194  		return 0, err
   195  	}
   196  	return uint64(count), nil
   197  }
   198  
   199  func (mi *maas1Instance) memory() (uint64, error) {
   200  	mem, err := mi.maasObject.GetMap()["memory"].GetFloat64()
   201  	if err != nil {
   202  		return 0, err
   203  	}
   204  	return uint64(mem), nil
   205  }
   206  
   207  func (mi *maas1Instance) tagNames() ([]string, error) {
   208  	obj := mi.maasObject.GetMap()["tag_names"]
   209  	if obj.IsNil() {
   210  		return nil, errors.NotFoundf("tag_names")
   211  	}
   212  	array, err := obj.GetArray()
   213  	if err != nil {
   214  		return nil, err
   215  	}
   216  	tags := make([]string, len(array))
   217  	for i, obj := range array {
   218  		tag, err := obj.GetString()
   219  		if err != nil {
   220  			return nil, err
   221  		}
   222  		tags[i] = tag
   223  	}
   224  	return tags, nil
   225  }
   226  
   227  func (mi *maas1Instance) hardwareCharacteristics() (*instance.HardwareCharacteristics, error) {
   228  	nodeArch, _, err := mi.architecture()
   229  	if err != nil {
   230  		return nil, errors.Annotate(err, "error determining architecture")
   231  	}
   232  	nodeCpuCount, err := mi.cpuCount()
   233  	if err != nil {
   234  		return nil, errors.Annotate(err, "error determining cpu count")
   235  	}
   236  	nodeMemoryMB, err := mi.memory()
   237  	if err != nil {
   238  		return nil, errors.Annotate(err, "error determining available memory")
   239  	}
   240  	zone, err := mi.zone()
   241  	if err != nil {
   242  		return nil, errors.Annotate(err, "error determining availability zone")
   243  	}
   244  	hc := &instance.HardwareCharacteristics{
   245  		Arch:             &nodeArch,
   246  		CpuCores:         &nodeCpuCount,
   247  		Mem:              &nodeMemoryMB,
   248  		AvailabilityZone: &zone,
   249  	}
   250  	nodeTags, err := mi.tagNames()
   251  	if err != nil && !errors.IsNotFound(err) {
   252  		return nil, errors.Annotate(err, "error determining tag names")
   253  	}
   254  	if len(nodeTags) > 0 {
   255  		hc.Tags = &nodeTags
   256  	}
   257  	return hc, nil
   258  }
   259  
   260  func (mi *maas1Instance) hostname() (string, error) {
   261  	// A MAAS instance has its DNS name immediately.
   262  	return mi.maasObject.GetField("hostname")
   263  }
   264  
   265  // MAAS does not do firewalling so these port methods do nothing.
   266  func (mi *maas1Instance) OpenPorts(machineId string, ports []network.PortRange) error {
   267  	logger.Debugf("unimplemented OpenPorts() called")
   268  	return nil
   269  }
   270  
   271  func (mi *maas1Instance) ClosePorts(machineId string, ports []network.PortRange) error {
   272  	logger.Debugf("unimplemented ClosePorts() called")
   273  	return nil
   274  }
   275  
   276  func (mi *maas1Instance) Ports(machineId string) ([]network.PortRange, error) {
   277  	logger.Debugf("unimplemented Ports() called")
   278  	return nil, nil
   279  }