~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/provider/openstack/cinder.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 2015 Canonical Ltd.
 
2
// Licensed under the AGPLv3, see LICENCE file for details.
 
3
 
 
4
package openstack
 
5
 
 
6
import (
 
7
        "math"
 
8
        "net/url"
 
9
        "sync"
 
10
        "time"
 
11
 
 
12
        "github.com/juju/errors"
 
13
        "github.com/juju/utils"
 
14
        "gopkg.in/goose.v1/cinder"
 
15
        "gopkg.in/goose.v1/identity"
 
16
        "gopkg.in/goose.v1/nova"
 
17
 
 
18
        "github.com/juju/juju/environs/tags"
 
19
        "github.com/juju/juju/instance"
 
20
        "github.com/juju/juju/storage"
 
21
)
 
22
 
 
23
const (
 
24
        CinderProviderType = storage.ProviderType("cinder")
 
25
        // autoAssignedMountPoint specifies the value to pass in when
 
26
        // you'd like Cinder to automatically assign a mount point.
 
27
        autoAssignedMountPoint = ""
 
28
 
 
29
        volumeStatusAvailable = "available"
 
30
        volumeStatusDeleting  = "deleting"
 
31
        volumeStatusError     = "error"
 
32
        volumeStatusInUse     = "in-use"
 
33
)
 
34
 
 
35
// StorageProviderTypes implements storage.ProviderRegistry.
 
36
func (*Environ) StorageProviderTypes() []storage.ProviderType {
 
37
        return []storage.ProviderType{CinderProviderType}
 
38
}
 
39
 
 
40
// StorageProvider implements storage.ProviderRegistry.
 
41
func (env *Environ) StorageProvider(t storage.ProviderType) (storage.Provider, error) {
 
42
        if t != CinderProviderType {
 
43
                return nil, errors.NotFoundf("storage provider %q", t)
 
44
        }
 
45
        return env.cinderProvider()
 
46
}
 
47
 
 
48
func (env *Environ) cinderProvider() (*cinderProvider, error) {
 
49
        env.ecfgMutex.Lock()
 
50
        envName := env.ecfgUnlocked.Config.Name()
 
51
        modelUUID := env.ecfgUnlocked.Config.UUID()
 
52
        env.ecfgMutex.Unlock()
 
53
 
 
54
        storageAdapter, err := newOpenstackStorage(env)
 
55
        if err != nil {
 
56
                return nil, errors.Trace(err)
 
57
        }
 
58
        return &cinderProvider{storageAdapter, envName, modelUUID}, nil
 
59
}
 
60
 
 
61
var newOpenstackStorage = func(env *Environ) (OpenstackStorage, error) {
 
62
        env.ecfgMutex.Lock()
 
63
        authClient := env.client
 
64
        envNovaClient := env.novaUnlocked
 
65
        env.ecfgMutex.Unlock()
 
66
 
 
67
        endpointUrl, err := getVolumeEndpointURL(authClient, env.cloud.Region)
 
68
        if err != nil {
 
69
                if errors.IsNotFound(err) {
 
70
                        return nil, errors.NewNotSupported(err, "volumes not supported")
 
71
                }
 
72
                return nil, errors.Annotate(err, "getting volume endpoint")
 
73
        }
 
74
 
 
75
        return &openstackStorageAdapter{
 
76
                cinderClient{cinder.Basic(endpointUrl, authClient.TenantId(), authClient.Token)},
 
77
                novaClient{envNovaClient},
 
78
        }, nil
 
79
}
 
80
 
 
81
type cinderProvider struct {
 
82
        storageAdapter OpenstackStorage
 
83
        envName        string
 
84
        modelUUID      string
 
85
}
 
86
 
 
87
var _ storage.Provider = (*cinderProvider)(nil)
 
88
 
 
89
var cinderAttempt = utils.AttemptStrategy{
 
90
        Total: 1 * time.Minute,
 
91
        Delay: 5 * time.Second,
 
92
}
 
