github.com/maier/nomad@v0.4.1-0.20161110003312-a9e3d0b8549d/client/allocdir/alloc_dir.go (about) 1 package allocdir 2 3 import ( 4 "archive/tar" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "os" 9 "path/filepath" 10 "time" 11 12 "gopkg.in/tomb.v1" 13 14 "github.com/hashicorp/go-multierror" 15 "github.com/hashicorp/nomad/nomad/structs" 16 "github.com/hpcloud/tail/watch" 17 ) 18 19 var ( 20 // The name of the directory that is shared across tasks in a task group. 21 SharedAllocName = "alloc" 22 23 // Name of the directory where logs of Tasks are written 24 LogDirName = "logs" 25 26 // The set of directories that exist inside eache shared alloc directory. 27 SharedAllocDirs = []string{LogDirName, "tmp", "data"} 28 29 // The name of the directory that exists inside each task directory 30 // regardless of driver. 31 TaskLocal = "local" 32 33 // TaskSecrets is the name of the secret directory inside each task 34 // directory 35 TaskSecrets = "secrets" 36 37 // TaskDirs is the set of directories created in each tasks directory. 38 TaskDirs = []string{"tmp"} 39 ) 40 41 type AllocDir struct { 42 // AllocDir is the directory used for storing any state 43 // of this allocation. It will be purged on alloc destroy. 44 AllocDir string 45 46 // The shared directory is available to all tasks within the same task 47 // group. 48 SharedDir string 49 50 // TaskDirs is a mapping of task names to their non-shared directory. 51 TaskDirs map[string]string 52 } 53 54 // AllocFileInfo holds information about a file inside the AllocDir 55 type AllocFileInfo struct { 56 Name string 57 IsDir bool 58 Size int64 59 FileMode string 60 ModTime time.Time 61 } 62 63 // AllocDirFS exposes file operations on the alloc dir 64 type AllocDirFS interface { 65 List(path string) ([]*AllocFileInfo, error) 66 Stat(path string) (*AllocFileInfo, error) 67 ReadAt(path string, offset int64) (io.ReadCloser, error) 68 Snapshot(w io.Writer) error 69 BlockUntilExists(path string, t *tomb.Tomb) (chan error, error) 70 ChangeEvents(path string, curOffset int64, t *tomb.Tomb) (*watch.FileChanges, error) 71 } 72 73 // NewAllocDir initializes the AllocDir struct with allocDir as base path for 74 // the allocation directory. 75 func NewAllocDir(allocDir string) *AllocDir { 76 d := &AllocDir{ 77 AllocDir: allocDir, 78 TaskDirs: make(map[string]string), 79 } 80 d.SharedDir = filepath.Join(d.AllocDir, SharedAllocName) 81 return d 82 } 83 84 // Snapshot creates an archive of the files and directories in the data dir of 85 // the allocation and the task local directories 86 func (d *AllocDir) Snapshot(w io.Writer) error { 87 allocDataDir := filepath.Join(d.SharedDir, "data") 88 rootPaths := []string{allocDataDir} 89 for _, path := range d.TaskDirs { 90 taskLocaPath := filepath.Join(path, "local") 91 rootPaths = append(rootPaths, taskLocaPath) 92 } 93 94 tw := tar.NewWriter(w) 95 defer tw.Close() 96 97 walkFn := func(path string, fileInfo os.FileInfo, err error) error { 98 // Ignore if the file is a symlink 99 if fileInfo.Mode() == os.ModeSymlink { 100 return nil 101 } 102 103 // Include the path of the file name relative to the alloc dir 104 // so that we can put the files in the right directories 105 relPath, err := filepath.Rel(d.AllocDir, path) 106 if err != nil { 107 return err 108 } 109 hdr, err := tar.FileInfoHeader(fileInfo, "") 110 if err != nil { 111 return fmt.Errorf("error creating file header: %v", err) 112 } 113 hdr.Name = relPath 114 tw.WriteHeader(hdr) 115 116 // If it's a directory we just write the header into the tar 117 if fileInfo.IsDir() { 118 return nil 119 } 120 121 // Write the file into the archive 122 file, err := os.Open(path) 123 if err != nil { 124 return err 125 } 126 defer file.Close() 127 128 if _, err := io.Copy(tw, file); err != nil { 129 return err 130 } 131 return nil 132 } 133 134 // Walk through all the top level directories and add the files and 135 // directories in the archive 136 for _, path := range rootPaths { 137 if err := filepath.Walk(path, walkFn); err != nil { 138 return err 139 } 140 } 141 142 return nil 143 } 144 145 // Move moves the shared data and task local dirs 146 func (d *AllocDir) Move(other *AllocDir, tasks []*structs.Task) error { 147 // Move the data directory 148 otherDataDir := filepath.Join(other.SharedDir, "data") 149 dataDir := filepath.Join(d.SharedDir, "data") 150 if fileInfo, err := os.Stat(otherDataDir); fileInfo != nil && err == nil { 151 if err := os.Rename(otherDataDir, dataDir); err != nil { 152 return fmt.Errorf("error moving data dir: %v", err) 153 } 154 } 155 156 // Move the task directories 157 for _, task := range tasks { 158 taskDir := filepath.Join(other.AllocDir, task.Name) 159 otherTaskLocal := filepath.Join(taskDir, TaskLocal) 160 161 if fileInfo, err := os.Stat(otherTaskLocal); fileInfo != nil && err == nil { 162 if taskDir, ok := d.TaskDirs[task.Name]; ok { 163 if err := os.Rename(otherTaskLocal, filepath.Join(taskDir, TaskLocal)); err != nil { 164 return fmt.Errorf("error moving task local dir: %v", err) 165 } 166 } 167 } 168 } 169 170 return nil 171 } 172 173 // Tears down previously build directory structure. 174 func (d *AllocDir) Destroy() error { 175 176 // Unmount all mounted shared alloc dirs. 177 var mErr multierror.Error 178 if err := d.UnmountAll(); err != nil { 179 mErr.Errors = append(mErr.Errors, err) 180 } 181 182 if err := os.RemoveAll(d.AllocDir); err != nil { 183 mErr.Errors = append(mErr.Errors, err) 184 } 185 186 return mErr.ErrorOrNil() 187 } 188 189 func (d *AllocDir) UnmountAll() error { 190 var mErr multierror.Error 191 for _, dir := range d.TaskDirs { 192 // Check if the directory has the shared alloc mounted. 193 taskAlloc := filepath.Join(dir, SharedAllocName) 194 if d.pathExists(taskAlloc) { 195 if err := d.unmountSharedDir(taskAlloc); err != nil { 196 mErr.Errors = append(mErr.Errors, 197 fmt.Errorf("failed to unmount shared alloc dir %q: %v", taskAlloc, err)) 198 } else if err := os.RemoveAll(taskAlloc); err != nil { 199 mErr.Errors = append(mErr.Errors, 200 fmt.Errorf("failed to delete shared alloc dir %q: %v", taskAlloc, err)) 201 } 202 } 203 204 taskSecret := filepath.Join(dir, TaskSecrets) 205 if d.pathExists(taskSecret) { 206 if err := d.removeSecretDir(taskSecret); err != nil { 207 mErr.Errors = append(mErr.Errors, 208 fmt.Errorf("failed to remove the secret dir %q: %v", taskSecret, err)) 209 } 210 } 211 212 // Unmount dev/ and proc/ have been mounted. 213 d.unmountSpecialDirs(dir) 214 } 215 216 return mErr.ErrorOrNil() 217 } 218 219 // Given a list of a task build the correct alloc structure. 220 func (d *AllocDir) Build(tasks []*structs.Task) error { 221 // Make the alloc directory, owned by the nomad process. 222 if err := os.MkdirAll(d.AllocDir, 0755); err != nil { 223 return fmt.Errorf("Failed to make the alloc directory %v: %v", d.AllocDir, err) 224 } 225 226 // Make the shared directory and make it available to all user/groups. 227 if err := os.MkdirAll(d.SharedDir, 0777); err != nil { 228 return err 229 } 230 231 // Make the shared directory have non-root permissions. 232 if err := d.dropDirPermissions(d.SharedDir); err != nil { 233 return err 234 } 235 236 for _, dir := range SharedAllocDirs { 237 p := filepath.Join(d.SharedDir, dir) 238 if err := os.MkdirAll(p, 0777); err != nil { 239 return err 240 } 241 if err := d.dropDirPermissions(p); err != nil { 242 return err 243 } 244 } 245 246 // Make the task directories. 247 for _, t := range tasks { 248 taskDir := filepath.Join(d.AllocDir, t.Name) 249 if err := os.MkdirAll(taskDir, 0777); err != nil { 250 return err 251 } 252 253 // Make the task directory have non-root permissions. 254 if err := d.dropDirPermissions(taskDir); err != nil { 255 return err 256 } 257 258 // Create a local directory that each task can use. 259 local := filepath.Join(taskDir, TaskLocal) 260 if err := os.MkdirAll(local, 0777); err != nil { 261 return err 262 } 263 264 if err := d.dropDirPermissions(local); err != nil { 265 return err 266 } 267 268 d.TaskDirs[t.Name] = taskDir 269 270 // Create the directories that should be in every task. 271 for _, dir := range TaskDirs { 272 local := filepath.Join(taskDir, dir) 273 if err := os.MkdirAll(local, 0777); err != nil { 274 return err 275 } 276 277 if err := d.dropDirPermissions(local); err != nil { 278 return err 279 } 280 } 281 282 // Create the secret directory 283 secret := filepath.Join(taskDir, TaskSecrets) 284 if err := d.createSecretDir(secret); err != nil { 285 return err 286 } 287 288 if err := d.dropDirPermissions(secret); err != nil { 289 return err 290 } 291 } 292 293 return nil 294 } 295 296 // Embed takes a mapping of absolute directory or file paths on the host to 297 // their intended, relative location within the task directory. Embed attempts 298 // hardlink and then defaults to copying. If the path exists on the host and 299 // can't be embedded an error is returned. 300 func (d *AllocDir) Embed(task string, entries map[string]string) error { 301 taskdir, ok := d.TaskDirs[task] 302 if !ok { 303 return fmt.Errorf("Task directory doesn't exist for task %v", task) 304 } 305 306 subdirs := make(map[string]string) 307 for source, dest := range entries { 308 // Check to see if directory exists on host. 309 s, err := os.Stat(source) 310 if os.IsNotExist(err) { 311 continue 312 } 313 314 // Embedding a single file 315 if !s.IsDir() { 316 if err := d.createDir(taskdir, filepath.Dir(dest)); err != nil { 317 return fmt.Errorf("Couldn't create destination directory %v: %v", dest, err) 318 } 319 320 // Copy the file. 321 taskEntry := filepath.Join(taskdir, dest) 322 if err := d.linkOrCopy(source, taskEntry, s.Mode().Perm()); err != nil { 323 return err 324 } 325 326 continue 327 } 328 329 // Create destination directory. 330 destDir := filepath.Join(taskdir, dest) 331 332 if err := d.createDir(taskdir, dest); err != nil { 333 return fmt.Errorf("Couldn't create destination directory %v: %v", destDir, err) 334 } 335 336 // Enumerate the files in source. 337 dirEntries, err := ioutil.ReadDir(source) 338 if err != nil { 339 return fmt.Errorf("Couldn't read directory %v: %v", source, err) 340 } 341 342 for _, entry := range dirEntries { 343 hostEntry := filepath.Join(source, entry.Name()) 344 taskEntry := filepath.Join(destDir, filepath.Base(hostEntry)) 345 if entry.IsDir() { 346 subdirs[hostEntry] = filepath.Join(dest, filepath.Base(hostEntry)) 347 continue 348 } 349 350 // Check if entry exists. This can happen if restarting a failed 351 // task. 352 if _, err := os.Lstat(taskEntry); err == nil { 353 continue 354 } 355 356 if !entry.Mode().IsRegular() { 357 // If it is a symlink we can create it, otherwise we skip it. 358 if entry.Mode()&os.ModeSymlink == 0 { 359 continue 360 } 361 362 link, err := os.Readlink(hostEntry) 363 if err != nil { 364 return fmt.Errorf("Couldn't resolve symlink for %v: %v", source, err) 365 } 366 367 if err := os.Symlink(link, taskEntry); err != nil { 368 // Symlinking twice 369 if err.(*os.LinkError).Err.Error() != "file exists" { 370 return fmt.Errorf("Couldn't create symlink: %v", err) 371 } 372 } 373 continue 374 } 375 376 if err := d.linkOrCopy(hostEntry, taskEntry, entry.Mode().Perm()); err != nil { 377 return err 378 } 379 } 380 } 381 382 // Recurse on self to copy subdirectories. 383 if len(subdirs) != 0 { 384 return d.Embed(task, subdirs) 385 } 386 387 return nil 388 } 389 390 // MountSharedDir mounts the shared directory into the specified task's 391 // directory. Mount is documented at an OS level in their respective 392 // implementation files. 393 func (d *AllocDir) MountSharedDir(task string) error { 394 taskDir, ok := d.TaskDirs[task] 395 if !ok { 396 return fmt.Errorf("No task directory exists for %v", task) 397 } 398 399 taskLoc := filepath.Join(taskDir, SharedAllocName) 400 if err := d.mountSharedDir(taskLoc); err != nil { 401 return fmt.Errorf("Failed to mount shared directory for task %v: %v", task, err) 402 } 403 404 return nil 405 } 406 407 // LogDir returns the log dir in the current allocation directory 408 func (d *AllocDir) LogDir() string { 409 return filepath.Join(d.AllocDir, SharedAllocName, LogDirName) 410 } 411 412 // List returns the list of files at a path relative to the alloc dir 413 func (d *AllocDir) List(path string) ([]*AllocFileInfo, error) { 414 if escapes, err := structs.PathEscapesAllocDir(path); err != nil { 415 return nil, fmt.Errorf("Failed to check if path escapes alloc directory: %v", err) 416 } else if escapes { 417 return nil, fmt.Errorf("Path escapes the alloc directory") 418 } 419 420 p := filepath.Join(d.AllocDir, path) 421 finfos, err := ioutil.ReadDir(p) 422 if err != nil { 423 return []*AllocFileInfo{}, err 424 } 425 files := make([]*AllocFileInfo, len(finfos)) 426 for idx, info := range finfos { 427 files[idx] = &AllocFileInfo{ 428 Name: info.Name(), 429 IsDir: info.IsDir(), 430 Size: info.Size(), 431 FileMode: info.Mode().String(), 432 ModTime: info.ModTime(), 433 } 434 } 435 return files, err 436 } 437 438 // Stat returns information about the file at a path relative to the alloc dir 439 func (d *AllocDir) Stat(path string) (*AllocFileInfo, error) { 440 if escapes, err := structs.PathEscapesAllocDir(path); err != nil { 441 return nil, fmt.Errorf("Failed to check if path escapes alloc directory: %v", err) 442 } else if escapes { 443 return nil, fmt.Errorf("Path escapes the alloc directory") 444 } 445 446 p := filepath.Join(d.AllocDir, path) 447 info, err := os.Stat(p) 448 if err != nil { 449 return nil, err 450 } 451 452 return &AllocFileInfo{ 453 Size: info.Size(), 454 Name: info.Name(), 455 IsDir: info.IsDir(), 456 FileMode: info.Mode().String(), 457 ModTime: info.ModTime(), 458 }, nil 459 } 460 461 // ReadAt returns a reader for a file at the path relative to the alloc dir 462 func (d *AllocDir) ReadAt(path string, offset int64) (io.ReadCloser, error) { 463 if escapes, err := structs.PathEscapesAllocDir(path); err != nil { 464 return nil, fmt.Errorf("Failed to check if path escapes alloc directory: %v", err) 465 } else if escapes { 466 return nil, fmt.Errorf("Path escapes the alloc directory") 467 } 468 469 p := filepath.Join(d.AllocDir, path) 470 471 // Check if it is trying to read into a secret directory 472 for _, dir := range d.TaskDirs { 473 sdir := filepath.Join(dir, TaskSecrets) 474 if filepath.HasPrefix(p, sdir) { 475 return nil, fmt.Errorf("Reading secret file prohibited: %s", path) 476 } 477 } 478 479 f, err := os.Open(p) 480 if err != nil { 481 return nil, err 482 } 483 if _, err := f.Seek(offset, 0); err != nil { 484 return nil, fmt.Errorf("can't seek to offset %q: %v", offset, err) 485 } 486 return f, nil 487 } 488 489 // BlockUntilExists blocks until the passed file relative the allocation 490 // directory exists. The block can be cancelled with the passed tomb. 491 func (d *AllocDir) BlockUntilExists(path string, t *tomb.Tomb) (chan error, error) { 492 if escapes, err := structs.PathEscapesAllocDir(path); err != nil { 493 return nil, fmt.Errorf("Failed to check if path escapes alloc directory: %v", err) 494 } else if escapes { 495 return nil, fmt.Errorf("Path escapes the alloc directory") 496 } 497 498 // Get the path relative to the alloc directory 499 p := filepath.Join(d.AllocDir, path) 500 watcher := getFileWatcher(p) 501 returnCh := make(chan error, 1) 502 go func() { 503 returnCh <- watcher.BlockUntilExists(t) 504 close(returnCh) 505 }() 506 return returnCh, nil 507 } 508 509 // ChangeEvents watches for changes to the passed path relative to the 510 // allocation directory. The offset should be the last read offset. The tomb is 511 // used to clean up the watch. 512 func (d *AllocDir) ChangeEvents(path string, curOffset int64, t *tomb.Tomb) (*watch.FileChanges, error) { 513 if escapes, err := structs.PathEscapesAllocDir(path); err != nil { 514 return nil, fmt.Errorf("Failed to check if path escapes alloc directory: %v", err) 515 } else if escapes { 516 return nil, fmt.Errorf("Path escapes the alloc directory") 517 } 518 519 // Get the path relative to the alloc directory 520 p := filepath.Join(d.AllocDir, path) 521 watcher := getFileWatcher(p) 522 return watcher.ChangeEvents(t, curOffset) 523 } 524 525 // getFileWatcher returns a FileWatcher for the given path. 526 func getFileWatcher(path string) watch.FileWatcher { 527 return watch.NewPollingFileWatcher(path) 528 } 529 530 func fileCopy(src, dst string, perm os.FileMode) error { 531 // Do a simple copy. 532 srcFile, err := os.Open(src) 533 if err != nil { 534 return fmt.Errorf("Couldn't open src file %v: %v", src, err) 535 } 536 defer srcFile.Close() 537 538 dstFile, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE, perm) 539 if err != nil { 540 return fmt.Errorf("Couldn't create destination file %v: %v", dst, err) 541 } 542 defer dstFile.Close() 543 544 if _, err := io.Copy(dstFile, srcFile); err != nil { 545 return fmt.Errorf("Couldn't copy %v to %v: %v", src, dst, err) 546 } 547 548 return nil 549 } 550 551 // pathExists is a helper function to check if the path exists. 552 func (d *AllocDir) pathExists(path string) bool { 553 if _, err := os.Stat(path); err != nil { 554 if os.IsNotExist(err) { 555 return false 556 } 557 } 558 return true 559 } 560 561 func (d *AllocDir) GetSecretDir(task string) (string, error) { 562 if t, ok := d.TaskDirs[task]; !ok { 563 return "", fmt.Errorf("Allocation directory doesn't contain task %q", task) 564 } else { 565 return filepath.Join(t, TaskSecrets), nil 566 } 567 } 568 569 // createDir creates a directory structure inside the basepath. This functions 570 // preserves the permissions of each of the subdirectories in the relative path 571 // by looking up the permissions in the host. 572 func (d *AllocDir) createDir(basePath, relPath string) error { 573 filePerms, err := d.splitPath(relPath) 574 if err != nil { 575 return err 576 } 577 578 // We are going backwards since we create the root of the directory first 579 // and then create the entire nested structure. 580 for i := len(filePerms) - 1; i >= 0; i-- { 581 fi := filePerms[i] 582 destDir := filepath.Join(basePath, fi.Name) 583 if err := os.MkdirAll(destDir, fi.Perm); err != nil { 584 return err 585 } 586 } 587 return nil 588 } 589 590 // fileInfo holds the path and the permissions of a file 591 type fileInfo struct { 592 Name string 593 Perm os.FileMode 594 } 595 596 // splitPath stats each subdirectory of a path. The first element of the array 597 // is the file passed to this method, and the last element is the root of the 598 // path. 599 func (d *AllocDir) splitPath(path string) ([]fileInfo, error) { 600 var mode os.FileMode 601 i, err := os.Stat(path) 602 603 // If the path is not present in the host then we respond with the most 604 // flexible permission. 605 if err != nil { 606 mode = os.ModePerm 607 } else { 608 mode = i.Mode() 609 } 610 var dirs []fileInfo 611 dirs = append(dirs, fileInfo{Name: path, Perm: mode}) 612 currentDir := path 613 for { 614 dir := filepath.Dir(filepath.Clean(currentDir)) 615 if dir == currentDir { 616 break 617 } 618 619 // We try to find the permission of the file in the host. If the path is not 620 // present in the host then we respond with the most flexible permission. 621 i, err = os.Stat(dir) 622 if err != nil { 623 mode = os.ModePerm 624 } else { 625 mode = i.Mode() 626 } 627 dirs = append(dirs, fileInfo{Name: dir, Perm: mode}) 628 currentDir = dir 629 } 630 return dirs, nil 631 }