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  }