93
 
 
94
// VolumeSource implements storage.Provider.
 
95
func (p *cinderProvider) VolumeSource(providerConfig *storage.Config) (storage.VolumeSource, error) {
 
96
        if err := p.ValidateConfig(providerConfig); err != nil {
 
97
                return nil, err
 
98
        }
 
99
        source := &cinderVolumeSource{
 
100
                storageAdapter: p.storageAdapter,
 
101
                envName:        p.envName,
 
102
                modelUUID:      p.modelUUID,
 
103
        }
 
104
        return source, nil
 
105
}
 
106
 
 
107
// FilesystemSource implements storage.Provider.
 
108
func (p *cinderProvider) FilesystemSource(providerConfig *storage.Config) (storage.FilesystemSource, error) {
 
109
        return nil, errors.NotSupportedf("filesystems")
 
110
}
 
111
 
 
112
// Supports implements storage.Provider.
 
113
func (p *cinderProvider) Supports(kind storage.StorageKind) bool {
 
114
        switch kind {
 
115
        case storage.StorageKindBlock:
 
116
                return true
 
117
        }
 
118
        return false
 
119
}
 
120
 
 
121
// Scope implements storage.Provider.
 
122
func (s *cinderProvider) Scope() storage.Scope {
 
123
        return storage.ScopeEnviron
 
124
}
 
125
 
 
126
// ValidateConfig implements storage.Provider.
 
127
func (p *cinderProvider) ValidateConfig(cfg *storage.Config) error {
 
128
        // TODO(axw) 2015-05-01 #1450737
 
129
        // Reject attempts to create non-persistent volumes.
 
130
        return nil
 
131
}
 
132
 
 
133
// Dynamic implements storage.Provider.
 
134
func (p *cinderProvider) Dynamic() bool {
 
135
        return true
 
136
}
 
137
 
 
138
// DefaultPools implements storage.Provider.
 
139
func (p *cinderProvider) DefaultPools() []*storage.Config {
 
140
        return nil
 
141
}
 
142
 
 
143
type cinderVolumeSource struct {
 
144
        storageAdapter OpenstackStorage
 
145
        envName        string // non unique, informational only
 
146
        modelUUID      string
 
147
}
 
148
 
 
149
var _ storage.VolumeSource = (*cinderVolumeSource)(nil)
 
150
 
 
151
// CreateVolumes implements storage.VolumeSource.
 
152
func (s *cinderVolumeSource) CreateVolumes(args []storage.VolumeParams) ([]storage.CreateVolumesResult, error) {
 
153
        results := make([]storage.CreateVolumesResult, len(args))
 
154
        for i, arg := range args {
 
155
                volume, err := s.createVolume(arg)
 
156
                if err != nil {
 
157
                        results[i].Error = errors.Trace(err)
 
158
                        continue
 
159
                }
 
160
                results[i].Volume = volume
 
161
        }
 
162
        return results, nil
 
163
}
 
164
 
 
165
func (s *cinderVolumeSource) createVolume(arg storage.VolumeParams) (*storage.Volume, error) {
 
166
        var metadata interface{}
 
167
        if len(arg.ResourceTags) > 0 {
 
168
                metadata = arg.ResourceTags
 
169
        }
 
170
        cinderVolume, err := s.storageAdapter.CreateVolume(cinder.CreateVolumeVolumeParams{
 
171
                // The Cinder documentation incorrectly states the
 
172
                // size parameter is in GB. It is actually GiB.
 
173
                Size: int(math.Ceil(float64(arg.Size / 1024))),
 
174
                Name: resourceName(arg.Tag, s.envName),
 
175
                // TODO(axw) use the AZ of the initially attached machine.
 
176
                AvailabilityZone: "",
 
177
                Metadata:         metadata,
 
178
        })
 
179
        if err != nil {
 
180
                return nil, errors.Trace(err)
 
181
        }
 
182
 
 
183
        // The response may (will?) come back before the volume transitions to
 
184
        // "creating", in which case it will not have a size or status. Wait for
 
185
        // the volume to transition, so we can record its actual size.
 
186
        volumeId := cinderVolume.ID
 
187
        cinderVolume, err = waitVolume(s.storageAdapter, volumeId, func(v *cinder.Volume) (bool, error) {
 
188
                return v.Status != "", nil
 
189
        })
 
190
        if err != nil {
 
191
                if err := s.storageAdapter.DeleteVolume(volumeId); err != nil {
 
192
                        logger.Warningf("destroying volume %s: %s", volumeId, err)
 
193
                }
 
194
                return nil, errors.Errorf("waiting for volume to be provisioned: %s", err)
 
195
        }
 
196
        logger.Debugf("created volume: %+v", cinderVolume)
 
197
        return &storage.Volume{arg.Tag, cinderToJujuVolumeInfo(cinderVolume)}, nil
 
198
}
 
