github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/provider/gce/google/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  	"fmt"
     8  	"path"
     9  	"strings"
    10  
    11  	"github.com/juju/errors"
    12  	"google.golang.org/api/compute/v1"
    13  
    14  	"github.com/juju/juju/network"
    15  )
    16  
    17  const (
    18  	partialMachineType = "zones/%s/machineTypes/%s"
    19  )
    20  
    21  // InstanceSpec holds all the information needed to create a new GCE
    22  // instance within some zone.
    23  // TODO(ericsnow) Validate the invariants?
    24  type InstanceSpec struct {
    25  	// ID is the "name" of the instance.
    26  	ID string
    27  	// Type is the name of the GCE instance type. The value is resolved
    28  	// relative to an availability zone when the API request is sent.
    29  	// The type must match one of the GCE-recognized types.
    30  	Type string
    31  	// Disks holds the information needed to request each of the disks
    32  	// that should be attached to a new instance. This must include a
    33  	// single root disk.
    34  	Disks []DiskSpec
    35  	// Network identifies the information for the network that a new
    36  	// instance should use. If the network does not exist then it will
    37  	// be added when the instance is. At least the network's name must
    38  	// be set.
    39  	Network NetworkSpec
    40  	// NetworkInterfaces is the names of the network interfaces to
    41  	// associate with the instance. They will be connected to the the
    42  	// network identified by the instance spec. At least one name must
    43  	// be provided.
    44  	NetworkInterfaces []string
    45  	// Metadata is the GCE instance "user-specified" metadata that will
    46  	// be initialized on the new instance.
    47  	Metadata map[string]string
    48  	// Tags are the labels to associate with the instance. This is
    49  	// useful when making bulk calls or in relation to some API methods
    50  	// (e.g. related to firewalls access rules).
    51  	Tags []string
    52  }
    53  
    54  func (is InstanceSpec) raw() *compute.Instance {
    55  	return &compute.Instance{
    56  		Name:              is.ID,
    57  		Disks:             is.disks(),
    58  		NetworkInterfaces: is.networkInterfaces(),
    59  		Metadata:          packMetadata(is.Metadata),
    60  		Tags:              &compute.Tags{Items: is.Tags},
    61  		// MachineType is set in the addInstance call.
    62  	}
    63  }
    64  
    65  // Summary builds an InstanceSummary based on the spec and returns it.
    66  func (is InstanceSpec) Summary() InstanceSummary {
    67  	raw := is.raw()
    68  	return newInstanceSummary(raw)
    69  }
    70  
    71  func (is InstanceSpec) disks() []*compute.AttachedDisk {
    72  	var result []*compute.AttachedDisk
    73  	for _, spec := range is.Disks {
    74  		result = append(result, spec.newAttached())
    75  	}
    76  	return result
    77  }
    78  
    79  func (is InstanceSpec) networkInterfaces() []*compute.NetworkInterface {
    80  	var result []*compute.NetworkInterface
    81  	for _, name := range is.NetworkInterfaces {
    82  		result = append(result, is.Network.newInterface(name))
    83  	}
    84  	return result
    85  }
    86  
    87  // RootDisk identifies the root disk for a given instance (or instance
    88  // spec) and returns it. If the root disk could not be determined then
    89  // nil is returned.
    90  // TODO(ericsnow) Return an error?
    91  func (is InstanceSpec) RootDisk() *compute.AttachedDisk {
    92  	return is.Disks[0].newAttached()
    93  }
    94  
    95  // InstanceSummary captures all the data needed by Instance.
    96  type InstanceSummary struct {
    97  	// ID is the "name" of the instance.
    98  	ID string
    99  	// ZoneName is the unqualified name of the zone in which the
   100  	// instance was provisioned.
   101  	ZoneName string
   102  	// Status holds the status of the instance at a certain point in time.
   103  	Status string
   104  	// Metadata is the instance metadata.
   105  	Metadata map[string]string
   106  	// Addresses are the IP Addresses associated with the instance.
   107  	Addresses []network.Address
   108  }
   109  
   110  func newInstanceSummary(raw *compute.Instance) InstanceSummary {
   111  	return InstanceSummary{
   112  		ID:        raw.Name,
   113  		ZoneName:  path.Base(raw.Zone),
   114  		Status:    raw.Status,
   115  		Metadata:  unpackMetadata(raw.Metadata),
   116  		Addresses: extractAddresses(raw.NetworkInterfaces...),
   117  	}
   118  }
   119  
   120  // Instance represents a single realized GCE compute instance.
   121  type Instance struct {
   122  	InstanceSummary
   123  
   124  	// spec is the InstanceSpec used to create this instance.
   125  	spec *InstanceSpec
   126  }
   127  
   128  func newInstance(raw *compute.Instance, spec *InstanceSpec) *Instance {
   129  	summary := newInstanceSummary(raw)
   130  	return NewInstance(summary, spec)
   131  }
   132  
   133  // NewInstance builds an instance from the provided summary and spec
   134  // and returns it.
   135  func NewInstance(summary InstanceSummary, spec *InstanceSpec) *Instance {
   136  	if spec != nil {
   137  		// Make a copy.
   138  		val := *spec
   139  		spec = &val
   140  	}
   141  	return &Instance{
   142  		InstanceSummary: summary,
   143  		spec:            spec,
   144  	}
   145  }
   146  
   147  // RootDisk returns an AttachedDisk
   148  func (gi Instance) RootDisk() *compute.AttachedDisk {
   149  	if gi.spec == nil {
   150  		return nil
   151  	}
   152  	return gi.spec.RootDisk()
   153  }
   154  
   155  // RootDiskGB returns the size of the instance's root disk. If it
   156  // cannot be determined then 0 is returned.
   157  func (gi Instance) RootDiskGB() uint64 {
   158  	if gi.spec == nil {
   159  		return 0
   160  	}
   161  	attached := gi.RootDisk()
   162  	return uint64(attached.InitializeParams.DiskSizeGb)
   163  }
   164  
   165  // Status returns a string identifying the status of the instance. The
   166  // value will match one of the Status* constants in the package.
   167  func (gi Instance) Status() string {
   168  	return gi.InstanceSummary.Status
   169  }
   170  
   171  // InstGetter exposes the Connection functionality needed by refresh.
   172  type InstGetter interface {
   173  	// Instance gets the up-to-date info about the given instance
   174  	// and returns it.
   175  	Instance(id, zone string) (Instance, error)
   176  }
   177  
   178  // Refresh updates the instance with its current data, utilizing the
   179  // provided connection to request it.
   180  func (gi *Instance) Refresh(conn InstGetter) error {
   181  	updated, err := conn.Instance(gi.ID, gi.ZoneName)
   182  	if err != nil {
   183  		return errors.Trace(err)
   184  	}
   185  
   186  	gi.InstanceSummary = updated.InstanceSummary
   187  	return nil
   188  }
   189  
   190  // Addresses identifies information about the network addresses
   191  // associated with the instance and returns it.
   192  func (gi Instance) Addresses() []network.Address {
   193  	// TODO*ericsnow) return a copy?
   194  	return gi.InstanceSummary.Addresses
   195  }
   196  
   197  // Metadata returns the user-specified metadata for the instance.
   198  func (gi Instance) Metadata() map[string]string {
   199  	// TODO*ericsnow) return a copy?
   200  	return gi.InstanceSummary.Metadata
   201  }
   202  
   203  // FormatAuthorizedKeys returns our authorizedKeys with
   204  // the username prepended to it. This is the format that
   205  // GCE expects when we upload sshKeys metadata. The sshKeys
   206  // metadata is what is used by our scripts and commands
   207  // like juju ssh to connect to juju machines.
   208  func FormatAuthorizedKeys(rawAuthorizedKeys, user string) (string, error) {
   209  	if rawAuthorizedKeys == "" {
   210  		return "", errors.New("empty rawAuthorizedKeys")
   211  	}
   212  	if user == "" {
   213  		return "", errors.New("empty user")
   214  	}
   215  
   216  	var userKeys string
   217  	keys := strings.Split(rawAuthorizedKeys, "\n")
   218  	for _, key := range keys {
   219  		userKeys += user + ":" + key + "\n"
   220  	}
   221  	return userKeys, nil
   222  }
   223  
   224  // packMetadata composes the provided data into the format required
   225  // by the GCE API.
   226  func packMetadata(data map[string]string) *compute.Metadata {
   227  	var items []*compute.MetadataItems
   228  	for key, value := range data {
   229  		item := compute.MetadataItems{
   230  			Key:   key,
   231  			Value: value,
   232  		}
   233  		items = append(items, &item)
   234  	}
   235  	return &compute.Metadata{Items: items}
   236  }
   237  
   238  // unpackMetadata decomposes the provided data from the format used
   239  // in the GCE API.
   240  func unpackMetadata(data *compute.Metadata) map[string]string {
   241  	if data == nil {
   242  		return nil
   243  	}
   244  
   245  	result := make(map[string]string)
   246  	for _, item := range data.Items {
   247  		result[item.Key] = item.Value
   248  	}
   249  	return result
   250  }
   251  
   252  func formatMachineType(zone, name string) string {
   253  	return fmt.Sprintf("zones/%s/machineTypes/%s", zone, name)
   254  }