github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/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/names" 13 "github.com/juju/utils" 14 15 "github.com/juju/juju/environs/config" 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(environConfig *config.Config, 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(environConfig *config.Config, 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 type tmpfsFilesystemSource struct { 88 dirFuncs dirFuncs 89 run runCommandFunc 90 storageDir string 91 } 92 93 var _ storage.FilesystemSource = (*tmpfsFilesystemSource)(nil) 94 95 // ValidateFilesystemParams is defined on the FilesystemSource interface. 96 func (s *tmpfsFilesystemSource) ValidateFilesystemParams(params storage.FilesystemParams) error { 97 // ValidateFilesystemParams may be called on a machine other than the 98 // machine where the filesystem will be mounted, so we cannot check 99 // available size until we get to createFilesystem. 100 return nil 101 } 102 103 // CreateFilesystems is defined on the FilesystemSource interface. 104 func (s *tmpfsFilesystemSource) CreateFilesystems(args []storage.FilesystemParams) ([]storage.CreateFilesystemsResult, error) { 105 results := make([]storage.CreateFilesystemsResult, len(args)) 106 for i, arg := range args { 107 filesystem, err := s.createFilesystem(arg) 108 if err != nil { 109 results[i].Error = err 110 continue 111 } 112 results[i].Filesystem = filesystem 113 } 114 return results, nil 115 } 116 117 var getpagesize = os.Getpagesize 118 119 func (s *tmpfsFilesystemSource) createFilesystem(params storage.FilesystemParams) (*storage.Filesystem, error) { 120 if err := s.ValidateFilesystemParams(params); err != nil { 121 return nil, errors.Trace(err) 122 } 123 // Align size to the page size in MiB. 124 sizeInMiB := params.Size 125 pageSizeInMiB := uint64(getpagesize()) / (1024 * 1024) 126 if pageSizeInMiB > 0 { 127 x := (sizeInMiB + pageSizeInMiB - 1) 128 sizeInMiB = x - x%pageSizeInMiB 129 } 130 131 info := storage.FilesystemInfo{ 132 FilesystemId: params.Tag.String(), 133 Size: sizeInMiB, 134 } 135 136 // Creating the mount is the responsibility of AttachFilesystems. 137 // AttachFilesystems needs to know the size so it can pass it onto 138 // "mount"; write the size of the filesystem to a file in the 139 // storage directory. 140 if err := s.writeFilesystemInfo(params.Tag, info); err != nil { 141 return nil, err 142 } 143 144 return &storage.Filesystem{params.Tag, params.Volume, info}, nil 145 } 146 147 // DestroyFilesystems is defined on the FilesystemSource interface. 148 func (s *tmpfsFilesystemSource) DestroyFilesystems(filesystemIds []string) ([]error, error) { 149 // DestroyFilesystems is a no-op; there is nothing to destroy, 150 // since the filesystem is ephemeral and disappears once 151 // detached. 152 return make([]error, len(filesystemIds)), nil 153 } 154 155 // AttachFilesystems is defined on the FilesystemSource interface. 156 func (s *tmpfsFilesystemSource) AttachFilesystems(args []storage.FilesystemAttachmentParams) ([]storage.AttachFilesystemsResult, error) { 157 results := make([]storage.AttachFilesystemsResult, len(args)) 158 for i, arg := range args { 159 attachment, err := s.attachFilesystem(arg) 160 if err != nil { 161 results[i].Error = err 162 continue 163 } 164 results[i].FilesystemAttachment = attachment 165 } 166 return results, nil 167 } 168 169 func (s *tmpfsFilesystemSource) attachFilesystem(arg storage.FilesystemAttachmentParams) (*storage.FilesystemAttachment, error) { 170 path := arg.Path 171 if path == "" { 172 return nil, errNoMountPoint 173 } 174 info, err := s.readFilesystemInfo(arg.Filesystem) 175 if err != nil { 176 return nil, err 177 } 178 if err := ensureDir(s.dirFuncs, path); err != nil { 179 return nil, errors.Trace(err) 180 } 181 182 // Check if the mount already exists. 183 source, err := s.dirFuncs.mountPointSource(path) 184 if err != nil { 185 return nil, errors.Trace(err) 186 } 187 if source != arg.Filesystem.String() { 188 if err := ensureEmptyDir(s.dirFuncs, path); err != nil { 189 return nil, err 190 } 191 options := fmt.Sprintf("size=%dm", info.Size) 192 if arg.ReadOnly { 193 options += ",ro" 194 } 195 if _, err := s.run( 196 "mount", "-t", "tmpfs", arg.Filesystem.String(), path, "-o", options, 197 ); err != nil { 198 os.Remove(path) 199 return nil, errors.Annotate(err, "cannot mount tmpfs") 200 } 201 } 202 203 return &storage.FilesystemAttachment{ 204 arg.Filesystem, 205 arg.Machine, 206 storage.FilesystemAttachmentInfo{ 207 Path: path, 208 ReadOnly: arg.ReadOnly, 209 }, 210 }, nil 211 } 212 213 // DetachFilesystems is defined on the FilesystemSource interface. 214 func (s *tmpfsFilesystemSource) DetachFilesystems(args []storage.FilesystemAttachmentParams) ([]error, error) { 215 results := make([]error, len(args)) 216 for i, arg := range args { 217 if err := maybeUnmount(s.run, s.dirFuncs, arg.Path); err != nil { 218 results[i] = err 219 } 220 } 221 return results, nil 222 } 223 224 func (s *tmpfsFilesystemSource) writeFilesystemInfo(tag names.FilesystemTag, info storage.FilesystemInfo) error { 225 filename := s.filesystemInfoFile(tag) 226 if _, err := os.Stat(filename); err == nil { 227 return errors.Errorf("filesystem %v already exists", tag.Id()) 228 } 229 if err := ensureDir(s.dirFuncs, filepath.Dir(filename)); err != nil { 230 return errors.Trace(err) 231 } 232 err := utils.WriteYaml(filename, filesystemInfo{&info.Size}) 233 if err != nil { 234 return errors.Annotate(err, "writing filesystem info to disk") 235 } 236 return err 237 } 238 239 func (s *tmpfsFilesystemSource) readFilesystemInfo(tag names.FilesystemTag) (storage.FilesystemInfo, error) { 240 var info filesystemInfo 241 if err := utils.ReadYaml(s.filesystemInfoFile(tag), &info); err != nil { 242 return storage.FilesystemInfo{}, errors.Annotate(err, "reading filesystem info from disk") 243 } 244 if info.Size == nil { 245 return storage.FilesystemInfo{}, errors.New("invalid filesystem info: missing size") 246 } 247 return storage.FilesystemInfo{ 248 FilesystemId: tag.String(), 249 Size: *info.Size, 250 }, nil 251 } 252 253 func (s *tmpfsFilesystemSource) filesystemInfoFile(tag names.FilesystemTag) string { 254 return filepath.Join(s.storageDir, tag.Id()+".info") 255 } 256 257 type filesystemInfo struct { 258 Size *uint64 `yaml:"size,omitempty"` 259 }