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 }