github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/storage/provider/rootfs.go (about) 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 }