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 }