github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/gce/google/conn_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 "path" 8 9 "github.com/juju/errors" 10 "google.golang.org/api/compute/v1" 11 ) 12 13 // addInstance sends a request to GCE to add a new instance to the 14 // connection's project, with the provided instance data and machine 15 // type. The instance that was passed in is updated with the new 16 // instance's data upon success. The call blocks until the instance 17 // is created or the request fails. 18 // TODO(ericsnow) Return a new inst. 19 func (gce *Connection) addInstance(requestedInst *compute.Instance, machineType string, zone string) error { 20 var waitErr error 21 inst := *requestedInst 22 inst.MachineType = formatMachineType(zone, machineType) 23 err := gce.raw.AddInstance(gce.projectID, zone, &inst) 24 if isWaitError(err) { 25 waitErr = err 26 } else if err != nil { 27 // We are guaranteed the insert failed at the point. 28 return errors.Annotate(err, "sending new instance request") 29 } 30 31 // Check if the instance was created. 32 realized, err := gce.raw.GetInstance(gce.projectID, zone, inst.Name) 33 if err != nil { 34 if waitErr != nil { 35 return errors.Trace(waitErr) 36 } 37 return errors.Trace(err) 38 } 39 40 // Success! 41 *requestedInst = *realized 42 return nil 43 } 44 45 // AddInstance creates a new instance based on the spec's data and 46 // returns it. The instance will be created using the provided 47 // connection and in the provided zone. 48 func (gce *Connection) AddInstance(spec InstanceSpec) (*Instance, error) { 49 raw := spec.raw() 50 if err := gce.addInstance(raw, spec.Type, spec.AvailabilityZone); err != nil { 51 return nil, errors.Trace(err) 52 } 53 54 return newInstance(raw, &spec), nil 55 } 56 57 // Instance gets the up-to-date info about the given instance 58 // and returns it. 59 func (gce *Connection) Instance(id, zone string) (Instance, error) { 60 var result Instance 61 raw, err := gce.raw.GetInstance(gce.projectID, zone, id) 62 if err != nil { 63 return result, errors.Trace(err) 64 } 65 result = *newInstance(raw, nil) 66 return result, nil 67 } 68 69 // Instances sends a request to the GCE API for a list of all instances 70 // (in the Connection's project) for which the name starts with the 71 // provided prefix. The result is also limited to those instances with 72 // one of the specified statuses (if any). 73 func (gce *Connection) Instances(prefix string, statuses ...string) ([]Instance, error) { 74 rawInsts, err := gce.raw.ListInstances(gce.projectID, prefix, statuses...) 75 if err != nil { 76 return nil, errors.Trace(err) 77 } 78 79 var insts []Instance 80 for _, rawInst := range rawInsts { 81 inst := newInstance(rawInst, nil) 82 insts = append(insts, *inst) 83 } 84 return insts, nil 85 } 86 87 // removeInstance sends a request to the GCE API to remove the instance 88 // with the provided ID (in the specified zone). The call blocks until 89 // the instance is removed (or the request fails). 90 func (gce *Connection) removeInstance(id, zone string) error { 91 err := gce.raw.RemoveInstance(gce.projectID, zone, id) 92 if err != nil { 93 // TODO(ericsnow) Try removing the firewall anyway? 94 return errors.Trace(err) 95 } 96 97 fwname := id 98 err = gce.raw.RemoveFirewall(gce.projectID, fwname) 99 if errors.IsNotFound(err) { 100 return nil 101 } 102 if err != nil { 103 return errors.Trace(err) 104 } 105 return nil 106 } 107 108 // RemoveInstances sends a request to the GCE API to terminate all 109 // instances (in the Connection's project) that match one of the 110 // provided IDs. If a prefix is provided, only IDs that start with the 111 // prefix will be considered. The call blocks until all the instances 112 // are removed or the request fails. 113 func (gce *Connection) RemoveInstances(prefix string, ids ...string) error { 114 if len(ids) == 0 { 115 return nil 116 } 117 118 instances, err := gce.Instances(prefix) 119 if err != nil { 120 return errors.Annotatef(err, "while removing instances %v", ids) 121 } 122 123 // TODO(ericsnow) Remove instances in parallel? 124 var failed []string 125 for _, instID := range ids { 126 for _, inst := range instances { 127 if inst.ID == instID { 128 zoneName := path.Base(inst.InstanceSummary.ZoneName) 129 if err := gce.removeInstance(instID, zoneName); err != nil { 130 failed = append(failed, instID) 131 logger.Errorf("while removing instance %q: %v", instID, err) 132 } 133 break 134 } 135 } 136 } 137 if len(failed) != 0 { 138 return errors.Errorf("some instance removals failed: %v", failed) 139 } 140 return nil 141 } 142 143 // UpdateMetadata sets the metadata key to the specified value for 144 // all of the instance ids given. The call blocks until all 145 // of the instances are updated or the request fails. 146 func (gce *Connection) UpdateMetadata(key, value string, ids ...string) error { 147 if len(ids) == 0 { 148 return nil 149 } 150 151 instances, err := gce.raw.ListInstances(gce.projectID, "") 152 if err != nil { 153 return errors.Annotatef(err, "updating metadata for instances %v", ids) 154 } 155 var failed []string 156 for _, instID := range ids { 157 for _, inst := range instances { 158 if inst.Name == instID { 159 if err := gce.updateInstanceMetadata(inst, key, value); err != nil { 160 failed = append(failed, instID) 161 logger.Errorf("while updating metadata for instance %q (%v=%q): %v", 162 instID, key, value, err) 163 } 164 break 165 } 166 } 167 } 168 if len(failed) != 0 { 169 return errors.Errorf("some metadata updates failed: %v", failed) 170 } 171 return nil 172 173 } 174 175 func (gce *Connection) updateInstanceMetadata(instance *compute.Instance, key, value string) error { 176 metadata := instance.Metadata 177 existingItem := findMetadataItem(metadata.Items, key) 178 if existingItem != nil && existingItem.Value != nil && *existingItem.Value == value { 179 // The value's already right. 180 return nil 181 } else if existingItem == nil { 182 metadata.Items = append(metadata.Items, &compute.MetadataItems{Key: key, Value: &value}) 183 } else { 184 existingItem.Value = &value 185 } 186 // The GCE API won't accept a full URL for the zone (lp:1667172). 187 zoneName := path.Base(instance.Zone) 188 return errors.Trace(gce.raw.SetMetadata(gce.projectID, zoneName, instance.Name, metadata)) 189 } 190 191 func findMetadataItem(items []*compute.MetadataItems, key string) *compute.MetadataItems { 192 for _, item := range items { 193 if item == nil { 194 continue 195 } 196 if item.Key == key { 197 return item 198 } 199 } 200 return nil 201 }