~nskaggs/+junk/juju-packaging-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/storage/provider/rootfs.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-27 20:23:11 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161027202311-sux4jk2o73p1d6rg
Re-add src

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 provider
 
5
 
 
6
import (
 
7
        "os"
 
8
        "path/filepath"
 
9
 
 
10
        "github.com/juju/errors"
 
11
        "github.com/juju/names"
 
12
 
 
13
        "github.com/juju/juju/environs/config"
 
14
        "github.com/juju/juju/storage"
 
15
)
 
16
 
 
17
const (
 
18
        RootfsProviderType = storage.ProviderType("rootfs")
 
19
)
 
20
 
 
21
// rootfsProviders create storage sources which provide access to filesystems.
 
22
type rootfsProvider struct {
 
23
        // run is a function type used for running commands on the local machine.
 
24
        run runCommandFunc
 
25
}
 
26
 
 
27
var (
 
28
        _ storage.Provider = (*rootfsProvider)(nil)
 
29
)
 
30
 
 
31
// ValidateConfig is defined on the Provider interface.
 
32
func (p *rootfsProvider) ValidateConfig(cfg *storage.Config) error {
 
33
        // Rootfs provider has no configuration.
 
34
        return nil
 
35
}
 
36
 
 
37
// validateFullConfig validates a fully-constructed storage config,
 
38
// combining the user-specified config and any internally specified
 
39
// config.
 
40
func (p *rootfsProvider) validateFullConfig(cfg *storage.Config) error {
 
41
        if err := p.ValidateConfig(cfg); err != nil {
 
42
                return err
 
43
        }
 
44
        storageDir, ok := cfg.ValueString(storage.ConfigStorageDir)
 
45
        if !ok || storageDir == "" {
 
46
                return errors.New("storage directory not specified")
 
47
        }
 
48
        return nil
 
49
}
 
50
 
 
51
// VolumeSource is defined on the Provider interface.
 
52
func (p *rootfsProvider) VolumeSource(environConfig *config.Config, providerConfig *storage.Config) (storage.VolumeSource, error) {
 
53
        return nil, errors.NotSupportedf("volumes")
 
54
}
 
55
 
 
56
// FilesystemSource is defined on the Provider interface.
 
57
func (p *rootfsProvider) FilesystemSource(environConfig *config.Config, sourceConfig *storage.Config) (storage.FilesystemSource, error) {
 
58
        if err := p.validateFullConfig(sourceConfig); err != nil {
 
59
                return nil, err
 
60
        }
 
61
        // storageDir is validated by validateFullConfig.
 
62
        storageDir, _ := sourceConfig.ValueString(storage.ConfigStorageDir)
 
63
        return &rootfsFilesystemSource{
 
64
                &osDirFuncs{p.run},
 
65
                p.run,
 
66
                storageDir,
 
67
        }, nil
 
68
}
 
69
 
 
70
// Supports is defined on the Provider interface.
 
71
func (*rootfsProvider) Supports(k storage.StorageKind) bool {
 
72
        return k == storage.StorageKindFilesystem
 
73
}
 
74
 
 
75
// Scope is defined on the Provider interface.
 
76
func (*rootfsProvider) Scope() storage.Scope {
 
77
        return storage.ScopeMachine
 
78
}
 
79
 
 
80
// Dynamic is defined on the Provider interface.
 
81
func (*rootfsProvider) Dynamic() bool {
 
82
        return true
 
83
}
 
84
 
 
85
type rootfsFilesystemSource struct {
 
86
        dirFuncs   dirFuncs
 
87
        run        runCommandFunc
 
88
        storageDir string
 
89
}
 
90
 
 
91
// ensureDir ensures the specified path is a directory, or
 
92
// if it does not exist, that a directory can be created there.
 
93
func ensureDir(d dirFuncs, path string) error {
 
94
        // If path already exists, we check that it is empty.
 
95
        // It is up to the storage provisioner to ensure that any
 
96
        // shared storage constraints and attachments with the same
 
97
        // path are validated etc. So the check here is more a sanity check.
 
98
        fi, err := d.lstat(path)
 
99
        if err == nil {
 
100
                if !fi.IsDir() {
 
101
                        return errors.Errorf("path %q must be a directory", path)
 
102
                }
 
103
                return nil
 
104
        }
 
105
        if !os.IsNotExist(err) {
 
106
                return errors.Trace(err)
 
107
        }
 
108
        if err := d.mkDirAll(path, 0755); err != nil {
 
109
                return errors.Annotate(err, "could not create directory")
 
110
        }
 
111
        return nil
 
112
}
 
