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