github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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/environs/context" 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(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(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 // Releasable is defined on the Provider interface. 86 func (*rootfsProvider) Releasable() bool { 87 return false 88 } 89 90 // DefaultPools is defined on the Provider interface. 91 func (*rootfsProvider) DefaultPools() []*storage.Config { 92 return nil 93 } 94 95 type rootfsFilesystemSource struct { 96 dirFuncs dirFuncs 97 run runCommandFunc 98 storageDir string 99 } 100 101 // ensureDir ensures the specified path is a directory, or 102 // if it does not exist, that a directory can be created there. 103 func ensureDir(d dirFuncs, path string) error { 104 // If path already exists, we check that it is empty. 105 // It is up to the storage provisioner to ensure that any 106 // shared storage constraints and attachments with the same 107 // path are validated etc. So the check here is more a sanity check. 108 fi, err := d.lstat(path) 109 if err == nil { 110 if !fi.IsDir() { 111 return errors.Errorf("path %q must be a directory", path) 112 } 113 return nil 114 } 115 if !os.IsNotExist(err) { 116 return errors.Trace(err) 117 } 118 if err := d.mkDirAll(path, 0755); err != nil { 119 return errors.Annotate(err, "could not create directory") 120 } 121 return nil 122 } 123 124 // ensureEmptyDir ensures the specified directory is empty. 125 func ensureEmptyDir(d dirFuncs, path string) error { 126 fileCount, err := d.fileCount(path) 127 if err != nil { 128 return errors.Annotate(err, "could not read directory") 129 } 130 if fileCount > 0 { 131 return errors.Errorf("%q is not empty", path) 132 } 133 return nil 134 } 135 136 var _ storage.FilesystemSource = (*rootfsFilesystemSource)(nil) 137 138 // ValidateFilesystemParams is defined on the FilesystemSource interface. 139 func (s *rootfsFilesystemSource) ValidateFilesystemParams(params storage.FilesystemParams) error { 140 // ValidateFilesystemParams may be called on a machine other than the 141 // machine where the filesystem will be mounted, so we cannot check 142 // available size until we get to CreateFilesystem. 143 return nil 144 } 145 146 // CreateFilesystems is defined on the FilesystemSource interface. 147 func (s *rootfsFilesystemSource) CreateFilesystems(ctx context.ProviderCallContext, args []storage.FilesystemParams) ([]storage.CreateFilesystemsResult, error) { 148 results := make([]storage.CreateFilesystemsResult, len(args)) 149 for i, arg := range args { 150 filesystem, err := s.createFilesystem(arg) 151 if err != nil { 152 results[i].Error = err 153 continue 154 } 155 results[i].Filesystem = filesystem 156 } 157 return results, nil 158 } 159 160 func (s *rootfsFilesystemSource) createFilesystem(params storage.FilesystemParams) (*storage.Filesystem, error) { 161 if err := s.ValidateFilesystemParams(params); err != nil { 162 return nil, errors.Trace(err) 163 } 164 path := filepath.Join(s.storageDir, params.Tag.Id()) 165 if err := ensureDir(s.dirFuncs, path); err != nil { 166 return nil, errors.Trace(err) 167 } 168 if err := ensureEmptyDir(s.dirFuncs, path); err != nil { 169 return nil, errors.Trace(err) 170 } 171 sizeInMiB, err := s.dirFuncs.calculateSize(s.storageDir) 172 if err != nil { 173 os.Remove(path) 174 return nil, errors.Trace(err) 175 } 176 if sizeInMiB < params.Size { 177 os.Remove(path) 178 return nil, errors.Errorf("filesystem is not big enough (%dM < %dM)", sizeInMiB, params.Size) 179 } 180 return &storage.Filesystem{ 181 params.Tag, 182 names.VolumeTag{}, 183 storage.FilesystemInfo{ 184 FilesystemId: params.Tag.Id(), 185 Size: sizeInMiB, 186 }, 187 }, nil 188 } 189 190 // DestroyFilesystems is defined on the FilesystemSource interface. 191 func (s *rootfsFilesystemSource) DestroyFilesystems(ctx context.ProviderCallContext, filesystemIds []string) ([]error, error) { 192 // DestroyFilesystems is a no-op; we leave the storage directory 193 // in tact for post-mortems and such. 194 return make([]error, len(filesystemIds)), nil 195 } 196 197 // ReleaseFilesystems is defined on the FilesystemSource interface. 198 func (s *rootfsFilesystemSource) ReleaseFilesystems(ctx context.ProviderCallContext, filesystemIds []string) ([]error, error) { 199 return make([]error, len(filesystemIds)), nil 200 } 201 202 // AttachFilesystems is defined on the FilesystemSource interface. 203 func (s *rootfsFilesystemSource) AttachFilesystems(ctx context.ProviderCallContext, args []storage.FilesystemAttachmentParams) ([]storage.AttachFilesystemsResult, error) { 204 results := make([]storage.AttachFilesystemsResult, len(args)) 205 for i, arg := range args { 206 attachment, err := s.attachFilesystem(arg) 207 if err != nil { 208 results[i].Error = err 209 continue 210 } 211 results[i].FilesystemAttachment = attachment 212 } 213 return results, nil 214 } 215 216 func (s *rootfsFilesystemSource) attachFilesystem(arg storage.FilesystemAttachmentParams) (*storage.FilesystemAttachment, error) { 217 mountPoint := arg.Path 218 if mountPoint == "" { 219 return nil, errNoMountPoint 220 } 221 // The filesystem is created at <storage-dir>/<storage-id>. 222 // If it is different to the attachment path, bind mount. 223 if err := s.mount(arg.Filesystem, mountPoint); err != nil { 224 return nil, err 225 } 226 return &storage.FilesystemAttachment{ 227 arg.Filesystem, 228 arg.Machine, 229 storage.FilesystemAttachmentInfo{ 230 Path: mountPoint, 231 }, 232 }, nil 233 } 234 235 func (s *rootfsFilesystemSource) mount(tag names.FilesystemTag, target string) error { 236 fsPath := filepath.Join(s.storageDir, tag.Id()) 237 if target == fsPath { 238 return nil 239 } 240 logger.Debugf("mounting filesystem %q at %q", fsPath, target) 241 242 if err := ensureDir(s.dirFuncs, target); err != nil { 243 return errors.Trace(err) 244 } 245 246 mounted, err := s.tryBindMount(fsPath, target) 247 if err != nil { 248 return errors.Trace(err) 249 } 250 if mounted { 251 return nil 252 } 253 // We couldn't bind-mount over the designated directory; 254 // carry on and check if it's on the same filesystem. If 255 // it is, and it's empty, then claim it as our own. 256 257 if err := s.validateSameMountPoints(fsPath, target); err != nil { 258 return err 259 } 260 261 // The first time we try to take the existing directory, we'll 262 // ensure that it's empty and create a file to "claim" it. 263 // Future attachments will simply ensure that the claim file 264 // exists. 265 targetClaimPath := filepath.Join(fsPath, "juju-target-claimed") 266 _, err = s.dirFuncs.lstat(targetClaimPath) 267 if err == nil { 268 return nil 269 } else if !os.IsNotExist(err) { 270 return errors.Trace(err) 271 } 272 if err := ensureEmptyDir(s.dirFuncs, target); err != nil { 273 return errors.Trace(err) 274 } 275 if err := s.dirFuncs.mkDirAll(targetClaimPath, 0755); err != nil { 276 return errors.Annotate(err, "writing claim file") 277 } 278 return nil 279 } 280 281 func (s *rootfsFilesystemSource) tryBindMount(source, target string) (bool, error) { 282 targetSource, err := s.dirFuncs.mountPointSource(target) 283 if err != nil { 284 return false, errors.Annotate(err, "getting target mount-point source") 285 } 286 if targetSource == source { 287 // Already bind mounted. 288 return true, nil 289 } 290 if err := s.dirFuncs.bindMount(source, target); err != nil { 291 logger.Debugf("cannot bind-mount: %v", err) 292 } else { 293 return true, nil 294 } 295 return false, nil 296 } 297 298 func (s *rootfsFilesystemSource) validateSameMountPoints(source, target string) error { 299 sourceMountPoint, err := s.dirFuncs.mountPoint(source) 300 if err != nil { 301 return errors.Trace(err) 302 } 303 targetMountPoint, err := s.dirFuncs.mountPoint(target) 304 if err != nil { 305 return errors.Trace(err) 306 } 307 if sourceMountPoint != targetMountPoint { 308 return errors.Errorf( 309 "%q (%q) and %q (%q) are on different filesystems", 310 source, sourceMountPoint, target, targetMountPoint, 311 ) 312 } 313 return nil 314 } 315 316 // DetachFilesystems is defined on the FilesystemSource interface. 317 func (s *rootfsFilesystemSource) DetachFilesystems(ctx context.ProviderCallContext, args []storage.FilesystemAttachmentParams) ([]error, error) { 318 results := make([]error, len(args)) 319 for i, arg := range args { 320 if err := maybeUnmount(s.run, s.dirFuncs, arg.Path); err != nil { 321 results[i] = err 322 } 323 } 324 return results, nil 325 }