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  }