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