github.com/zhouyu0/docker-note@v0.0.0-20190722021225-b8d3825084db/pkg/archive/changes_test.go (about) 1 package archive // import "github.com/docker/docker/pkg/archive" 2 3 import ( 4 "io/ioutil" 5 "os" 6 "os/exec" 7 "path" 8 "runtime" 9 "sort" 10 "testing" 11 "time" 12 13 "github.com/docker/docker/pkg/system" 14 "gotest.tools/assert" 15 "gotest.tools/skip" 16 ) 17 18 func max(x, y int) int { 19 if x >= y { 20 return x 21 } 22 return y 23 } 24 25 func copyDir(src, dst string) error { 26 return exec.Command("cp", "-a", src, dst).Run() 27 } 28 29 type FileType uint32 30 31 const ( 32 Regular FileType = iota 33 Dir 34 Symlink 35 ) 36 37 type FileData struct { 38 filetype FileType 39 path string 40 contents string 41 permissions os.FileMode 42 } 43 44 func createSampleDir(t *testing.T, root string) { 45 files := []FileData{ 46 {filetype: Regular, path: "file1", contents: "file1\n", permissions: 0600}, 47 {filetype: Regular, path: "file2", contents: "file2\n", permissions: 0666}, 48 {filetype: Regular, path: "file3", contents: "file3\n", permissions: 0404}, 49 {filetype: Regular, path: "file4", contents: "file4\n", permissions: 0600}, 50 {filetype: Regular, path: "file5", contents: "file5\n", permissions: 0600}, 51 {filetype: Regular, path: "file6", contents: "file6\n", permissions: 0600}, 52 {filetype: Regular, path: "file7", contents: "file7\n", permissions: 0600}, 53 {filetype: Dir, path: "dir1", contents: "", permissions: 0740}, 54 {filetype: Regular, path: "dir1/file1-1", contents: "file1-1\n", permissions: 01444}, 55 {filetype: Regular, path: "dir1/file1-2", contents: "file1-2\n", permissions: 0666}, 56 {filetype: Dir, path: "dir2", contents: "", permissions: 0700}, 57 {filetype: Regular, path: "dir2/file2-1", contents: "file2-1\n", permissions: 0666}, 58 {filetype: Regular, path: "dir2/file2-2", contents: "file2-2\n", permissions: 0666}, 59 {filetype: Dir, path: "dir3", contents: "", permissions: 0700}, 60 {filetype: Regular, path: "dir3/file3-1", contents: "file3-1\n", permissions: 0666}, 61 {filetype: Regular, path: "dir3/file3-2", contents: "file3-2\n", permissions: 0666}, 62 {filetype: Dir, path: "dir4", contents: "", permissions: 0700}, 63 {filetype: Regular, path: "dir4/file3-1", contents: "file4-1\n", permissions: 0666}, 64 {filetype: Regular, path: "dir4/file3-2", contents: "file4-2\n", permissions: 0666}, 65 {filetype: Symlink, path: "symlink1", contents: "target1", permissions: 0666}, 66 {filetype: Symlink, path: "symlink2", contents: "target2", permissions: 0666}, 67 {filetype: Symlink, path: "symlink3", contents: root + "/file1", permissions: 0666}, 68 {filetype: Symlink, path: "symlink4", contents: root + "/symlink3", permissions: 0666}, 69 {filetype: Symlink, path: "dirSymlink", contents: root + "/dir1", permissions: 0740}, 70 } 71 provisionSampleDir(t, root, files) 72 } 73 74 func provisionSampleDir(t *testing.T, root string, files []FileData) { 75 now := time.Now() 76 for _, info := range files { 77 p := path.Join(root, info.path) 78 if info.filetype == Dir { 79 err := os.MkdirAll(p, info.permissions) 80 assert.NilError(t, err) 81 } else if info.filetype == Regular { 82 err := ioutil.WriteFile(p, []byte(info.contents), info.permissions) 83 assert.NilError(t, err) 84 } else if info.filetype == Symlink { 85 err := os.Symlink(info.contents, p) 86 assert.NilError(t, err) 87 } 88 89 if info.filetype != Symlink { 90 // Set a consistent ctime, atime for all files and dirs 91 err := system.Chtimes(p, now, now) 92 assert.NilError(t, err) 93 } 94 } 95 } 96 97 func TestChangeString(t *testing.T) { 98 modifyChange := Change{"change", ChangeModify} 99 toString := modifyChange.String() 100 if toString != "C change" { 101 t.Fatalf("String() of a change with ChangeModify Kind should have been %s but was %s", "C change", toString) 102 } 103 addChange := Change{"change", ChangeAdd} 104 toString = addChange.String() 105 if toString != "A change" { 106 t.Fatalf("String() of a change with ChangeAdd Kind should have been %s but was %s", "A change", toString) 107 } 108 deleteChange := Change{"change", ChangeDelete} 109 toString = deleteChange.String() 110 if toString != "D change" { 111 t.Fatalf("String() of a change with ChangeDelete Kind should have been %s but was %s", "D change", toString) 112 } 113 } 114 115 func TestChangesWithNoChanges(t *testing.T) { 116 // TODO Windows. There may be a way of running this, but turning off for now 117 // as createSampleDir uses symlinks. 118 if runtime.GOOS == "windows" { 119 t.Skip("symlinks on Windows") 120 } 121 rwLayer, err := ioutil.TempDir("", "docker-changes-test") 122 assert.NilError(t, err) 123 defer os.RemoveAll(rwLayer) 124 layer, err := ioutil.TempDir("", "docker-changes-test-layer") 125 assert.NilError(t, err) 126 defer os.RemoveAll(layer) 127 createSampleDir(t, layer) 128 changes, err := Changes([]string{layer}, rwLayer) 129 assert.NilError(t, err) 130 if len(changes) != 0 { 131 t.Fatalf("Changes with no difference should have detect no changes, but detected %d", len(changes)) 132 } 133 } 134 135 func TestChangesWithChanges(t *testing.T) { 136 // TODO Windows. There may be a way of running this, but turning off for now 137 // as createSampleDir uses symlinks. 138 if runtime.GOOS == "windows" { 139 t.Skip("symlinks on Windows") 140 } 141 // Mock the readonly layer 142 layer, err := ioutil.TempDir("", "docker-changes-test-layer") 143 assert.NilError(t, err) 144 defer os.RemoveAll(layer) 145 createSampleDir(t, layer) 146 os.MkdirAll(path.Join(layer, "dir1/subfolder"), 0740) 147 148 // Mock the RW layer 149 rwLayer, err := ioutil.TempDir("", "docker-changes-test") 150 assert.NilError(t, err) 151 defer os.RemoveAll(rwLayer) 152 153 // Create a folder in RW layer 154 dir1 := path.Join(rwLayer, "dir1") 155 os.MkdirAll(dir1, 0740) 156 deletedFile := path.Join(dir1, ".wh.file1-2") 157 ioutil.WriteFile(deletedFile, []byte{}, 0600) 158 modifiedFile := path.Join(dir1, "file1-1") 159 ioutil.WriteFile(modifiedFile, []byte{0x00}, 01444) 160 // Let's add a subfolder for a newFile 161 subfolder := path.Join(dir1, "subfolder") 162 os.MkdirAll(subfolder, 0740) 163 newFile := path.Join(subfolder, "newFile") 164 ioutil.WriteFile(newFile, []byte{}, 0740) 165 166 changes, err := Changes([]string{layer}, rwLayer) 167 assert.NilError(t, err) 168 169 expectedChanges := []Change{ 170 {"/dir1", ChangeModify}, 171 {"/dir1/file1-1", ChangeModify}, 172 {"/dir1/file1-2", ChangeDelete}, 173 {"/dir1/subfolder", ChangeModify}, 174 {"/dir1/subfolder/newFile", ChangeAdd}, 175 } 176 checkChanges(expectedChanges, changes, t) 177 } 178 179 // See https://github.com/docker/docker/pull/13590 180 func TestChangesWithChangesGH13590(t *testing.T) { 181 // TODO Windows. There may be a way of running this, but turning off for now 182 // as createSampleDir uses symlinks. 183 if runtime.GOOS == "windows" { 184 t.Skip("symlinks on Windows") 185 } 186 baseLayer, err := ioutil.TempDir("", "docker-changes-test.") 187 assert.NilError(t, err) 188 defer os.RemoveAll(baseLayer) 189 190 dir3 := path.Join(baseLayer, "dir1/dir2/dir3") 191 os.MkdirAll(dir3, 07400) 192 193 file := path.Join(dir3, "file.txt") 194 ioutil.WriteFile(file, []byte("hello"), 0666) 195 196 layer, err := ioutil.TempDir("", "docker-changes-test2.") 197 assert.NilError(t, err) 198 defer os.RemoveAll(layer) 199 200 // Test creating a new file 201 if err := copyDir(baseLayer+"/dir1", layer+"/"); err != nil { 202 t.Fatalf("Cmd failed: %q", err) 203 } 204 205 os.Remove(path.Join(layer, "dir1/dir2/dir3/file.txt")) 206 file = path.Join(layer, "dir1/dir2/dir3/file1.txt") 207 ioutil.WriteFile(file, []byte("bye"), 0666) 208 209 changes, err := Changes([]string{baseLayer}, layer) 210 assert.NilError(t, err) 211 212 expectedChanges := []Change{ 213 {"/dir1/dir2/dir3", ChangeModify}, 214 {"/dir1/dir2/dir3/file1.txt", ChangeAdd}, 215 } 216 checkChanges(expectedChanges, changes, t) 217 218 // Now test changing a file 219 layer, err = ioutil.TempDir("", "docker-changes-test3.") 220 assert.NilError(t, err) 221 defer os.RemoveAll(layer) 222 223 if err := copyDir(baseLayer+"/dir1", layer+"/"); err != nil { 224 t.Fatalf("Cmd failed: %q", err) 225 } 226 227 file = path.Join(layer, "dir1/dir2/dir3/file.txt") 228 ioutil.WriteFile(file, []byte("bye"), 0666) 229 230 changes, err = Changes([]string{baseLayer}, layer) 231 assert.NilError(t, err) 232 233 expectedChanges = []Change{ 234 {"/dir1/dir2/dir3/file.txt", ChangeModify}, 235 } 236 checkChanges(expectedChanges, changes, t) 237 } 238 239 // Create a directory, copy it, make sure we report no changes between the two 240 func TestChangesDirsEmpty(t *testing.T) { 241 // TODO Windows. There may be a way of running this, but turning off for now 242 // as createSampleDir uses symlinks. 243 if runtime.GOOS == "windows" { 244 t.Skip("symlinks on Windows") 245 } 246 src, err := ioutil.TempDir("", "docker-changes-test") 247 assert.NilError(t, err) 248 defer os.RemoveAll(src) 249 createSampleDir(t, src) 250 dst := src + "-copy" 251 err = copyDir(src, dst) 252 assert.NilError(t, err) 253 defer os.RemoveAll(dst) 254 changes, err := ChangesDirs(dst, src) 255 assert.NilError(t, err) 256 257 if len(changes) != 0 { 258 t.Fatalf("Reported changes for identical dirs: %v", changes) 259 } 260 os.RemoveAll(src) 261 os.RemoveAll(dst) 262 } 263 264 func mutateSampleDir(t *testing.T, root string) { 265 // Remove a regular file 266 err := os.RemoveAll(path.Join(root, "file1")) 267 assert.NilError(t, err) 268 269 // Remove a directory 270 err = os.RemoveAll(path.Join(root, "dir1")) 271 assert.NilError(t, err) 272 273 // Remove a symlink 274 err = os.RemoveAll(path.Join(root, "symlink1")) 275 assert.NilError(t, err) 276 277 // Rewrite a file 278 err = ioutil.WriteFile(path.Join(root, "file2"), []byte("fileNN\n"), 0777) 279 assert.NilError(t, err) 280 281 // Replace a file 282 err = os.RemoveAll(path.Join(root, "file3")) 283 assert.NilError(t, err) 284 err = ioutil.WriteFile(path.Join(root, "file3"), []byte("fileMM\n"), 0404) 285 assert.NilError(t, err) 286 287 // Touch file 288 err = system.Chtimes(path.Join(root, "file4"), time.Now().Add(time.Second), time.Now().Add(time.Second)) 289 assert.NilError(t, err) 290 291 // Replace file with dir 292 err = os.RemoveAll(path.Join(root, "file5")) 293 assert.NilError(t, err) 294 err = os.MkdirAll(path.Join(root, "file5"), 0666) 295 assert.NilError(t, err) 296 297 // Create new file 298 err = ioutil.WriteFile(path.Join(root, "filenew"), []byte("filenew\n"), 0777) 299 assert.NilError(t, err) 300 301 // Create new dir 302 err = os.MkdirAll(path.Join(root, "dirnew"), 0766) 303 assert.NilError(t, err) 304 305 // Create a new symlink 306 err = os.Symlink("targetnew", path.Join(root, "symlinknew")) 307 assert.NilError(t, err) 308 309 // Change a symlink 310 err = os.RemoveAll(path.Join(root, "symlink2")) 311 assert.NilError(t, err) 312 313 err = os.Symlink("target2change", path.Join(root, "symlink2")) 314 assert.NilError(t, err) 315 316 // Replace dir with file 317 err = os.RemoveAll(path.Join(root, "dir2")) 318 assert.NilError(t, err) 319 err = ioutil.WriteFile(path.Join(root, "dir2"), []byte("dir2\n"), 0777) 320 assert.NilError(t, err) 321 322 // Touch dir 323 err = system.Chtimes(path.Join(root, "dir3"), time.Now().Add(time.Second), time.Now().Add(time.Second)) 324 assert.NilError(t, err) 325 } 326 327 func TestChangesDirsMutated(t *testing.T) { 328 // TODO Windows. There may be a way of running this, but turning off for now 329 // as createSampleDir uses symlinks. 330 if runtime.GOOS == "windows" { 331 t.Skip("symlinks on Windows") 332 } 333 src, err := ioutil.TempDir("", "docker-changes-test") 334 assert.NilError(t, err) 335 createSampleDir(t, src) 336 dst := src + "-copy" 337 err = copyDir(src, dst) 338 assert.NilError(t, err) 339 defer os.RemoveAll(src) 340 defer os.RemoveAll(dst) 341 342 mutateSampleDir(t, dst) 343 344 changes, err := ChangesDirs(dst, src) 345 assert.NilError(t, err) 346 347 sort.Sort(changesByPath(changes)) 348 349 expectedChanges := []Change{ 350 {"/dir1", ChangeDelete}, 351 {"/dir2", ChangeModify}, 352 {"/dirnew", ChangeAdd}, 353 {"/file1", ChangeDelete}, 354 {"/file2", ChangeModify}, 355 {"/file3", ChangeModify}, 356 {"/file4", ChangeModify}, 357 {"/file5", ChangeModify}, 358 {"/filenew", ChangeAdd}, 359 {"/symlink1", ChangeDelete}, 360 {"/symlink2", ChangeModify}, 361 {"/symlinknew", ChangeAdd}, 362 } 363 364 for i := 0; i < max(len(changes), len(expectedChanges)); i++ { 365 if i >= len(expectedChanges) { 366 t.Fatalf("unexpected change %s\n", changes[i].String()) 367 } 368 if i >= len(changes) { 369 t.Fatalf("no change for expected change %s\n", expectedChanges[i].String()) 370 } 371 if changes[i].Path == expectedChanges[i].Path { 372 if changes[i] != expectedChanges[i] { 373 t.Fatalf("Wrong change for %s, expected %s, got %s\n", changes[i].Path, changes[i].String(), expectedChanges[i].String()) 374 } 375 } else if changes[i].Path < expectedChanges[i].Path { 376 t.Fatalf("unexpected change %s\n", changes[i].String()) 377 } else { 378 t.Fatalf("no change for expected change %s != %s\n", expectedChanges[i].String(), changes[i].String()) 379 } 380 } 381 } 382 383 func TestApplyLayer(t *testing.T) { 384 // TODO Windows. There may be a way of running this, but turning off for now 385 // as createSampleDir uses symlinks. 386 if runtime.GOOS == "windows" { 387 t.Skip("symlinks on Windows") 388 } 389 src, err := ioutil.TempDir("", "docker-changes-test") 390 assert.NilError(t, err) 391 createSampleDir(t, src) 392 defer os.RemoveAll(src) 393 dst := src + "-copy" 394 err = copyDir(src, dst) 395 assert.NilError(t, err) 396 mutateSampleDir(t, dst) 397 defer os.RemoveAll(dst) 398 399 changes, err := ChangesDirs(dst, src) 400 assert.NilError(t, err) 401 402 layer, err := ExportChanges(dst, changes, nil, nil) 403 assert.NilError(t, err) 404 405 layerCopy, err := NewTempArchive(layer, "") 406 assert.NilError(t, err) 407 408 _, err = ApplyLayer(src, layerCopy) 409 assert.NilError(t, err) 410 411 changes2, err := ChangesDirs(src, dst) 412 assert.NilError(t, err) 413 414 if len(changes2) != 0 { 415 t.Fatalf("Unexpected differences after reapplying mutation: %v", changes2) 416 } 417 } 418 419 func TestChangesSizeWithHardlinks(t *testing.T) { 420 // TODO Windows. There may be a way of running this, but turning off for now 421 // as createSampleDir uses symlinks. 422 if runtime.GOOS == "windows" { 423 t.Skip("hardlinks on Windows") 424 } 425 srcDir, err := ioutil.TempDir("", "docker-test-srcDir") 426 assert.NilError(t, err) 427 defer os.RemoveAll(srcDir) 428 429 destDir, err := ioutil.TempDir("", "docker-test-destDir") 430 assert.NilError(t, err) 431 defer os.RemoveAll(destDir) 432 433 creationSize, err := prepareUntarSourceDirectory(100, destDir, true) 434 assert.NilError(t, err) 435 436 changes, err := ChangesDirs(destDir, srcDir) 437 assert.NilError(t, err) 438 439 got := ChangesSize(destDir, changes) 440 if got != int64(creationSize) { 441 t.Errorf("Expected %d bytes of changes, got %d", creationSize, got) 442 } 443 } 444 445 func TestChangesSizeWithNoChanges(t *testing.T) { 446 size := ChangesSize("/tmp", nil) 447 if size != 0 { 448 t.Fatalf("ChangesSizes with no changes should be 0, was %d", size) 449 } 450 } 451 452 func TestChangesSizeWithOnlyDeleteChanges(t *testing.T) { 453 changes := []Change{ 454 {Path: "deletedPath", Kind: ChangeDelete}, 455 } 456 size := ChangesSize("/tmp", changes) 457 if size != 0 { 458 t.Fatalf("ChangesSizes with only delete changes should be 0, was %d", size) 459 } 460 } 461 462 func TestChangesSize(t *testing.T) { 463 parentPath, err := ioutil.TempDir("", "docker-changes-test") 464 assert.NilError(t, err) 465 defer os.RemoveAll(parentPath) 466 addition := path.Join(parentPath, "addition") 467 err = ioutil.WriteFile(addition, []byte{0x01, 0x01, 0x01}, 0744) 468 assert.NilError(t, err) 469 modification := path.Join(parentPath, "modification") 470 err = ioutil.WriteFile(modification, []byte{0x01, 0x01, 0x01}, 0744) 471 assert.NilError(t, err) 472 473 changes := []Change{ 474 {Path: "addition", Kind: ChangeAdd}, 475 {Path: "modification", Kind: ChangeModify}, 476 } 477 size := ChangesSize(parentPath, changes) 478 if size != 6 { 479 t.Fatalf("Expected 6 bytes of changes, got %d", size) 480 } 481 } 482 483 func checkChanges(expectedChanges, changes []Change, t *testing.T) { 484 skip.If(t, os.Getuid() != 0, "skipping test that requires root") 485 sort.Sort(changesByPath(expectedChanges)) 486 sort.Sort(changesByPath(changes)) 487 for i := 0; i < max(len(changes), len(expectedChanges)); i++ { 488 if i >= len(expectedChanges) { 489 t.Fatalf("unexpected change %s\n", changes[i].String()) 490 } 491 if i >= len(changes) { 492 t.Fatalf("no change for expected change %s\n", expectedChanges[i].String()) 493 } 494 if changes[i].Path == expectedChanges[i].Path { 495 if changes[i] != expectedChanges[i] { 496 t.Fatalf("Wrong change for %s, expected %s, got %s\n", changes[i].Path, changes[i].String(), expectedChanges[i].String()) 497 } 498 } else if changes[i].Path < expectedChanges[i].Path { 499 t.Fatalf("unexpected change %s\n", changes[i].String()) 500 } else { 501 t.Fatalf("no change for expected change %s != %s\n", expectedChanges[i].String(), changes[i].String()) 502 } 503 } 504 }