199
 
 
200
// ListVolumes is specified on the storage.VolumeSource interface.
 
201
func (s *cinderVolumeSource) ListVolumes() ([]string, error) {
 
202
        volumes, err := listVolumes(s.storageAdapter, func(v *cinder.Volume) bool {
 
203
                return v.Metadata[tags.JujuModel] == s.modelUUID
 
204
        })
 
205
        if err != nil {
 
206
                return nil, errors.Trace(err)
 
207
        }
 
208
        return volumeInfoToVolumeIds(volumes), nil
 
209
}
 
210
 
 
211
func volumeInfoToVolumeIds(volumes []storage.VolumeInfo) []string {
 
212
        volumeIds := make([]string, len(volumes))
 
213
        for i, volume := range volumes {
 
214
                volumeIds[i] = volume.VolumeId
 
215
        }
 
216
        return volumeIds
 
217
}
 
218
 
 
219
// listVolumes returns all of the volumes matching the given function.
 
220
func listVolumes(storageAdapter OpenstackStorage, match func(*cinder.Volume) bool) ([]storage.VolumeInfo, error) {
 
221
        cinderVolumes, err := storageAdapter.GetVolumesDetail()
 
222
        if err != nil {
 
223
                return nil, err
 
224
        }
 
225
        volumes := make([]storage.VolumeInfo, 0, len(cinderVolumes))
 
226
        for _, volume := range cinderVolumes {
 
227
                if !match(&volume) {
 
228
                        continue
 
229
                }
 
230
                volumes = append(volumes, cinderToJujuVolumeInfo(&volume))
 
231
        }
 
232
        return volumes, nil
 
233
}
 
234
 
 
235
// DescribeVolumes implements storage.VolumeSource.
 
236
func (s *cinderVolumeSource) DescribeVolumes(volumeIds []string) ([]storage.DescribeVolumesResult, error) {
 
237
        // In most cases, it is quicker to get all volumes and loop
 
238
        // locally than to make several round-trips to the provider.
 
239
        cinderVolumes, err := s.storageAdapter.GetVolumesDetail()
 
240
        if err != nil {
 
241
                return nil, errors.Trace(err)
 
242
        }
 
243
        volumesById := make(map[string]*cinder.Volume)
 
244
        for i, volume := range cinderVolumes {
 
245
                volumesById[volume.ID] = &cinderVolumes[i]
 
246
        }
 
247
        results := make([]storage.DescribeVolumesResult, len(volumeIds))
 
248
        for i, volumeId := range volumeIds {
 
249
                cinderVolume, ok := volumesById[volumeId]
 
250
                if !ok {
 
251
                        results[i].Error = errors.NotFoundf("volume %q", volumeId)
 
252
                        continue
 
253
                }
 
254
                info := cinderToJujuVolumeInfo(cinderVolume)
 
255
                results[i].VolumeInfo = &info
 
256
        }
 
257
        return results, nil
 
258
}
 
259
 
 
260
// DestroyVolumes implements storage.VolumeSource.
 