113
 
 
114
// ensureEmptyDir ensures the specified directory is empty.
 
115
func ensureEmptyDir(d dirFuncs, path string) error {
 
116
        fileCount, err := d.fileCount(path)
 
117
        if err != nil {
 
118
                return errors.Annotate(err, "could not read directory")
 
119
        }
 
120
        if fileCount > 0 {
 
121
                return errors.Errorf("%q is not empty", path)
 
122
        }
 
123
        return nil
 
124
}
 
125
 
 
126
var _ storage.FilesystemSource = (*rootfsFilesystemSource)(nil)
 
127
 
 
128
// ValidateFilesystemParams is defined on the FilesystemSource interface.
 
129
func (s *rootfsFilesystemSource) ValidateFilesystemParams(params storage.FilesystemParams) error {
 
130
        // ValidateFilesystemParams may be called on a machine other than the
 
131
        // machine where the filesystem will be mounted, so we cannot check
 
132
        // available size until we get to CreateFilesystem.
 
133
        return nil
 
134
}
 
135
 
 
136
// CreateFilesystems is defined on the FilesystemSource interface.
 
137
func (s *rootfsFilesystemSource) CreateFilesystems(args []storage.FilesystemParams) ([]storage.CreateFilesystemsResult, error) {
 
138
        results := make([]storage.CreateFilesystemsResult, len(args))
 
139
        for i, arg := range args {
 
140
                filesystem, err := s.createFilesystem(arg)
 
141
                if err != nil {
 
142
                        results[i].Error = err
 
143
                        continue
 
144
                }
 
145
                results[i].Filesystem = filesystem
 
146
        }
 
147
        return results, nil
 
148
}
 
149
 
 
150
func (s *rootfsFilesystemSource) createFilesystem(params storage.FilesystemParams) (*storage.Filesystem, error) {
 
151
        if err := s.ValidateFilesystemParams(params); err != nil {
 
152
                return nil, errors.Trace(err)
 
153
        }
 
154
        path := filepath.Join(s.storageDir, params.Tag.Id())
 
155
        if err := ensureDir(s.dirFuncs, path); err != nil {
 
156
                return nil, errors.Trace(err)
 
157
        }
 
158
        if err := ensureEmptyDir(s.dirFuncs, path); err != nil {
 
159
                return nil, errors.Trace(err)
 
160
        }
 
161
        sizeInMiB, err := s.dirFuncs.calculateSize(s.storageDir)
 
162
        if err != nil {
 
163
                os.Remove(path)
 
164
                return nil, errors.Trace(err)
 
165
        }
 
166
        if sizeInMiB < params.Size {
 
167
                os.Remove(path)
 
168
                return nil, errors.Errorf("filesystem is not big enough (%dM < %dM)", sizeInMiB, params.Size)
 
169
        }
 
170
        return &storage.Filesystem{
 
171
                params.Tag,
 
172
                names.VolumeTag{},
 
173
                storage.FilesystemInfo{
 
174
                        FilesystemId: params.Tag.Id(),
 
175
                        Size:         sizeInMiB,
 
176
                },
 
177
        }, nil
 
178
}
 
179
 
 
180
// DestroyFilesystems is defined on the FilesystemSource interface.
 
181
func (s *rootfsFilesystemSource) DestroyFilesystems(filesystemIds []string) ([]error, error) {
 
182
        // DestroyFilesystems is a no-op; we leave the storage directory
 
183
        // in tact for post-mortems and such.
 
184
        return make([]error, len(filesystemIds)), nil
 
185
}
 
186
 
 
187
// AttachFilesystems is defined on the FilesystemSource interface.
 
188
func (s *rootfsFilesystemSource) AttachFilesystems(args []storage.FilesystemAttachmentParams) ([]storage.AttachFilesystemsResult, error) {
 
189
        results := make([]storage.AttachFilesystemsResult, len(args))
 
190
        for i, arg := range args {
 
191
                attachment, err := s.attachFilesystem(arg)
 
192
                if err != nil {
 
193
                        results[i].Error = err
 
194
                        continue
 
195
                }
 
196
                results[i].FilesystemAttachment = attachment
 
197
        }
 
198
        return results, nil
 
199
}
 
