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