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