~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/provider/gce/google/raw.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
        // TODO(katco): 2016-08-09: lp:1611427
 
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
//
 
329
// TODO(katco): 2016-08-09: lp:1611427
 
330
func (rc *rawConn) waitOperation(projectID string, op *compute.Operation, attempts utils.AttemptStrategy) error {
 
331
        // TODO(perrito666) 2016-05-02 lp:1558657
 
332
        started := time.Now()
 
333
        logger.Infof("GCE operation %q, waiting...", op.Name)
 
334
        for a := attempts.Start(); a.Next(); {
 
335
                if op.Status == StatusDone {
 
336
                        break
 
337
                }
 
338
 
 
339
                var err error
 
340
                op, err = rc.checkOperation(projectID, op)
 
341
                if err != nil {
 
342
                        return errors.Trace(err)
 
343
                }
 
344
        }
 
345
        if op.Status != StatusDone {
 
346
                // lp:1558657
 
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
}