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