Signed-off-by: David Negstad <David.Negstad@microsoft.com>
| ... | ... |
@@ -949,7 +949,10 @@ func (na *cnmNetworkAllocator) allocatePools(n *api.Network) (map[netip.Prefix]s |
| 949 | 949 |
} |
| 950 | 950 |
} |
| 951 | 951 |
|
| 952 |
- if ic.Subnet == "" {
|
|
| 952 |
+ // The IPAM config contain an unspecified subnet if a network with a specific prefix size |
|
| 953 |
+ // was requested from the default pools. Therefore it's important to update the value in the |
|
| 954 |
+ // config with the actual allocated subnet if available. |
|
| 955 |
+ if alloc.Pool.String() != "" {
|
|
| 953 | 956 |
ic.Subnet = alloc.Pool.String() |
| 954 | 957 |
} |
| 955 | 958 |
|
| ... | ... |
@@ -133,7 +133,7 @@ func (aSpace *addrSpace) allocatePool(nw netip.Prefix) error {
|
| 133 | 133 |
// with existing allocations and 'reserved' prefixes. |
| 134 | 134 |
// |
| 135 | 135 |
// This method is safe for concurrent use. |
| 136 |
-func (aSpace *addrSpace) allocatePredefinedPool(reserved []netip.Prefix) (netip.Prefix, error) {
|
|
| 136 |
+func (aSpace *addrSpace) allocatePredefinedPool(reserved []netip.Prefix, prefixSize int) (netip.Prefix, error) {
|
|
| 137 | 137 |
aSpace.mu.Lock() |
| 138 | 138 |
defer aSpace.mu.Unlock() |
| 139 | 139 |
|
| ... | ... |
@@ -150,6 +150,29 @@ func (aSpace *addrSpace) allocatePredefinedPool(reserved []netip.Prefix) (netip. |
| 150 | 150 |
return subnet |
| 151 | 151 |
} |
| 152 | 152 |
|
| 153 |
+ // Filter the pools to only those that match the requested subnet size (if one is specified). |
|
| 154 |
+ var predefined []*ipamutils.NetworkToSplit |
|
| 155 |
+ if prefixSize == 0 {
|
|
| 156 |
+ predefined = aSpace.predefined |
|
| 157 |
+ } else {
|
|
| 158 |
+ for _, pdf := range aSpace.predefined {
|
|
| 159 |
+ if pdf.Base.Bits() > prefixSize || prefixSize > pdf.Base.Addr().BitLen() {
|
|
| 160 |
+ // The subnet size isn't valid for the pool |
|
| 161 |
+ continue |
|
| 162 |
+ } |
|
| 163 |
+ |
|
| 164 |
+ predefined = append(predefined, &ipamutils.NetworkToSplit{
|
|
| 165 |
+ Base: pdf.Base, |
|
| 166 |
+ Size: prefixSize, |
|
| 167 |
+ }) |
|
| 168 |
+ } |
|
| 169 |
+ } |
|
| 170 |
+ |
|
| 171 |
+ if len(predefined) == 0 {
|
|
| 172 |
+ // If we don't have any valid predefined networks |
|
| 173 |
+ return netip.Prefix{}, ipamapi.ErrInvalidPool
|
|
| 174 |
+ } |
|
| 175 |
+ |
|
| 153 | 176 |
for {
|
| 154 | 177 |
allocated := it.Get() |
| 155 | 178 |
if allocated == (netip.Prefix{}) {
|
| ... | ... |
@@ -157,10 +180,11 @@ func (aSpace *addrSpace) allocatePredefinedPool(reserved []netip.Prefix) (netip. |
| 157 | 157 |
break |
| 158 | 158 |
} |
| 159 | 159 |
|
| 160 |
- if pdfID >= len(aSpace.predefined) {
|
|
| 160 |
+ if pdfID >= len(predefined) {
|
|
| 161 |
+ // We ran out of predefined networks. |
|
| 161 | 162 |
return netip.Prefix{}, ipamapi.ErrNoMoreSubnets
|
| 162 | 163 |
} |
| 163 |
- pdf := aSpace.predefined[pdfID] |
|
| 164 |
+ pdf := predefined[pdfID] |
|
| 164 | 165 |
|
| 165 | 166 |
if allocated.Overlaps(pdf.Base) {
|
| 166 | 167 |
if allocated.Bits() <= pdf.Base.Bits() {
|
| ... | ... |
@@ -240,7 +264,7 @@ func (aSpace *addrSpace) allocatePredefinedPool(reserved []netip.Prefix) (netip. |
| 240 | 240 |
prevAlloc = allocated |
| 241 | 241 |
} |
| 242 | 242 |
|
| 243 |
- if pdfID >= len(aSpace.predefined) {
|
|
| 243 |
+ if pdfID >= len(predefined) {
|
|
| 244 | 244 |
return netip.Prefix{}, ipamapi.ErrNoMoreSubnets
|
| 245 | 245 |
} |
| 246 | 246 |
|
| ... | ... |
@@ -248,7 +272,7 @@ func (aSpace *addrSpace) allocatePredefinedPool(reserved []netip.Prefix) (netip. |
| 248 | 248 |
// networks. Let's try two more times (once on the current 'pdf', and once |
| 249 | 249 |
// on the next network if any). |
| 250 | 250 |
if partialOverlap {
|
| 251 |
- pdf := aSpace.predefined[pdfID] |
|
| 251 |
+ pdf := predefined[pdfID] |
|
| 252 | 252 |
|
| 253 | 253 |
if next := netiputil.PrefixAfter(prevAlloc, pdf.Size); pdf.Overlaps(next) {
|
| 254 | 254 |
return makeAlloc(next), nil |
| ... | ... |
@@ -268,8 +292,8 @@ func (aSpace *addrSpace) allocatePredefinedPool(reserved []netip.Prefix) (netip. |
| 268 | 268 |
// overlapped at all. |
| 269 | 269 |
// |
| 270 | 270 |
// Hence, we're sure 'pdfID' has never been subnetted yet. |
| 271 |
- if pdfID < len(aSpace.predefined) {
|
|
| 272 |
- pdf := aSpace.predefined[pdfID] |
|
| 271 |
+ if pdfID < len(predefined) {
|
|
| 272 |
+ pdf := predefined[pdfID] |
|
| 273 | 273 |
|
| 274 | 274 |
next := pdf.FirstPrefix() |
| 275 | 275 |
return makeAlloc(next), nil |
| ... | ... |
@@ -32,6 +32,7 @@ func TestDynamicPoolAllocation(t *testing.T) {
|
| 32 | 32 |
testcases := []struct {
|
| 33 | 33 |
name string |
| 34 | 34 |
predefined []*ipamutils.NetworkToSplit |
| 35 |
+ subnetSize int |
|
| 35 | 36 |
allocated []netip.Prefix |
| 36 | 37 |
reserved []netip.Prefix |
| 37 | 38 |
expPrefix netip.Prefix |
| ... | ... |
@@ -343,6 +344,92 @@ func TestDynamicPoolAllocation(t *testing.T) {
|
| 343 | 343 |
}, |
| 344 | 344 |
expPrefix: netip.MustParsePrefix("192.168.0.0/24"),
|
| 345 | 345 |
}, |
| 346 |
+ {
|
|
| 347 |
+ name: "Smaller requested network subnet size than predefined", |
|
| 348 |
+ predefined: []*ipamutils.NetworkToSplit{
|
|
| 349 |
+ {Base: netip.MustParsePrefix("172.17.0.0/12"), Size: 16},
|
|
| 350 |
+ }, |
|
| 351 |
+ subnetSize: 24, |
|
| 352 |
+ expPrefix: netip.MustParsePrefix("172.17.0.0/24"),
|
|
| 353 |
+ }, |
|
| 354 |
+ {
|
|
| 355 |
+ name: "Larger requested network subnet size than predefined", |
|
| 356 |
+ predefined: []*ipamutils.NetworkToSplit{
|
|
| 357 |
+ {Base: netip.MustParsePrefix("172.17.0.0/12"), Size: 20},
|
|
| 358 |
+ }, |
|
| 359 |
+ subnetSize: 16, |
|
| 360 |
+ expPrefix: netip.MustParsePrefix("172.17.0.0/16"),
|
|
| 361 |
+ }, |
|
| 362 |
+ {
|
|
| 363 |
+ name: "Invalid specified network subnet size", |
|
| 364 |
+ predefined: []*ipamutils.NetworkToSplit{
|
|
| 365 |
+ {Base: netip.MustParsePrefix("172.17.0.0/12"), Size: 16},
|
|
| 366 |
+ }, |
|
| 367 |
+ subnetSize: 150, |
|
| 368 |
+ expErr: ipamapi.ErrInvalidPool, |
|
| 369 |
+ }, |
|
| 370 |
+ {
|
|
| 371 |
+ name: "Partially allocated predefined pool with specified size", |
|
| 372 |
+ predefined: []*ipamutils.NetworkToSplit{
|
|
| 373 |
+ {Base: netip.MustParsePrefix("172.17.0.0/12"), Size: 16},
|
|
| 374 |
+ }, |
|
| 375 |
+ allocated: []netip.Prefix{
|
|
| 376 |
+ netip.MustParsePrefix("172.17.0.0/20"),
|
|
| 377 |
+ }, |
|
| 378 |
+ subnetSize: 24, |
|
| 379 |
+ expPrefix: netip.MustParsePrefix("172.17.16.0/24"),
|
|
| 380 |
+ }, |
|
| 381 |
+ {
|
|
| 382 |
+ name: "Partially allocated predefined with gap and specified size", |
|
| 383 |
+ predefined: []*ipamutils.NetworkToSplit{
|
|
| 384 |
+ {Base: netip.MustParsePrefix("172.17.0.0/12"), Size: 16},
|
|
| 385 |
+ }, |
|
| 386 |
+ allocated: []netip.Prefix{
|
|
| 387 |
+ netip.MustParsePrefix("172.17.0.0/20"),
|
|
| 388 |
+ netip.MustParsePrefix("172.18.0.0/20"),
|
|
| 389 |
+ }, |
|
| 390 |
+ subnetSize: 24, |
|
| 391 |
+ expPrefix: netip.MustParsePrefix("172.17.16.0/24"),
|
|
| 392 |
+ }, |
|
| 393 |
+ {
|
|
| 394 |
+ name: "Partially allocated avoids overlapping", |
|
| 395 |
+ predefined: []*ipamutils.NetworkToSplit{
|
|
| 396 |
+ {Base: netip.MustParsePrefix("172.17.0.0/12"), Size: 16},
|
|
| 397 |
+ }, |
|
| 398 |
+ allocated: []netip.Prefix{
|
|
| 399 |
+ netip.MustParsePrefix("172.17.0.0/24"),
|
|
| 400 |
+ }, |
|
| 401 |
+ expPrefix: netip.MustParsePrefix("172.18.0.0/16"),
|
|
| 402 |
+ }, |
|
| 403 |
+ {
|
|
| 404 |
+ name: "Multiple predefined pools last one satisfies specified size", |
|
| 405 |
+ predefined: []*ipamutils.NetworkToSplit{
|
|
| 406 |
+ {Base: netip.MustParsePrefix("172.17.0.0/12"), Size: 24},
|
|
| 407 |
+ {Base: netip.MustParsePrefix("10.0.0.0/12"), Size: 20},
|
|
| 408 |
+ }, |
|
| 409 |
+ subnetSize: 20, |
|
| 410 |
+ expPrefix: netip.MustParsePrefix("10.0.0.0/20"),
|
|
| 411 |
+ }, |
|
| 412 |
+ {
|
|
| 413 |
+ name: "Multiple predefined pools none matching specified size", |
|
| 414 |
+ predefined: []*ipamutils.NetworkToSplit{
|
|
| 415 |
+ {Base: netip.MustParsePrefix("172.17.0.0/24"), Size: 24},
|
|
| 416 |
+ {Base: netip.MustParsePrefix("172.18.0.0/20"), Size: 20},
|
|
| 417 |
+ {Base: netip.MustParsePrefix("172.19.0.0/24"), Size: 24},
|
|
| 418 |
+ }, |
|
| 419 |
+ subnetSize: 16, |
|
| 420 |
+ expErr: ipamapi.ErrInvalidPool, |
|
| 421 |
+ }, |
|
| 422 |
+ {
|
|
| 423 |
+ name: "Multiple predefined pools none valid for specified size", |
|
| 424 |
+ predefined: []*ipamutils.NetworkToSplit{
|
|
| 425 |
+ {Base: netip.MustParsePrefix("172.17.0.0/24"), Size: 24},
|
|
| 426 |
+ {Base: netip.MustParsePrefix("172.18.0.0/20"), Size: 20},
|
|
| 427 |
+ {Base: netip.MustParsePrefix("172.19.0.0/24"), Size: 23},
|
|
| 428 |
+ }, |
|
| 429 |
+ subnetSize: 33, |
|
| 430 |
+ expErr: ipamapi.ErrInvalidPool, |
|
| 431 |
+ }, |
|
| 346 | 432 |
} |
| 347 | 433 |
|
| 348 | 434 |
for _, tc := range testcases {
|
| ... | ... |
@@ -351,7 +438,7 @@ func TestDynamicPoolAllocation(t *testing.T) {
|
| 351 | 351 |
assert.NilError(t, err) |
| 352 | 352 |
as.allocated = tc.allocated |
| 353 | 353 |
|
| 354 |
- p, err := as.allocatePredefinedPool(tc.reserved) |
|
| 354 |
+ p, err := as.allocatePredefinedPool(tc.reserved, tc.subnetSize) |
|
| 355 | 355 |
|
| 356 | 356 |
assert.Check(t, is.ErrorIs(err, tc.expErr)) |
| 357 | 357 |
assert.Check(t, is.Equal(p, tc.expPrefix)) |
| ... | ... |
@@ -465,7 +552,7 @@ func TestPoolAllocateAndRelease(t *testing.T) {
|
| 465 | 465 |
// Allocate a pool for netname, check that a subnet is returned that |
| 466 | 466 |
// isn't already allocated, and doesn't overlap with a reserved range. |
| 467 | 467 |
alloc: func(netname string) {
|
| 468 |
- subnet, err := as.allocatePredefinedPool(tc.reserved) |
|
| 468 |
+ subnet, err := as.allocatePredefinedPool(tc.reserved, 0) |
|
| 469 | 469 |
assert.NilError(t, err) |
| 470 | 470 |
|
| 471 | 471 |
otherNetname, exists := subnetToNetname[subnet] |
| ... | ... |
@@ -134,22 +134,42 @@ func (a *Allocator) RequestPool(req ipamapi.PoolRequest) (ipamapi.AllocatedPool, |
| 134 | 134 |
if err != nil {
|
| 135 | 135 |
return ipamapi.AllocatedPool{}, err
|
| 136 | 136 |
} |
| 137 |
+ |
|
| 138 |
+ k := PoolID{AddressSpace: req.AddressSpace}
|
|
| 139 |
+ |
|
| 140 |
+ prefixLength := 0 |
|
| 141 |
+ |
|
| 142 |
+ if req.Pool != "" {
|
|
| 143 |
+ prefix, err := netip.ParsePrefix(req.Pool) |
|
| 144 |
+ if err != nil {
|
|
| 145 |
+ return ipamapi.AllocatedPool{}, parseErr(ipamapi.ErrInvalidPool)
|
|
| 146 |
+ } |
|
| 147 |
+ |
|
| 148 |
+ if prefix.Addr().IsUnspecified() {
|
|
| 149 |
+ // If the prefix is unspecified, we're only interested in the prefix size. |
|
| 150 |
+ // We'll attempt to use the specified size to allocate a subnet from the |
|
| 151 |
+ // predefined pools. |
|
| 152 |
+ req.Pool = "" |
|
| 153 |
+ |
|
| 154 |
+ if prefix.Bits() > 0 {
|
|
| 155 |
+ prefixLength = prefix.Bits() |
|
| 156 |
+ } |
|
| 157 |
+ } else {
|
|
| 158 |
+ k.Subnet = prefix |
|
| 159 |
+ } |
|
| 160 |
+ } |
|
| 161 |
+ |
|
| 137 | 162 |
if req.Pool == "" && req.SubPool != "" {
|
| 138 | 163 |
return ipamapi.AllocatedPool{}, parseErr(ipamapi.ErrInvalidSubPool)
|
| 139 | 164 |
} |
| 140 | 165 |
|
| 141 |
- k := PoolID{AddressSpace: req.AddressSpace}
|
|
| 142 | 166 |
if req.Pool == "" {
|
| 143 |
- if k.Subnet, err = aSpace.allocatePredefinedPool(req.Exclude); err != nil {
|
|
| 167 |
+ if k.Subnet, err = aSpace.allocatePredefinedPool(req.Exclude, prefixLength); err != nil {
|
|
| 144 | 168 |
return ipamapi.AllocatedPool{}, err
|
| 145 | 169 |
} |
| 146 | 170 |
return ipamapi.AllocatedPool{PoolID: k.String(), Pool: k.Subnet}, nil
|
| 147 | 171 |
} |
| 148 | 172 |
|
| 149 |
- if k.Subnet, err = netip.ParsePrefix(req.Pool); err != nil {
|
|
| 150 |
- return ipamapi.AllocatedPool{}, parseErr(ipamapi.ErrInvalidPool)
|
|
| 151 |
- } |
|
| 152 |
- |
|
| 153 | 173 |
if req.SubPool != "" {
|
| 154 | 174 |
if k.ChildSubnet, err = netip.ParsePrefix(req.SubPool); err != nil {
|
| 155 | 175 |
return ipamapi.AllocatedPool{}, types.InternalErrorf("invalid pool request: %v", ipamapi.ErrInvalidSubPool)
|
| ... | ... |
@@ -285,6 +285,61 @@ func TestPredefinedPool(t *testing.T) {
|
| 285 | 285 |
} |
| 286 | 286 |
} |
| 287 | 287 |
|
| 288 |
+func TestPredefinedPoolWithPreferredSubnetSize(t *testing.T) {
|
|
| 289 |
+ a, err := NewAllocator(ipamutils.GetLocalScopeDefaultNetworks(), ipamutils.GetGlobalScopeDefaultNetworks()) |
|
| 290 |
+ assert.NilError(t, err) |
|
| 291 |
+ |
|
| 292 |
+ alloc1, err := a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: "0.0.0.0/24"})
|
|
| 293 |
+ if err != nil {
|
|
| 294 |
+ t.Fatal(err) |
|
| 295 |
+ } |
|
| 296 |
+ |
|
| 297 |
+ alloc2, err := a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace})
|
|
| 298 |
+ if err != nil {
|
|
| 299 |
+ t.Fatal(err) |
|
| 300 |
+ } |
|
| 301 |
+ |
|
| 302 |
+ if alloc1.Pool == alloc2.Pool {
|
|
| 303 |
+ t.Fatalf("Unexpected default network returned: %s = %s", alloc2.Pool, alloc1.Pool)
|
|
| 304 |
+ } |
|
| 305 |
+ |
|
| 306 |
+ if alloc1.Pool.Bits() != 24 {
|
|
| 307 |
+ t.Fatalf("Unexpected default network size: %s != 24", alloc1.Pool)
|
|
| 308 |
+ } |
|
| 309 |
+ |
|
| 310 |
+ if alloc2.Pool.Bits() == 24 {
|
|
| 311 |
+ t.Fatalf("Unexpected default network size: %s == 24", alloc2.Pool)
|
|
| 312 |
+ } |
|
| 313 |
+ |
|
| 314 |
+ // Release the second pool first |
|
| 315 |
+ if err := a.ReleasePool(alloc2.PoolID); err != nil {
|
|
| 316 |
+ t.Fatal(err) |
|
| 317 |
+ } |
|
| 318 |
+ |
|
| 319 |
+ _, err = a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: "/24"})
|
|
| 320 |
+ if err == nil {
|
|
| 321 |
+ t.Fatal(err, "Expected failure requesting pool with unspecified address family") |
|
| 322 |
+ } |
|
| 323 |
+ |
|
| 324 |
+ alloc4, err := a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: "0.0.0.0/25"})
|
|
| 325 |
+ if err != nil {
|
|
| 326 |
+ t.Fatal(err) |
|
| 327 |
+ } |
|
| 328 |
+ |
|
| 329 |
+ if alloc4.Pool.Bits() != 25 {
|
|
| 330 |
+ t.Fatalf("Unexpected default network size: %s != 25", alloc4.Pool)
|
|
| 331 |
+ } |
|
| 332 |
+ |
|
| 333 |
+ if err := a.ReleasePool(alloc4.PoolID); err != nil {
|
|
| 334 |
+ t.Fatal(err) |
|
| 335 |
+ } |
|
| 336 |
+ |
|
| 337 |
+ // Check invalid subnet size requests |
|
| 338 |
+ if _, err := a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: "0.0.0.0/AB"}); err == nil {
|
|
| 339 |
+ t.Fatalf("Expected failure requesting pool with invalid subnet size")
|
|
| 340 |
+ } |
|
| 341 |
+} |
|
| 342 |
+ |
|
| 288 | 343 |
func TestRemoveSubnet(t *testing.T) {
|
| 289 | 344 |
a, err := NewAllocator(ipamutils.GetLocalScopeDefaultNetworks(), ipamutils.GetGlobalScopeDefaultNetworks()) |
| 290 | 345 |
assert.NilError(t, err) |
| ... | ... |
@@ -755,14 +810,15 @@ func TestOverlappingRequests(t *testing.T) {
|
| 755 | 755 |
{[]string{"10.0.0.0/8"}, "11.0.0.0/8", true},
|
| 756 | 756 |
{[]string{"74.0.0.0/7"}, "9.111.99.72/30", true},
|
| 757 | 757 |
{[]string{"110.192.0.0/10"}, "16.0.0.0/10", true},
|
| 758 |
+ {[]string{"0.0.0.0/16"}, "0.0.0.0/16", true}, // two default allocations should succeed
|
|
| 758 | 759 |
|
| 759 | 760 |
// Previously allocated network entirely contains request |
| 760 | 761 |
{[]string{"10.0.0.0/8"}, "10.0.0.0/8", false}, // exact overlap
|
| 761 |
- {[]string{"0.0.0.0/1"}, "16.182.0.0/15", false},
|
|
| 762 |
+ {[]string{"16.182.0.0/15"}, "16.182.0.0/16", false},
|
|
| 762 | 763 |
{[]string{"16.0.0.0/4"}, "17.11.66.0/23", false},
|
| 763 | 764 |
|
| 764 | 765 |
// Previously allocated network overlaps beginning of request |
| 765 |
- {[]string{"0.0.0.0/1"}, "0.0.0.0/0", false},
|
|
| 766 |
+ {[]string{"16.182.0.0/16"}, "16.182.0.0/15", false},
|
|
| 766 | 767 |
{[]string{"64.0.0.0/6"}, "64.0.0.0/3", false},
|
| 767 | 768 |
{[]string{"112.0.0.0/6"}, "112.0.0.0/4", false},
|
| 768 | 769 |
|
| ... | ... |
@@ -774,18 +830,21 @@ func TestOverlappingRequests(t *testing.T) {
|
| 774 | 774 |
// Previously allocated network entirely contained within request |
| 775 | 775 |
{[]string{"10.0.0.0/8"}, "10.0.0.0/6", false}, // non-canonical
|
| 776 | 776 |
{[]string{"10.0.0.0/8"}, "8.0.0.0/6", false}, // canonical
|
| 777 |
- {[]string{"25.173.144.0/20"}, "0.0.0.0/0", false},
|
|
| 777 |
+ {[]string{"25.173.144.0/20"}, "25.173.143.0/16", false},
|
|
| 778 | 778 |
|
| 779 | 779 |
// IPv6 |
| 780 |
+ {[]string{"::/0"}, "::/0", true}, // two default allocations should succeed
|
|
| 781 |
+ {[]string{"f000::/4"}, "::/0", true}, // default allocation shouldn't overlap explicit allocation
|
|
| 782 |
+ |
|
| 780 | 783 |
// Previously allocated network entirely contains request |
| 781 |
- {[]string{"::/0"}, "f656:3484:c878:a05:e540:a6ed:4d70:3740/123", false},
|
|
| 784 |
+ {[]string{"f656::/0"}, "f656:3484:c878:a05:e540:a6ed:4d70:3740/123", false},
|
|
| 782 | 785 |
{[]string{"8000::/1"}, "8fe8:e7c4:5779::/49", false},
|
| 783 | 786 |
{[]string{"f000::/4"}, "ffc7:6000::/19", false},
|
| 784 | 787 |
|
| 785 | 788 |
// Previously allocated network overlaps beginning of request |
| 786 |
- {[]string{"::/2"}, "::/0", false},
|
|
| 787 |
- {[]string{"::/3"}, "::/1", false},
|
|
| 788 |
- {[]string{"::/6"}, "::/5", false},
|
|
| 789 |
+ {[]string{"f656::/20"}, "f656::/16", false},
|
|
| 790 |
+ {[]string{"8000::/32"}, "8000::/31", false},
|
|
| 791 |
+ {[]string{"f000::/60"}, "f000::/20", false},
|
|
| 789 | 792 |
|
| 790 | 793 |
// Previously allocated network overlaps end of request |
| 791 | 794 |
{[]string{"c000::/2"}, "8000::/1", false},
|
| ... | ... |
@@ -793,7 +852,7 @@ func TestOverlappingRequests(t *testing.T) {
|
| 793 | 793 |
{[]string{"cf80::/9"}, "c000::/4", false},
|
| 794 | 794 |
|
| 795 | 795 |
// Previously allocated network entirely contained within request |
| 796 |
- {[]string{"ff77:93f8::/29"}, "::/0", false},
|
|
| 796 |
+ {[]string{"ff77:93f8::/29"}, "ff77:93f7::/28", false},
|
|
| 797 | 797 |
{[]string{"9287:2e20:5134:fab6:9061:a0c6:bfe3:9400/119"}, "8000::/1", false},
|
| 798 | 798 |
{[]string{"3ea1:bfa9:8691:d1c6:8c46:519b:db6d:e700/120"}, "3000::/4", false},
|
| 799 | 799 |
} |
| ... | ... |
@@ -805,15 +864,15 @@ func TestOverlappingRequests(t *testing.T) {
|
| 805 | 805 |
// Set up some existing allocations. This should always succeed. |
| 806 | 806 |
for _, env := range tc.environment {
|
| 807 | 807 |
_, err = a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: env})
|
| 808 |
- assert.NilError(t, err) |
|
| 808 |
+ assert.NilError(t, err, "error requesting pool %v, %v", localAddressSpace, env) |
|
| 809 | 809 |
} |
| 810 | 810 |
|
| 811 | 811 |
// Make the test allocation. |
| 812 | 812 |
_, err = a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: tc.subnet})
|
| 813 | 813 |
if tc.ok {
|
| 814 |
- assert.NilError(t, err) |
|
| 814 |
+ assert.NilError(t, err, "error requesting pool %v, %v", localAddressSpace, tc.subnet) |
|
| 815 | 815 |
} else {
|
| 816 |
- assert.Check(t, is.ErrorContains(err, "")) |
|
| 816 |
+ assert.Check(t, is.ErrorContains(err, ""), "expected error requesting overlapping pool %v, %v", localAddressSpace, tc.subnet) |
|
| 817 | 817 |
} |
| 818 | 818 |
} |
| 819 | 819 |
} |
| ... | ... |
@@ -1533,10 +1533,21 @@ func (n *Network) ipamAllocateVersion(ipam ipamapi.Ipam, v6 bool, ipamConf []*Ip |
| 1533 | 1533 |
reserved = netutils.InferReservedNetworks(v6) |
| 1534 | 1534 |
} |
| 1535 | 1535 |
|
| 1536 |
+ // Determine if the preferred pool is unspecified (blank, or a 0.0.0.0 or :: address) |
|
| 1537 |
+ prefPool := cfg.PreferredPool |
|
| 1538 |
+ isDefaultPool := prefPool == "" |
|
| 1539 |
+ if !isDefaultPool {
|
|
| 1540 |
+ if prefix, err := netip.ParsePrefix(prefPool); err != nil {
|
|
| 1541 |
+ // This should never happen |
|
| 1542 |
+ return nil, types.InvalidParameterErrorf("invalid preferred address %q: %v", prefPool, err)
|
|
| 1543 |
+ } else if prefix.Addr().IsUnspecified() {
|
|
| 1544 |
+ isDefaultPool = true |
|
| 1545 |
+ } |
|
| 1546 |
+ } |
|
| 1547 |
+ |
|
| 1536 | 1548 |
// During network restore, if no subnet was specified in the original network-create |
| 1537 | 1549 |
// request, use the previously allocated subnet. |
| 1538 |
- prefPool := cfg.PreferredPool |
|
| 1539 |
- if prefPool == "" && len(ipamInfo) > i {
|
|
| 1550 |
+ if isDefaultPool && len(ipamInfo) > i {
|
|
| 1540 | 1551 |
prefPool = ipamInfo[i].Pool.String() |
| 1541 | 1552 |
} |
| 1542 | 1553 |
|
| ... | ... |
@@ -812,27 +812,49 @@ func buildIPAMResources(nw *libnetwork.Network) networktypes.IPAM {
|
| 812 | 812 |
var ipamConfig []networktypes.IPAMConfig |
| 813 | 813 |
|
| 814 | 814 |
ipamDriver, ipamOptions, ipv4Conf, ipv6Conf := nw.IpamConfig() |
| 815 |
+ ipv4Info, ipv6Info := nw.IpamInfo() |
|
| 815 | 816 |
|
| 816 | 817 |
hasIPv4Config := false |
| 817 |
- for _, cfg := range ipv4Conf {
|
|
| 818 |
- if cfg.PreferredPool == "" {
|
|
| 819 |
- continue |
|
| 818 |
+ if len(ipv4Info) > 0 {
|
|
| 819 |
+ // Only check ipv4 networks if there were any allocated |
|
| 820 |
+ for i, cfg := range ipv4Conf {
|
|
| 821 |
+ if cfg.PreferredPool == "" {
|
|
| 822 |
+ continue |
|
| 823 |
+ } |
|
| 824 |
+ hasIPv4Config = true |
|
| 825 |
+ subnet := ipv4Info[i].IPAMData.Pool |
|
| 826 |
+ if subnet != nil {
|
|
| 827 |
+ cfg.PreferredPool = subnet.String() |
|
| 828 |
+ } |
|
| 829 |
+ if ipv4Info[i].IPAMData.Gateway != nil && cfg.Gateway == "" {
|
|
| 830 |
+ cfg.Gateway = ipv4Info[i].IPAMData.Gateway.IP.String() |
|
| 831 |
+ } |
|
| 832 |
+ |
|
| 833 |
+ ipamConfig = append(ipamConfig, cfg.IPAMConfig()) |
|
| 820 | 834 |
} |
| 821 |
- hasIPv4Config = true |
|
| 822 |
- ipamConfig = append(ipamConfig, cfg.IPAMConfig()) |
|
| 823 | 835 |
} |
| 824 | 836 |
|
| 825 | 837 |
hasIPv6Config := false |
| 826 |
- for _, cfg := range ipv6Conf {
|
|
| 827 |
- if cfg.PreferredPool == "" {
|
|
| 828 |
- continue |
|
| 838 |
+ if len(ipv6Info) > 0 {
|
|
| 839 |
+ // Only check ipv6 networks if there were any allocated |
|
| 840 |
+ for i, cfg := range ipv6Conf {
|
|
| 841 |
+ if cfg.PreferredPool == "" {
|
|
| 842 |
+ continue |
|
| 843 |
+ } |
|
| 844 |
+ hasIPv6Config = true |
|
| 845 |
+ subnet := ipv6Info[i].IPAMData.Pool |
|
| 846 |
+ if subnet != nil {
|
|
| 847 |
+ cfg.PreferredPool = subnet.String() |
|
| 848 |
+ } |
|
| 849 |
+ |
|
| 850 |
+ if ipv6Info[i].IPAMData.Gateway != nil && cfg.Gateway == "" {
|
|
| 851 |
+ cfg.Gateway = ipv6Info[i].IPAMData.Gateway.IP.String() |
|
| 852 |
+ } |
|
| 853 |
+ ipamConfig = append(ipamConfig, cfg.IPAMConfig()) |
|
| 829 | 854 |
} |
| 830 |
- hasIPv6Config = true |
|
| 831 |
- ipamConfig = append(ipamConfig, cfg.IPAMConfig()) |
|
| 832 | 855 |
} |
| 833 | 856 |
|
| 834 | 857 |
if !hasIPv4Config || !hasIPv6Config {
|
| 835 |
- ipv4Info, ipv6Info := nw.IpamInfo() |
|
| 836 | 858 |
if !hasIPv4Config {
|
| 837 | 859 |
for _, info := range ipv4Info {
|
| 838 | 860 |
ipamConfig = append(ipamConfig, info.IPAMData.IPAMConfig()) |
| ... | ... |
@@ -70,8 +70,8 @@ func TestDaemonDefaultBridgeIPAM_Docker0(t *testing.T) {
|
| 70 | 70 |
"--fixed-cidr-v6", "fdd1:8161:2d2c::/64", |
| 71 | 71 |
}, |
| 72 | 72 |
expIPAMConfig: []network.IPAMConfig{
|
| 73 |
- {Subnet: netip.MustParsePrefix("192.168.176.0/24"), IPRange: netip.MustParsePrefix("192.168.176.0/24")},
|
|
| 74 |
- {Subnet: netip.MustParsePrefix("fdd1:8161:2d2c::/64"), IPRange: netip.MustParsePrefix("fdd1:8161:2d2c::/64")},
|
|
| 73 |
+ {Subnet: netip.MustParsePrefix("192.168.176.0/24"), IPRange: netip.MustParsePrefix("192.168.176.0/24"), Gateway: netip.MustParseAddr("192.168.176.1")},
|
|
| 74 |
+ {Subnet: netip.MustParsePrefix("fdd1:8161:2d2c::/64"), IPRange: netip.MustParsePrefix("fdd1:8161:2d2c::/64"), Gateway: netip.MustParseAddr("fdd1:8161:2d2c::1")},
|
|
| 75 | 75 |
}, |
| 76 | 76 |
}, |
| 77 | 77 |
{
|
| ... | ... |
@@ -123,7 +123,7 @@ func TestDaemonDefaultBridgeIPAM_Docker0(t *testing.T) {
|
| 123 | 123 |
"--fixed-cidr-v6", "fe80::/64", |
| 124 | 124 |
}, |
| 125 | 125 |
expIPAMConfig: []network.IPAMConfig{
|
| 126 |
- {Subnet: netip.MustParsePrefix("192.168.176.0/24"), IPRange: netip.MustParsePrefix("192.168.176.0/24")},
|
|
| 126 |
+ {Subnet: netip.MustParsePrefix("192.168.176.0/24"), IPRange: netip.MustParsePrefix("192.168.176.0/24"), Gateway: netip.MustParseAddr("192.168.176.1")},
|
|
| 127 | 127 |
{Subnet: netip.MustParsePrefix("fe80::/64"), IPRange: netip.MustParsePrefix("fe80::/64"), Gateway: llGwPlaceholder},
|
| 128 | 128 |
}, |
| 129 | 129 |
}, |
| ... | ... |
@@ -173,15 +173,8 @@ func TestDaemonDefaultBridgeIPAM_Docker0(t *testing.T) {
|
| 173 | 173 |
// The bridge's address/subnet should be ignored, this is a change |
| 174 | 174 |
// of fixed-cidr. |
| 175 | 175 |
expIPAMConfig: []network.IPAMConfig{
|
| 176 |
- {Subnet: netip.MustParsePrefix("192.168.177.0/24"), IPRange: netip.MustParsePrefix("192.168.177.0/24")},
|
|
| 177 |
- {Subnet: netip.MustParsePrefix("fdd1:8161:2d2c:1::/64"), IPRange: netip.MustParsePrefix("fdd1:8161:2d2c:1::/64")},
|
|
| 178 |
- // No Gateway is configured, because the address could not be learnt from the |
|
| 179 |
- // bridge. An address will have been allocated but, because there's config (the |
|
| 180 |
- // fixed-cidr), inspect shows just the config. (Surprisingly, when there's no |
|
| 181 |
- // config at all, the inspect output still says its showing config but actually |
|
| 182 |
- // shows the running state.) When the daemon is restarted, after a gateway |
|
| 183 |
- // address has been assigned to the bridge, that address will become config - so |
|
| 184 |
- // a Gateway address will show up in the inspect output. |
|
| 176 |
+ {Subnet: netip.MustParsePrefix("192.168.177.0/24"), IPRange: netip.MustParsePrefix("192.168.177.0/24"), Gateway: netip.MustParseAddr("192.168.177.1")},
|
|
| 177 |
+ {Subnet: netip.MustParsePrefix("fdd1:8161:2d2c:1::/64"), IPRange: netip.MustParsePrefix("fdd1:8161:2d2c:1::/64"), Gateway: netip.MustParseAddr("fdd1:8161:2d2c:1::1")},
|
|
| 185 | 178 |
}, |
| 186 | 179 |
}, |
| 187 | 180 |
{
|
| ... | ... |
@@ -225,8 +218,8 @@ func TestDaemonDefaultBridgeIPAM_UserBr(t *testing.T) {
|
| 225 | 225 |
"--fixed-cidr-v6", "fdd1:8161:2d2c::/64", |
| 226 | 226 |
}, |
| 227 | 227 |
expIPAMConfig: []network.IPAMConfig{
|
| 228 |
- {Subnet: netip.MustParsePrefix("192.168.176.0/24"), IPRange: netip.MustParsePrefix("192.168.176.0/24")},
|
|
| 229 |
- {Subnet: netip.MustParsePrefix("fdd1:8161:2d2c::/64"), IPRange: netip.MustParsePrefix("fdd1:8161:2d2c::/64")},
|
|
| 228 |
+ {Subnet: netip.MustParsePrefix("192.168.176.0/24"), IPRange: netip.MustParsePrefix("192.168.176.0/24"), Gateway: netip.MustParseAddr("192.168.176.1")},
|
|
| 229 |
+ {Subnet: netip.MustParsePrefix("fdd1:8161:2d2c::/64"), IPRange: netip.MustParsePrefix("fdd1:8161:2d2c::/64"), Gateway: netip.MustParseAddr("fdd1:8161:2d2c::1")},
|
|
| 230 | 230 |
}, |
| 231 | 231 |
}, |
| 232 | 232 |
{
|
| ... | ... |
@@ -428,7 +421,7 @@ func testDefaultBridgeIPAM(ctx context.Context, t *testing.T, tc defaultBridgeIP |
| 428 | 428 |
expIPAMConfig[i].Gateway = llAddr |
| 429 | 429 |
} |
| 430 | 430 |
} |
| 431 |
- assert.Check(t, is.DeepEqual(res.Network.IPAM.Config, expIPAMConfig, cmpopts.EquateComparable(netip.Addr{}, netip.Prefix{})))
|
|
| 431 |
+ assert.Check(t, is.DeepEqual(res.Network.IPAM.Config, expIPAMConfig, cmpopts.EquateComparable(netip.Addr{}, netip.Prefix{})), "unexpected IPAM config '%s'", tc.name)
|
|
| 432 | 432 |
}) |
| 433 | 433 |
}) |
| 434 | 434 |
} |
| ... | ... |
@@ -33,6 +33,21 @@ func CreateNoError(ctx context.Context, t *testing.T, apiClient client.APIClient |
| 33 | 33 |
return name |
| 34 | 34 |
} |
| 35 | 35 |
|
| 36 |
+// Inspect inspects a network with the specified options |
|
| 37 |
+func Inspect(ctx context.Context, apiClient client.APIClient, name string, options client.NetworkInspectOptions) (client.NetworkInspectResult, error) {
|
|
| 38 |
+ return apiClient.NetworkInspect(ctx, name, options) |
|
| 39 |
+} |
|
| 40 |
+ |
|
| 41 |
+// InspectNoError inspects a network with the specified options and verifies there were no errors |
|
| 42 |
+func InspectNoError(ctx context.Context, t *testing.T, apiClient client.APIClient, name string, options client.NetworkInspectOptions) client.NetworkInspectResult {
|
|
| 43 |
+ t.Helper() |
|
| 44 |
+ |
|
| 45 |
+ c, err := apiClient.NetworkInspect(ctx, name, options) |
|
| 46 |
+ assert.NilError(t, err) |
|
| 47 |
+ |
|
| 48 |
+ return c |
|
| 49 |
+} |
|
| 50 |
+ |
|
| 36 | 51 |
func RemoveNoError(ctx context.Context, t *testing.T, apiClient client.APIClient, name string) {
|
| 37 | 52 |
t.Helper() |
| 38 | 53 |
|
| ... | ... |
@@ -1287,3 +1287,95 @@ func TestJoinError(t *testing.T) {
|
| 1287 | 1287 |
res = ctr.ExecT(ctx, t, c, cid, []string{"ip", "link", "show", "eth1"})
|
| 1288 | 1288 |
assert.Check(t, is.Equal(res.ExitCode, 0), "container should have an eth1") |
| 1289 | 1289 |
} |
| 1290 |
+ |
|
| 1291 |
+func TestPreferredSubnetRestore(t *testing.T) {
|
|
| 1292 |
+ skip.If(t, testEnv.IsRootless(), "fails before and after restart") |
|
| 1293 |
+ |
|
| 1294 |
+ ctx := setupTest(t) |
|
| 1295 |
+ d := daemon.New(t) |
|
| 1296 |
+ d.StartWithBusybox(ctx, t) |
|
| 1297 |
+ defer d.Stop(t) |
|
| 1298 |
+ c := d.NewClientT(t) |
|
| 1299 |
+ |
|
| 1300 |
+ const v4netName = "testnetv4restore" |
|
| 1301 |
+ network.CreateNoError(ctx, t, c, v4netName, |
|
| 1302 |
+ network.WithIPv4(true), |
|
| 1303 |
+ network.WithIPAMConfig(networktypes.IPAMConfig{
|
|
| 1304 |
+ Subnet: netip.MustParsePrefix("0.0.0.0/24"),
|
|
| 1305 |
+ }), |
|
| 1306 |
+ ) |
|
| 1307 |
+ |
|
| 1308 |
+ defer func() { network.RemoveNoError(ctx, t, c, v4netName) }()
|
|
| 1309 |
+ |
|
| 1310 |
+ const v6netName = "testnetv6restore" |
|
| 1311 |
+ network.CreateNoError(ctx, t, c, v6netName, |
|
| 1312 |
+ network.WithIPv4(false), |
|
| 1313 |
+ network.WithIPv6(), |
|
| 1314 |
+ network.WithIPAMConfig(networktypes.IPAMConfig{
|
|
| 1315 |
+ Subnet: netip.MustParsePrefix("::/120"),
|
|
| 1316 |
+ }), |
|
| 1317 |
+ ) |
|
| 1318 |
+ |
|
| 1319 |
+ defer func() { network.RemoveNoError(ctx, t, c, v6netName) }()
|
|
| 1320 |
+ |
|
| 1321 |
+ const dualStackNetName = "testnetdualrestore" |
|
| 1322 |
+ network.CreateNoError(ctx, t, c, dualStackNetName, |
|
| 1323 |
+ network.WithIPv4(true), |
|
| 1324 |
+ network.WithIPv6(), |
|
| 1325 |
+ network.WithIPAMConfig(networktypes.IPAMConfig{
|
|
| 1326 |
+ Subnet: netip.MustParsePrefix("0.0.0.0/24"),
|
|
| 1327 |
+ }, networktypes.IPAMConfig{
|
|
| 1328 |
+ Subnet: netip.MustParsePrefix("::/120"),
|
|
| 1329 |
+ }), |
|
| 1330 |
+ ) |
|
| 1331 |
+ |
|
| 1332 |
+ defer func() { network.RemoveNoError(ctx, t, c, dualStackNetName) }()
|
|
| 1333 |
+ |
|
| 1334 |
+ inspOpts := client.NetworkInspectOptions{}
|
|
| 1335 |
+ |
|
| 1336 |
+ v4Insp := network.InspectNoError(ctx, t, c, v4netName, inspOpts) |
|
| 1337 |
+ assert.Check(t, is.Len(v4Insp.Network.IPAM.Config, 1)) |
|
| 1338 |
+ v4allocCidr := v4Insp.Network.IPAM.Config[0].Subnet |
|
| 1339 |
+ assert.Check(t, is.Equal(v4allocCidr.Addr().IsUnspecified(), false), "expected specific subnet") |
|
| 1340 |
+ |
|
| 1341 |
+ v6Insp := network.InspectNoError(ctx, t, c, v6netName, inspOpts) |
|
| 1342 |
+ assert.Check(t, is.Len(v6Insp.Network.IPAM.Config, 1)) |
|
| 1343 |
+ v6allocCidr := v6Insp.Network.IPAM.Config[0].Subnet |
|
| 1344 |
+ assert.Check(t, is.Equal(v6allocCidr.Addr().IsUnspecified(), false), "expected specific subnet") |
|
| 1345 |
+ |
|
| 1346 |
+ dualStackInsp := network.InspectNoError(ctx, t, c, dualStackNetName, inspOpts) |
|
| 1347 |
+ assert.Check(t, is.Len(dualStackInsp.Network.IPAM.Config, 2)) |
|
| 1348 |
+ var dualv4, dualv6 netip.Prefix |
|
| 1349 |
+ if dualStackInsp.Network.IPAM.Config[0].Subnet.Addr().Is4() {
|
|
| 1350 |
+ dualv4 = dualStackInsp.Network.IPAM.Config[0].Subnet |
|
| 1351 |
+ dualv6 = dualStackInsp.Network.IPAM.Config[1].Subnet |
|
| 1352 |
+ } else {
|
|
| 1353 |
+ dualv4 = dualStackInsp.Network.IPAM.Config[1].Subnet |
|
| 1354 |
+ dualv6 = dualStackInsp.Network.IPAM.Config[0].Subnet |
|
| 1355 |
+ } |
|
| 1356 |
+ assert.Check(t, is.Equal(dualv4.Addr().IsUnspecified(), false), "expected specific v4 subnet") |
|
| 1357 |
+ assert.Check(t, is.Equal(dualv6.Addr().IsUnspecified(), false), "expected specific v6 subnet") |
|
| 1358 |
+ |
|
| 1359 |
+ d.Restart(t) |
|
| 1360 |
+ |
|
| 1361 |
+ v4Insp = network.InspectNoError(ctx, t, c, v4netName, inspOpts) |
|
| 1362 |
+ assert.Check(t, is.Len(v4Insp.Network.IPAM.Config, 1)) |
|
| 1363 |
+ assert.Check(t, is.Equal(v4Insp.Network.IPAM.Config[0].Subnet, v4allocCidr)) |
|
| 1364 |
+ |
|
| 1365 |
+ v6Insp = network.InspectNoError(ctx, t, c, v6netName, inspOpts) |
|
| 1366 |
+ assert.Check(t, is.Len(v6Insp.Network.IPAM.Config, 1)) |
|
| 1367 |
+ assert.Check(t, is.Equal(v6Insp.Network.IPAM.Config[0].Subnet, v6allocCidr)) |
|
| 1368 |
+ |
|
| 1369 |
+ dualStackInsp = network.InspectNoError(ctx, t, c, dualStackNetName, inspOpts) |
|
| 1370 |
+ assert.Check(t, is.Len(dualStackInsp.Network.IPAM.Config, 2)) |
|
| 1371 |
+ var dualv4after, dualv6after netip.Prefix |
|
| 1372 |
+ if dualStackInsp.Network.IPAM.Config[0].Subnet.Addr().Is4() {
|
|
| 1373 |
+ dualv4after = dualStackInsp.Network.IPAM.Config[0].Subnet |
|
| 1374 |
+ dualv6after = dualStackInsp.Network.IPAM.Config[1].Subnet |
|
| 1375 |
+ } else {
|
|
| 1376 |
+ dualv4after = dualStackInsp.Network.IPAM.Config[1].Subnet |
|
| 1377 |
+ dualv6after = dualStackInsp.Network.IPAM.Config[0].Subnet |
|
| 1378 |
+ } |
|
| 1379 |
+ assert.Check(t, is.Equal(dualv4after, dualv4), "expected same v4 subnet after restart") |
|
| 1380 |
+ assert.Check(t, is.Equal(dualv6after, dualv6), "expected same v6 subnet after restart") |
|
| 1381 |
+} |