github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/gce/google/conn_instance.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package google
     5  
     6  import (
     7  	"path"
     8  
     9  	"github.com/juju/errors"
    10  	"google.golang.org/api/compute/v1"
    11  )
    12  
    13  // addInstance sends a request to GCE to add a new instance to the
    14  // connection's project, with the provided instance data and machine
    15  // type. The instance that was passed in is updated with the new
    16  // instance's data upon success. The call blocks until the instance
    17  // is created or the request fails.
    18  // TODO(ericsnow) Return a new inst.
    19  func (gce *Connection) addInstance(requestedInst *compute.Instance, machineType string, zone string) error {
    20  	var waitErr error
    21  	inst := *requestedInst
    22  	inst.MachineType = formatMachineType(zone, machineType)
    23  	err := gce.raw.AddInstance(gce.projectID, zone, &inst)
    24  	if isWaitError(err) {
    25  		waitErr = err
    26  	} else if err != nil {
    27  		// We are guaranteed the insert failed at the point.
    28  		return errors.Annotate(err, "sending new instance request")
    29  	}
    30  
    31  	// Check if the instance was created.
    32  	realized, err := gce.raw.GetInstance(gce.projectID, zone, inst.Name)
    33  	if err != nil {
    34  		if waitErr != nil {
    35  			return errors.Trace(waitErr)
    36  		}
    37  		return errors.Trace(err)
    38  	}
    39  
    40  	// Success!
    41  	*requestedInst = *realized
    42  	return nil
    43  }
    44  
    45  // AddInstance creates a new instance based on the spec's data and
    46  // returns it. The instance will be created using the provided
    47  // connection and in the provided zone.
    48  func (gce *Connection) AddInstance(spec InstanceSpec) (*Instance, error) {
    49  	raw := spec.raw()
    50  	if err := gce.addInstance(raw, spec.Type, spec.AvailabilityZone); err != nil {
    51  		return nil, errors.Trace(err)
    52  	}
    53  
    54  	return newInstance(raw, &spec), nil
    55  }
    56  
    57  // Instance gets the up-to-date info about the given instance
    58  // and returns it.
    59  func (gce *Connection) Instance(id, zone string) (Instance, error) {
    60  	var result Instance
    61  	raw, err := gce.raw.GetInstance(gce.projectID, zone, id)
    62  	if err != nil {
    63  		return result, errors.Trace(err)
    64  	}
    65  	result = *newInstance(raw, nil)
    66  	return result, nil
    67  }
    68  
    69  // Instances sends a request to the GCE API for a list of all instances
    70  // (in the Connection's project) for which the name starts with the
    71  // provided prefix. The result is also limited to those instances with
    72  // one of the specified statuses (if any).
    73  func (gce *Connection) Instances(prefix string, statuses ...string) ([]Instance, error) {
    74  	rawInsts, err := gce.raw.ListInstances(gce.projectID, prefix, statuses...)
    75  	if err != nil {
    76  		return nil, errors.Trace(err)
    77  	}
    78  
    79  	var insts []Instance
    80  	for _, rawInst := range rawInsts {
    81  		inst := newInstance(rawInst, nil)
    82  		insts = append(insts, *inst)
    83  	}
    84  	return insts, nil
    85  }
    86  
    87  // removeInstance sends a request to the GCE API to remove the instance
    88  // with the provided ID (in the specified zone). The call blocks until
    89  // the instance is removed (or the request fails).
    90  func (gce *Connection) removeInstance(id, zone string) error {
    91  	err := gce.raw.RemoveInstance(gce.projectID, zone, id)
    92  	if err != nil {
    93  		// TODO(ericsnow) Try removing the firewall anyway?
    94  		return errors.Trace(err)
    95  	}
    96  
    97  	fwname := id
    98  	err = gce.raw.RemoveFirewall(gce.projectID, fwname)
    99  	if errors.IsNotFound(err) {
   100  		return nil
   101  	}
   102  	if err != nil {
   103  		return errors.Trace(err)
   104  	}
   105  	return nil
   106  }
   107  
   108  // RemoveInstances sends a request to the GCE API to terminate all
   109  // instances (in the Connection's project) that match one of the
   110  // provided IDs. If a prefix is provided, only IDs that start with the
   111  // prefix will be considered. The call blocks until all the instances
   112  // are removed or the request fails.
   113  func (gce *Connection) RemoveInstances(prefix string, ids ...string) error {
   114  	if len(ids) == 0 {
   115  		return nil
   116  	}
   117  
   118  	instances, err := gce.Instances(prefix)
   119  	if err != nil {
   120  		return errors.Annotatef(err, "while removing instances %v", ids)
   121  	}
   122  
   123  	// TODO(ericsnow) Remove instances in parallel?
   124  	var failed []string
   125  	for _, instID := range ids {
   126  		for _, inst := range instances {
   127  			if inst.ID == instID {
   128  				zoneName := path.Base(inst.InstanceSummary.ZoneName)
   129  				if err := gce.removeInstance(instID, zoneName); err != nil {
   130  					failed = append(failed, instID)
   131  					logger.Errorf("while removing instance %q: %v", instID, err)
   132  				}
   133  				break
   134  			}
   135  		}
   136  	}
   137  	if len(failed) != 0 {
   138  		return errors.Errorf("some instance removals failed: %v", failed)
   139  	}
   140  	return nil
   141  }
   142  
   143  // UpdateMetadata sets the metadata key to the specified value for
   144  // all of the instance ids given. The call blocks until all
   145  // of the instances are updated or the request fails.
   146  func (gce *Connection) UpdateMetadata(key, value string, ids ...string) error {
   147  	if len(ids) == 0 {
   148  		return nil
   149  	}
   150  
   151  	instances, err := gce.raw.ListInstances(gce.projectID, "")
   152  	if err != nil {
   153  		return errors.Annotatef(err, "updating metadata for instances %v", ids)
   154  	}
   155  	var failed []string
   156  	for _, instID := range ids {
   157  		for _, inst := range instances {
   158  			if inst.Name == instID {
   159  				if err := gce.updateInstanceMetadata(inst, key, value); err != nil {
   160  					failed = append(failed, instID)
   161  					logger.Errorf("while updating metadata for instance %q (%v=%q): %v",
   162  						instID, key, value, err)
   163  				}
   164  				break
   165  			}
   166  		}
   167  	}
   168  	if len(failed) != 0 {
   169  		return errors.Errorf("some metadata updates failed: %v", failed)
   170  	}
   171  	return nil
   172  
   173  }
   174  
   175  func (gce *Connection) updateInstanceMetadata(instance *compute.Instance, key, value string) error {
   176  	metadata := instance.Metadata
   177  	existingItem := findMetadataItem(metadata.Items, key)
   178  	if existingItem != nil && existingItem.Value != nil && *existingItem.Value == value {
   179  		// The value's already right.
   180  		return nil
   181  	} else if existingItem == nil {
   182  		metadata.Items = append(metadata.Items, &compute.MetadataItems{Key: key, Value: &value})
   183  	} else {
   184  		existingItem.Value = &value
   185  	}
   186  	// The GCE API won't accept a full URL for the zone (lp:1667172).
   187  	zoneName := path.Base(instance.Zone)
   188  	return errors.Trace(gce.raw.SetMetadata(gce.projectID, zoneName, instance.Name, metadata))
   189  }
   190  
   191  func findMetadataItem(items []*compute.MetadataItems, key string) *compute.MetadataItems {
   192  	for _, item := range items {
   193  		if item == nil {
   194  			continue
   195  		}
   196  		if item.Key == key {
   197  			return item
   198  		}
   199  	}
   200  	return nil
   201  }