github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/provider/gce/google/raw.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 "net/http" 9 "path" 10 "time" 11 12 "github.com/juju/errors" 13 "github.com/juju/utils" 14 "google.golang.org/api/compute/v1" 15 "google.golang.org/api/googleapi" 16 ) 17 18 // These are attempt strategies used in waitOperation. 19 var ( 20 // TODO(ericsnow) Tune the timeouts and delays. 21 22 attemptsLong = utils.AttemptStrategy{ 23 Total: 5 * time.Minute, 24 Delay: 2 * time.Second, 25 } 26 attemptsShort = utils.AttemptStrategy{ 27 Total: 1 * time.Minute, 28 Delay: 1 * time.Second, 29 } 30 ) 31 32 func convertRawAPIError(err error) error { 33 if err2, ok := err.(*googleapi.Error); ok { 34 if err2.Code == http.StatusNotFound { 35 return errors.NewNotFound(err, "") 36 } 37 } 38 return err 39 } 40 41 type rawConn struct { 42 *compute.Service 43 } 44 45 func (rc *rawConn) GetProject(projectID string) (*compute.Project, error) { 46 call := rc.Projects.Get(projectID) 47 proj, err := call.Do() 48 return proj, errors.Trace(err) 49 } 50 51 func (rc *rawConn) GetInstance(projectID, zone, id string) (*compute.Instance, error) { 52 call := rc.Instances.Get(projectID, zone, id) 53 inst, err := call.Do() 54 return inst, errors.Trace(err) 55 } 56 57 func (rc *rawConn) ListInstances(projectID, prefix string, statuses ...string) ([]*compute.Instance, error) { 58 call := rc.Instances.AggregatedList(projectID) 59 call = call.Filter("name eq " + prefix + ".*") 60 61 var results []*compute.Instance 62 for { 63 rawResult, err := call.Do() 64 if err != nil { 65 return nil, errors.Trace(err) 66 } 67 68 for _, instList := range rawResult.Items { 69 for _, inst := range instList.Instances { 70 if !checkInstStatus(inst, statuses) { 71 continue 72 } 73 results = append(results, inst) 74 } 75 } 76 if rawResult.NextPageToken == "" { 77 break 78 } 79 call = call.PageToken(rawResult.NextPageToken) 80 } 81 return results, nil 82 } 83 84 func checkInstStatus(inst *compute.Instance, statuses []string) bool { 85 if len(statuses) == 0 { 86 return true 87 } 88 for _, status := range statuses { 89 if inst.Status == status { 90 return true 91 } 92 } 93 return false 94 } 95 96 func (rc *rawConn) AddInstance(projectID, zoneName string, spec *compute.Instance) error { 97 call := rc.Instances.Insert(projectID, zoneName, spec) 98 operation, err := call.Do() 99 if err != nil { 100 // We are guaranteed the insert failed at the point. 101 return errors.Annotate(err, "sending new instance request") 102 } 103 104 err = rc.waitOperation(projectID, operation, attemptsLong) 105 return errors.Trace(err) 106 } 107 108 func (rc *rawConn) RemoveInstance(projectID, zone, id string) error { 109 call := rc.Instances.Delete(projectID, zone, id) 110 operation, err := call.Do() 111 if err != nil { 112 return errors.Trace(err) 113 } 114 115 err = rc.waitOperation(projectID, operation, attemptsLong) 116 return errors.Trace(err) 117 } 118 119 func (rc *rawConn) GetFirewall(projectID, name string) (*compute.Firewall, error) { 120 call := rc.Firewalls.List(projectID) 121 call = call.Filter("name eq " + name) 122 firewallList, err := call.Do() 123 if err != nil { 124 return nil, errors.Annotate(err, "while getting firewall from GCE") 125 } 126 127 if len(firewallList.Items) == 0 { 128 return nil, errors.NotFoundf("firewall %q", name) 129 } 130 return firewallList.Items[0], nil 131 } 132 133 func (rc *rawConn) AddFirewall(projectID string, firewall *compute.Firewall) error { 134 call := rc.Firewalls.Insert(projectID, firewall) 135 operation, err := call.Do() 136 if err != nil { 137 return errors.Trace(err) 138 } 139 140 err = rc.waitOperation(projectID, operation, attemptsLong) 141 return errors.Trace(err) 142 } 143 144 func (rc *rawConn) UpdateFirewall(projectID, name string, firewall *compute.Firewall) error { 145 call := rc.Firewalls.Update(projectID, name, firewall) 146 operation, err := call.Do() 147 if err != nil { 148 return errors.Trace(err) 149 } 150 151 err = rc.waitOperation(projectID, operation, attemptsLong) 152 return errors.Trace(err) 153 } 154 155 func (rc *rawConn) RemoveFirewall(projectID, name string) error { 156 call := rc.Firewalls.Delete(projectID, name) 157 operation, err := call.Do() 158 if err != nil { 159 return errors.Trace(convertRawAPIError(err)) 160 } 161 162 err = rc.waitOperation(projectID, operation, attemptsLong) 163 return errors.Trace(convertRawAPIError(err)) 164 } 165 166 func (rc *rawConn) ListAvailabilityZones(projectID, region string) ([]*compute.Zone, error) { 167 call := rc.Zones.List(projectID) 168 if region != "" { 169 call = call.Filter("name eq " + region + "-.*") 170 } 171 172 var results []*compute.Zone 173 for { 174 zoneList, err := call.Do() 175 if err != nil { 176 return nil, errors.Trace(err) 177 } 178 179 for _, zone := range zoneList.Items { 180 results = append(results, zone) 181 } 182 if zoneList.NextPageToken == "" { 183 break 184 } 185 call = call.PageToken(zoneList.NextPageToken) 186 } 187 return results, nil 188 } 189 190 type waitError struct { 191 op *compute.Operation 192 cause error 193 } 194 195 func (err waitError) Error() string { 196 if err.cause != nil { 197 return fmt.Sprintf("GCE operation %q failed: %v", err.op.Name, err.cause) 198 } 199 return fmt.Sprintf("GCE operation %q failed", err.op.Name) 200 } 201 202 func isWaitError(err error) bool { 203 _, ok := err.(*waitError) 204 return ok 205 } 206 207 type opDoer interface { 208 Do() (*compute.Operation, error) 209 } 210 211 // checkOperation requests a new copy of the given operation from the 212 // GCE API and returns it. The new copy will have the operation's 213 // current status. 214 func (rc *rawConn) checkOperation(projectID string, op *compute.Operation) (*compute.Operation, error) { 215 var call opDoer 216 if op.Zone != "" { 217 zoneName := path.Base(op.Zone) 218 call = rc.ZoneOperations.Get(projectID, zoneName, op.Name) 219 } else if op.Region != "" { 220 region := path.Base(op.Region) 221 call = rc.RegionOperations.Get(projectID, region, op.Name) 222 } else { 223 call = rc.GlobalOperations.Get(projectID, op.Name) 224 } 225 226 operation, err := doOpCall(call) 227 if err != nil { 228 return nil, errors.Annotatef(err, "request for GCE operation %q failed", op.Name) 229 } 230 return operation, nil 231 } 232 233 var doOpCall = func(call opDoer) (*compute.Operation, error) { 234 return call.Do() 235 } 236 237 // waitOperation waits for the provided operation to reach the "done" 238 // status. It follows the given attempt strategy (e.g. wait time between 239 // attempts) and may time out. 240 func (rc *rawConn) waitOperation(projectID string, op *compute.Operation, attempts utils.AttemptStrategy) error { 241 started := time.Now() 242 logger.Infof("GCE operation %q, waiting...", op.Name) 243 for a := attempts.Start(); a.Next(); { 244 if op.Status == StatusDone { 245 break 246 } 247 248 var err error 249 op, err = rc.checkOperation(projectID, op) 250 if err != nil { 251 return errors.Trace(err) 252 } 253 } 254 if op.Status != StatusDone { 255 err := errors.Errorf("timed out after %d seconds", time.Now().Sub(started)/time.Second) 256 return waitError{op, err} 257 } 258 if op.Error != nil { 259 for _, err := range op.Error.Errors { 260 logger.Errorf("GCE operation error: (%s) %s", err.Code, err.Message) 261 } 262 return waitError{op, nil} 263 } 264 265 logger.Infof("GCE operation %q finished", op.Name) 266 return nil 267 }