261
func (s *cinderVolumeSource) DestroyVolumes(volumeIds []string) ([]error, error) {
 
262
        return destroyVolumes(s.storageAdapter, volumeIds), nil
 
263
}
 
264
 
 
265
func destroyVolumes(storageAdapter OpenstackStorage, volumeIds []string) []error {
 
266
        var wg sync.WaitGroup
 
267
        wg.Add(len(volumeIds))
 
268
        results := make([]error, len(volumeIds))
 
269
        for i, volumeId := range volumeIds {
 
270
                go func(i int, volumeId string) {
 
271
                        defer wg.Done()
 
272
                        results[i] = destroyVolume(storageAdapter, volumeId)
 
273
                }(i, volumeId)
 
274
        }
 
275
        wg.Wait()
 
276
        return results
 
277
}
 
278
 
 
279
func destroyVolume(storageAdapter OpenstackStorage, volumeId string) error {
 
280
        logger.Debugf("destroying volume %q", volumeId)
 
281
        // Volumes must not be in-use when destroying. A volume may
 
282
        // still be in-use when the instance it is attached to is
 
283
        // in the process of being terminated.
 
284
        var issuedDetach bool
 
285
        volume, err := waitVolume(storageAdapter, volumeId, func(v *cinder.Volume) (bool, error) {
 
286
                switch v.Status {
 
287
                default:
 
288
                        // Not ready for deletion; keep waiting.
 
289
                        return false, nil
 
290
                case volumeStatusAvailable, volumeStatusDeleting, volumeStatusError:
 
291
                        return true, nil
 
292
                case volumeStatusInUse:
 
293
                        // Detach below.
 
294
                        break
 
295
                }
 
296
                // Volume is still attached, so detach it.
 
297
                if !issuedDetach {
 
298
                        args := make([]storage.VolumeAttachmentParams, len(v.Attachments))
 
299
                        for i, a := range v.Attachments {
 
300
                                args[i].VolumeId = volumeId
 
301
                                args[i].InstanceId = instance.Id(a.ServerId)
 
302
                        }
 
303
                        if len(args) > 0 {
 
304
                                results, err := detachVolumes(storageAdapter, args)
 
305
                                if err != nil {
 
306
                                        return false, errors.Trace(err)
 
307
                                }
 
308
                                for _, err := range results {
 
309
                                        if err != nil {
 
310
                                                return false, errors.Trace(err)
 
311
                                        }
 
312
                                }
 
313
                        }
 
314
                        issuedDetach = true
 
315
                }
 
316
                return false, nil
 
317
        })
 
318
        if err != nil {
 
319
                return errors.Trace(err)
 
320
        }
 
321
        if volume.Status == volumeStatusDeleting {
 
322
                // Already being deleted, nothing to do.
 
323
                return nil
 
324
        }
 
325
        if err := storageAdapter.DeleteVolume(volumeId); err != nil {
 
326
                return errors.Trace(err)
 
327
        }
 
328
        return nil
 
329
}
 
330
 
 
331
// ValidateVolumeParams implements storage.VolumeSource.
 
332
func (s *cinderVolumeSource) ValidateVolumeParams(params storage.VolumeParams) error {
 
333
        return nil
 
334
}
 
335
 
 
336
// AttachVolumes implements storage.VolumeSource.
 
337
func (s *cinderVolumeSource) AttachVolumes(args []storage.VolumeAttachmentParams) ([]storage.AttachVolumesResult, error) {
 
338
        results := make([]storage.AttachVolumesResult, len(args))
 
339
        for i, arg := range args {
 
340
                attachment, err := s.attachVolume(arg)
 
341
                if err != nil {
 
342
                        results[i].Error = errors.Trace(err)
 
343
                        continue
 
344
                }
 
345
                results[i].VolumeAttachment = attachment
 
346
        }
 
347
        return results, nil
 
348
}
 
