github.com/dkerwin/nomad@v0.3.3-0.20160525181927-74554135514b/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  			} else if err := os.RemoveAll(taskAlloc); err != nil {
    93  				mErr.Errors = append(mErr.Errors,
    94  					fmt.Errorf("failed to delete shared alloc dir %q: %v", taskAlloc, err))
    95  			}
    96  		}
    97  
    98  		// Unmount dev/ and proc/ have been mounted.
    99  		d.unmountSpecialDirs(dir)
   100  	}
   101  
   102  	return mErr.ErrorOrNil()
   103  }
   104  
   105  // Given a list of a task build the correct alloc structure.
   106  func (d *AllocDir) Build(tasks []*structs.Task) error {
   107  	// Make the alloc directory, owned by the nomad process.
   108  	if err := os.MkdirAll(d.AllocDir, 0755); err != nil {
   109  		return fmt.Errorf("Failed to make the alloc directory %v: %v", d.AllocDir, err)
   110  	}
   111  
   112  	// Make the shared directory and make it available to all user/groups.
   113  	if err := os.MkdirAll(d.SharedDir, 0777); err != nil {
   114  		return err
   115  	}
   116  
   117  	// Make the shared directory have non-root permissions.
   118  	if err := d.dropDirPermissions(d.SharedDir); err != nil {
   119  		return err
   120  	}
   121  
   122  	for _, dir := range SharedAllocDirs {
   123  		p := filepath.Join(d.SharedDir, dir)
   124  		if err := os.MkdirAll(p, 0777); err != nil {
   125  			return err
   126  		}
   127  		if err := d.dropDirPermissions(p); err != nil {
   128  			return err
   129  		}
   130  	}
   131  
   132  	// Make the task directories.
   133  	for _, t := range tasks {
   134  		taskDir := filepath.Join(d.AllocDir, t.Name)
   135  		if err := os.MkdirAll(taskDir, 0777); err != nil {
   136  			return err
   137  		}
   138  
   139  		// Make the task directory have non-root permissions.
   140  		if err := d.dropDirPermissions(taskDir); err != nil {
   141  			return err
   142  		}
   143  
   144  		// Create a local directory that each task can use.
   145  		local := filepath.Join(taskDir, TaskLocal)
   146  		if err := os.MkdirAll(local, 0777); err != nil {
   147  			return err
   148  		}
   149  
   150  		if err := d.dropDirPermissions(local); err != nil {
   151  			return err
   152  		}
   153  
   154  		d.TaskDirs[t.Name] = taskDir
   155  
   156  		// Create the directories that should be in every task.
   157  		for _, dir := range TaskDirs {
   158  			local := filepath.Join(taskDir, dir)
   159  			if err := os.MkdirAll(local, 0777); err != nil {
   160  				return err
   161  			}
   162  
   163  			if err := d.dropDirPermissions(local); err != nil {
   164  				return err
   165  			}
   166  		}
   167  	}
   168  
   169  	return nil
   170  }
   171  
   172  // Embed takes a mapping of absolute directory or file paths on the host to
   173  // their intended, relative location within the task directory. Embed attempts
   174  // hardlink and then defaults to copying. If the path exists on the host and
   175  // can't be embedded an error is returned.
   176  func (d *AllocDir) Embed(task string, entries map[string]string) error {
   177  	taskdir, ok := d.TaskDirs[task]
   178  	if !ok {
   179  		return fmt.Errorf("Task directory doesn't exist for task %v", task)
   180  	}
   181  
   182  	subdirs := make(map[string]string)
   183  	for source, dest := range entries {
   184  		// Check to see if directory exists on host.
   185  		s, err := os.Stat(source)
   186  		if os.IsNotExist(err) {
   187  			continue
   188  		}
   189  
   190  		// Embedding a single file
   191  		if !s.IsDir() {
   192  			destDir := filepath.Join(taskdir, filepath.Dir(dest))
   193  			if err := os.MkdirAll(destDir, s.Mode().Perm()); err != nil {
   194  				return fmt.Errorf("Couldn't create destination directory %v: %v", destDir, err)
   195  			}
   196  
   197  			// Copy the file.
   198  			taskEntry := filepath.Join(destDir, filepath.Base(dest))
   199  			if err := d.linkOrCopy(source, taskEntry, s.Mode().Perm()); err != nil {
   200  				return err
   201  			}
   202  
   203  			continue
   204  		}
   205  
   206  		// Create destination directory.
   207  		destDir := filepath.Join(taskdir, dest)
   208  		if err := os.MkdirAll(destDir, s.Mode().Perm()); err != nil {
   209  			return fmt.Errorf("Couldn't create destination directory %v: %v", destDir, err)
   210  		}
   211  
   212  		// Enumerate the files in source.
   213  		dirEntries, err := ioutil.ReadDir(source)
   214  		if err != nil {
   215  			return fmt.Errorf("Couldn't read directory %v: %v", source, err)
   216  		}
   217  
   218  		for _, entry := range dirEntries {
   219  			hostEntry := filepath.Join(source, entry.Name())
   220  			taskEntry := filepath.Join(destDir, filepath.Base(hostEntry))
   221  			if entry.IsDir() {
   222  				subdirs[hostEntry] = filepath.Join(dest, filepath.Base(hostEntry))
   223  				continue
   224  			}
   225  
   226  			// Check if entry exists. This can happen if restarting a failed
   227  			// task.
   228  			if _, err := os.Lstat(taskEntry); err == nil {
   229  				continue
   230  			}
   231  
   232  			if !entry.Mode().IsRegular() {
   233  				// If it is a symlink we can create it, otherwise we skip it.
   234  				if entry.Mode()&os.ModeSymlink == 0 {
   235  					continue
   236  				}
   237  
   238  				link, err := os.Readlink(hostEntry)
   239  				if err != nil {
   240  					return fmt.Errorf("Couldn't resolve symlink for %v: %v", source, err)
   241  				}
   242  
   243  				if err := os.Symlink(link, taskEntry); err != nil {
   244  					// Symlinking twice
   245  					if err.(*os.LinkError).Err.Error() != "file exists" {
   246  						return fmt.Errorf("Couldn't create symlink: %v", err)
   247  					}
   248  				}
   249  				continue
   250  			}
   251  
   252  			if err := d.linkOrCopy(hostEntry, taskEntry, entry.Mode().Perm()); err != nil {
   253  				return err
   254  			}
   255  		}
   256  	}
   257  
   258  	// Recurse on self to copy subdirectories.
   259  	if len(subdirs) != 0 {
   260  		return d.Embed(task, subdirs)
   261  	}
   262  
   263  	return nil
   264  }
   265  
   266  // MountSharedDir mounts the shared directory into the specified task's
   267  // directory. Mount is documented at an OS level in their respective
   268  // implementation files.
   269  func (d *AllocDir) MountSharedDir(task string) error {
   270  	taskDir, ok := d.TaskDirs[task]
   271  	if !ok {
   272  		return fmt.Errorf("No task directory exists for %v", task)
   273  	}
   274  
   275  	taskLoc := filepath.Join(taskDir, SharedAllocName)
   276  	if err := d.mountSharedDir(taskLoc); err != nil {
   277  		return fmt.Errorf("Failed to mount shared directory for task %v: %v", task, err)
   278  	}
   279  
   280  	return nil
   281  }
   282  
   283  // LogDir returns the log dir in the current allocation directory
   284  func (d *AllocDir) LogDir() string {
   285  	return filepath.Join(d.AllocDir, SharedAllocName, LogDirName)
   286  }
   287  
   288  // List returns the list of files at a path relative to the alloc dir
   289  func (d *AllocDir) List(path string) ([]*AllocFileInfo, error) {
   290  	p := filepath.Join(d.AllocDir, path)
   291  	finfos, err := ioutil.ReadDir(p)
   292  	if err != nil {
   293  		return []*AllocFileInfo{}, err
   294  	}
   295  	files := make([]*AllocFileInfo, len(finfos))
   296  	for idx, info := range finfos {
   297  		files[idx] = &AllocFileInfo{
   298  			Name:     info.Name(),
   299  			IsDir:    info.IsDir(),
   300  			Size:     info.Size(),
   301  			FileMode: info.Mode().String(),
   302  			ModTime:  info.ModTime(),
   303  		}
   304  	}
   305  	return files, err
   306  }
   307  
   308  // Stat returns information about the file at a path relative to the alloc dir
   309  func (d *AllocDir) Stat(path string) (*AllocFileInfo, error) {
   310  	p := filepath.Join(d.AllocDir, path)
   311  	info, err := os.Stat(p)
   312  	if err != nil {
   313  		return nil, err
   314  	}
   315  
   316  	return &AllocFileInfo{
   317  		Size:     info.Size(),
   318  		Name:     info.Name(),
   319  		IsDir:    info.IsDir(),
   320  		FileMode: info.Mode().String(),
   321  		ModTime:  info.ModTime(),
   322  	}, nil
   323  }
   324  
   325  // ReadAt returns a reader  for a file at the path relative to the alloc dir
   326  // which will read a chunk of bytes at a particular offset
   327  func (d *AllocDir) ReadAt(path string, offset int64, limit int64) (io.ReadCloser, error) {
   328  	p := filepath.Join(d.AllocDir, path)
   329  	f, err := os.Open(p)
   330  	if err != nil {
   331  		return nil, err
   332  	}
   333  	if _, err := f.Seek(offset, 0); err != nil {
   334  		return nil, fmt.Errorf("can't seek to offset %q: %v", offset, err)
   335  	}
   336  	return &ReadCloserWrapper{Reader: io.LimitReader(f, limit), Closer: f}, nil
   337  }
   338  
   339  // ReadCloserWrapper wraps a LimitReader so that a file is closed once it has been
   340  // read
   341  type ReadCloserWrapper struct {
   342  	io.Reader
   343  	io.Closer
   344  }
   345  
   346  func fileCopy(src, dst string, perm os.FileMode) error {
   347  	// Do a simple copy.
   348  	srcFile, err := os.Open(src)
   349  	if err != nil {
   350  		return fmt.Errorf("Couldn't open src file %v: %v", src, err)
   351  	}
   352  
   353  	dstFile, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE, perm)
   354  	if err != nil {
   355  		return fmt.Errorf("Couldn't create destination file %v: %v", dst, err)
   356  	}
   357  
   358  	if _, err := io.Copy(dstFile, srcFile); err != nil {
   359  		return fmt.Errorf("Couldn't copy %v to %v: %v", src, dst, err)
   360  	}
   361  
   362  	return nil
   363  }
   364  
   365  // pathExists is a helper function to check if the path exists.
   366  func (d *AllocDir) pathExists(path string) bool {
   367  	if _, err := os.Stat(path); err != nil {
   368  		if os.IsNotExist(err) {
   369  			return false
   370  		}
   371  	}
   372  	return true
   373  }