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 }