349
 
 
350
func (s *cinderVolumeSource) attachVolume(arg storage.VolumeAttachmentParams) (*storage.VolumeAttachment, error) {
 
351
        // Check to see if the volume is already attached.
 
352
        existingAttachments, err := s.storageAdapter.ListVolumeAttachments(string(arg.InstanceId))
 
353
        if err != nil {
 
354
                return nil, err
 
355
        }
 
356
        novaAttachment := findAttachment(arg.VolumeId, existingAttachments)
 
357
        if novaAttachment == nil {
 
358
                // A volume must be "available" before it can be attached.
 
359
                if _, err := waitVolume(s.storageAdapter, arg.VolumeId, func(v *cinder.Volume) (bool, error) {
 
360
                        return v.Status == "available", nil
 
361
                }); err != nil {
 
362
                        return nil, errors.Annotate(err, "waiting for volume to become available")
 
363
                }
 
364
                novaAttachment, err = s.storageAdapter.AttachVolume(
 
365
                        string(arg.InstanceId),
 
366
                        arg.VolumeId,
 
367
                        autoAssignedMountPoint,
 
368
                )
 
369
                if err != nil {
 
370
                        return nil, err
 
371
                }
 
372
        }
 
373
        return &storage.VolumeAttachment{
 
374
                arg.Volume,
 
375
                arg.Machine,
 
376
                storage.VolumeAttachmentInfo{
 
377
                        DeviceName: novaAttachment.Device[len("/dev/"):],
 
378
                },
 
379
        }, nil
 
380
}
 
381
 
 
382
func waitVolume(
 
383
        storageAdapter OpenstackStorage,
 
384
        volumeId string,
 
385
        pred func(*cinder.Volume) (bool, error),
 
386
) (*cinder.Volume, error) {
 
387
        for a := cinderAttempt.Start(); a.Next(); {
 
388
                volume, err := storageAdapter.GetVolume(volumeId)
 
389
                if err != nil {
 
390
                        return nil, errors.Annotate(err, "getting volume")
 
391
                }
 
392
                ok, err := pred(volume)
 
393
                if err != nil {
 
394
                        return nil, errors.Trace(err)
 
395
                }
 
396
                if ok {
 
397
                        return volume, nil
 
398
                }
 
399
        }
 
400
        return nil, errors.New("timed out")
 
401
}
 
402
 
 
403
// DetachVolumes implements storage.VolumeSource.
 
404
func (s *cinderVolumeSource) DetachVolumes(args []storage.VolumeAttachmentParams) ([]error, error) {
 
405
        return detachVolumes(s.storageAdapter, args)
 
406
}
 
407
 
 
408
func detachVolumes(storageAdapter OpenstackStorage, args []storage.VolumeAttachmentParams) ([]error, error) {
 
409
        results := make([]error, len(args))
 
410
        for i, arg := range args {
 
411
                // Check to see if the volume is already detached.
 
412
                attachments, err := storageAdapter.ListVolumeAttachments(string(arg.InstanceId))
 
413
                if err != nil {
 
414
                        results[i] = errors.Annotate(err, "listing volume attachments")
 
415
                        continue
 
416
                }
 
417
                if err := detachVolume(
 
418
                        string(arg.InstanceId),
 
419
                        arg.VolumeId,
 
420
                        attachments,
 
421
                        storageAdapter,
 
422
                ); err != nil {
 
423
                        results[i] = errors.Annotatef(
 
424
                                err, "detaching volume %s from server %s",
 
425
                                arg.VolumeId, arg.InstanceId,
 
426
                        )
 
427
                        continue
 
428
                }
 
429
        }
 
430
        return results, nil
 
431
}
 
432
 
 
433
func cinderToJujuVolumeInfo(volume *cinder.Volume) storage.VolumeInfo {
 
434
        return storage.VolumeInfo{
 
435
                VolumeId:   volume.ID,
 
436
                Size:       uint64(volume.Size * 1024),
 
437
                Persistent: true,
 
438
        }
 
439
}
 
