github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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/core/instance"
    15  	"github.com/juju/juju/core/status"
    16  	"github.com/juju/juju/environs/context"
    17  	"github.com/juju/juju/environs/instances"
    18  	"github.com/juju/juju/network"
    19  	"github.com/juju/juju/provider/common"
    20  	"github.com/juju/juju/storage"
    21  )
    22  
    23  type maasInstance interface {
    24  	instances.Instance
    25  	zone() (string, error)
    26  	hostname() (string, error)
    27  	hardwareCharacteristics() (*instance.HardwareCharacteristics, error)
    28  	volumes(names.MachineTag, []names.VolumeTag) ([]storage.Volume, []storage.VolumeAttachment, error)
    29  }
    30  
    31  type maas1Instance struct {
    32  	maasObject   *gomaasapi.MAASObject
    33  	environ      *maasEnviron
    34  	statusGetter func(context.ProviderCallContext, instance.Id) (string, string)
    35  }
    36  
    37  var _ maasInstance = (*maas1Instance)(nil)
    38  
    39  func (mi *maas1Instance) String() string {
    40  	hostname, err := mi.hostname()
    41  	if err != nil {
    42  		// This is meant to be impossible, but be paranoid.
    43  		logger.Errorf("unable to detect MAAS instance hostname: %q", err)
    44  		hostname = fmt.Sprintf("<DNSName failed: %q>", err)
    45  	}
    46  	return fmt.Sprintf("%s:%s", hostname, mi.Id())
    47  }
    48  
    49  func (mi *maas1Instance) Id() instance.Id {
    50  	// Note: URI excludes the hostname
    51  	return instance.Id(mi.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.Status {
    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.Status{
    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(ctx context.ProviderCallContext) instance.Status {
    89  	statusMsg, substatus := mi.statusGetter(ctx, mi.Id())
    90  	return convertInstanceStatus(statusMsg, substatus, mi.Id())
    91  }
    92  
    93  func (mi *maas1Instance) Addresses(ctx context.ProviderCallContext) ([]network.Address, error) {
    94  	interfaceAddresses, err := mi.interfaceAddresses(ctx)
    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(ctx context.ProviderCallContext) ([]network.Address, error) {
   114  	// Fetch a fresh copy of the instance JSON first.
   115  	obj, err := refreshMAASObject(mi.maasObject)
   116  	if err != nil {
   117  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   118  		return nil, errors.Annotate(err, "getting instance details")
   119  	}
   120  
   121  	subnetsMap, err := mi.environ.subnetToSpaceIds(ctx)
   122  	if err != nil {
   123  		return nil, errors.Trace(err)
   124  	}
   125  	// Get all the interface details and extract the addresses.
   126  	interfaces, err := maasObjectNetworkInterfaces(ctx, &obj, subnetsMap)
   127  	if err != nil {
   128  		return nil, errors.Trace(err)
   129  	}
   130  
   131  	var addresses []network.Address
   132  	for _, iface := range interfaces {
   133  		if iface.Address.Value != "" {
   134  			logger.Debugf("found address %q on interface %q", iface.Address, iface.InterfaceName)
   135  			addresses = append(addresses, iface.Address)
   136  		} else {
   137  			logger.Infof("no address found on interface %q", iface.InterfaceName)
   138  		}
   139  	}
   140  	return addresses, nil
   141  }
   142  
   143  func (mi *maas1Instance) architecture() (arch, subarch string, err error) {
   144  	// MAAS may return an architecture of the form, for example,
   145  	// "amd64/generic"; we only care about the major part.
   146  	arch, err = mi.maasObject.GetField("architecture")
   147  	if err != nil {
   148  		return "", "", err
   149  	}
   150  	parts := strings.SplitN(arch, "/", 2)
   151  	arch = parts[0]
   152  	if len(parts) == 2 {
   153  		subarch = parts[1]
   154  	}
   155  	return arch, subarch, nil
   156  }
   157  
   158  func (mi *maas1Instance) zone() (string, error) {
   159  	// TODO (anastasiamac 2016-03-31)
   160  	// This code is needed until gomaasapi testing code is
   161  	// updated to align with MAAS.
   162  	// Currently, "zone" property is still treated as field
   163  	// by gomaasi infrastructure and is searched for
   164  	// using matchField(node, "zone", zoneName) instead of
   165  	// getMap.
   166  	// @see gomaasapi/testservice.go#findFreeNode
   167  	// bug https://bugs.launchpad.net/gomaasapi/+bug/1563631
   168  	zone, fieldErr := mi.maasObject.GetField("zone")
   169  	if fieldErr == nil && zone != "" {
   170  		return zone, nil
   171  	}
   172  
   173  	obj := mi.maasObject.GetMap()["zone"]
   174  	if obj.IsNil() {
   175  		return "", errors.New("zone property not set on maas")
   176  	}
   177  	zoneMap, err := obj.GetMap()
   178  	if err != nil {
   179  		return "", errors.New("zone property is not an expected type")
   180  	}
   181  	nameObj, ok := zoneMap["name"]
   182  	if !ok {
   183  		return "", errors.New("zone property is not set correctly: name is missing")
   184  	}
   185  	str, err := nameObj.GetString()
   186  	if err != nil {
   187  		return "", err
   188  	}
   189  	return str, nil
   190  }
   191  
   192  func (mi *maas1Instance) cpuCount() (uint64, error) {
   193  	count, err := mi.maasObject.GetMap()["cpu_count"].GetFloat64()
   194  	if err != nil {
   195  		return 0, err
   196  	}
   197  	return uint64(count), nil
   198  }
   199  
   200  func (mi *maas1Instance) memory() (uint64, error) {
   201  	mem, err := mi.maasObject.GetMap()["memory"].GetFloat64()
   202  	if err != nil {
   203  		return 0, err
   204  	}
   205  	return uint64(mem), nil
   206  }
   207  
   208  func (mi *maas1Instance) tagNames() ([]string, error) {
   209  	obj := mi.maasObject.GetMap()["tag_names"]
   210  	if obj.IsNil() {
   211  		return nil, errors.NotFoundf("tag_names")
   212  	}
   213  	array, err := obj.GetArray()
   214  	if err != nil {
   215  		return nil, err
   216  	}
   217  	tags := make([]string, len(array))
   218  	for i, obj := range array {
   219  		tag, err := obj.GetString()
   220  		if err != nil {
   221  			return nil, err
   222  		}
   223  		tags[i] = tag
   224  	}
   225  	return tags, nil
   226  }
   227  
   228  func (mi *maas1Instance) hardwareCharacteristics() (*instance.HardwareCharacteristics, error) {
   229  	nodeArch, _, err := mi.architecture()
   230  	if err != nil {
   231  		return nil, errors.Annotate(err, "error determining architecture")
   232  	}
   233  	nodeCpuCount, err := mi.cpuCount()
   234  	if err != nil {
   235  		return nil, errors.Annotate(err, "error determining cpu count")
   236  	}
   237  	nodeMemoryMB, err := mi.memory()
   238  	if err != nil {
   239  		return nil, errors.Annotate(err, "error determining available memory")
   240  	}
   241  	zone, err := mi.zone()
   242  	if err != nil {
   243  		return nil, errors.Annotate(err, "error determining availability zone")
   244  	}
   245  	hc := &instance.HardwareCharacteristics{
   246  		Arch:             &nodeArch,
   247  		CpuCores:         &nodeCpuCount,
   248  		Mem:              &nodeMemoryMB,
   249  		AvailabilityZone: &zone,
   250  	}
   251  	nodeTags, err := mi.tagNames()
   252  	if err != nil && !errors.IsNotFound(err) {
   253  		return nil, errors.Annotate(err, "error determining tag names")
   254  	}
   255  	if len(nodeTags) > 0 {
   256  		hc.Tags = &nodeTags
   257  	}
   258  	return hc, nil
   259  }
   260  
   261  func (mi *maas1Instance) hostname() (string, error) {
   262  	// A MAAS instance has its DNS name immediately.
   263  	return mi.maasObject.GetField("hostname")
   264  }