github.com/ranjib/nomad@v0.1.1-0.20160225204057-97751b02f70b/client/allocdir/alloc_dir.go (about)

     1  package allocdir
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"time"
    10  
    11  	"github.com/hashicorp/go-multierror"
    12  	"github.com/hashicorp/nomad/nomad/structs"
    13  )
    14  
    15  var (
    16  	// The name of the directory that is shared across tasks in a task group.
    17  	SharedAllocName = "alloc"
    18  
    19  	// Name of the directory where logs of Tasks are written
    20  	LogDirName = "logs"
    21  
    22  	// The set of directories that exist inside eache shared alloc directory.
    23  	SharedAllocDirs = []string{LogDirName, "tmp", "data"}
    24  
    25  	// The name of the directory that exists inside each task directory
    26  	// regardless of driver.
    27  	TaskLocal = "local"
    28  
    29  	// TaskDirs is the set of directories created in each tasks directory.
    30  	TaskDirs = []string{"tmp"}
    31  )
    32  
    33  type AllocDir struct {
    34  	// AllocDir is the directory used for storing any state
    35  	// of this allocation. It will be purged on alloc destroy.
    36  	AllocDir string
    37  
    38  	// The shared directory is available to all tasks within the same task
    39  	// group.
    40  	SharedDir string
    41  
    42  	// TaskDirs is a mapping of task names to their non-shared directory.
    43  	TaskDirs map[string]string
    44  }
    45  
    46  // AllocFileInfo holds information about a file inside the AllocDir
    47  type AllocFileInfo struct {
    48  	Name     string
    49  	IsDir    bool
    50  	Size     int64
    51  	FileMode string
    52  	ModTime  time.Time
    53  }
    54  
    55  // AllocDirFS exposes file operations on the alloc dir
    56  type AllocDirFS interface {
    57  	List(path string) ([]*AllocFileInfo, error)
    58  	Stat(path string) (*AllocFileInfo, error)
    59  	ReadAt(path string, offset int64, limit int64) (io.ReadCloser, error)
    60  }
    61  
    62  func NewAllocDir(allocDir string) *AllocDir {
    63  	d := &AllocDir{AllocDir: allocDir, TaskDirs: make(map[string]string)}
    64  	d.SharedDir = filepath.Join(d.AllocDir, SharedAllocName)
    65  	return d
    66  }
    67  
    68  // Tears down previously build directory structure.
    69  func (d *AllocDir) Destroy() error {
    70  	// Unmount all mounted shared alloc dirs.
    71  	var mErr multierror.Error
    72  	if err := d.UnmountAll(); err != nil {
    73  		mErr.Errors = append(mErr.Errors, err)
    74  	}
    75  
    76  	if err := os.RemoveAll(d.AllocDir); err != nil {
    77  		mErr.Errors = append(mErr.Errors, err)
    78  	}
    79  
    80  	return mErr.ErrorOrNil()
    81  }
    82  
    83  func (d *AllocDir) UnmountAll() error {
    84  	var mErr multierror.Error
    85  	for _, dir := range d.TaskDirs {
    86  		// Check if the directory has the shared alloc mounted.
    87  		taskAlloc := filepath.Join(dir, SharedAllocName)
    88  		if d.pathExists(taskAlloc) {
    89  			if err := d.unmountSharedDir(taskAlloc); err != nil {
    90  				mErr.Errors = append(mErr.Errors,
    91  					fmt.Errorf("failed to unmount shared alloc dir %q: %v", taskAlloc, err))
    92  			}
    93  			if err := os.RemoveAll(taskAlloc); err != nil {
    94  				mErr.Errors = append(mErr.Errors,
    95  					fmt.Errorf("failed to delete shared alloc dir %q: %v", taskAlloc, err))
    96  			}
    97  		}
    98  
    99  		// Unmount dev/ and proc/ have been mounted.
   100  		d.unmountSpecialDirs(dir)
   101  	}
   102  
   103  	return mErr.ErrorOrNil()
   104  }
   105  
   106  // Given a list of a task build the correct alloc structure.
   107  func (d *AllocDir) Build(tasks []*structs.Task) error {
   108  	// Make the alloc directory, owned by the nomad process.
   109  	if err := os.MkdirAll(d.AllocDir, 0755); err != nil {
   110  		return fmt.Errorf("Failed to make the alloc directory %v: %v", d.AllocDir, err)
   111  	}
   112  
   113  	// Make the shared directory and make it availabe to all user/groups.
   114  	if err := os.Mkdir(d.SharedDir, 0777); err != nil {
   115  		return err
   116  	}
   117  
   118  	// Make the shared directory have non-root permissions.
   119  	if err := d.dropDirPermissions(d.SharedDir); err != nil {
   120  		return err
   121  	}
   122  
   123  	for _, dir := range SharedAllocDirs {
   124  		p := filepath.Join(d.SharedDir, dir)
   125  		if err := os.Mkdir(p, 0777); err != nil {
   126  			return err
   127  		}
   128  		if err := d.dropDirPermissions(p); err != nil {
   129  			return err
   130  		}
   131  	}
   132  
   133  	// Make the task directories.
   134  	for _, t := range tasks {
   135  		taskDir := filepath.Join(d.AllocDir, t.Name)
   136  		if err := os.Mkdir(taskDir, 0777); err != nil {
   137  			return err
   138  		}
   139  
   140  		// Make the task directory have non-root permissions.
   141  		if err := d.dropDirPermissions(taskDir); err != nil {
   142  			return err
   143  		}
   144  
   145  		// Create a local directory that each task can use.
   146  		local := filepath.Join(taskDir, TaskLocal)
   147  		if err := os.Mkdir(local, 0777); err != nil {
   148  			return err
   149  		}
   150  
   151  		if err := d.dropDirPermissions(local); err != nil {
   152  			return err
   153  		}
   154  
   155  		d.TaskDirs[t.Name] = taskDir
   156  
   157  		// Create the directories that should be in every task.
   158  		for _, dir := range TaskDirs {
   159  			local := filepath.Join(taskDir, dir)
   160  			if err := os.Mkdir(local, 0777); err != nil {
   161  				return err
   162  			}
   163  
   164  			if err := d.dropDirPermissions(local); err != nil {
   165  				return err
   166  			}
   167  		}
   168  	}
   169  
   170  	return nil
   171  }
   172  
   173  // Embed takes a mapping of absolute directory or file paths on the host to
   174  // their intended, relative location within the task directory. Embed attempts
   175  // hardlink and then defaults to copying. If the path exists on the host and
   176  // can't be embeded an error is returned.
   177  func (d *AllocDir) Embed(task string, entries map[string]string) error {
   178  	taskdir, ok := d.TaskDirs[task]
   179  	if !ok {
   180  		return fmt.Errorf("Task directory doesn't exist for task %v", task)
   181  	}
   182  
   183  	subdirs := make(map[string]string)
   184  	for source, dest := range entries {
   185  		// Check to see if directory exists on host.
   186  		s, err := os.Stat(source)
   187  		if os.IsNotExist(err) {
   188  			continue
   189  		}
   190  
   191  		// Embedding a single file
   192  		if !s.IsDir() {
   193  			destDir := filepath.Join(taskdir, filepath.Dir(dest))
   194  			if err := os.MkdirAll(destDir, s.Mode().Perm()); err != nil {
   195  				return fmt.Errorf("Couldn't create destination directory %v: %v", destDir, err)
   196  			}
   197  
   198  			// Copy the file.
   199  			taskEntry := filepath.Join(destDir, filepath.Base(dest))
   200  			if err := d.linkOrCopy(source, taskEntry, s.Mode().Perm()); err != nil {
   201  				return err
   202  			}
   203  
   204  			continue
   205  		}
   206  
   207  		// Create destination directory.
   208  		destDir := filepath.Join(taskdir, dest)
   209  		if err := os.MkdirAll(destDir, s.Mode().Perm()); err != nil {
   210  			return fmt.Errorf("Couldn't create destination directory %v: %v", destDir, err)
   211  		}
   212  
   213  		// Enumerate the files in source.
   214  		dirEntries, err := ioutil.ReadDir(source)
   215  		if err != nil {
   216  			return fmt.Errorf("Couldn't read directory %v: %v", source, err)
   217  		}
   218  
   219  		for _, entry := range dirEntries {
   220  			hostEntry := filepath.Join(source, entry.Name())
   221  			taskEntry := filepath.Join(destDir, filepath.Base(hostEntry))
   222  			if entry.IsDir() {
   223  				subdirs[hostEntry] = filepath.Join(dest, filepath.Base(hostEntry))
   224  				continue
   225  			}
   226  
   227  			// Check if entry exists. This can happen if restarting a failed
   228  			// task.
   229  			if _, err := os.Lstat(taskEntry); err == nil {
   230  				continue
   231  			}
   232  
   233  			if !entry.Mode().IsRegular() {
   234  				// If it is a symlink we can create it, otherwise we skip it.
   235  				if entry.Mode()&os.ModeSymlink == 0 {
   236  					continue
   237  				}
   238  
   239  				link, err := os.Readlink(hostEntry)
   240  				if err != nil {
   241  					return fmt.Errorf("Couldn't resolve symlink for %v: %v", source, err)
   242  				}
   243  
   244  				if err := os.Symlink(link, taskEntry); err != nil {
   245  					// Symlinking twice
   246  					if err.(*os.LinkError).Err.Error() != "file exists" {
   247  						return fmt.Errorf("Couldn't create symlink: %v", err)
   248  					}
   249  				}
   250  				continue
   251  			}
   252  
   253  			if err := d.linkOrCopy(hostEntry, taskEntry, entry.Mode().Perm()); err != nil {
   254  				return err
   255  			}
   256  		}
   257  	}
   258  
   259  	// Recurse on self to copy subdirectories.
   260  	if len(subdirs) != 0 {
   261  		return d.Embed(task, subdirs)
   262  	}
   263  
   264  	return nil
   265  }
   266  
   267  // MountSharedDir mounts the shared directory into the specified task's
   268  // directory. Mount is documented at an OS level in their respective
   269  // implementation files.
   270  func (d *AllocDir) MountSharedDir(task string) error {
   271  	taskDir, ok := d.TaskDirs[task]
   272  	if !ok {
   273  		return fmt.Errorf("No task directory exists for %v", task)
   274  	}
   275  
   276  	taskLoc := filepath.Join(taskDir, SharedAllocName)
   277  	if err := d.mountSharedDir(taskLoc); err != nil {
   278  		return fmt.Errorf("Failed to mount shared directory for task %v: %v", task, err)
   279  	}
   280  
   281  	return nil
   282  }
   283  
   284  // LogDir returns the log dir in the current allocation directory
   285  func (d *AllocDir) LogDir() string {
   286  	return filepath.Join(d.AllocDir, SharedAllocName, LogDirName)
   287  }
   288  
   289  // List returns the list of files at a path relative to the alloc dir
   290  func (d *AllocDir) List(path string) ([]*AllocFileInfo, error) {
   291  	p := filepath.Join(d.AllocDir, path)
   292  	finfos, err := ioutil.ReadDir(p)
   293  	if err != nil {
   294  		return []*AllocFileInfo{}, err
   295  	}
   296  	files := make([]*AllocFileInfo, len(finfos))
   297  	for idx, info := range finfos {
   298  		files[idx] = &AllocFileInfo{
   299  			Name:     info.Name(),
   300  			IsDir:    info.IsDir(),
   301  			Size:     info.Size(),
   302  			FileMode: info.Mode().String(),
   303  			ModTime:  info.ModTime(),
   304  		}
   305  	}
   306  	return files, err
   307  }
   308  
   309  // Stat returns information about the file at a path relative to the alloc dir
   310  func (d *AllocDir) Stat(path string) (*AllocFileInfo, error) {
   311  	p := filepath.Join(d.AllocDir, path)
   312  	info, err := os.Stat(p)
   313  	if err != nil {
   314  		return nil, err
   315  	}
   316  
   317  	return &AllocFileInfo{
   318  		Size:     info.Size(),
   319  		Name:     info.Name(),
   320  		IsDir:    info.IsDir(),
   321  		FileMode: info.Mode().String(),
   322  		ModTime:  info.ModTime(),
   323  	}, nil
   324  }
   325  
   326  // ReadAt returns a reader  for a file at the path relative to the alloc dir
   327  // which will read a chunk of bytes at a particular offset
   328  func (d *AllocDir) ReadAt(path string, offset int64, limit int64) (io.ReadCloser, error) {
   329  	p := filepath.Join(d.AllocDir, path)
   330  	f, err := os.Open(p)
   331  	if err != nil {
   332  		return nil, err
   333  	}
   334  	return &ReadCloserWrapper{Reader: io.LimitReader(f, limit), Closer: f}, nil
   335  }
   336  
   337  // ReadCloserWrapper wraps a LimitReader so that a file is closed once it has been
   338  // read
   339  type ReadCloserWrapper struct {
   340  	io.Reader
   341  	io.Closer
   342  }
   343  
   344  func fileCopy(src, dst string, perm os.FileMode) error {
   345  	// Do a simple copy.
   346  	srcFile, err := os.Open(src)
   347  	if err != nil {
   348  		return fmt.Errorf("Couldn't open src file %v: %v", src, err)
   349  	}
   350  
   351  	dstFile, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE, perm)
   352  	if err != nil {
   353  		return fmt.Errorf("Couldn't create destination file %v: %v", dst, err)
   354  	}
   355  
   356  	if _, err := io.Copy(dstFile, srcFile); err != nil {
   357  		return fmt.Errorf("Couldn't copy %v to %v: %v", src, dst, err)
   358  	}
   359  
   360  	return nil
   361  }
   362  
   363  // pathExists is a helper function to check if the path exists.
   364  func (d *AllocDir) pathExists(path string) bool {
   365  	if _, err := os.Stat(path); err != nil {
   366  		if os.IsNotExist(err) {
   367  			return false
   368  		}
   369  	}
   370  	return true
   371  }