440
 
 
441
func detachVolume(instanceId, volumeId string, attachments []nova.VolumeAttachment, storageAdapter OpenstackStorage) error {
 
442
        // TODO(axw) verify whether we need to do this find step. From looking at the example
 
443
        // responses in the OpenStack docs, the "attachment ID" is always the same as the
 
444
        // volume ID. So we should just be able to issue a blind detach request, and then
 
445
        // ignore errors that indicate the volume is already detached.
 
446
        if findAttachment(volumeId, attachments) == nil {
 
447
                return nil
 
448
        }
 
449
        return storageAdapter.DetachVolume(instanceId, volumeId)
 
450
}
 
451
 
 
452
func findAttachment(volId string, attachments []nova.VolumeAttachment) *nova.VolumeAttachment {
 
453
        for _, attachment := range attachments {
 
454
                if attachment.VolumeId == volId {
 
455
                        return &attachment
 
456
                }
 
457
        }
 
458
        return nil
 
459
}
 
460
 
 
461
type OpenstackStorage interface {
 
462
        GetVolume(volumeId string) (*cinder.Volume, error)
 
463
        GetVolumesDetail() ([]cinder.Volume, error)
 
464
        DeleteVolume(volumeId string) error
 
465
        CreateVolume(cinder.CreateVolumeVolumeParams) (*cinder.Volume, error)
 
466
        AttachVolume(serverId, volumeId, mountPoint string) (*nova.VolumeAttachment, error)
 
467
        DetachVolume(serverId, attachmentId string) error
 
468
        ListVolumeAttachments(serverId string) ([]nova.VolumeAttachment, error)
 
469
}
 
470
 
 
471
type endpointResolver interface {
 
472
        EndpointsForRegion(region string) identity.ServiceURLs
 
473
}
 
474
 
 
475
func getVolumeEndpointURL(client endpointResolver, region string) (*url.URL, error) {
 
476
        endpointMap := client.EndpointsForRegion(region)
 
477
        // The cinder openstack charm appends 'v2' to the type for the v2 api.
 
478
        endpoint, ok := endpointMap["volumev2"]
 
479
        if !ok {
 
480
                logger.Debugf(`endpoint "volumev2" not found for %q region, trying "volume"`, region)
 
481
                endpoint, ok = endpointMap["volume"]
 
482
                if !ok {
 
483
                        return nil, errors.NotFoundf(`endpoint "volume" in region %q`, region)
 
484
                }
 
485
        }
 
486
        return url.Parse(endpoint)
 
487
}
 
488
 
 
489
type openstackStorageAdapter struct {
 
490
        cinderClient
 
491
        novaClient
 
492
}
 
493
 
 
494
type cinderClient struct {
 
495
        *cinder.Client
 
496
}
 
497
 
 
498
type novaClient struct {
 
499
        *nova.Client
 
500
}
 
501
 
 
502
// CreateVolume is part of the OpenstackStorage interface.
 
503
func (ga *openstackStorageAdapter) CreateVolume(args cinder.CreateVolumeVolumeParams) (*cinder.Volume, error) {
 
504
        resp, err := ga.cinderClient.CreateVolume(args)
 
505
        if err != nil {
 
506
                return nil, err
 
507
        }
 
508
        return &resp.Volume, nil
 
509
}
 
510
 
 
511
// GetVolumesDetail is part of the OpenstackStorage interface.
 
512
func (ga *openstackStorageAdapter) GetVolumesDetail() ([]cinder.Volume, error) {
 
513
        resp, err := ga.cinderClient.GetVolumesDetail()
 
514
        if err != nil {
 
515
                return nil, err
 
516
        }
 
517
        return resp.Volumes, nil
 
518
}
 
519
 
 
520
// GetVolume is part of the OpenstackStorage interface.
 
521
func (ga *openstackStorageAdapter) GetVolume(volumeId string) (*cinder.Volume, error) {
 
522
        resp, err := ga.cinderClient.GetVolume(volumeId)
 
523
        if err != nil {
 
524
                return nil, err
 
525
        }
 
526
        return &resp.Volume, nil
 
527
}