github.com/demonoid81/containerd@v1.3.4/snapshots/storage/metastore_test.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package storage 18 19 import ( 20 "context" 21 "fmt" 22 "io/ioutil" 23 "os" 24 "testing" 25 "time" 26 27 "github.com/containerd/containerd/errdefs" 28 "github.com/containerd/containerd/snapshots" 29 "github.com/google/go-cmp/cmp" 30 "github.com/pkg/errors" 31 "gotest.tools/assert" 32 is "gotest.tools/assert/cmp" 33 ) 34 35 type testFunc func(context.Context, *testing.T, *MetaStore) 36 37 type metaFactory func(string) (*MetaStore, error) 38 39 type populateFunc func(context.Context, *MetaStore) error 40 41 // MetaStoreSuite runs a test suite on the metastore given a factory function. 42 func MetaStoreSuite(t *testing.T, name string, meta func(root string) (*MetaStore, error)) { 43 t.Run("GetInfo", makeTest(t, name, meta, inReadTransaction(testGetInfo, basePopulate))) 44 t.Run("GetInfoNotExist", makeTest(t, name, meta, inReadTransaction(testGetInfoNotExist, basePopulate))) 45 t.Run("GetInfoEmptyDB", makeTest(t, name, meta, inReadTransaction(testGetInfoNotExist, nil))) 46 t.Run("Walk", makeTest(t, name, meta, inReadTransaction(testWalk, basePopulate))) 47 t.Run("GetSnapshot", makeTest(t, name, meta, testGetSnapshot)) 48 t.Run("GetSnapshotNotExist", makeTest(t, name, meta, inReadTransaction(testGetSnapshotNotExist, basePopulate))) 49 t.Run("GetSnapshotCommitted", makeTest(t, name, meta, inReadTransaction(testGetSnapshotCommitted, basePopulate))) 50 t.Run("GetSnapshotEmptyDB", makeTest(t, name, meta, inReadTransaction(testGetSnapshotNotExist, basePopulate))) 51 t.Run("CreateActive", makeTest(t, name, meta, inWriteTransaction(testCreateActive))) 52 t.Run("CreateActiveNotExist", makeTest(t, name, meta, inWriteTransaction(testCreateActiveNotExist))) 53 t.Run("CreateActiveExist", makeTest(t, name, meta, inWriteTransaction(testCreateActiveExist))) 54 t.Run("CreateActiveFromActive", makeTest(t, name, meta, inWriteTransaction(testCreateActiveFromActive))) 55 t.Run("Commit", makeTest(t, name, meta, inWriteTransaction(testCommit))) 56 t.Run("CommitNotExist", makeTest(t, name, meta, inWriteTransaction(testCommitExist))) 57 t.Run("CommitExist", makeTest(t, name, meta, inWriteTransaction(testCommitExist))) 58 t.Run("CommitCommitted", makeTest(t, name, meta, inWriteTransaction(testCommitCommitted))) 59 t.Run("CommitViewFails", makeTest(t, name, meta, inWriteTransaction(testCommitViewFails))) 60 t.Run("Remove", makeTest(t, name, meta, inWriteTransaction(testRemove))) 61 t.Run("RemoveNotExist", makeTest(t, name, meta, inWriteTransaction(testRemoveNotExist))) 62 t.Run("RemoveWithChildren", makeTest(t, name, meta, inWriteTransaction(testRemoveWithChildren))) 63 t.Run("ParentIDs", makeTest(t, name, meta, inWriteTransaction(testParents))) 64 } 65 66 // makeTest creates a testsuite with a writable transaction 67 func makeTest(t *testing.T, name string, metaFn metaFactory, fn testFunc) func(t *testing.T) { 68 return func(t *testing.T) { 69 ctx := context.Background() 70 tmpDir, err := ioutil.TempDir("", "metastore-test-"+name+"-") 71 if err != nil { 72 t.Fatal(err) 73 } 74 defer os.RemoveAll(tmpDir) 75 76 ms, err := metaFn(tmpDir) 77 if err != nil { 78 t.Fatal(err) 79 } 80 81 fn(ctx, t, ms) 82 } 83 } 84 85 func inReadTransaction(fn testFunc, pf populateFunc) testFunc { 86 return func(ctx context.Context, t *testing.T, ms *MetaStore) { 87 if pf != nil { 88 ctx, tx, err := ms.TransactionContext(ctx, true) 89 if err != nil { 90 t.Fatal(err) 91 } 92 if err := pf(ctx, ms); err != nil { 93 if rerr := tx.Rollback(); rerr != nil { 94 t.Logf("Rollback failed: %+v", rerr) 95 } 96 t.Fatalf("Populate failed: %+v", err) 97 } 98 if err := tx.Commit(); err != nil { 99 t.Fatalf("Populate commit failed: %+v", err) 100 } 101 } 102 103 ctx, tx, err := ms.TransactionContext(ctx, false) 104 if err != nil { 105 t.Fatalf("Failed start transaction: %+v", err) 106 } 107 defer func() { 108 if err := tx.Rollback(); err != nil { 109 t.Logf("Rollback failed: %+v", err) 110 if !t.Failed() { 111 t.FailNow() 112 } 113 } 114 }() 115 116 fn(ctx, t, ms) 117 } 118 } 119 120 func inWriteTransaction(fn testFunc) testFunc { 121 return func(ctx context.Context, t *testing.T, ms *MetaStore) { 122 ctx, tx, err := ms.TransactionContext(ctx, true) 123 if err != nil { 124 t.Fatalf("Failed to start transaction: %+v", err) 125 } 126 defer func() { 127 if t.Failed() { 128 if err := tx.Rollback(); err != nil { 129 t.Logf("Rollback failed: %+v", err) 130 } 131 } else { 132 if err := tx.Commit(); err != nil { 133 t.Fatalf("Commit failed: %+v", err) 134 } 135 } 136 }() 137 fn(ctx, t, ms) 138 } 139 } 140 141 // basePopulate creates 7 snapshots 142 // - "committed-1": committed without parent 143 // - "committed-2": committed with parent "committed-1" 144 // - "active-1": active without parent 145 // - "active-2": active with parent "committed-1" 146 // - "active-3": active with parent "committed-2" 147 // - "active-4": readonly active without parent" 148 // - "active-5": readonly active with parent "committed-2" 149 func basePopulate(ctx context.Context, ms *MetaStore) error { 150 if _, err := CreateSnapshot(ctx, snapshots.KindActive, "committed-tmp-1", ""); err != nil { 151 return errors.Wrap(err, "failed to create active") 152 } 153 if _, err := CommitActive(ctx, "committed-tmp-1", "committed-1", snapshots.Usage{Size: 1}); err != nil { 154 return errors.Wrap(err, "failed to create active") 155 } 156 if _, err := CreateSnapshot(ctx, snapshots.KindActive, "committed-tmp-2", "committed-1"); err != nil { 157 return errors.Wrap(err, "failed to create active") 158 } 159 if _, err := CommitActive(ctx, "committed-tmp-2", "committed-2", snapshots.Usage{Size: 2}); err != nil { 160 return errors.Wrap(err, "failed to create active") 161 } 162 if _, err := CreateSnapshot(ctx, snapshots.KindActive, "active-1", ""); err != nil { 163 return errors.Wrap(err, "failed to create active") 164 } 165 if _, err := CreateSnapshot(ctx, snapshots.KindActive, "active-2", "committed-1"); err != nil { 166 return errors.Wrap(err, "failed to create active") 167 } 168 if _, err := CreateSnapshot(ctx, snapshots.KindActive, "active-3", "committed-2"); err != nil { 169 return errors.Wrap(err, "failed to create active") 170 } 171 if _, err := CreateSnapshot(ctx, snapshots.KindView, "view-1", ""); err != nil { 172 return errors.Wrap(err, "failed to create active") 173 } 174 if _, err := CreateSnapshot(ctx, snapshots.KindView, "view-2", "committed-2"); err != nil { 175 return errors.Wrap(err, "failed to create active") 176 } 177 return nil 178 } 179 180 var baseInfo = map[string]snapshots.Info{ 181 "committed-1": { 182 Name: "committed-1", 183 Parent: "", 184 Kind: snapshots.KindCommitted, 185 }, 186 "committed-2": { 187 Name: "committed-2", 188 Parent: "committed-1", 189 Kind: snapshots.KindCommitted, 190 }, 191 "active-1": { 192 Name: "active-1", 193 Parent: "", 194 Kind: snapshots.KindActive, 195 }, 196 "active-2": { 197 Name: "active-2", 198 Parent: "committed-1", 199 Kind: snapshots.KindActive, 200 }, 201 "active-3": { 202 Name: "active-3", 203 Parent: "committed-2", 204 Kind: snapshots.KindActive, 205 }, 206 "view-1": { 207 Name: "view-1", 208 Parent: "", 209 Kind: snapshots.KindView, 210 }, 211 "view-2": { 212 Name: "view-2", 213 Parent: "committed-2", 214 Kind: snapshots.KindView, 215 }, 216 } 217 218 func assertNotExist(t *testing.T, err error) { 219 t.Helper() 220 assert.Assert(t, errdefs.IsNotFound(err), "got %+v", err) 221 } 222 223 func assertNotActive(t *testing.T, err error) { 224 t.Helper() 225 assert.Assert(t, errdefs.IsFailedPrecondition(err), "got %+v", err) 226 } 227 228 func assertNotCommitted(t *testing.T, err error) { 229 t.Helper() 230 assert.Assert(t, errdefs.IsInvalidArgument(err), "got %+v", err) 231 } 232 233 func assertExist(t *testing.T, err error) { 234 t.Helper() 235 assert.Assert(t, errdefs.IsAlreadyExists(err), "got %+v", err) 236 } 237 238 func testGetInfo(ctx context.Context, t *testing.T, _ *MetaStore) { 239 for key, expected := range baseInfo { 240 _, info, _, err := GetInfo(ctx, key) 241 assert.NilError(t, err, "on key %v", key) 242 assert.Check(t, is.DeepEqual(expected, info, cmpSnapshotInfo), "on key %v", key) 243 } 244 } 245 246 // compare snapshot.Info Updated and Created fields by checking they are 247 // within a threshold of time.Now() 248 var cmpSnapshotInfo = cmp.FilterPath( 249 func(path cmp.Path) bool { 250 field := path.Last().String() 251 return field == ".Created" || field == ".Updated" 252 }, 253 cmp.Comparer(func(expected, actual time.Time) bool { 254 // cmp.Options must be symmetric, so swap the args 255 if actual.IsZero() { 256 actual, expected = expected, actual 257 } 258 if !expected.IsZero() { 259 return false 260 } 261 // actual value should be within a few seconds of now 262 now := time.Now() 263 delta := now.Sub(actual) 264 threshold := 10 * time.Second 265 return delta > -threshold && delta < threshold 266 })) 267 268 func testGetInfoNotExist(ctx context.Context, t *testing.T, _ *MetaStore) { 269 _, _, _, err := GetInfo(ctx, "active-not-exist") 270 assertNotExist(t, err) 271 } 272 273 func testWalk(ctx context.Context, t *testing.T, _ *MetaStore) { 274 found := map[string]snapshots.Info{} 275 err := WalkInfo(ctx, func(ctx context.Context, info snapshots.Info) error { 276 if _, ok := found[info.Name]; ok { 277 return errors.Errorf("entry already encountered") 278 } 279 found[info.Name] = info 280 return nil 281 }) 282 assert.NilError(t, err) 283 assert.Assert(t, is.DeepEqual(baseInfo, found, cmpSnapshotInfo)) 284 } 285 286 func testGetSnapshot(ctx context.Context, t *testing.T, ms *MetaStore) { 287 snapshotMap := map[string]Snapshot{} 288 populate := func(ctx context.Context, ms *MetaStore) error { 289 if _, err := CreateSnapshot(ctx, snapshots.KindActive, "committed-tmp-1", ""); err != nil { 290 return errors.Wrap(err, "failed to create active") 291 } 292 if _, err := CommitActive(ctx, "committed-tmp-1", "committed-1", snapshots.Usage{}); err != nil { 293 return errors.Wrap(err, "failed to create active") 294 } 295 296 for _, opts := range []struct { 297 Kind snapshots.Kind 298 Name string 299 Parent string 300 }{ 301 { 302 Name: "active-1", 303 Kind: snapshots.KindActive, 304 }, 305 { 306 Name: "active-2", 307 Parent: "committed-1", 308 Kind: snapshots.KindActive, 309 }, 310 { 311 Name: "view-1", 312 Kind: snapshots.KindView, 313 }, 314 { 315 Name: "view-2", 316 Parent: "committed-1", 317 Kind: snapshots.KindView, 318 }, 319 } { 320 active, err := CreateSnapshot(ctx, opts.Kind, opts.Name, opts.Parent) 321 if err != nil { 322 return errors.Wrap(err, "failed to create active") 323 } 324 snapshotMap[opts.Name] = active 325 } 326 return nil 327 } 328 329 test := func(ctx context.Context, t *testing.T, ms *MetaStore) { 330 for key, expected := range snapshotMap { 331 s, err := GetSnapshot(ctx, key) 332 assert.NilError(t, err, "failed to get snapshot %s", key) 333 assert.Check(t, is.DeepEqual(expected, s), "on key %s", key) 334 } 335 } 336 337 inReadTransaction(test, populate)(ctx, t, ms) 338 } 339 340 func testGetSnapshotCommitted(ctx context.Context, t *testing.T, ms *MetaStore) { 341 _, err := GetSnapshot(ctx, "committed-1") 342 assertNotActive(t, err) 343 } 344 345 func testGetSnapshotNotExist(ctx context.Context, t *testing.T, ms *MetaStore) { 346 _, err := GetSnapshot(ctx, "active-not-exist") 347 assertNotExist(t, err) 348 } 349 350 func testCreateActive(ctx context.Context, t *testing.T, ms *MetaStore) { 351 a1, err := CreateSnapshot(ctx, snapshots.KindActive, "active-1", "") 352 if err != nil { 353 t.Fatal(err) 354 } 355 if a1.Kind != snapshots.KindActive { 356 t.Fatal("Expected writable active") 357 } 358 359 a2, err := CreateSnapshot(ctx, snapshots.KindView, "view-1", "") 360 if err != nil { 361 t.Fatal(err) 362 } 363 if a2.ID == a1.ID { 364 t.Fatal("Returned active identifiers must be unique") 365 } 366 if a2.Kind != snapshots.KindView { 367 t.Fatal("Expected a view") 368 } 369 370 commitID, err := CommitActive(ctx, "active-1", "committed-1", snapshots.Usage{}) 371 if err != nil { 372 t.Fatal(err) 373 } 374 if commitID != a1.ID { 375 t.Fatal("Snapshot identifier must not change on commit") 376 } 377 378 a3, err := CreateSnapshot(ctx, snapshots.KindActive, "active-3", "committed-1") 379 if err != nil { 380 t.Fatal(err) 381 } 382 if a3.ID == a1.ID { 383 t.Fatal("Returned active identifiers must be unique") 384 } 385 if len(a3.ParentIDs) != 1 { 386 t.Fatalf("Expected 1 parent, got %d", len(a3.ParentIDs)) 387 } 388 if a3.ParentIDs[0] != commitID { 389 t.Fatal("Expected active parent to be same as commit ID") 390 } 391 if a3.Kind != snapshots.KindActive { 392 t.Fatal("Expected writable active") 393 } 394 395 a4, err := CreateSnapshot(ctx, snapshots.KindView, "view-2", "committed-1") 396 if err != nil { 397 t.Fatal(err) 398 } 399 if a4.ID == a1.ID { 400 t.Fatal("Returned active identifiers must be unique") 401 } 402 if len(a3.ParentIDs) != 1 { 403 t.Fatalf("Expected 1 parent, got %d", len(a3.ParentIDs)) 404 } 405 if a3.ParentIDs[0] != commitID { 406 t.Fatal("Expected active parent to be same as commit ID") 407 } 408 if a4.Kind != snapshots.KindView { 409 t.Fatal("Expected a view") 410 } 411 } 412 413 func testCreateActiveExist(ctx context.Context, t *testing.T, ms *MetaStore) { 414 if err := basePopulate(ctx, ms); err != nil { 415 t.Fatalf("Populate failed: %+v", err) 416 } 417 _, err := CreateSnapshot(ctx, snapshots.KindActive, "active-1", "") 418 assertExist(t, err) 419 _, err = CreateSnapshot(ctx, snapshots.KindActive, "committed-1", "") 420 assertExist(t, err) 421 } 422 423 func testCreateActiveNotExist(ctx context.Context, t *testing.T, ms *MetaStore) { 424 _, err := CreateSnapshot(ctx, snapshots.KindActive, "active-1", "does-not-exist") 425 assertNotExist(t, err) 426 } 427 428 func testCreateActiveFromActive(ctx context.Context, t *testing.T, ms *MetaStore) { 429 if err := basePopulate(ctx, ms); err != nil { 430 t.Fatalf("Populate failed: %+v", err) 431 } 432 _, err := CreateSnapshot(ctx, snapshots.KindActive, "active-new", "active-1") 433 assertNotCommitted(t, err) 434 } 435 436 func testCommit(ctx context.Context, t *testing.T, ms *MetaStore) { 437 a1, err := CreateSnapshot(ctx, snapshots.KindActive, "active-1", "") 438 if err != nil { 439 t.Fatal(err) 440 } 441 if a1.Kind != snapshots.KindActive { 442 t.Fatal("Expected writable active") 443 } 444 445 commitID, err := CommitActive(ctx, "active-1", "committed-1", snapshots.Usage{}) 446 if err != nil { 447 t.Fatal(err) 448 } 449 if commitID != a1.ID { 450 t.Fatal("Snapshot identifier must not change on commit") 451 } 452 453 _, err = GetSnapshot(ctx, "active-1") 454 assertNotExist(t, err) 455 _, err = GetSnapshot(ctx, "committed-1") 456 assertNotActive(t, err) 457 } 458 459 func testCommitExist(ctx context.Context, t *testing.T, ms *MetaStore) { 460 if err := basePopulate(ctx, ms); err != nil { 461 t.Fatalf("Populate failed: %+v", err) 462 } 463 _, err := CommitActive(ctx, "active-1", "committed-1", snapshots.Usage{}) 464 assertExist(t, err) 465 } 466 467 func testCommitCommitted(ctx context.Context, t *testing.T, ms *MetaStore) { 468 if err := basePopulate(ctx, ms); err != nil { 469 t.Fatalf("Populate failed: %+v", err) 470 } 471 _, err := CommitActive(ctx, "committed-1", "committed-3", snapshots.Usage{}) 472 assertNotActive(t, err) 473 } 474 475 func testCommitViewFails(ctx context.Context, t *testing.T, ms *MetaStore) { 476 if err := basePopulate(ctx, ms); err != nil { 477 t.Fatalf("Populate failed: %+v", err) 478 } 479 _, err := CommitActive(ctx, "view-1", "committed-3", snapshots.Usage{}) 480 if err == nil { 481 t.Fatal("Expected error committing readonly active") 482 } 483 } 484 485 func testRemove(ctx context.Context, t *testing.T, ms *MetaStore) { 486 a1, err := CreateSnapshot(ctx, snapshots.KindActive, "active-1", "") 487 if err != nil { 488 t.Fatal(err) 489 } 490 491 commitID, err := CommitActive(ctx, "active-1", "committed-1", snapshots.Usage{}) 492 if err != nil { 493 t.Fatal(err) 494 } 495 if commitID != a1.ID { 496 t.Fatal("Snapshot identifier must not change on commit") 497 } 498 499 a2, err := CreateSnapshot(ctx, snapshots.KindView, "view-1", "committed-1") 500 if err != nil { 501 t.Fatal(err) 502 } 503 504 a3, err := CreateSnapshot(ctx, snapshots.KindView, "view-2", "committed-1") 505 if err != nil { 506 t.Fatal(err) 507 } 508 509 _, _, err = Remove(ctx, "active-1") 510 assertNotExist(t, err) 511 512 r3, k3, err := Remove(ctx, "view-2") 513 if err != nil { 514 t.Fatal(err) 515 } 516 if r3 != a3.ID { 517 t.Fatal("Expected remove ID to match create ID") 518 } 519 if k3 != snapshots.KindView { 520 t.Fatalf("Expected view kind, got %v", k3) 521 } 522 523 r2, k2, err := Remove(ctx, "view-1") 524 if err != nil { 525 t.Fatal(err) 526 } 527 if r2 != a2.ID { 528 t.Fatal("Expected remove ID to match create ID") 529 } 530 if k2 != snapshots.KindView { 531 t.Fatalf("Expected view kind, got %v", k2) 532 } 533 534 r1, k1, err := Remove(ctx, "committed-1") 535 if err != nil { 536 t.Fatal(err) 537 } 538 if r1 != commitID { 539 t.Fatal("Expected remove ID to match commit ID") 540 } 541 if k1 != snapshots.KindCommitted { 542 t.Fatalf("Expected committed kind, got %v", k1) 543 } 544 } 545 546 func testRemoveWithChildren(ctx context.Context, t *testing.T, ms *MetaStore) { 547 if err := basePopulate(ctx, ms); err != nil { 548 t.Fatalf("Populate failed: %+v", err) 549 } 550 _, _, err := Remove(ctx, "committed-1") 551 if err == nil { 552 t.Fatalf("Expected removal of snapshot with children to error") 553 } 554 _, _, err = Remove(ctx, "committed-1") 555 if err == nil { 556 t.Fatalf("Expected removal of snapshot with children to error") 557 } 558 } 559 560 func testRemoveNotExist(ctx context.Context, t *testing.T, _ *MetaStore) { 561 _, _, err := Remove(ctx, "does-not-exist") 562 assertNotExist(t, err) 563 } 564 565 func testParents(ctx context.Context, t *testing.T, ms *MetaStore) { 566 if err := basePopulate(ctx, ms); err != nil { 567 t.Fatalf("Populate failed: %+v", err) 568 } 569 570 testcases := []struct { 571 Name string 572 Parents int 573 }{ 574 {"committed-1", 0}, 575 {"committed-2", 1}, 576 {"active-1", 0}, 577 {"active-2", 1}, 578 {"active-3", 2}, 579 {"view-1", 0}, 580 {"view-2", 2}, 581 } 582 583 for _, tc := range testcases { 584 name := tc.Name 585 expectedID := "" 586 expectedParents := []string{} 587 for i := tc.Parents; i >= 0; i-- { 588 sid, info, _, err := GetInfo(ctx, name) 589 if err != nil { 590 t.Fatalf("Failed to get snapshot %s: %v", tc.Name, err) 591 } 592 var ( 593 id string 594 parents []string 595 ) 596 if info.Kind == snapshots.KindCommitted { 597 // When committed, create view and resolve from view 598 nid := fmt.Sprintf("test-%s-%d", tc.Name, i) 599 s, err := CreateSnapshot(ctx, snapshots.KindView, nid, name) 600 if err != nil { 601 t.Fatalf("Failed to get snapshot %s: %v", tc.Name, err) 602 } 603 if len(s.ParentIDs) != i+1 { 604 t.Fatalf("Unexpected number of parents for view of %s: %d, expected %d", name, len(s.ParentIDs), i+1) 605 } 606 id = s.ParentIDs[0] 607 parents = s.ParentIDs[1:] 608 } else { 609 s, err := GetSnapshot(ctx, name) 610 if err != nil { 611 t.Fatalf("Failed to get snapshot %s: %v", tc.Name, err) 612 } 613 if len(s.ParentIDs) != i { 614 t.Fatalf("Unexpected number of parents for %s: %d, expected %d", name, len(s.ParentIDs), i) 615 } 616 617 id = s.ID 618 parents = s.ParentIDs 619 } 620 if sid != id { 621 t.Fatalf("Info ID mismatched resolved snapshot ID for %s, %s vs %s", name, sid, id) 622 } 623 624 if expectedID != "" { 625 if id != expectedID { 626 t.Errorf("Unexpected ID of parent: %s, expected %s", id, expectedID) 627 } 628 } 629 630 if len(expectedParents) > 0 { 631 for j := range expectedParents { 632 if parents[j] != expectedParents[j] { 633 t.Errorf("Unexpected ID in parent array at %d: %s, expected %s", j, parents[j], expectedParents[j]) 634 } 635 } 636 } 637 638 if i > 0 { 639 name = info.Parent 640 expectedID = parents[0] 641 expectedParents = parents[1:] 642 } 643 644 } 645 } 646 }