github.com/djenriquez/nomad-1@v0.8.1/client/alloc_watcher_test.go (about) 1 package client 2 3 import ( 4 "archive/tar" 5 "bytes" 6 "context" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "os" 11 "path/filepath" 12 "strings" 13 "syscall" 14 "testing" 15 "time" 16 17 "github.com/hashicorp/nomad/client/allocdir" 18 "github.com/hashicorp/nomad/client/config" 19 "github.com/hashicorp/nomad/client/testutil" 20 "github.com/hashicorp/nomad/nomad/mock" 21 ) 22 23 // TestPrevAlloc_LocalPrevAlloc asserts that when a previous alloc runner is 24 // set a localPrevAlloc will block on it. 25 func TestPrevAlloc_LocalPrevAlloc(t *testing.T) { 26 _, prevAR := testAllocRunner(t, false) 27 prevAR.alloc.Job.TaskGroups[0].Tasks[0].Config["run_for"] = "10s" 28 29 newAlloc := mock.Alloc() 30 newAlloc.PreviousAllocation = prevAR.Alloc().ID 31 newAlloc.Job.TaskGroups[0].EphemeralDisk.Sticky = false 32 task := newAlloc.Job.TaskGroups[0].Tasks[0] 33 task.Driver = "mock_driver" 34 task.Config["run_for"] = "500ms" 35 36 waiter := newAllocWatcher(newAlloc, prevAR, nil, nil, testLogger(), "") 37 38 // Wait in a goroutine with a context to make sure it exits at the right time 39 ctx, cancel := context.WithCancel(context.Background()) 40 defer cancel() 41 go func() { 42 defer cancel() 43 waiter.Wait(ctx) 44 }() 45 46 select { 47 case <-ctx.Done(): 48 t.Fatalf("Wait exited too early") 49 case <-time.After(33 * time.Millisecond): 50 // Good! It's blocking 51 } 52 53 // Start the previous allocs to cause it to update but not terminate 54 go prevAR.Run() 55 defer prevAR.Destroy() 56 57 select { 58 case <-ctx.Done(): 59 t.Fatalf("Wait exited too early") 60 case <-time.After(33 * time.Millisecond): 61 // Good! It's still blocking 62 } 63 64 // Stop the previous alloc 65 prevAR.Destroy() 66 67 select { 68 case <-ctx.Done(): 69 // Good! We unblocked when the previous alloc stopped 70 case <-time.After(time.Second): 71 t.Fatalf("Wait exited too early") 72 } 73 } 74 75 // TestPrevAlloc_StreamAllocDir_Ok asserts that streaming a tar to an alloc dir 76 // works. 77 func TestPrevAlloc_StreamAllocDir_Ok(t *testing.T) { 78 testutil.RequireRoot(t) 79 t.Parallel() 80 dir, err := ioutil.TempDir("", "") 81 if err != nil { 82 t.Fatalf("err: %v", err) 83 } 84 defer os.RemoveAll(dir) 85 86 // Create foo/ 87 fooDir := filepath.Join(dir, "foo") 88 if err := os.Mkdir(fooDir, 0777); err != nil { 89 t.Fatalf("err: %v", err) 90 } 91 92 // Change ownership of foo/ to test #3702 (any non-root user is fine) 93 const uid, gid = 1, 1 94 if err := os.Chown(fooDir, uid, gid); err != nil { 95 t.Fatalf("err : %v", err) 96 } 97 98 dirInfo, err := os.Stat(fooDir) 99 if err != nil { 100 t.Fatalf("err: %v", err) 101 } 102 103 // Create foo/bar 104 f, err := os.Create(filepath.Join(fooDir, "bar")) 105 if err != nil { 106 t.Fatalf("err: %v", err) 107 } 108 if _, err := f.WriteString("123"); err != nil { 109 t.Fatalf("err: %v", err) 110 } 111 if err := f.Chmod(0644); err != nil { 112 t.Fatalf("err: %v", err) 113 } 114 fInfo, err := f.Stat() 115 if err != nil { 116 t.Fatalf("err: %v", err) 117 } 118 f.Close() 119 120 // Create foo/baz -> bar symlink 121 if err := os.Symlink("bar", filepath.Join(dir, "foo", "baz")); err != nil { 122 t.Fatalf("err: %v", err) 123 } 124 linkInfo, err := os.Lstat(filepath.Join(dir, "foo", "baz")) 125 if err != nil { 126 t.Fatalf("err: %v", err) 127 } 128 129 buf := new(bytes.Buffer) 130 tw := tar.NewWriter(buf) 131 132 walkFn := func(path string, fileInfo os.FileInfo, err error) error { 133 // Include the path of the file name relative to the alloc dir 134 // so that we can put the files in the right directories 135 link := "" 136 if fileInfo.Mode()&os.ModeSymlink != 0 { 137 target, err := os.Readlink(path) 138 if err != nil { 139 return fmt.Errorf("error reading symlink: %v", err) 140 } 141 link = target 142 } 143 hdr, err := tar.FileInfoHeader(fileInfo, link) 144 if err != nil { 145 return fmt.Errorf("error creating file header: %v", err) 146 } 147 hdr.Name = fileInfo.Name() 148 tw.WriteHeader(hdr) 149 150 // If it's a directory or symlink we just write the header into the tar 151 if fileInfo.IsDir() || (fileInfo.Mode()&os.ModeSymlink != 0) { 152 return nil 153 } 154 155 // Write the file into the archive 156 file, err := os.Open(path) 157 if err != nil { 158 return err 159 } 160 defer file.Close() 161 162 if _, err := io.Copy(tw, file); err != nil { 163 return err 164 } 165 166 return nil 167 } 168 169 if err := filepath.Walk(dir, walkFn); err != nil { 170 t.Fatalf("err: %v", err) 171 } 172 tw.Close() 173 174 dir1, err := ioutil.TempDir("", "nomadtest-") 175 if err != nil { 176 t.Fatalf("err: %v", err) 177 } 178 defer os.RemoveAll(dir1) 179 180 c1 := TestClient(t, func(c *config.Config) { 181 c.RPCHandler = nil 182 }) 183 defer c1.Shutdown() 184 185 rc := ioutil.NopCloser(buf) 186 187 prevAlloc := &remotePrevAlloc{logger: testLogger()} 188 if err := prevAlloc.streamAllocDir(context.Background(), rc, dir1); err != nil { 189 t.Fatalf("err: %v", err) 190 } 191 192 // Ensure foo is present 193 fi, err := os.Stat(filepath.Join(dir1, "foo")) 194 if err != nil { 195 t.Fatalf("err: %v", err) 196 } 197 if fi.Mode() != dirInfo.Mode() { 198 t.Fatalf("mode: %v", fi.Mode()) 199 } 200 stat := fi.Sys().(*syscall.Stat_t) 201 if stat.Uid != uid || stat.Gid != gid { 202 t.Fatalf("foo/ has incorrect ownership: expected %d:%d found %d:%d", 203 uid, gid, stat.Uid, stat.Gid) 204 } 205 206 fi1, err := os.Stat(filepath.Join(dir1, "bar")) 207 if err != nil { 208 t.Fatalf("err: %v", err) 209 } 210 if fi1.Mode() != fInfo.Mode() { 211 t.Fatalf("mode: %v", fi1.Mode()) 212 } 213 214 fi2, err := os.Lstat(filepath.Join(dir1, "baz")) 215 if err != nil { 216 t.Fatalf("err: %v", err) 217 } 218 if fi2.Mode() != linkInfo.Mode() { 219 t.Fatalf("mode: %v", fi2.Mode()) 220 } 221 } 222 223 // TestPrevAlloc_StreamAllocDir_Error asserts that errors encountered while 224 // streaming a tar cause the migration to be cancelled and no files are written 225 // (migrations are atomic). 226 func TestPrevAlloc_StreamAllocDir_Error(t *testing.T) { 227 t.Parallel() 228 dest, err := ioutil.TempDir("", "nomadtest-") 229 if err != nil { 230 t.Fatalf("err: %v", err) 231 } 232 defer os.RemoveAll(dest) 233 234 // This test only unit tests streamAllocDir so we only need a partially 235 // complete remotePrevAlloc 236 prevAlloc := &remotePrevAlloc{ 237 logger: testLogger(), 238 allocID: "123", 239 prevAllocID: "abc", 240 migrate: true, 241 } 242 243 tarBuf := bytes.NewBuffer(nil) 244 tw := tar.NewWriter(tarBuf) 245 fooHdr := tar.Header{ 246 Name: "foo.txt", 247 Mode: 0666, 248 Size: 1, 249 ModTime: time.Now(), 250 Typeflag: tar.TypeReg, 251 } 252 err = tw.WriteHeader(&fooHdr) 253 if err != nil { 254 t.Fatalf("error writing file header: %v", err) 255 } 256 if _, err := tw.Write([]byte{'a'}); err != nil { 257 t.Fatalf("error writing file: %v", err) 258 } 259 260 // Now write the error file 261 contents := []byte("SENTINEL ERROR") 262 err = tw.WriteHeader(&tar.Header{ 263 Name: allocdir.SnapshotErrorFilename(prevAlloc.prevAllocID), 264 Mode: 0666, 265 Size: int64(len(contents)), 266 AccessTime: allocdir.SnapshotErrorTime, 267 ChangeTime: allocdir.SnapshotErrorTime, 268 ModTime: allocdir.SnapshotErrorTime, 269 Typeflag: tar.TypeReg, 270 }) 271 if err != nil { 272 t.Fatalf("error writing sentinel file header: %v", err) 273 } 274 if _, err := tw.Write(contents); err != nil { 275 t.Fatalf("error writing sentinel file: %v", err) 276 } 277 278 // Assert streamAllocDir fails 279 err = prevAlloc.streamAllocDir(context.Background(), ioutil.NopCloser(tarBuf), dest) 280 if err == nil { 281 t.Fatalf("expected an error from streamAllocDir") 282 } 283 if !strings.HasSuffix(err.Error(), string(contents)) { 284 t.Fatalf("expected error to end with %q but found: %v", string(contents), err) 285 } 286 287 // streamAllocDir leaves cleanup to the caller on error, so assert 288 // "foo.txt" was written 289 fi, err := os.Stat(filepath.Join(dest, "foo.txt")) 290 if err != nil { 291 t.Fatalf("error reading foo.txt: %v", err) 292 } 293 if fi.Size() != fooHdr.Size { 294 t.Fatalf("expected foo.txt to be size 1 but found %d", fi.Size()) 295 } 296 }