1
// Copyright 2015 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
10
"github.com/juju/errors"
11
"github.com/juju/names"
13
"github.com/juju/juju/environs/config"
14
"github.com/juju/juju/storage"
18
RootfsProviderType = storage.ProviderType("rootfs")
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.
28
_ storage.Provider = (*rootfsProvider)(nil)
31
// ValidateConfig is defined on the Provider interface.
32
func (p *rootfsProvider) ValidateConfig(cfg *storage.Config) error {
33
// Rootfs provider has no configuration.
37
// validateFullConfig validates a fully-constructed storage config,
38
// combining the user-specified config and any internally specified
40
func (p *rootfsProvider) validateFullConfig(cfg *storage.Config) error {
41
if err := p.ValidateConfig(cfg); err != nil {
44
storageDir, ok := cfg.ValueString(storage.ConfigStorageDir)
45
if !ok || storageDir == "" {
46
return errors.New("storage directory not specified")
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")
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 {
61
// storageDir is validated by validateFullConfig.
62
storageDir, _ := sourceConfig.ValueString(storage.ConfigStorageDir)
63
return &rootfsFilesystemSource{
70
// Supports is defined on the Provider interface.
71
func (*rootfsProvider) Supports(k storage.StorageKind) bool {
72
return k == storage.StorageKindFilesystem
75
// Scope is defined on the Provider interface.
76
func (*rootfsProvider) Scope() storage.Scope {
77
return storage.ScopeMachine
80
// Dynamic is defined on the Provider interface.
81
func (*rootfsProvider) Dynamic() bool {
85
type rootfsFilesystemSource struct {
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)
101
return errors.Errorf("path %q must be a directory", path)
105
if !os.IsNotExist(err) {
106
return errors.Trace(err)
108
if err := d.mkDirAll(path, 0755); err != nil {
109
return errors.Annotate(err, "could not create directory")
114
// ensureEmptyDir ensures the specified directory is empty.
115
func ensureEmptyDir(d dirFuncs, path string) error {
116
fileCount, err := d.fileCount(path)
118
return errors.Annotate(err, "could not read directory")
121
return errors.Errorf("%q is not empty", path)
126
var _ storage.FilesystemSource = (*rootfsFilesystemSource)(nil)
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.
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)
142
results[i].Error = err
145
results[i].Filesystem = filesystem
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)
154
path := filepath.Join(s.storageDir, params.Tag.Id())
155
if err := ensureDir(s.dirFuncs, path); err != nil {
156
return nil, errors.Trace(err)
158
if err := ensureEmptyDir(s.dirFuncs, path); err != nil {
159
return nil, errors.Trace(err)
161
sizeInMiB, err := s.dirFuncs.calculateSize(s.storageDir)
164
return nil, errors.Trace(err)
166
if sizeInMiB < params.Size {
168
return nil, errors.Errorf("filesystem is not big enough (%dM < %dM)", sizeInMiB, params.Size)
170
return &storage.Filesystem{
173
storage.FilesystemInfo{
174
FilesystemId: params.Tag.Id(),
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
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)
193
results[i].Error = err
196
results[i].FilesystemAttachment = attachment
201
func (s *rootfsFilesystemSource) attachFilesystem(arg storage.FilesystemAttachmentParams) (*storage.FilesystemAttachment, error) {
202
mountPoint := arg.Path
203
if mountPoint == "" {
204
return nil, errNoMountPoint
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 {
211
return &storage.FilesystemAttachment{
214
storage.FilesystemAttachmentInfo{
220
func (s *rootfsFilesystemSource) mount(tag names.FilesystemTag, target string) error {
221
fsPath := filepath.Join(s.storageDir, tag.Id())
222
if target == fsPath {
225
logger.Debugf("mounting filesystem %q at %q", fsPath, target)
227
if err := ensureDir(s.dirFuncs, target); err != nil {
228
return errors.Trace(err)
231
mounted, err := s.tryBindMount(fsPath, target)
233
return errors.Trace(err)
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.
242
if err := s.validateSameMountPoints(fsPath, target); err != nil {
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
250
targetClaimPath := filepath.Join(fsPath, "juju-target-claimed")
251
_, err = s.dirFuncs.lstat(targetClaimPath)
254
} else if !os.IsNotExist(err) {
255
return errors.Trace(err)
257
if err := ensureEmptyDir(s.dirFuncs, target); err != nil {
258
return errors.Trace(err)
260
if err := s.dirFuncs.mkDirAll(targetClaimPath, 0755); err != nil {
261
return errors.Annotate(err, "writing claim file")
266
func (s *rootfsFilesystemSource) tryBindMount(source, target string) (bool, error) {
267
targetSource, err := s.dirFuncs.mountPointSource(target)
269
return false, errors.Annotate(err, "getting target mount-point source")
271
if targetSource == source {
272
// Already bind mounted.
275
if err := s.dirFuncs.bindMount(source, target); err != nil {
276
logger.Debugf("cannot bind-mount: %v", err)
283
func (s *rootfsFilesystemSource) validateSameMountPoints(source, target string) error {
284
sourceMountPoint, err := s.dirFuncs.mountPoint(source)
286
return errors.Trace(err)
288
targetMountPoint, err := s.dirFuncs.mountPoint(target)
290
return errors.Trace(err)
292
if sourceMountPoint != targetMountPoint {
293
return errors.Errorf(
294
"%q (%q) and %q (%q) are on different filesystems",
295
source, sourceMountPoint, target, targetMountPoint,
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 {