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