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  }