github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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 "golang.org/x/net/context" 16 "google.golang.org/api/compute/v1" 17 "google.golang.org/api/googleapi" 18 ) 19 20 const diskTypesBase = "https://www.googleapis.com/compute/v1/projects/%s/zones/%s/diskTypes/%s" 21 22 // These are attempt strategies used in waitOperation. 23 var ( 24 // TODO(ericsnow) Tune the timeouts and delays. 25 // TODO(katco): 2016-08-09: lp:1611427 26 attemptsLong = utils.AttemptStrategy{ 27 Total: 5 * time.Minute, 28 Delay: 2 * 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) GetFirewalls(projectID, namePrefix string) ([]*compute.Firewall, error) { 120 call := rc.Firewalls.List(projectID) 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", namePrefix) 128 } 129 var result []*compute.Firewall 130 for _, fw := range firewallList.Items { 131 if strings.HasPrefix(fw.Name, namePrefix) { 132 result = append(result, fw) 133 } 134 } 135 return result, nil 136 } 137 138 func (rc *rawConn) AddFirewall(projectID string, firewall *compute.Firewall) error { 139 call := rc.Firewalls.Insert(projectID, firewall) 140 operation, err := call.Do() 141 if err != nil { 142 return errors.Trace(err) 143 } 144 145 err = rc.waitOperation(projectID, operation, attemptsLong) 146 return errors.Trace(err) 147 } 148 149 func (rc *rawConn) UpdateFirewall(projectID, name string, firewall *compute.Firewall) error { 150 call := rc.Firewalls.Update(projectID, name, firewall) 151 operation, err := call.Do() 152 if err != nil { 153 return errors.Trace(err) 154 } 155 156 err = rc.waitOperation(projectID, operation, attemptsLong) 157 return errors.Trace(err) 158 } 159 160 func (rc *rawConn) RemoveFirewall(projectID, name string) error { 161 call := rc.Firewalls.Delete(projectID, name) 162 operation, err := call.Do() 163 if err != nil { 164 return errors.Trace(convertRawAPIError(err)) 165 } 166 167 err = rc.waitOperation(projectID, operation, attemptsLong) 168 return errors.Trace(convertRawAPIError(err)) 169 } 170 171 func (rc *rawConn) ListAvailabilityZones(projectID, region string) ([]*compute.Zone, error) { 172 call := rc.Zones.List(projectID) 173 if region != "" { 174 call = call.Filter("name eq " + region + "-.*") 175 } 176 177 var results []*compute.Zone 178 for { 179 zoneList, err := call.Do() 180 if err != nil { 181 return nil, errors.Trace(err) 182 } 183 184 for _, zone := range zoneList.Items { 185 results = append(results, zone) 186 } 187 if zoneList.NextPageToken == "" { 188 break 189 } 190 call = call.PageToken(zoneList.NextPageToken) 191 } 192 return results, nil 193 } 194 195 func formatDiskType(project, zone string, spec *compute.Disk) { 196 // empty will default in pd-standard 197 if spec.Type == "" { 198 return 199 } 200 // see https://cloud.google.com/compute/docs/reference/latest/disks#resource 201 if strings.HasPrefix(spec.Type, "http") || strings.HasPrefix(spec.Type, "projects") || strings.HasPrefix(spec.Type, "global") { 202 return 203 } 204 spec.Type = fmt.Sprintf(diskTypesBase, project, zone, spec.Type) 205 } 206 207 func (rc *rawConn) CreateDisk(project, zone string, spec *compute.Disk) error { 208 ds := rc.Service.Disks 209 formatDiskType(project, zone, spec) 210 call := ds.Insert(project, zone, spec) 211 op, err := call.Do() 212 if err != nil { 213 return errors.Annotate(err, "could not create a new disk") 214 } 215 return errors.Trace(rc.waitOperation(project, op, attemptsLong)) 216 } 217 218 func (rc *rawConn) ListDisks(project string) ([]*compute.Disk, error) { 219 ds := rc.Service.Disks 220 call := ds.AggregatedList(project) 221 var results []*compute.Disk 222 for { 223 diskList, err := call.Do() 224 if err != nil { 225 return nil, errors.Trace(err) 226 } 227 for _, list := range diskList.Items { 228 results = append(results, list.Disks...) 229 } 230 if diskList.NextPageToken == "" { 231 break 232 } 233 call = call.PageToken(diskList.NextPageToken) 234 } 235 return results, nil 236 } 237 238 func (rc *rawConn) RemoveDisk(project, zone, id string) error { 239 ds := rc.Disks 240 call := ds.Delete(project, zone, id) 241 op, err := call.Do() 242 if err != nil { 243 return errors.Annotatef(err, "could not delete disk %q", id) 244 } 245 return errors.Trace(rc.waitOperation(project, op, attemptsLong)) 246 } 247 248 func (rc *rawConn) GetDisk(project, zone, id string) (*compute.Disk, error) { 249 ds := rc.Disks 250 call := ds.Get(project, zone, id) 251 disk, err := call.Do() 252 if err != nil { 253 return nil, errors.Annotatef(err, "cannot get disk %q at zone %q in project %q", id, zone, project) 254 } 255 return disk, nil 256 } 257 258 func (rc *rawConn) SetDiskLabels(project, zone, id, labelFingerprint string, labels map[string]string) error { 259 ds := rc.Service.Disks 260 call := ds.SetLabels(project, zone, id, &compute.ZoneSetLabelsRequest{ 261 LabelFingerprint: labelFingerprint, 262 Labels: labels, 263 }) 264 _, err := call.Do() 265 return errors.Trace(err) 266 } 267 268 func (rc *rawConn) AttachDisk(project, zone, instanceId string, disk *compute.AttachedDisk) error { 269 call := rc.Instances.AttachDisk(project, zone, instanceId, disk) 270 _, err := call.Do() // Perhaps return something from the Op 271 if err != nil { 272 return errors.Annotatef(err, "cannot attach volume into %q", instanceId) 273 } 274 return nil 275 } 276 277 func (rc *rawConn) DetachDisk(project, zone, instanceId, diskDeviceName string) error { 278 call := rc.Instances.DetachDisk(project, zone, instanceId, diskDeviceName) 279 _, err := call.Do() 280 if err != nil { 281 return errors.Annotatef(err, "cannot detach volume from %q", instanceId) 282 } 283 return nil 284 } 285 286 func (rc *rawConn) InstanceDisks(project, zone, instanceId string) ([]*compute.AttachedDisk, error) { 287 instance, err := rc.GetInstance(project, zone, instanceId) 288 if err != nil { 289 return nil, errors.Annotatef(err, "cannot get instance %q to list its disks", instanceId) 290 } 291 return instance.Disks, nil 292 } 293 294 type waitError struct { 295 op *compute.Operation 296 cause error 297 } 298 299 func (err waitError) Error() string { 300 if err.cause != nil { 301 return fmt.Sprintf("GCE operation %q failed: %v", err.op.Name, err.cause) 302 } 303 return fmt.Sprintf("GCE operation %q failed", err.op.Name) 304 } 305 306 func isWaitError(err error) bool { 307 _, ok := err.(*waitError) 308 return ok 309 } 310 311 type opDoer interface { 312 Do(...googleapi.CallOption) (*compute.Operation, error) 313 } 314 315 // checkOperation requests a new copy of the given operation from the 316 // GCE API and returns it. The new copy will have the operation's 317 // current status. 318 func (rc *rawConn) checkOperation(projectID string, op *compute.Operation) (*compute.Operation, error) { 319 var call opDoer 320 if op.Zone != "" { 321 zoneName := path.Base(op.Zone) 322 call = rc.ZoneOperations.Get(projectID, zoneName, op.Name) 323 } else if op.Region != "" { 324 region := path.Base(op.Region) 325 call = rc.RegionOperations.Get(projectID, region, op.Name) 326 } else { 327 call = rc.GlobalOperations.Get(projectID, op.Name) 328 } 329 330 operation, err := doOpCall(call) 331 if err != nil { 332 return nil, errors.Annotatef(err, "request for GCE operation %q failed", op.Name) 333 } 334 return operation, nil 335 } 336 337 var doOpCall = func(call opDoer) (*compute.Operation, error) { 338 return call.Do() 339 } 340 341 // waitOperation waits for the provided operation to reach the "done" 342 // status. It follows the given attempt strategy (e.g. wait time between 343 // attempts) and may time out. 344 // 345 // TODO(katco): 2016-08-09: lp:1611427 346 func (rc *rawConn) waitOperation(projectID string, op *compute.Operation, attempts utils.AttemptStrategy) error { 347 // TODO(perrito666) 2016-05-02 lp:1558657 348 started := time.Now() 349 logger.Infof("GCE operation %q, waiting...", op.Name) 350 for a := attempts.Start(); a.Next(); { 351 if op.Status == StatusDone { 352 break 353 } 354 355 var err error 356 op, err = rc.checkOperation(projectID, op) 357 if err != nil { 358 return errors.Trace(err) 359 } 360 } 361 if op.Status != StatusDone { 362 // lp:1558657 363 err := errors.Errorf("timed out after %d seconds", time.Now().Sub(started)/time.Second) 364 return waitError{op, err} 365 } 366 if op.Error != nil { 367 for _, err := range op.Error.Errors { 368 logger.Errorf("GCE operation error: (%s) %s", err.Code, err.Message) 369 } 370 return waitError{op, nil} 371 } 372 373 logger.Infof("GCE operation %q finished", op.Name) 374 return nil 375 } 376 377 // ListMachineTypes returns a list of machines available in the project and zone provided. 378 func (rc *rawConn) ListMachineTypes(projectID, zone string) (*compute.MachineTypeList, error) { 379 op := rc.MachineTypes.List(projectID, zone) 380 machines, err := op.Do() 381 if err != nil { 382 return nil, errors.Annotatef(err, "listing machine types for project %q and zone %q", projectID, zone) 383 } 384 return machines, nil 385 } 386 387 func (rc *rawConn) SetMetadata(projectID, zone, instanceID string, metadata *compute.Metadata) error { 388 call := rc.Instances.SetMetadata(projectID, zone, instanceID, metadata) 389 op, err := call.Do() 390 if err != nil { 391 return errors.Trace(err) 392 } 393 err = rc.waitOperation(projectID, op, attemptsLong) 394 return errors.Trace(err) 395 } 396 397 func (rc *rawConn) ListSubnetworks(projectID, region string) ([]*compute.Subnetwork, error) { 398 ctx := context.Background() 399 call := rc.Subnetworks.List(projectID, region) 400 var results []*compute.Subnetwork 401 err := call.Pages(ctx, func(page *compute.SubnetworkList) error { 402 results = append(results, page.Items...) 403 return nil 404 }) 405 if err != nil { 406 return nil, errors.Trace(err) 407 } 408 return results, nil 409 } 410 411 func (rc *rawConn) ListNetworks(projectID string) ([]*compute.Network, error) { 412 ctx := context.Background() 413 call := rc.Networks.List(projectID) 414 var results []*compute.Network 415 err := call.Pages(ctx, func(page *compute.NetworkList) error { 416 results = append(results, page.Items...) 417 return nil 418 }) 419 if err != nil { 420 return nil, errors.Trace(err) 421 } 422 return results, nil 423 }