k8s.io/kubernetes@v1.29.3/pkg/registry/core/service/ipallocator/ipallocator_test.go (about) 1 /* 2 Copyright 2022 The Kubernetes 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 ipallocator 18 19 import ( 20 "fmt" 21 "math" 22 "net" 23 "net/netip" 24 "reflect" 25 "testing" 26 "time" 27 28 networkingv1alpha1 "k8s.io/api/networking/v1alpha1" 29 "k8s.io/apimachinery/pkg/runtime" 30 "k8s.io/apimachinery/pkg/util/sets" 31 "k8s.io/client-go/informers" 32 "k8s.io/client-go/kubernetes/fake" 33 k8stesting "k8s.io/client-go/testing" 34 "k8s.io/component-base/metrics/testutil" 35 api "k8s.io/kubernetes/pkg/apis/core" 36 netutils "k8s.io/utils/net" 37 ) 38 39 func newTestAllocator(cidr *net.IPNet) (*Allocator, error) { 40 client := fake.NewSimpleClientset() 41 42 informerFactory := informers.NewSharedInformerFactory(client, 0*time.Second) 43 ipInformer := informerFactory.Networking().V1alpha1().IPAddresses() 44 ipStore := ipInformer.Informer().GetIndexer() 45 46 client.PrependReactor("create", "ipaddresses", k8stesting.ReactionFunc(func(action k8stesting.Action) (bool, runtime.Object, error) { 47 ip := action.(k8stesting.CreateAction).GetObject().(*networkingv1alpha1.IPAddress) 48 _, exists, err := ipStore.GetByKey(ip.Name) 49 if exists && err != nil { 50 return false, nil, fmt.Errorf("ip already exist") 51 } 52 ip.Generation = 1 53 err = ipStore.Add(ip) 54 return false, ip, err 55 })) 56 client.PrependReactor("delete", "ipaddresses", k8stesting.ReactionFunc(func(action k8stesting.Action) (bool, runtime.Object, error) { 57 name := action.(k8stesting.DeleteAction).GetName() 58 obj, exists, err := ipStore.GetByKey(name) 59 ip := &networkingv1alpha1.IPAddress{} 60 if exists && err == nil { 61 ip = obj.(*networkingv1alpha1.IPAddress) 62 err = ipStore.Delete(ip) 63 } 64 return false, ip, err 65 })) 66 67 c, err := NewIPAllocator(cidr, client.NetworkingV1alpha1(), ipInformer) 68 if err != nil { 69 return nil, err 70 } 71 c.ipAddressSynced = func() bool { return true } 72 return c, nil 73 } 74 75 func TestAllocateIPAllocator(t *testing.T) { 76 testCases := []struct { 77 name string 78 cidr string 79 family api.IPFamily 80 free int 81 released string 82 outOfRange []string 83 alreadyAllocated string 84 }{ 85 { 86 name: "IPv4", 87 cidr: "192.168.1.0/24", 88 free: 254, 89 released: "192.168.1.5", 90 outOfRange: []string{ 91 "192.168.0.1", // not in 192.168.1.0/24 92 "192.168.1.0", // reserved (base address) 93 "192.168.1.255", // reserved (broadcast address) 94 "192.168.2.2", // not in 192.168.1.0/24 95 }, 96 alreadyAllocated: "192.168.1.1", 97 }, 98 { 99 name: "IPv6", 100 cidr: "2001:db8:1::/116", 101 free: 4095, 102 released: "2001:db8:1::5", 103 outOfRange: []string{ 104 "2001:db8::1", // not in 2001:db8:1::/48 105 "2001:db8:1::", // reserved (base address) 106 "2001:db8:2::2", // not in 2001:db8:1::/48 107 }, 108 alreadyAllocated: "2001:db8:1::1", 109 }, 110 } 111 for _, tc := range testCases { 112 t.Run(tc.name, func(t *testing.T) { 113 _, cidr, err := netutils.ParseCIDRSloppy(tc.cidr) 114 if err != nil { 115 t.Fatal(err) 116 } 117 r, err := newTestAllocator(cidr) 118 if err != nil { 119 t.Fatal(err) 120 } 121 defer r.Destroy() 122 if f := r.Free(); f != tc.free { 123 t.Errorf("[%s] wrong free: expected %d, got %d", tc.name, tc.free, f) 124 } 125 126 if f := r.Used(); f != 0 { 127 t.Errorf("[%s]: wrong used: expected %d, got %d", tc.name, 0, f) 128 } 129 found := sets.NewString() 130 count := 0 131 for r.Free() > 0 { 132 ip, err := r.AllocateNext() 133 if err != nil { 134 t.Fatalf("[%s] error @ free: %d used: %d count: %d: %v", tc.name, r.Free(), r.Used(), count, err) 135 } 136 count++ 137 //if !cidr.Contains(ip) { 138 // t.Fatalf("[%s] allocated %s which is outside of %s", tc.name, ip, cidr) 139 //} 140 if found.Has(ip.String()) { 141 t.Fatalf("[%s] allocated %s twice @ %d", tc.name, ip, count) 142 } 143 found.Insert(ip.String()) 144 } 145 if _, err := r.AllocateNext(); err == nil { 146 t.Fatal(err) 147 } 148 149 if !found.Has(tc.released) { 150 t.Fatalf("not allocated address to be releases %s found %d", tc.released, len(found)) 151 } 152 released := netutils.ParseIPSloppy(tc.released) 153 if err := r.Release(released); err != nil { 154 t.Fatal(err) 155 } 156 if f := r.Free(); f != 1 { 157 t.Errorf("[%s] wrong free: expected %d, got %d", tc.name, 1, f) 158 } 159 if f := r.Used(); f != (tc.free - 1) { 160 t.Errorf("[%s] wrong free: expected %d, got %d", tc.name, tc.free-1, f) 161 } 162 ip, err := r.AllocateNext() 163 if err != nil { 164 t.Fatal(err) 165 } 166 if !released.Equal(ip) { 167 t.Errorf("[%s] unexpected %s : %s", tc.name, ip, released) 168 } 169 170 if err := r.Release(released); err != nil { 171 t.Fatal(err) 172 } 173 for _, outOfRange := range tc.outOfRange { 174 err = r.Allocate(netutils.ParseIPSloppy(outOfRange)) 175 if err == nil { 176 t.Fatalf("unexpacted allocating of %s", outOfRange) 177 } 178 } 179 if err := r.Allocate(netutils.ParseIPSloppy(tc.alreadyAllocated)); err == nil { 180 t.Fatalf("unexpected allocation of %s", tc.alreadyAllocated) 181 } 182 if f := r.Free(); f != 1 { 183 t.Errorf("[%s] wrong free: expected %d, got %d", tc.name, 1, f) 184 } 185 if f := r.Used(); f != (tc.free - 1) { 186 t.Errorf("[%s] wrong free: expected %d, got %d", tc.name, tc.free-1, f) 187 } 188 if err := r.Allocate(released); err != nil { 189 t.Fatal(err) 190 } 191 if f := r.Free(); f != 0 { 192 t.Errorf("[%s] wrong free: expected %d, got %d", tc.name, 0, f) 193 } 194 if f := r.Used(); f != tc.free { 195 t.Errorf("[%s] wrong free: expected %d, got %d", tc.name, tc.free, f) 196 } 197 }) 198 } 199 } 200 201 func TestAllocateTinyIPAllocator(t *testing.T) { 202 _, cidr, err := netutils.ParseCIDRSloppy("192.168.1.0/32") 203 if err != nil { 204 t.Fatal(err) 205 } 206 207 r, err := newTestAllocator(cidr) 208 if err != nil { 209 t.Fatal(err) 210 } 211 defer r.Destroy() 212 213 if f := r.Free(); f != 0 { 214 t.Errorf("free: %d", f) 215 } 216 if _, err := r.AllocateNext(); err == nil { 217 t.Error(err) 218 } 219 } 220 221 func TestAllocateReservedIPAllocator(t *testing.T) { 222 _, cidr, err := netutils.ParseCIDRSloppy("192.168.1.0/25") 223 if err != nil { 224 t.Fatal(err) 225 } 226 r, err := newTestAllocator(cidr) 227 if err != nil { 228 t.Fatal(err) 229 } 230 defer r.Destroy() 231 // allocate all addresses on the dynamic block 232 // subnet /25 = 128 ; dynamic block size is min(max(16,128/16),256) = 16 233 dynamicOffset := calculateRangeOffset(cidr) 234 dynamicBlockSize := int(r.size) - dynamicOffset 235 for i := 0; i < dynamicBlockSize; i++ { 236 _, err := r.AllocateNext() 237 if err != nil { 238 t.Errorf("Unexpected error trying to allocate: %v", err) 239 } 240 } 241 for i := dynamicOffset; i < int(r.size); i++ { 242 ip := fmt.Sprintf("192.168.1.%d", i+1) 243 if !r.Has(netutils.ParseIPSloppy(ip)) { 244 t.Errorf("IP %s expected to be allocated", ip) 245 } 246 } 247 if f := r.Free(); f != dynamicOffset { 248 t.Errorf("expected %d free addresses, got %d", dynamicOffset, f) 249 } 250 // allocate all addresses on the static block 251 for i := 0; i < dynamicOffset; i++ { 252 ip := fmt.Sprintf("192.168.1.%d", i+1) 253 if err := r.Allocate(netutils.ParseIPSloppy(ip)); err != nil { 254 t.Errorf("Unexpected error trying to allocate IP %s: %v", ip, err) 255 } 256 } 257 if f := r.Free(); f != 0 { 258 t.Errorf("expected free equal to 0 got: %d", f) 259 } 260 // release one address in the allocated block and another a new one randomly 261 if err := r.Release(netutils.ParseIPSloppy("192.168.1.10")); err != nil { 262 t.Fatalf("Unexpected error trying to release ip 192.168.1.10: %v", err) 263 } 264 if _, err := r.AllocateNext(); err != nil { 265 t.Error(err) 266 } 267 if f := r.Free(); f != 0 { 268 t.Errorf("expected free equal to 0 got: %d", f) 269 } 270 } 271 272 func TestAllocateSmallIPAllocator(t *testing.T) { 273 _, cidr, err := netutils.ParseCIDRSloppy("192.168.1.240/30") 274 if err != nil { 275 t.Fatal(err) 276 } 277 r, err := newTestAllocator(cidr) 278 if err != nil { 279 t.Fatal(err) 280 } 281 defer r.Destroy() 282 283 if f := r.Free(); f != 2 { 284 t.Errorf("expected free equal to 2 got: %d", f) 285 } 286 found := sets.NewString() 287 for i := 0; i < 2; i++ { 288 ip, err := r.AllocateNext() 289 if err != nil { 290 t.Fatalf("error allocating %s try %d : %v", ip, i, err) 291 } 292 if found.Has(ip.String()) { 293 t.Fatalf("address %s has been already allocated", ip) 294 } 295 found.Insert(ip.String()) 296 } 297 for s := range found { 298 if !r.Has(netutils.ParseIPSloppy(s)) { 299 t.Fatalf("missing: %s", s) 300 } 301 if err := r.Allocate(netutils.ParseIPSloppy(s)); err == nil { 302 t.Fatal(err) 303 } 304 } 305 if f := r.Free(); f != 0 { 306 t.Errorf("expected free equal to 0 got: %d", f) 307 } 308 309 for i := 0; i < 100; i++ { 310 if ip, err := r.AllocateNext(); err == nil { 311 t.Fatalf("suddenly became not-full: %s", ip.String()) 312 } 313 } 314 315 } 316 317 func TestForEachIPAllocator(t *testing.T) { 318 _, cidr, err := netutils.ParseCIDRSloppy("192.168.1.0/24") 319 if err != nil { 320 t.Fatal(err) 321 } 322 testCases := []sets.String{ 323 sets.NewString(), 324 sets.NewString("192.168.1.1"), 325 sets.NewString("192.168.1.1", "192.168.1.254"), 326 sets.NewString("192.168.1.1", "192.168.1.128", "192.168.1.254"), 327 } 328 329 for i, tc := range testCases { 330 r, err := newTestAllocator(cidr) 331 if err != nil { 332 t.Fatal(err) 333 } 334 defer r.Destroy() 335 336 for ips := range tc { 337 ip := netutils.ParseIPSloppy(ips) 338 if err := r.Allocate(ip); err != nil { 339 t.Errorf("[%d] error allocating IP %v: %v", i, ip, err) 340 } 341 if !r.Has(ip) { 342 t.Errorf("[%d] expected IP %v allocated", i, ip) 343 } 344 } 345 calls := sets.NewString() 346 r.ForEach(func(ip net.IP) { 347 calls.Insert(ip.String()) 348 }) 349 if len(calls) != len(tc) { 350 t.Errorf("[%d] expected %d calls, got %d", i, len(tc), len(calls)) 351 } 352 if !calls.Equal(tc) { 353 t.Errorf("[%d] expected calls to equal testcase: %v vs %v", i, calls.List(), tc.List()) 354 } 355 } 356 } 357 358 func TestIPAllocatorClusterIPMetrics(t *testing.T) { 359 clearMetrics() 360 // create IPv4 allocator 361 cidrIPv4 := "10.0.0.0/24" 362 _, clusterCIDRv4, _ := netutils.ParseCIDRSloppy(cidrIPv4) 363 a, err := newTestAllocator(clusterCIDRv4) 364 if err != nil { 365 t.Fatal(err) 366 } 367 a.EnableMetrics() 368 // create IPv6 allocator 369 cidrIPv6 := "2001:db8::/112" 370 _, clusterCIDRv6, _ := netutils.ParseCIDRSloppy(cidrIPv6) 371 b, err := newTestAllocator(clusterCIDRv6) 372 if err != nil { 373 t.Fatalf("unexpected error creating CidrSet: %v", err) 374 } 375 b.EnableMetrics() 376 377 // Check initial state 378 em := testMetrics{ 379 free: 0, 380 used: 0, 381 allocated: 0, 382 errors: 0, 383 } 384 expectMetrics(t, cidrIPv4, em) 385 em = testMetrics{ 386 free: 0, 387 used: 0, 388 allocated: 0, 389 errors: 0, 390 } 391 expectMetrics(t, cidrIPv6, em) 392 393 // allocate 2 IPv4 addresses 394 found := sets.NewString() 395 for i := 0; i < 2; i++ { 396 ip, err := a.AllocateNext() 397 if err != nil { 398 t.Fatal(err) 399 } 400 if found.Has(ip.String()) { 401 t.Fatalf("already reserved: %s", ip) 402 } 403 found.Insert(ip.String()) 404 } 405 406 em = testMetrics{ 407 free: 252, 408 used: 2, 409 allocated: 2, 410 errors: 0, 411 } 412 expectMetrics(t, cidrIPv4, em) 413 414 // try to allocate the same IP addresses 415 for s := range found { 416 if !a.Has(netutils.ParseIPSloppy(s)) { 417 t.Fatalf("missing: %s", s) 418 } 419 if err := a.Allocate(netutils.ParseIPSloppy(s)); err != ErrAllocated { 420 t.Fatal(err) 421 } 422 } 423 em = testMetrics{ 424 free: 252, 425 used: 2, 426 allocated: 2, 427 errors: 2, 428 } 429 expectMetrics(t, cidrIPv4, em) 430 431 // release the addresses allocated 432 for s := range found { 433 if !a.Has(netutils.ParseIPSloppy(s)) { 434 t.Fatalf("missing: %s", s) 435 } 436 if err := a.Release(netutils.ParseIPSloppy(s)); err != nil { 437 t.Fatal(err) 438 } 439 } 440 em = testMetrics{ 441 free: 254, 442 used: 0, 443 allocated: 2, 444 errors: 2, 445 } 446 expectMetrics(t, cidrIPv4, em) 447 448 // allocate 264 addresses for each allocator 449 // the full range and 10 more (254 + 10 = 264) for IPv4 450 for i := 0; i < 264; i++ { 451 a.AllocateNext() 452 b.AllocateNext() 453 } 454 em = testMetrics{ 455 free: 0, 456 used: 254, 457 allocated: 256, // this is a counter, we already had 2 allocations and we did 254 more 458 errors: 12, 459 } 460 expectMetrics(t, cidrIPv4, em) 461 em = testMetrics{ 462 free: 65271, // IPv6 clusterIP range is capped to 2^16 and consider the broadcast address as valid 463 used: 264, 464 allocated: 264, 465 errors: 0, 466 } 467 expectMetrics(t, cidrIPv6, em) 468 } 469 470 func TestIPAllocatorClusterIPAllocatedMetrics(t *testing.T) { 471 clearMetrics() 472 // create IPv4 allocator 473 cidrIPv4 := "10.0.0.0/25" 474 _, clusterCIDRv4, _ := netutils.ParseCIDRSloppy(cidrIPv4) 475 a, err := newTestAllocator(clusterCIDRv4) 476 if err != nil { 477 t.Fatal(err) 478 } 479 a.EnableMetrics() 480 481 em := testMetrics{ 482 free: 0, 483 used: 0, 484 allocated: 0, 485 errors: 0, 486 } 487 expectMetrics(t, cidrIPv4, em) 488 489 // allocate 2 dynamic IPv4 addresses 490 found := sets.NewString() 491 for i := 0; i < 2; i++ { 492 ip, err := a.AllocateNext() 493 if err != nil { 494 t.Fatal(err) 495 } 496 if found.Has(ip.String()) { 497 t.Fatalf("already reserved: %s", ip) 498 } 499 found.Insert(ip.String()) 500 } 501 502 dynamic_allocated, err := testutil.GetCounterMetricValue(clusterIPAllocations.WithLabelValues(cidrIPv4, "dynamic")) 503 if err != nil { 504 t.Errorf("failed to get %s value, err: %v", clusterIPAllocations.Name, err) 505 } 506 if dynamic_allocated != 2 { 507 t.Fatalf("Expected 2 received %f", dynamic_allocated) 508 } 509 510 // try to allocate the same IP addresses 511 for s := range found { 512 if !a.Has(netutils.ParseIPSloppy(s)) { 513 t.Fatalf("missing: %s", s) 514 } 515 if err := a.Allocate(netutils.ParseIPSloppy(s)); err != ErrAllocated { 516 t.Fatal(err) 517 } 518 } 519 520 static_errors, err := testutil.GetCounterMetricValue(clusterIPAllocationErrors.WithLabelValues(cidrIPv4, "static")) 521 if err != nil { 522 t.Errorf("failed to get %s value, err: %v", clusterIPAllocationErrors.Name, err) 523 } 524 if static_errors != 2 { 525 t.Fatalf("Expected 2 received %f", dynamic_allocated) 526 } 527 } 528 529 func Test_addOffsetAddress(t *testing.T) { 530 tests := []struct { 531 name string 532 address netip.Addr 533 offset uint64 534 want netip.Addr 535 }{ 536 { 537 name: "IPv4 offset 0", 538 address: netip.MustParseAddr("192.168.0.0"), 539 offset: 0, 540 want: netip.MustParseAddr("192.168.0.0"), 541 }, 542 { 543 name: "IPv4 offset 0 not nibble boundary", 544 address: netip.MustParseAddr("192.168.0.11"), 545 offset: 0, 546 want: netip.MustParseAddr("192.168.0.11"), 547 }, 548 { 549 name: "IPv4 offset 1", 550 address: netip.MustParseAddr("192.168.0.0"), 551 offset: 1, 552 want: netip.MustParseAddr("192.168.0.1"), 553 }, 554 { 555 name: "IPv4 offset 1 not nibble boundary", 556 address: netip.MustParseAddr("192.168.0.11"), 557 offset: 1, 558 want: netip.MustParseAddr("192.168.0.12"), 559 }, 560 { 561 name: "IPv6 offset 1", 562 address: netip.MustParseAddr("fd00:1:2:3::"), 563 offset: 1, 564 want: netip.MustParseAddr("fd00:1:2:3::1"), 565 }, 566 { 567 name: "IPv6 offset 1 not nibble boundary", 568 address: netip.MustParseAddr("fd00:1:2:3::a"), 569 offset: 1, 570 want: netip.MustParseAddr("fd00:1:2:3::b"), 571 }, 572 { 573 name: "IPv4 offset last", 574 address: netip.MustParseAddr("192.168.0.0"), 575 offset: 255, 576 want: netip.MustParseAddr("192.168.0.255"), 577 }, 578 { 579 name: "IPv6 offset last", 580 address: netip.MustParseAddr("fd00:1:2:3::"), 581 offset: 0x7FFFFFFFFFFFFFFF, 582 want: netip.MustParseAddr("fd00:1:2:3:7FFF:FFFF:FFFF:FFFF"), 583 }, 584 { 585 name: "IPv4 offset middle", 586 address: netip.MustParseAddr("192.168.0.0"), 587 offset: 128, 588 want: netip.MustParseAddr("192.168.0.128"), 589 }, 590 { 591 name: "IPv4 with leading zeros", 592 address: netip.MustParseAddr("0.0.1.8"), 593 offset: 138, 594 want: netip.MustParseAddr("0.0.1.146"), 595 }, 596 { 597 name: "IPv6 with leading zeros", 598 address: netip.MustParseAddr("00fc::1"), 599 offset: 255, 600 want: netip.MustParseAddr("fc::100"), 601 }, 602 { 603 name: "IPv6 offset 255", 604 address: netip.MustParseAddr("2001:db8:1::101"), 605 offset: 255, 606 want: netip.MustParseAddr("2001:db8:1::200"), 607 }, 608 { 609 name: "IPv6 offset 1025", 610 address: netip.MustParseAddr("fd00:1:2:3::"), 611 offset: 1025, 612 want: netip.MustParseAddr("fd00:1:2:3::401"), 613 }, 614 } 615 for _, tt := range tests { 616 t.Run(tt.name, func(t *testing.T) { 617 got, err := addOffsetAddress(tt.address, tt.offset) 618 if !reflect.DeepEqual(got, tt.want) || err != nil { 619 t.Errorf("offsetAddress() = %v, want %v", got, tt.want) 620 } 621 // double check to avoid mistakes on the hardcoded values 622 // avoid large numbers or it will timeout the test 623 if tt.offset < 2048 { 624 want := tt.address 625 var i uint64 626 for i = 0; i < tt.offset; i++ { 627 want = want.Next() 628 } 629 if !reflect.DeepEqual(got, tt.want) || err != nil { 630 t.Errorf("offsetAddress() = %v, want %v", got, tt.want) 631 } 632 } 633 }) 634 } 635 } 636 637 func Test_broadcastAddress(t *testing.T) { 638 tests := []struct { 639 name string 640 subnet netip.Prefix 641 want netip.Addr 642 }{ 643 { 644 name: "ipv4", 645 subnet: netip.MustParsePrefix("192.168.0.0/24"), 646 want: netip.MustParseAddr("192.168.0.255"), 647 }, 648 { 649 name: "ipv4 no nibble boundary", 650 subnet: netip.MustParsePrefix("10.0.0.0/12"), 651 want: netip.MustParseAddr("10.15.255.255"), 652 }, 653 { 654 name: "ipv6", 655 subnet: netip.MustParsePrefix("fd00:1:2:3::/64"), 656 want: netip.MustParseAddr("fd00:1:2:3:FFFF:FFFF:FFFF:FFFF"), 657 }, 658 { 659 name: "ipv6 00fc::/112", 660 subnet: netip.MustParsePrefix("00fc::/112"), 661 want: netip.MustParseAddr("fc::ffff"), 662 }, 663 { 664 name: "ipv6 fc00::/112", 665 subnet: netip.MustParsePrefix("fc00::/112"), 666 want: netip.MustParseAddr("fc00::ffff"), 667 }, 668 } 669 for _, tt := range tests { 670 t.Run(tt.name, func(t *testing.T) { 671 if got, err := broadcastAddress(tt.subnet); !reflect.DeepEqual(got, tt.want) || err != nil { 672 t.Errorf("broadcastAddress() = %v, want %v", got, tt.want) 673 } 674 }) 675 } 676 } 677 678 func Test_hostsPerNetwork(t *testing.T) { 679 testCases := []struct { 680 name string 681 cidr string 682 addrs uint64 683 }{ 684 { 685 name: "supported IPv4 cidr", 686 cidr: "192.168.1.0/24", 687 addrs: 254, 688 }, 689 { 690 name: "single IPv4 host", 691 cidr: "192.168.1.0/32", 692 addrs: 0, 693 }, 694 { 695 name: "small IPv4 cidr", 696 cidr: "192.168.1.0/31", 697 addrs: 0, 698 }, 699 { 700 name: "very large IPv4 cidr", 701 cidr: "0.0.0.0/1", 702 addrs: math.MaxInt32 - 1, 703 }, 704 { 705 name: "full IPv4 range", 706 cidr: "0.0.0.0/0", 707 addrs: math.MaxUint32 - 1, 708 }, 709 { 710 name: "supported IPv6 cidr", 711 cidr: "2001:db2::/112", 712 addrs: 65535, 713 }, 714 { 715 name: "single IPv6 host", 716 cidr: "2001:db8::/128", 717 addrs: 0, 718 }, 719 { 720 name: "small IPv6 cidr", 721 cidr: "2001:db8::/127", 722 addrs: 1, 723 }, 724 { 725 name: "largest IPv6 for Int64", 726 cidr: "2001:db8::/65", 727 addrs: math.MaxInt64, 728 }, 729 { 730 name: "largest IPv6 for Uint64", 731 cidr: "2001:db8::/64", 732 addrs: math.MaxUint64, 733 }, 734 { 735 name: "very large IPv6 cidr", 736 cidr: "2001:db8::/1", 737 addrs: math.MaxUint64, 738 }, 739 } 740 741 for _, tc := range testCases { 742 _, cidr, err := netutils.ParseCIDRSloppy(tc.cidr) 743 if err != nil { 744 t.Errorf("failed to parse cidr for test %s, unexpected error: '%s'", tc.name, err) 745 } 746 if size := hostsPerNetwork(cidr); size != tc.addrs { 747 t.Errorf("test %s failed. %s should have a range size of %d, got %d", 748 tc.name, tc.cidr, tc.addrs, size) 749 } 750 } 751 } 752 753 func Test_ipIterator(t *testing.T) { 754 tests := []struct { 755 name string 756 first netip.Addr 757 last netip.Addr 758 offset uint64 759 want []string 760 }{ 761 { 762 name: "start from first address small range", 763 first: netip.MustParseAddr("192.168.0.1"), 764 last: netip.MustParseAddr("192.168.0.2"), 765 offset: 0, 766 want: []string{"192.168.0.1", "192.168.0.2"}, 767 }, { 768 name: "start from last address small range", 769 first: netip.MustParseAddr("192.168.0.1"), 770 last: netip.MustParseAddr("192.168.0.2"), 771 offset: 1, 772 want: []string{"192.168.0.2", "192.168.0.1"}, 773 }, { 774 name: "start from offset out of range address small range", 775 first: netip.MustParseAddr("192.168.0.1"), 776 last: netip.MustParseAddr("192.168.0.2"), 777 offset: 10, 778 want: []string{"192.168.0.1", "192.168.0.2"}, 779 }, { 780 name: "start from first address", 781 first: netip.MustParseAddr("192.168.0.1"), 782 last: netip.MustParseAddr("192.168.0.7"), 783 offset: 0, 784 want: []string{"192.168.0.1", "192.168.0.2", "192.168.0.3", "192.168.0.4", "192.168.0.5", "192.168.0.6", "192.168.0.7"}, 785 }, { 786 name: "start from middle address", 787 first: netip.MustParseAddr("192.168.0.1"), 788 last: netip.MustParseAddr("192.168.0.7"), 789 offset: 2, 790 want: []string{"192.168.0.3", "192.168.0.4", "192.168.0.5", "192.168.0.6", "192.168.0.7", "192.168.0.1", "192.168.0.2"}, 791 }, { 792 name: "start from last address", 793 first: netip.MustParseAddr("192.168.0.1"), 794 last: netip.MustParseAddr("192.168.0.7"), 795 offset: 6, 796 want: []string{"192.168.0.7", "192.168.0.1", "192.168.0.2", "192.168.0.3", "192.168.0.4", "192.168.0.5", "192.168.0.6"}, 797 }, 798 } 799 for _, tt := range tests { 800 t.Run(tt.name, func(t *testing.T) { 801 got := []string{} 802 iterator := ipIterator(tt.first, tt.last, tt.offset) 803 804 for { 805 ip := iterator() 806 if !ip.IsValid() { 807 break 808 } 809 got = append(got, ip.String()) 810 } 811 if !reflect.DeepEqual(got, tt.want) { 812 t.Errorf("ipIterator() = %v, want %v", got, tt.want) 813 } 814 // check the iterator is fully stopped 815 for i := 0; i < 5; i++ { 816 if ip := iterator(); ip.IsValid() { 817 t.Errorf("iterator should not return more addresses: %v", ip) 818 } 819 } 820 }) 821 } 822 } 823 824 func Test_ipIterator_Number(t *testing.T) { 825 tests := []struct { 826 name string 827 first netip.Addr 828 last netip.Addr 829 offset uint64 830 want uint64 831 }{ 832 { 833 name: "start from first address small range", 834 first: netip.MustParseAddr("192.168.0.1"), 835 last: netip.MustParseAddr("192.168.0.2"), 836 offset: 0, 837 want: 2, 838 }, { 839 name: "start from last address small range", 840 first: netip.MustParseAddr("192.168.0.1"), 841 last: netip.MustParseAddr("192.168.0.2"), 842 offset: 1, 843 want: 2, 844 }, { 845 name: "start from offset out of range small range", 846 first: netip.MustParseAddr("192.168.0.1"), 847 last: netip.MustParseAddr("192.168.0.2"), 848 offset: 10, 849 want: 2, 850 }, { 851 name: "start from first address", 852 first: netip.MustParseAddr("192.168.0.1"), 853 last: netip.MustParseAddr("192.168.0.7"), 854 offset: 0, 855 want: 7, 856 }, { 857 name: "start from middle address", 858 first: netip.MustParseAddr("192.168.0.1"), 859 last: netip.MustParseAddr("192.168.0.7"), 860 offset: 2, 861 want: 7, 862 }, { 863 name: "start from last address", 864 first: netip.MustParseAddr("192.168.0.1"), 865 last: netip.MustParseAddr("192.168.0.7"), 866 offset: 6, 867 want: 7, 868 }, { 869 name: "start from first address large range", 870 first: netip.MustParseAddr("2001:db8:1::101"), 871 last: netip.MustParseAddr("2001:db8:1::fff"), 872 offset: 0, 873 want: 3839, 874 }, { 875 name: "start from address in the middle", 876 first: netip.MustParseAddr("2001:db8:1::101"), 877 last: netip.MustParseAddr("2001:db8:1::fff"), 878 offset: 255, 879 want: 3839, 880 }, { 881 name: "start from last address", 882 first: netip.MustParseAddr("2001:db8:1::101"), 883 last: netip.MustParseAddr("2001:db8:1::fff"), 884 offset: 3838, 885 want: 3839, 886 }, 887 } 888 for _, tt := range tests { 889 t.Run(tt.name, func(t *testing.T) { 890 var got uint64 891 iterator := ipIterator(tt.first, tt.last, tt.offset) 892 893 for { 894 ip := iterator() 895 if !ip.IsValid() { 896 break 897 } 898 got++ 899 } 900 if got != tt.want { 901 t.Errorf("ipIterator() = %d, want %d", got, tt.want) 902 } 903 // check the iterator is fully stopped 904 for i := 0; i < 5; i++ { 905 if ip := iterator(); ip.IsValid() { 906 t.Errorf("iterator should not return more addresses: %v", ip) 907 } 908 } 909 }) 910 } 911 } 912 913 func TestAllocateNextFC(t *testing.T) { 914 _, cidr, err := netutils.ParseCIDRSloppy("fc::/112") 915 if err != nil { 916 t.Fatal(err) 917 } 918 t.Logf("CIDR %s", cidr) 919 920 r, err := newTestAllocator(cidr) 921 if err != nil { 922 t.Fatal(err) 923 } 924 defer r.Destroy() 925 ip, err := r.AllocateNext() 926 if err != nil { 927 t.Fatalf("wrong ip %s : %v", ip, err) 928 } 929 t.Log(ip.String()) 930 } 931 932 func BenchmarkIPAllocatorAllocateNextIPv4Size1048574(b *testing.B) { 933 _, cidr, err := netutils.ParseCIDRSloppy("10.0.0.0/12") 934 if err != nil { 935 b.Fatal(err) 936 } 937 r, err := newTestAllocator(cidr) 938 if err != nil { 939 b.Fatal(err) 940 } 941 defer r.Destroy() 942 943 for n := 0; n < b.N; n++ { 944 r.AllocateNext() 945 } 946 } 947 948 func BenchmarkIPAllocatorAllocateNextIPv6Size65535(b *testing.B) { 949 _, cidr, err := netutils.ParseCIDRSloppy("fd00::/120") 950 if err != nil { 951 b.Fatal(err) 952 } 953 r, err := newTestAllocator(cidr) 954 if err != nil { 955 b.Fatal(err) 956 } 957 defer r.Destroy() 958 959 for n := 0; n < b.N; n++ { 960 r.AllocateNext() 961 } 962 }