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