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  }