github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/storage/provider/tmpfs.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 "fmt" 8 "os" 9 "path/filepath" 10 11 "github.com/juju/errors" 12 "github.com/juju/utils" 13 "gopkg.in/juju/names.v2" 14 15 "github.com/juju/juju/environs/context" 16 "github.com/juju/juju/storage" 17 ) 18 19 const ( 20 TmpfsProviderType = storage.ProviderType("tmpfs") 21 ) 22 23 // tmpfsProviders create storage sources which provide access to filesystems. 24 type tmpfsProvider struct { 25 // run is a function type used for running commands on the local machine. 26 run runCommandFunc 27 } 28 29 var ( 30 _ storage.Provider = (*tmpfsProvider)(nil) 31 ) 32 33 // ValidateConfig is defined on the Provider interface. 34 func (p *tmpfsProvider) ValidateConfig(cfg *storage.Config) error { 35 // Tmpfs provider has no configuration. 36 return nil 37 } 38 39 // validateFullConfig validates a fully-constructed storage config, 40 // combining the user-specified config and any internally specified 41 // config. 42 func (p *tmpfsProvider) validateFullConfig(cfg *storage.Config) error { 43 if err := p.ValidateConfig(cfg); err != nil { 44 return err 45 } 46 storageDir, ok := cfg.ValueString(storage.ConfigStorageDir) 47 if !ok || storageDir == "" { 48 return errors.New("storage directory not specified") 49 } 50 return nil 51 } 52 53 // VolumeSource is defined on the Provider interface. 54 func (p *tmpfsProvider) VolumeSource(providerConfig *storage.Config) (storage.VolumeSource, error) { 55 return nil, errors.NotSupportedf("volumes") 56 } 57 58 // FilesystemSource is defined on the Provider interface. 59 func (p *tmpfsProvider) FilesystemSource(sourceConfig *storage.Config) (storage.FilesystemSource, error) { 60 if err := p.validateFullConfig(sourceConfig); err != nil { 61 return nil, err 62 } 63 // storageDir is validated by validateFullConfig. 64 storageDir, _ := sourceConfig.ValueString(storage.ConfigStorageDir) 65 return &tmpfsFilesystemSource{ 66 &osDirFuncs{p.run}, 67 p.run, 68 storageDir, 69 }, nil 70 } 71 72 // Supports is defined on the Provider interface. 73 func (*tmpfsProvider) Supports(k storage.StorageKind) bool { 74 return k == storage.StorageKindFilesystem 75 } 76 77 // Scope is defined on the Provider interface. 78 func (*tmpfsProvider) Scope() storage.Scope { 79 return storage.ScopeMachine 80 } 81 82 // Dynamic is defined on the Provider interface. 83 func (*tmpfsProvider) Dynamic() bool { 84 return true 85 } 86 87 // Releasable is defined on the Provider interface. 88 func (*tmpfsProvider) Releasable() bool { 89 return false 90 } 91 92 // DefaultPools is defined on the Provider interface. 93 func (*tmpfsProvider) DefaultPools() []*storage.Config { 94 return nil 95 } 96 97 type tmpfsFilesystemSource struct { 98 dirFuncs dirFuncs 99 run runCommandFunc 100 storageDir string 101 } 102 103 var _ storage.FilesystemSource = (*tmpfsFilesystemSource)(nil) 104 105 // ValidateFilesystemParams is defined on the FilesystemSource interface. 106 func (s *tmpfsFilesystemSource) ValidateFilesystemParams(params storage.FilesystemParams) error { 107 // ValidateFilesystemParams may be called on a machine other than the 108 // machine where the filesystem will be mounted, so we cannot check 109 // available size until we get to createFilesystem. 110 return nil 111 } 112 113 // CreateFilesystems is defined on the FilesystemSource interface. 114 func (s *tmpfsFilesystemSource) CreateFilesystems(ctx context.ProviderCallContext, args []storage.FilesystemParams) ([]storage.CreateFilesystemsResult, error) { 115 results := make([]storage.CreateFilesystemsResult, len(args)) 116 for i, arg := range args { 117 filesystem, err := s.createFilesystem(arg) 118 if err != nil { 119 results[i].Error = err 120 continue 121 } 122 results[i].Filesystem = filesystem 123 } 124 return results, nil 125 } 126 127 var getpagesize = os.Getpagesize 128 129 func (s *tmpfsFilesystemSource) createFilesystem(params storage.FilesystemParams) (*storage.Filesystem, error) { 130 if err := s.ValidateFilesystemParams(params); err != nil { 131 return nil, errors.Trace(err) 132 } 133 // Align size to the page size in MiB. 134 sizeInMiB := params.Size 135 pageSizeInMiB := uint64(getpagesize()) / (1024 * 1024) 136 if pageSizeInMiB > 0 { 137 x := (sizeInMiB + pageSizeInMiB - 1) 138 sizeInMiB = x - x%pageSizeInMiB 139 } 140 141 info := storage.FilesystemInfo{ 142 FilesystemId: params.Tag.String(), 143 Size: sizeInMiB, 144 } 145 146 // Creating the mount is the responsibility of AttachFilesystems. 147 // AttachFilesystems needs to know the size so it can pass it onto 148 // "mount"; write the size of the filesystem to a file in the 149 // storage directory. 150 if err := s.writeFilesystemInfo(params.Tag, info); err != nil { 151 return nil, err 152 } 153 154 return &storage.Filesystem{params.Tag, params.Volume, info}, nil 155 } 156 157 // DestroyFilesystems is defined on the FilesystemSource interface. 158 func (s *tmpfsFilesystemSource) DestroyFilesystems(ctx context.ProviderCallContext, filesystemIds []string) ([]error, error) { 159 // DestroyFilesystems is a no-op; there is nothing to destroy, 160 // since the filesystem is ephemeral and disappears once 161 // detached. 162 return make([]error, len(filesystemIds)), nil 163 } 164 165 // ReleaseFilesystems is defined on the FilesystemSource interface. 166 func (s *tmpfsFilesystemSource) ReleaseFilesystems(ctx context.ProviderCallContext, filesystemIds []string) ([]error, error) { 167 return make([]error, len(filesystemIds)), nil 168 } 169 170 // AttachFilesystems is defined on the FilesystemSource interface. 171 func (s *tmpfsFilesystemSource) AttachFilesystems(ctx context.ProviderCallContext, args []storage.FilesystemAttachmentParams) ([]storage.AttachFilesystemsResult, error) { 172 results := make([]storage.AttachFilesystemsResult, len(args)) 173 for i, arg := range args { 174 attachment, err := s.attachFilesystem(arg) 175 if err != nil { 176 results[i].Error = err 177 continue 178 } 179 results[i].FilesystemAttachment = attachment 180 } 181 return results, nil 182 } 183 184 func (s *tmpfsFilesystemSource) attachFilesystem(arg storage.FilesystemAttachmentParams) (*storage.FilesystemAttachment, error) { 185 path := arg.Path 186 if path == "" { 187 return nil, errNoMountPoint 188 } 189 info, err := s.readFilesystemInfo(arg.Filesystem) 190 if err != nil { 191 return nil, err 192 } 193 if err := ensureDir(s.dirFuncs, path); err != nil { 194 return nil, errors.Trace(err) 195 } 196 197 // Check if the mount already exists. 198 source, err := s.dirFuncs.mountPointSource(path) 199 if err != nil { 200 return nil, errors.Trace(err) 201 } 202 if source != arg.Filesystem.String() { 203 if err := ensureEmptyDir(s.dirFuncs, path); err != nil { 204 return nil, err 205 } 206 options := fmt.Sprintf("size=%dm", info.Size) 207 if arg.ReadOnly { 208 options += ",ro" 209 } 210 if _, err := s.run( 211 "mount", "-t", "tmpfs", arg.Filesystem.String(), path, "-o", options, 212 ); err != nil { 213 os.Remove(path) 214 return nil, errors.Annotate(err, "cannot mount tmpfs") 215 } 216 } 217 218 return &storage.FilesystemAttachment{ 219 arg.Filesystem, 220 arg.Machine, 221 storage.FilesystemAttachmentInfo{ 222 Path: path, 223 ReadOnly: arg.ReadOnly, 224 }, 225 }, nil 226 } 227 228 // DetachFilesystems is defined on the FilesystemSource interface. 229 func (s *tmpfsFilesystemSource) DetachFilesystems(ctx context.ProviderCallContext, args []storage.FilesystemAttachmentParams) ([]error, error) { 230 results := make([]error, len(args)) 231 for i, arg := range args { 232 if err := maybeUnmount(s.run, s.dirFuncs, arg.Path); err != nil { 233 results[i] = err 234 } 235 } 236 return results, nil 237 } 238 239 func (s *tmpfsFilesystemSource) writeFilesystemInfo(tag names.FilesystemTag, info storage.FilesystemInfo) error { 240 filename := s.filesystemInfoFile(tag) 241 if _, err := os.Stat(filename); err == nil { 242 return errors.Errorf("filesystem %v already exists", tag.Id()) 243 } 244 if err := ensureDir(s.dirFuncs, filepath.Dir(filename)); err != nil { 245 return errors.Trace(err) 246 } 247 err := utils.WriteYaml(filename, filesystemInfo{&info.Size}) 248 if err != nil { 249 return errors.Annotate(err, "writing filesystem info to disk") 250 } 251 return err 252 } 253 254 func (s *tmpfsFilesystemSource) readFilesystemInfo(tag names.FilesystemTag) (storage.FilesystemInfo, error) { 255 var info filesystemInfo 256 if err := utils.ReadYaml(s.filesystemInfoFile(tag), &info); err != nil { 257 return storage.FilesystemInfo{}, errors.Annotate(err, "reading filesystem info from disk") 258 } 259 if info.Size == nil { 260 return storage.FilesystemInfo{}, errors.New("invalid filesystem info: missing size") 261 } 262 return storage.FilesystemInfo{ 263 FilesystemId: tag.String(), 264 Size: *info.Size, 265 }, nil 266 } 267 268 func (s *tmpfsFilesystemSource) filesystemInfoFile(tag names.FilesystemTag) string { 269 return filepath.Join(s.storageDir, tag.Id()+".info") 270 } 271 272 type filesystemInfo struct { 273 Size *uint64 `yaml:"size,omitempty"` 274 }