github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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 "strings" 11 "time" 12 13 "github.com/juju/errors" 14 "github.com/juju/utils" 15 "google.golang.org/api/compute/v1" 16 "google.golang.org/api/googleapi" 17 ) 18 19 const diskTypesBase = "https://www.googleapis.com/compute/v1/projects/%s/zones/%s/diskTypes/%s" 20 21 // These are attempt strategies used in waitOperation. 22 var ( 23 // TODO(ericsnow) Tune the timeouts and delays. 24 25 attemptsLong = utils.AttemptStrategy{ 26 Total: 5 * time.Minute, 27 Delay: 2 * time.Second, 28 } 29 attemptsShort = utils.AttemptStrategy{ 30 Total: 1 * time.Minute, 31 Delay: 1 * time.Second, 32 } 33 ) 34 35 func convertRawAPIError(err error) error { 36 if err2, ok := err.(*googleapi.Error); ok { 37 if err2.Code == http.StatusNotFound { 38 return errors.NewNotFound(err, "") 39 } 40 } 41 return err 42 } 43 44 type rawConn struct { 45 *compute.Service 46 } 47 48 func (rc *rawConn) GetProject(projectID string) (*compute.Project, error) { 49 call := rc.Projects.Get(projectID) 50 proj, err := call.Do() 51 return proj, errors.Trace(err) 52 } 53 54 func (rc *rawConn) GetInstance(projectID, zone, id string) (*compute.Instance, error) { 55 call := rc.Instances.Get(projectID, zone, id) 56 inst, err := call.Do() 57 return inst, errors.Trace(err) 58 } 59 60 func (rc *rawConn) ListInstances(projectID, prefix string, statuses ...string) ([]*compute.Instance, error) { 61 call := rc.Instances.AggregatedList(projectID) 62 call = call.Filter("name eq " + prefix + ".*") 63 64 var results []*compute.Instance 65 for { 66 rawResult, err := call.Do() 67 if err != nil { 68 return nil, errors.Trace(err) 69 } 70 71 for _, instList := range rawResult.Items { 72 for _, inst := range instList.Instances { 73 if !checkInstStatus(inst, statuses) { 74 continue 75 } 76 results = append(results, inst) 77 } 78 } 79 if rawResult.NextPageToken == "" { 80 break 81 } 82 call = call.PageToken(rawResult.NextPageToken) 83 } 84 return results, nil 85 } 86 87 func checkInstStatus(inst *compute.Instance, statuses []string) bool { 88 if len(statuses) == 0 { 89 return true 90 } 91 for _, status := range statuses { 92 if inst.Status == status { 93 return true 94 } 95 } 96 return false 97 } 98 99 func (rc *rawConn) AddInstance(projectID, zoneName string, spec *compute.Instance) error { 100 call := rc.Instances.Insert(projectID, zoneName, spec) 101 operation, err := call.Do() 102 if err != nil { 103 // We are guaranteed the insert failed at the point. 104 return errors.Annotate(err, "sending new instance request") 105 } 106 107 err = rc.waitOperation(projectID, operation, attemptsLong) 108 return errors.Trace(err) 109 } 110 111 func (rc *rawConn) RemoveInstance(projectID, zone, id string) error { 112 call := rc.Instances.Delete(projectID, zone, id) 113 operation, err := call.Do() 114 if err != nil { 115 return errors.Trace(err) 116 } 117 118 err = rc.waitOperation(projectID, operation, attemptsLong) 119 return errors.Trace(err) 120 } 121 122 func (rc *rawConn) GetFirewall(projectID, name string) (*compute.Firewall, error) { 123 call := rc.Firewalls.List(projectID) 124 call = call.Filter("name eq " + name) 125 firewallList, err := call.Do() 126 if err != nil { 127 return nil, errors.Annotate(err, "while getting firewall from GCE") 128 } 129 130 if len(firewallList.Items) == 0 { 131 return nil, errors.NotFoundf("firewall %q", name) 132 } 133 return firewallList.Items[0], nil 134 } 135 136 func (rc *rawConn) AddFirewall(projectID string, firewall *compute.Firewall) error { 137 call := rc.Firewalls.Insert(projectID, firewall) 138 operation, err := call.Do() 139 if err != nil { 140 return errors.Trace(err) 141 } 142 143 err = rc.waitOperation(projectID, operation, attemptsLong) 144 return errors.Trace(err) 145 } 146 147 func (rc *rawConn) UpdateFirewall(projectID, name string, firewall *compute.Firewall) error { 148 call := rc.Firewalls.Update(projectID, name, firewall) 149 operation, err := call.Do() 150 if err != nil { 151 return errors.Trace(err) 152 } 153 154 err = rc.waitOperation(projectID, operation, attemptsLong) 155 return errors.Trace(err) 156 } 157 158 func (rc *rawConn) RemoveFirewall(projectID, name string) error { 159 call := rc.Firewalls.Delete(projectID, name) 160 operation, err := call.Do() 161 if err != nil { 162 return errors.Trace(convertRawAPIError(err)) 163 } 164 165 err = rc.waitOperation(projectID, operation, attemptsLong) 166 return errors.Trace(convertRawAPIError(err)) 167 } 168 169 func (rc *rawConn) ListAvailabilityZones(projectID, region string) ([]*compute.Zone, error) { 170 call := rc.Zones.List(projectID) 171 if region != "" { 172 call = call.Filter("name eq " + region + "-.*") 173 } 174 175 var results []*compute.Zone 176 for { 177 zoneList, err := call.Do() 178 if err != nil { 179 return nil, errors.Trace(err) 180 } 181 182 for _, zone := range zoneList.Items { 183 results = append(results, zone) 184 } 185 if zoneList.NextPageToken == "" { 186 break 187 } 188 call = call.PageToken(zoneList.NextPageToken) 189 } 190 return results, nil 191 } 192 193 func formatDiskType(project, zone string, spec *compute.Disk) { 194 // empty will default in pd-standard 195 if spec.Type == "" { 196 return 197 } 198 // see https://cloud.google.com/compute/docs/reference/latest/disks#resource 199 if strings.HasPrefix(spec.Type, "http") || strings.HasPrefix(spec.Type, "projects") || strings.HasPrefix(spec.Type, "global") { 200 return 201 } 202 spec.Type = fmt.Sprintf(diskTypesBase, project, zone, spec.Type) 203 } 204 205 func (rc *rawConn) CreateDisk(project, zone string, spec *compute.Disk) error { 206 ds := rc.Service.Disks 207 formatDiskType(project, zone, spec) 208 call := ds.Insert(project, zone, spec) 209 op, err := call.Do() 210 if err != nil { 211 return errors.Annotate(err, "could not create a new disk") 212 } 213 return errors.Trace(rc.waitOperation(project, op, attemptsLong)) 214 } 215 216 func (rc *rawConn) ListDisks(project, zone string) ([]*compute.Disk, error) { 217 ds := rc.Service.Disks 218 call := ds.List(project, zone) 219 var results []*compute.Disk 220 for { 221 diskList, err := call.Do() 222 if err != nil { 223 return nil, errors.Trace(err) 224 } 225 for _, disk := range diskList.Items { 226 results = append(results, disk) 227 } 228 if diskList.NextPageToken == "" { 229 break 230 } 231 call = call.PageToken(diskList.NextPageToken) 232 } 233 return results, nil 234 } 235 236 func (rc *rawConn) RemoveDisk(project, zone, id string) error { 237 ds := rc.Disks 238 call := ds.Delete(project, zone, id) 239 op, err := call.Do() 240 if err != nil { 241 return errors.Annotatef(err, "could not delete disk %q", id) 242 } 243 return errors.Trace(rc.waitOperation(project, op, attemptsLong)) 244 } 245 246 func (rc *rawConn) GetDisk(project, zone, id string) (*compute.Disk, error) { 247 ds := rc.Disks 248 call := ds.Get(project, zone, id) 249 disk, err := call.Do() 250 if err != nil { 251 return nil, errors.Annotatef(err, "cannot get disk %q at zone %q in project %q", id, zone, project) 252 } 253 return disk, nil 254 } 255 256 func (rc *rawConn) AttachDisk(project, zone, instanceId string, disk *compute.AttachedDisk) error { 257 call := rc.Instances.AttachDisk(project, zone, instanceId, disk) 258 _, err := call.Do() // Perhaps return something from the Op 259 if err != nil { 260 return errors.Annotatef(err, "cannot attach volume into %q", instanceId) 261 } 262 return nil 263 } 264 265 func (rc *rawConn) DetachDisk(project, zone, instanceId, diskDeviceName string) error { 266 call := rc.Instances.DetachDisk(project, zone, instanceId, diskDeviceName) 267 _, err := call.Do() 268 if err != nil { 269 return errors.Annotatef(err, "cannot detach volume from %q", instanceId) 270 } 271 return nil 272 } 273 274 func (rc *rawConn) InstanceDisks(project, zone, instanceId string) ([]*compute.AttachedDisk, error) { 275 instance, err := rc.GetInstance(project, zone, instanceId) 276 if err != nil { 277 return nil, errors.Annotatef(err, "cannot get instance %q to list its disks", instanceId) 278 } 279 return instance.Disks, nil 280 } 281 282 type waitError struct { 283 op *compute.Operation 284 cause error 285 } 286 287 func (err waitError) Error() string { 288 if err.cause != nil { 289 return fmt.Sprintf("GCE operation %q failed: %v", err.op.Name, err.cause) 290 } 291 return fmt.Sprintf("GCE operation %q failed", err.op.Name) 292 } 293 294 func isWaitError(err error) bool { 295 _, ok := err.(*waitError) 296 return ok 297 } 298 299 type opDoer interface { 300 Do() (*compute.Operation, error) 301 } 302 303 // checkOperation requests a new copy of the given operation from the 304 // GCE API and returns it. The new copy will have the operation's 305 // current status. 306 func (rc *rawConn) checkOperation(projectID string, op *compute.Operation) (*compute.Operation, error) { 307 var call opDoer 308 if op.Zone != "" { 309 zoneName := path.Base(op.Zone) 310 call = rc.ZoneOperations.Get(projectID, zoneName, op.Name) 311 } else if op.Region != "" { 312 region := path.Base(op.Region) 313 call = rc.RegionOperations.Get(projectID, region, op.Name) 314 } else { 315 call = rc.GlobalOperations.Get(projectID, op.Name) 316 } 317 318 operation, err := doOpCall(call) 319 if err != nil { 320 return nil, errors.Annotatef(err, "request for GCE operation %q failed", op.Name) 321 } 322 return operation, nil 323 } 324 325 var doOpCall = func(call opDoer) (*compute.Operation, error) { 326 return call.Do() 327 } 328 329 // waitOperation waits for the provided operation to reach the "done" 330 // status. It follows the given attempt strategy (e.g. wait time between 331 // attempts) and may time out. 332 func (rc *rawConn) waitOperation(projectID string, op *compute.Operation, attempts utils.AttemptStrategy) error { 333 started := time.Now() 334 logger.Infof("GCE operation %q, waiting...", op.Name) 335 for a := attempts.Start(); a.Next(); { 336 if op.Status == StatusDone { 337 break 338 } 339 340 var err error 341 op, err = rc.checkOperation(projectID, op) 342 if err != nil { 343 return errors.Trace(err) 344 } 345 } 346 if op.Status != StatusDone { 347 err := errors.Errorf("timed out after %d seconds", time.Now().Sub(started)/time.Second) 348 return waitError{op, err} 349 } 350 if op.Error != nil { 351 for _, err := range op.Error.Errors { 352 logger.Errorf("GCE operation error: (%s) %s", err.Code, err.Message) 353 } 354 return waitError{op, nil} 355 } 356 357 logger.Infof("GCE operation %q finished", op.Name) 358 return nil 359 }