|
1 | 1 | package defaultipam |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "fmt" |
4 | 5 | "net/netip" |
5 | 6 | "testing" |
6 | 7 |
|
@@ -387,3 +388,113 @@ func TestStaticAllocation(t *testing.T) { |
387 | 388 | netip.MustParsePrefix("192.168.3.0/24"), |
388 | 389 | }, cmpopts.EquateComparable(netip.Prefix{}))) |
389 | 390 | } |
| 391 | + |
| 392 | +// Regression test for https://github.com/moby/moby/issues/48069 |
| 393 | +func TestPoolAllocateAndRelease(t *testing.T) { |
| 394 | + type testClosures struct { |
| 395 | + alloc func(netname string) |
| 396 | + release func(netname string) |
| 397 | + } |
| 398 | + |
| 399 | + testcases := []struct { |
| 400 | + name string |
| 401 | + predefined []*ipamutils.NetworkToSplit |
| 402 | + reserved []netip.Prefix |
| 403 | + calls []func(tcs testClosures) |
| 404 | + tcs testClosures |
| 405 | + }{ |
| 406 | + { |
| 407 | + name: "allocate after reserved", |
| 408 | + predefined: []*ipamutils.NetworkToSplit{ |
| 409 | + {Base: netip.MustParsePrefix("10.0.0.0/24"), Size: 24}, |
| 410 | + {Base: netip.MustParsePrefix("10.0.1.0/24"), Size: 24}, |
| 411 | + {Base: netip.MustParsePrefix("10.1.0.0/16"), Size: 24}, |
| 412 | + }, |
| 413 | + reserved: []netip.Prefix{ |
| 414 | + netip.MustParsePrefix("10.0.0.0/16"), |
| 415 | + }, |
| 416 | + calls: []func(tcs testClosures){ |
| 417 | + func(tcs testClosures) { tcs.alloc("n1") }, |
| 418 | + func(tcs testClosures) { tcs.alloc("n2") }, |
| 419 | + }, |
| 420 | + }, |
| 421 | + { |
| 422 | + name: "reallocate first subnet", |
| 423 | + predefined: []*ipamutils.NetworkToSplit{ |
| 424 | + {Base: netip.MustParsePrefix("10.0.0.0/8"), Size: 24}, |
| 425 | + }, |
| 426 | + calls: []func(tcs testClosures){ |
| 427 | + func(tcs testClosures) { tcs.alloc("n1") }, |
| 428 | + func(tcs testClosures) { tcs.alloc("n2") }, |
| 429 | + func(tcs testClosures) { tcs.alloc("n3") }, |
| 430 | + func(tcs testClosures) { tcs.release("n1") }, |
| 431 | + func(tcs testClosures) { tcs.alloc("n4") }, |
| 432 | + func(tcs testClosures) { tcs.alloc("n5") }, |
| 433 | + }, |
| 434 | + }, |
| 435 | + { |
| 436 | + name: "reallocate after release", |
| 437 | + predefined: []*ipamutils.NetworkToSplit{ |
| 438 | + {Base: netip.MustParsePrefix("10.0.0.0/8"), Size: 24}, |
| 439 | + }, |
| 440 | + calls: []func(tcs testClosures){ |
| 441 | + func(tcs testClosures) { tcs.alloc("n1") }, |
| 442 | + func(tcs testClosures) { tcs.alloc("n2") }, |
| 443 | + func(tcs testClosures) { tcs.alloc("n3") }, |
| 444 | + func(tcs testClosures) { tcs.alloc("n4") }, |
| 445 | + func(tcs testClosures) { tcs.release("n2") }, |
| 446 | + func(tcs testClosures) { tcs.release("n3") }, |
| 447 | + func(tcs testClosures) { tcs.alloc("n5") }, |
| 448 | + func(tcs testClosures) { tcs.alloc("n6") }, |
| 449 | + func(tcs testClosures) { tcs.alloc("n7") }, |
| 450 | + }, |
| 451 | + }, |
| 452 | + } |
| 453 | + |
| 454 | + for _, tc := range testcases { |
| 455 | + t.Run(tc.name, func(t *testing.T) { |
| 456 | + as, err := newAddrSpace(tc.predefined) |
| 457 | + assert.NilError(t, err) |
| 458 | + subnetToNetname := map[netip.Prefix]string{} |
| 459 | + netnameToSubnet := map[string]netip.Prefix{} |
| 460 | + |
| 461 | + // To avoid passing as,subnetToNetname,netnameToSubnet into each of the |
| 462 | + // functions in tc.calls (cluttering the list of testcases), create closures |
| 463 | + // that use them and pass those. |
| 464 | + tcs := testClosures{ |
| 465 | + // Allocate a pool for netname, check that a subnet is returned that |
| 466 | + // isn't already allocated, and doesn't overlap with a reserved range. |
| 467 | + alloc: func(netname string) { |
| 468 | + subnet, err := as.allocatePredefinedPool(tc.reserved) |
| 469 | + assert.NilError(t, err) |
| 470 | + |
| 471 | + otherNetname, exists := subnetToNetname[subnet] |
| 472 | + assert.Assert(t, !exists, fmt.Sprintf( |
| 473 | + "subnet %s allocated to %s, reallocated for %s", subnet, otherNetname, netname)) |
| 474 | + for _, reserved := range tc.reserved { |
| 475 | + assert.Assert(t, !reserved.Overlaps(subnet), |
| 476 | + fmt.Sprintf("subnet %s allocated in reserved range %s", subnet, reserved)) |
| 477 | + } |
| 478 | + |
| 479 | + subnetToNetname[subnet] = netname |
| 480 | + netnameToSubnet[netname] = subnet |
| 481 | + }, |
| 482 | + // Release a pool for a netname - the test must ensure netname currently |
| 483 | + // has an allocated subnet. |
| 484 | + release: func(netname string) { |
| 485 | + subnet, ok := netnameToSubnet[netname] |
| 486 | + assert.Assert(t, ok) |
| 487 | + err := as.releaseSubnet(subnet, netip.Prefix{}) |
| 488 | + assert.NilError(t, err) |
| 489 | + |
| 490 | + delete(netnameToSubnet, netname) |
| 491 | + delete(subnetToNetname, subnet) |
| 492 | + }, |
| 493 | + } |
| 494 | + |
| 495 | + for _, f := range tc.calls { |
| 496 | + f(tcs) |
| 497 | + } |
| 498 | + }) |
| 499 | + } |
| 500 | +} |
0 commit comments