github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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  // Addresses identifies information about the network addresses
   172  // associated with the instance and returns it.
   173  func (gi Instance) Addresses() []network.Address {
   174  	// TODO*ericsnow) return a copy?
   175  	return gi.InstanceSummary.Addresses
   176  }
   177  
   178  // Metadata returns the user-specified metadata for the instance.
   179  func (gi Instance) Metadata() map[string]string {
   180  	// TODO*ericsnow) return a copy?
   181  	return gi.InstanceSummary.Metadata
   182  }
   183  
   184  // FormatAuthorizedKeys returns our authorizedKeys with
   185  // the username prepended to it. This is the format that
   186  // GCE expects when we upload sshKeys metadata. The sshKeys
   187  // metadata is what is used by our scripts and commands
   188  // like juju ssh to connect to juju machines.
   189  func FormatAuthorizedKeys(rawAuthorizedKeys, user string) (string, error) {
   190  	if rawAuthorizedKeys == "" {
   191  		return "", errors.New("empty rawAuthorizedKeys")
   192  	}
   193  	if user == "" {
   194  		return "", errors.New("empty user")
   195  	}
   196  
   197  	var userKeys string
   198  	keys := strings.Split(rawAuthorizedKeys, "\n")
   199  	for _, key := range keys {
   200  		userKeys += user + ":" + key + "\n"
   201  	}
   202  	return userKeys, nil
   203  }
   204  
   205  // packMetadata composes the provided data into the format required
   206  // by the GCE API.
   207  func packMetadata(data map[string]string) *compute.Metadata {
   208  	var items []*compute.MetadataItems
   209  	for key, value := range data {
   210  		item := compute.MetadataItems{
   211  			Key:   key,
   212  			Value: value,
   213  		}
   214  		items = append(items, &item)
   215  	}
   216  	return &compute.Metadata{Items: items}
   217  }
   218  
   219  // unpackMetadata decomposes the provided data from the format used
   220  // in the GCE API.
   221  func unpackMetadata(data *compute.Metadata) map[string]string {
   222  	if data == nil {
   223  		return nil
   224  	}
   225  
   226  	result := make(map[string]string)
   227  	for _, item := range data.Items {
   228  		result[item.Key] = item.Value
   229  	}
   230  	return result
   231  }
   232  
   233  func formatMachineType(zone, name string) string {
   234  	return fmt.Sprintf("zones/%s/machineTypes/%s", zone, name)
   235  }