github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/provider/gce/environ_instance.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package gce
     5  
     6  import (
     7  	"strings"
     8  
     9  	"github.com/juju/errors"
    10  
    11  	"github.com/juju/juju/constraints"
    12  	"github.com/juju/juju/environs"
    13  	"github.com/juju/juju/instance"
    14  	"github.com/juju/juju/provider/common"
    15  	"github.com/juju/juju/provider/gce/google"
    16  )
    17  
    18  // instStatus is the list of statuses to accept when filtering
    19  // for "alive" instances.
    20  var instStatuses = []string{
    21  	google.StatusPending,
    22  	google.StatusStaging,
    23  	google.StatusRunning,
    24  }
    25  
    26  // Instances returns the available instances in the environment that
    27  // match the provided instance IDs. For IDs that did not match any
    28  // instances, the result at the corresponding index will be nil. In that
    29  // case the error will be environs.ErrPartialInstances (or
    30  // ErrNoInstances if none of the IDs match an instance).
    31  func (env *environ) Instances(ids []instance.Id) ([]instance.Instance, error) {
    32  	if len(ids) == 0 {
    33  		return nil, environs.ErrNoInstances
    34  	}
    35  
    36  	instances, err := getInstances(env)
    37  	if err != nil {
    38  		// We don't return the error since we need to pack one instance
    39  		// for each ID into the result. If there is a problem then we
    40  		// will return either ErrPartialInstances or ErrNoInstances.
    41  		// TODO(ericsnow) Skip returning here only for certain errors?
    42  		logger.Errorf("failed to get instances from GCE: %v", err)
    43  		err = errors.Trace(err)
    44  	}
    45  
    46  	// Build the result, matching the provided instance IDs.
    47  	numFound := 0 // This will never be greater than len(ids).
    48  	results := make([]instance.Instance, len(ids))
    49  	for i, id := range ids {
    50  		inst := findInst(id, instances)
    51  		if inst != nil {
    52  			numFound++
    53  		}
    54  		results[i] = inst
    55  	}
    56  
    57  	if numFound == 0 {
    58  		if err == nil {
    59  			err = environs.ErrNoInstances
    60  		}
    61  	} else if numFound != len(ids) {
    62  		err = environs.ErrPartialInstances
    63  	}
    64  	return results, err
    65  }
    66  
    67  var getInstances = func(env *environ) ([]instance.Instance, error) {
    68  	return env.instances()
    69  }
    70  
    71  // instances returns a list of all "alive" instances in the environment.
    72  // This means only instances where the IDs match
    73  // "juju-<env name>-machine-*". This is important because otherwise juju
    74  // will see they are not tracked in state, assume they're stale/rogue,
    75  // and shut them down.
    76  func (env *environ) instances() ([]instance.Instance, error) {
    77  	env = env.getSnapshot()
    78  
    79  	prefix := common.MachineFullName(env, "")
    80  	instances, err := env.gce.Instances(prefix, instStatuses...)
    81  	err = errors.Trace(err)
    82  
    83  	// Turn google.Instance values into *environInstance values,
    84  	// whether or not we got an error.
    85  	var results []instance.Instance
    86  	for _, base := range instances {
    87  		// If we don't make a copy then the same pointer is used for the
    88  		// base of all resulting instances.
    89  		copied := base
    90  		inst := newInstance(&copied, env)
    91  		results = append(results, inst)
    92  	}
    93  
    94  	return results, err
    95  }
    96  
    97  // StateServerInstances returns the IDs of the instances corresponding
    98  // to juju state servers.
    99  func (env *environ) StateServerInstances() ([]instance.Id, error) {
   100  	env = env.getSnapshot()
   101  
   102  	prefix := common.MachineFullName(env, "")
   103  	instances, err := env.gce.Instances(prefix, instStatuses...)
   104  	if err != nil {
   105  		return nil, errors.Trace(err)
   106  	}
   107  
   108  	var results []instance.Id
   109  	for _, inst := range instances {
   110  		metadata := inst.Metadata()
   111  		isState, ok := metadata[metadataKeyIsState]
   112  		if ok && isState == metadataValueTrue {
   113  			results = append(results, instance.Id(inst.ID))
   114  		}
   115  	}
   116  	if len(results) == 0 {
   117  		return nil, environs.ErrNotBootstrapped
   118  	}
   119  	return results, nil
   120  }
   121  
   122  // TODO(ericsnow) Turn into an interface.
   123  type instPlacement struct {
   124  	Zone *google.AvailabilityZone
   125  }
   126  
   127  // parsePlacement extracts the availability zone from the placement
   128  // string and returns it. If no zone is found there then an error is
   129  // returned.
   130  func (env *environ) parsePlacement(placement string) (*instPlacement, error) {
   131  	if placement == "" {
   132  		return nil, nil
   133  	}
   134  
   135  	pos := strings.IndexRune(placement, '=')
   136  	if pos == -1 {
   137  		return nil, errors.Errorf("unknown placement directive: %v", placement)
   138  	}
   139  
   140  	switch key, value := placement[:pos], placement[pos+1:]; key {
   141  	case "zone":
   142  		zone, err := env.availZoneUp(value)
   143  		if err != nil {
   144  			return nil, errors.Trace(err)
   145  		}
   146  		return &instPlacement{Zone: zone}, nil
   147  	}
   148  	return nil, errors.Errorf("unknown placement directive: %v", placement)
   149  }
   150  
   151  // checkInstanceType is used to ensure the the provided constraints
   152  // specify a recognized instance type.
   153  func checkInstanceType(cons constraints.Value) bool {
   154  	// Constraint has an instance-type constraint so let's see if it is valid.
   155  	for _, itype := range allInstanceTypes {
   156  		if itype.Name == *cons.InstanceType {
   157  			return true
   158  		}
   159  	}
   160  	return false
   161  }