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 }