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