200
 
 
201
func (s *rootfsFilesystemSource) attachFilesystem(arg storage.FilesystemAttachmentParams) (*storage.FilesystemAttachment, error) {
 
202
        mountPoint := arg.Path
 
203
        if mountPoint == "" {
 
204
                return nil, errNoMountPoint
 
205
        }
 
206
        // The filesystem is created at <storage-dir>/<storage-id>.
 
207
        // If it is different to the attachment path, bind mount.
 
208
        if err := s.mount(arg.Filesystem, mountPoint); err != nil {
 
209
                return nil, err
 
210
        }
 
211
        return &storage.FilesystemAttachment{
 
212
                arg.Filesystem,
 
213
                arg.Machine,
 
214
                storage.FilesystemAttachmentInfo{
 
215
                        Path: mountPoint,
 
216
                },
 
217
        }, nil
 
218
}
 
219
 
 
220
func (s *rootfsFilesystemSource) mount(tag names.FilesystemTag, target string) error {
 
221
        fsPath := filepath.Join(s.storageDir, tag.Id())
 
222
        if target == fsPath {
 
223
                return nil
 
224
        }
 
225
        logger.Debugf("mounting filesystem %q at %q", fsPath, target)
 
226
 
 
227
        if err := ensureDir(s.dirFuncs, target); err != nil {
 
228
                return errors.Trace(err)
 
229
        }
 
230
 
 
231
        mounted, err := s.tryBindMount(fsPath, target)
 
232
        if err != nil {
 
233
                return errors.Trace(err)
 
234
        }
 
235
        if mounted {
 
236
                return nil
 
237
        }
 
238
        // We couldn't bind-mount over the designated directory;
 
239
        // carry on and check if it's on the same filesystem. If
 
240
        // it is, and it's empty, then claim it as our own.
 
241
 
 
242
        if err := s.validateSameMountPoints(fsPath, target); err != nil {
 
243
                return err
 
244
        }
 
245
 
 
246
        // The first time we try to take the existing directory, we'll
 
247
        // ensure that it's empty and create a file to "claim" it.
 
248
        // Future attachments will simply ensure that the claim file
 
249
        // exists.
 
250
        targetClaimPath := filepath.Join(fsPath, "juju-target-claimed")
 
251
        _, err = s.dirFuncs.lstat(targetClaimPath)
 
252
        if err == nil {
 
253
                return nil
 
254
        } else if !os.IsNotExist(err) {
 
255
                return errors.Trace(err)
 
256
        }
 
257
        if err := ensureEmptyDir(s.dirFuncs, target); err != nil {
 
258
                return errors.Trace(err)
 
259
        }
 
260
        if err := s.dirFuncs.mkDirAll(targetClaimPath, 0755); err != nil {
 
261
                return errors.Annotate(err, "writing claim file")
 
262
        }
 
263
        return nil
 
264
}
 
265
 
 
266
func (s *rootfsFilesystemSource) tryBindMount(source, target string) (bool, error) {
 
267
        targetSource, err := s.dirFuncs.mountPointSource(target)
 
268
        if err != nil {
 
269
                return false, errors.Annotate(err, "getting target mount-point source")
 
270
        }
 
271
        if targetSource == source {
 
272
                // Already bind mounted.
 
273
                return true, nil
 
274
        }
 
275
        if err := s.dirFuncs.bindMount(source, target); err != nil {
 
276
                logger.Debugf("cannot bind-mount: %v", err)
 
277
        } else {
 
278
                return true, nil
 
279
        }
 
280
        return false, nil
 
281
}
 
282
 
 
283
func (s *rootfsFilesystemSource) validateSameMountPoints(source, target string) error {
 
284
        sourceMountPoint, err := s.dirFuncs.mountPoint(source)
 
285
        if err != nil {
 
286
                return errors.Trace(err)
 
287
        }
 
288
        targetMountPoint, err := s.dirFuncs.mountPoint(target)
 
289
        if err != nil {
 
290
                return errors.Trace(err)
 
291
        }
 
292
        if sourceMountPoint != targetMountPoint {
 
293
                return errors.Errorf(
 
294
                        "%q (%q) and %q (%q) are on different filesystems",
 
295
                        source, sourceMountPoint, target, targetMountPoint,
 
296
                )
 
297
        }
 
298
        return nil
 
299
}
 
300
 
 
301
// DetachFilesystems is defined on the FilesystemSource interface.
 
302
func (s *rootfsFilesystemSource) DetachFilesystems(args []storage.FilesystemAttachmentParams) ([]error, error) {
 
303
        results := make([]error, len(args))
 
304
        for i, arg := range args {
 
305
                if err := maybeUnmount(s.run, s.dirFuncs, arg.Path); err != nil {
 
306
                        results[i] = err
 
307
                }
 
308
        }
 
309
        return results, nil
 
310
}