github.com/vmware/go-vcloud-director/v2@v2.24.0/govcd/nsxt_edgegateway_unit_test.go (about) 1 //go:build unit || ALL 2 3 /* 4 * Copyright 2023 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. 5 */ 6 7 package govcd 8 9 import ( 10 "net/netip" 11 "reflect" 12 "testing" 13 14 "github.com/vmware/go-vcloud-director/v2/types/v56" 15 ) 16 17 func Test_filterIpSlicesBySubnet(t *testing.T) { 18 type args struct { 19 ipRange []netip.Addr 20 subnet netip.Prefix 21 } 22 tests := []struct { 23 name string 24 args args 25 want []netip.Addr 26 wantErr bool 27 }{ 28 {name: "BothArgsEmpty", args: args{}, want: nil, wantErr: true}, 29 {name: "EmptyRange", args: args{subnet: netip.MustParsePrefix("10.10.10.1/24")}, want: nil, wantErr: true}, 30 {name: "EmptySubnet", args: args{ipRange: []netip.Addr{netip.MustParseAddr("10.1.1.1")}}, want: nil, wantErr: true}, 31 { 32 name: "SingleIpMatchingSubnet", 33 args: args{ 34 ipRange: []netip.Addr{netip.MustParseAddr("10.0.0.2")}, 35 subnet: netip.MustParsePrefix("10.0.0.1/24"), 36 }, 37 want: []netip.Addr{netip.MustParseAddr("10.0.0.2")}, 38 wantErr: false, 39 }, 40 { 41 name: "SingleIpNotMatchingSubnet", 42 args: args{ 43 ipRange: []netip.Addr{netip.MustParseAddr("10.0.0.2")}, 44 subnet: netip.MustParsePrefix("20.0.0.1/24"), 45 }, 46 want: []netip.Addr{}, 47 wantErr: false, 48 }, 49 { 50 name: "ManyIPsSomeMatch", 51 args: args{ 52 ipRange: []netip.Addr{ 53 netip.MustParseAddr("10.0.0.2"), 54 netip.MustParseAddr("192.0.0.2"), 55 netip.MustParseAddr("11.0.0.2"), 56 netip.MustParseAddr("20.0.0.2"), 57 netip.MustParseAddr("20.0.0.3"), 58 netip.MustParseAddr("10.0.0.2"), 59 }, 60 subnet: netip.MustParsePrefix("20.0.0.1/24"), 61 }, 62 want: []netip.Addr{ 63 netip.MustParseAddr("20.0.0.2"), 64 netip.MustParseAddr("20.0.0.3"), 65 }, 66 wantErr: false, 67 }, 68 { 69 name: "DuplicateIPsInRange", 70 args: args{ 71 ipRange: []netip.Addr{ 72 netip.MustParseAddr("10.0.0.2"), 73 netip.MustParseAddr("10.0.0.2"), 74 netip.MustParseAddr("192.0.0.2"), 75 netip.MustParseAddr("11.0.0.2"), 76 netip.MustParseAddr("20.0.0.2"), 77 netip.MustParseAddr("20.0.0.3"), 78 netip.MustParseAddr("20.0.0.3"), 79 netip.MustParseAddr("10.0.0.2"), 80 }, 81 subnet: netip.MustParsePrefix("20.0.0.1/24"), 82 }, 83 want: []netip.Addr{ 84 netip.MustParseAddr("20.0.0.2"), 85 netip.MustParseAddr("20.0.0.3"), 86 netip.MustParseAddr("20.0.0.3"), 87 }, 88 wantErr: false, 89 }, 90 // IPv6 91 { 92 name: "IPv6SingleMatchingSubnet", 93 args: args{ 94 ipRange: []netip.Addr{netip.MustParseAddr("2001:0DB8:0000:000b:0000:0000:0000:0001")}, 95 subnet: netip.MustParsePrefix("2001:0DB8:0000:000b::/64"), 96 }, 97 want: []netip.Addr{netip.MustParseAddr("2001:0DB8:0000:000b:0000:0000:0000:0001")}, 98 wantErr: false, 99 }, 100 { 101 name: "IPv6SingleNotMatchingSubnet", 102 args: args{ 103 ipRange: []netip.Addr{netip.MustParseAddr("2001:0DB6:0000:000b:0000:0000:0000:0001")}, 104 subnet: netip.MustParsePrefix("2001:0DB8:0000:000b::/64"), 105 }, 106 want: []netip.Addr{}, 107 wantErr: false, 108 }, 109 { 110 name: "IPv6ManyIPsSomeMatch", 111 args: args{ 112 ipRange: []netip.Addr{ 113 netip.MustParseAddr("2001:1111:0000:000b:0000:0000:0000:0001"), 114 netip.MustParseAddr("2222:0DB8:0000:000b:0000:0000:0000:0001"), 115 netip.MustParseAddr("2001:0DB8:0000:000b:0000:0000:0000:0001"), 116 netip.MustParseAddr("2001:0DB8:0000:000b:0000:0000:0000:0002"), 117 netip.MustParseAddr("2001:0DB8:0000:000b:0000:0000:0000:0003"), 118 netip.MustParseAddr("4001:0DB8:0000:000b:0000:0000:0000:0001"), 119 netip.MustParseAddr("4001:0DB8:0000:000b:0000:0000:0000:0001"), 120 }, 121 subnet: netip.MustParsePrefix("2001:0DB8:0000:000b::/64"), 122 }, 123 want: []netip.Addr{ 124 netip.MustParseAddr("2001:0DB8:0000:000b:0000:0000:0000:0001"), 125 netip.MustParseAddr("2001:0DB8:0000:000b:0000:0000:0000:0002"), 126 netip.MustParseAddr("2001:0DB8:0000:000b:0000:0000:0000:0003"), 127 }, 128 wantErr: false, 129 }, 130 { 131 name: "IPv6ManyIPsSomeDuplicatesMatch", 132 args: args{ 133 ipRange: []netip.Addr{ 134 netip.MustParseAddr("2001:1111:0000:000b:0000:0000:0000:0001"), 135 netip.MustParseAddr("2222:0DB8:0000:000b:0000:0000:0000:0001"), 136 netip.MustParseAddr("2001:0DB8:0000:000b:0000:0000:0000:0001"), 137 netip.MustParseAddr("2001:0DB8:0000:000b:0000:0000:0000:0002"), 138 netip.MustParseAddr("2001:0DB8:0000:000b:0000:0000:0000:0002"), 139 netip.MustParseAddr("2001:0DB8:0000:000b:0000:0000:0000:0003"), 140 netip.MustParseAddr("4001:0DB8:0000:000b:0000:0000:0000:0001"), 141 netip.MustParseAddr("4001:0DB8:0000:000b:0000:0000:0000:0001"), 142 }, 143 subnet: netip.MustParsePrefix("2001:0DB8:0000:000b::/64"), 144 }, 145 want: []netip.Addr{ 146 netip.MustParseAddr("2001:0DB8:0000:000b:0000:0000:0000:0001"), 147 netip.MustParseAddr("2001:0DB8:0000:000b:0000:0000:0000:0002"), 148 netip.MustParseAddr("2001:0DB8:0000:000b:0000:0000:0000:0002"), 149 netip.MustParseAddr("2001:0DB8:0000:000b:0000:0000:0000:0003"), 150 }, 151 wantErr: false, 152 }, 153 } 154 for _, tt := range tests { 155 t.Run(tt.name, func(t *testing.T) { 156 got, err := filterIpSlicesBySubnet(tt.args.ipRange, tt.args.subnet) 157 if (err != nil) != tt.wantErr { 158 t.Errorf("filterIpRangesInSubnet() error = %v, wantErr %v", err, tt.wantErr) 159 return 160 } 161 if !reflect.DeepEqual(got, tt.want) { 162 t.Errorf("filterIpRangesInSubnet() = %v, want %v", got, tt.want) 163 } 164 }) 165 } 166 } 167 168 func Test_ipSliceDifference(t *testing.T) { 169 type args struct { 170 minuendSlice []netip.Addr 171 subtrahendSlice []netip.Addr 172 } 173 tests := []struct { 174 name string 175 args args 176 want []netip.Addr 177 }{ 178 { 179 name: "BothParamsNil", 180 args: args{ 181 minuendSlice: nil, 182 subtrahendSlice: nil, 183 }, 184 want: nil, 185 }, 186 { 187 name: "MinuendNil", 188 args: args{ 189 minuendSlice: nil, 190 subtrahendSlice: []netip.Addr{ 191 netip.MustParseAddr("10.0.0.1"), 192 }, 193 }, 194 want: nil, 195 }, 196 { 197 name: "MinuendEmptySliceNilSubtrahend", 198 args: args{ 199 minuendSlice: make([]netip.Addr, 0), 200 subtrahendSlice: nil, 201 }, 202 want: make([]netip.Addr, 0), 203 }, 204 { 205 name: "MinuendEmptySlice", 206 args: args{ 207 minuendSlice: []netip.Addr{{}}, 208 subtrahendSlice: []netip.Addr{ 209 netip.MustParseAddr("10.0.0.1"), 210 }, 211 }, 212 want: []netip.Addr{{}}, 213 }, 214 { 215 name: "SubtrahendNil", 216 args: args{ 217 minuendSlice: []netip.Addr{ 218 netip.MustParseAddr("10.0.0.1"), 219 }, 220 subtrahendSlice: nil, 221 }, 222 want: []netip.Addr{ 223 netip.MustParseAddr("10.0.0.1"), 224 }, 225 }, 226 { 227 name: "SubtractUnavailableIP", 228 args: args{ 229 minuendSlice: []netip.Addr{ 230 netip.MustParseAddr("10.0.0.1"), 231 netip.MustParseAddr("10.0.0.2"), 232 }, 233 subtrahendSlice: []netip.Addr{ 234 netip.MustParseAddr("20.0.0.1"), 235 }, 236 }, 237 want: []netip.Addr{ 238 netip.MustParseAddr("10.0.0.1"), 239 netip.MustParseAddr("10.0.0.2"), 240 }, 241 }, 242 { 243 name: "SubtractIP", 244 args: args{ 245 minuendSlice: []netip.Addr{ 246 netip.MustParseAddr("10.0.0.1"), 247 netip.MustParseAddr("10.0.0.2"), 248 }, 249 subtrahendSlice: []netip.Addr{ 250 netip.MustParseAddr("10.0.0.2"), 251 }, 252 }, 253 want: []netip.Addr{ 254 netip.MustParseAddr("10.0.0.1"), 255 }, 256 }, 257 { 258 name: "RemoveAll", 259 args: args{ 260 minuendSlice: []netip.Addr{ 261 netip.MustParseAddr("10.0.0.1"), 262 netip.MustParseAddr("10.0.0.2"), 263 }, 264 subtrahendSlice: []netip.Addr{ 265 netip.MustParseAddr("10.0.0.1"), 266 netip.MustParseAddr("10.0.0.2"), 267 }, 268 }, 269 want: nil, 270 }, 271 { 272 name: "SubtractIPWithDuplicates", 273 args: args{ 274 minuendSlice: []netip.Addr{ 275 netip.MustParseAddr("10.0.0.1"), 276 netip.MustParseAddr("10.0.0.2"), 277 netip.MustParseAddr("10.0.0.2"), 278 }, 279 subtrahendSlice: []netip.Addr{ 280 netip.MustParseAddr("10.0.0.2"), 281 }, 282 }, 283 want: []netip.Addr{ 284 netip.MustParseAddr("10.0.0.1"), 285 }, 286 }, 287 // IPv6 288 { 289 name: "IPv6MinuendNil", 290 args: args{ 291 minuendSlice: nil, 292 subtrahendSlice: []netip.Addr{ 293 netip.MustParseAddr("4001:0DB8:0000:000b:0000:0000:0000:0001"), 294 }, 295 }, 296 want: nil, 297 }, 298 { 299 name: "IPv6SubtrahendNil", 300 args: args{ 301 minuendSlice: []netip.Addr{ 302 netip.MustParseAddr("4001:0DB8:0000:000b:0000:0000:0000:0001"), 303 }, 304 subtrahendSlice: nil, 305 }, 306 want: []netip.Addr{ 307 netip.MustParseAddr("4001:0DB8:0000:000b:0000:0000:0000:0001"), 308 }, 309 }, 310 { 311 name: "IPv6SubtractUnavailableIP", 312 args: args{ 313 minuendSlice: []netip.Addr{ 314 netip.MustParseAddr("4001:0DB8:0000:000b:0000:0000:0000:0001"), 315 netip.MustParseAddr("4001:0DB8:0000:000b:0000:0000:0000:0002"), 316 }, 317 subtrahendSlice: []netip.Addr{ 318 netip.MustParseAddr("9001:0DB8:0000:000b:0000:0000:0000:0002"), 319 }, 320 }, 321 want: []netip.Addr{ 322 netip.MustParseAddr("4001:0DB8:0000:000b:0000:0000:0000:0001"), 323 netip.MustParseAddr("4001:0DB8:0000:000b:0000:0000:0000:0002"), 324 }, 325 }, 326 { 327 name: "IPv6SubtractIP", 328 args: args{ 329 minuendSlice: []netip.Addr{ 330 netip.MustParseAddr("4001:0DB8:0000:000b:0000:0000:0000:0001"), 331 netip.MustParseAddr("4001:0DB8:0000:000b:0000:0000:0000:0002"), 332 }, 333 subtrahendSlice: []netip.Addr{ 334 netip.MustParseAddr("4001:0DB8:0000:000b:0000:0000:0000:0002"), 335 }, 336 }, 337 want: []netip.Addr{ 338 netip.MustParseAddr("4001:0DB8:0000:000b:0000:0000:0000:0001"), 339 }, 340 }, 341 { 342 name: "IPv6SubtractIPWithDuplicates", 343 args: args{ 344 minuendSlice: []netip.Addr{ 345 netip.MustParseAddr("4001:0DB8:0000:000b:0000:0000:0000:0001"), 346 netip.MustParseAddr("4001:0DB8:0000:000b:0000:0000:0000:0002"), 347 netip.MustParseAddr("4001:0DB8:0000:000b:0000:0000:0000:0002"), 348 }, 349 subtrahendSlice: []netip.Addr{ 350 netip.MustParseAddr("4001:0DB8:0000:000b:0000:0000:0000:0002"), 351 }, 352 }, 353 want: []netip.Addr{ 354 netip.MustParseAddr("4001:0DB8:0000:000b:0000:0000:0000:0001"), 355 }, 356 }, 357 } 358 for _, tt := range tests { 359 t.Run(tt.name, func(t *testing.T) { 360 if got := ipSliceDifference(tt.args.minuendSlice, tt.args.subtrahendSlice); !reflect.DeepEqual(got, tt.want) { 361 t.Errorf("ipRangeDifference() = %v, want %v", got, tt.want) 362 } 363 }) 364 } 365 } 366 367 func Test_flattenEdgeGatewayUplinkToIpSlice(t *testing.T) { 368 type args struct { 369 uplinks []types.EdgeGatewayUplinks 370 } 371 tests := []struct { 372 name string 373 args args 374 want []netip.Addr 375 wantErr bool 376 }{ 377 { 378 name: "SingleStartAndEndAddresses", 379 args: args{ 380 uplinks: []types.EdgeGatewayUplinks{ 381 { 382 Subnets: types.OpenAPIEdgeGatewaySubnets{ 383 Values: []types.OpenAPIEdgeGatewaySubnetValue{ 384 { 385 IPRanges: &types.OpenApiIPRanges{ 386 Values: []types.OpenApiIPRangeValues{ 387 { 388 StartAddress: "10.10.10.1", 389 EndAddress: "10.10.10.2", 390 }, 391 }, 392 }, 393 }, 394 }, 395 }, 396 }, 397 }, 398 }, 399 want: []netip.Addr{ 400 netip.MustParseAddr("10.10.10.1"), 401 netip.MustParseAddr("10.10.10.2"), 402 }, 403 wantErr: false, 404 }, 405 { 406 name: "ReverseStartAndEnd", 407 args: args{ 408 uplinks: []types.EdgeGatewayUplinks{ 409 { 410 Subnets: types.OpenAPIEdgeGatewaySubnets{ 411 Values: []types.OpenAPIEdgeGatewaySubnetValue{ 412 { 413 IPRanges: &types.OpenApiIPRanges{ 414 Values: []types.OpenApiIPRangeValues{ 415 { 416 StartAddress: "10.10.10.2", 417 EndAddress: "10.10.10.1", 418 }, 419 }, 420 }, 421 }, 422 }, 423 }, 424 }, 425 }, 426 }, 427 want: nil, 428 wantErr: true, 429 }, 430 { 431 name: "SameStartAndEndAddresses", 432 args: args{ 433 uplinks: []types.EdgeGatewayUplinks{ 434 { 435 Subnets: types.OpenAPIEdgeGatewaySubnets{ 436 Values: []types.OpenAPIEdgeGatewaySubnetValue{ 437 { 438 IPRanges: &types.OpenApiIPRanges{ 439 Values: []types.OpenApiIPRangeValues{ 440 { 441 StartAddress: "10.10.10.1", 442 EndAddress: "10.10.10.1", 443 }, 444 }, 445 }, 446 }, 447 }, 448 }, 449 }, 450 }, 451 }, 452 want: []netip.Addr{ 453 netip.MustParseAddr("10.10.10.1"), 454 }, 455 wantErr: false, 456 }, 457 { 458 name: "StartAddressOnly", 459 args: args{ 460 uplinks: []types.EdgeGatewayUplinks{ 461 { 462 Subnets: types.OpenAPIEdgeGatewaySubnets{ 463 Values: []types.OpenAPIEdgeGatewaySubnetValue{ 464 { 465 IPRanges: &types.OpenApiIPRanges{ 466 Values: []types.OpenApiIPRangeValues{ 467 { 468 StartAddress: "10.10.10.1", 469 }, 470 }, 471 }, 472 }, 473 }, 474 }, 475 }, 476 }, 477 }, 478 want: []netip.Addr{ 479 netip.MustParseAddr("10.10.10.1"), 480 }, 481 wantErr: false, 482 }, 483 { 484 name: "EmptyUplink", 485 args: args{ 486 uplinks: []types.EdgeGatewayUplinks{ 487 {}, 488 }, 489 }, 490 want: make([]netip.Addr, 0), 491 wantErr: false, 492 }, 493 { 494 name: "EmptySubnets", 495 args: args{ 496 uplinks: []types.EdgeGatewayUplinks{ 497 { 498 Subnets: types.OpenAPIEdgeGatewaySubnets{}, 499 }, 500 }, 501 }, 502 want: make([]netip.Addr, 0), 503 wantErr: false, 504 }, 505 { 506 name: "EmptySubnetValues", 507 args: args{ 508 uplinks: []types.EdgeGatewayUplinks{ 509 { 510 Subnets: types.OpenAPIEdgeGatewaySubnets{ 511 Values: []types.OpenAPIEdgeGatewaySubnetValue{}, 512 }, 513 }, 514 }, 515 }, 516 want: make([]netip.Addr, 0), 517 wantErr: false, 518 }, 519 { 520 name: "EmptySubnetValueIpRanges", 521 args: args{ 522 uplinks: []types.EdgeGatewayUplinks{ 523 { 524 Subnets: types.OpenAPIEdgeGatewaySubnets{ 525 Values: []types.OpenAPIEdgeGatewaySubnetValue{ 526 { 527 IPRanges: &types.OpenApiIPRanges{}, 528 }, 529 }, 530 }, 531 }, 532 }, 533 }, 534 want: make([]netip.Addr, 0), 535 wantErr: false, 536 }, 537 { 538 name: "EmptySubnetValueIpRangeValues", 539 args: args{ 540 uplinks: []types.EdgeGatewayUplinks{ 541 { 542 Subnets: types.OpenAPIEdgeGatewaySubnets{ 543 Values: []types.OpenAPIEdgeGatewaySubnetValue{ 544 { 545 IPRanges: &types.OpenApiIPRanges{ 546 Values: []types.OpenApiIPRangeValues{}, 547 }, 548 }, 549 }, 550 }, 551 }, 552 }, 553 }, 554 want: make([]netip.Addr, 0), 555 wantErr: false, 556 }, 557 } 558 for _, tt := range tests { 559 t.Run(tt.name, func(t *testing.T) { 560 got, err := flattenEdgeGatewayUplinkToIpSlice(tt.args.uplinks) 561 if (err != nil) != tt.wantErr { 562 t.Errorf("ipSliceFromEdgeGatewayUplinks() error = %v, wantErr %v", err, tt.wantErr) 563 return 564 } 565 if !reflect.DeepEqual(got, tt.want) { 566 t.Errorf("ipSliceFromEdgeGatewayUplinks() = %v, want %v", got, tt.want) 567 } 568 }) 569 } 570 } 571 572 // buildSimpleUplinkStructure helps to avoid deep indentation in Test_getUnusedExternalIPAddress 573 // where the structure itself is simple enough that has only one subnet and one IP range. Other 574 // tests in this table test still contain the full structure as it would be less readable if it was 575 // wrapped into multiple function calls. 576 func buildSimpleUplinkStructure(ipRangeValues []types.OpenApiIPRangeValues) []types.EdgeGatewayUplinks { 577 return []types.EdgeGatewayUplinks{ 578 { 579 Subnets: types.OpenAPIEdgeGatewaySubnets{ 580 Values: []types.OpenAPIEdgeGatewaySubnetValue{ 581 { 582 IPRanges: &types.OpenApiIPRanges{ 583 Values: ipRangeValues, 584 }, 585 }, 586 }, 587 }, 588 }, 589 } 590 } 591 592 func Test_getUnusedExternalIPAddress(t *testing.T) { 593 type args struct { 594 uplinks []types.EdgeGatewayUplinks 595 usedIpAddresses []*types.GatewayUsedIpAddress 596 requiredCount int 597 optionalSubnet netip.Prefix 598 } 599 tests := []struct { 600 name string 601 args args 602 want []netip.Addr 603 wantErr bool 604 }{ 605 { 606 name: "EmptyStructureError", 607 args: args{ 608 uplinks: []types.EdgeGatewayUplinks{}, 609 usedIpAddresses: []*types.GatewayUsedIpAddress{{}}, 610 requiredCount: 1, 611 optionalSubnet: netip.Prefix{}, 612 }, 613 want: nil, 614 wantErr: true, 615 }, 616 { 617 name: "SingleIpAvailable", 618 args: args{ 619 uplinks: buildSimpleUplinkStructure([]types.OpenApiIPRangeValues{ 620 { 621 StartAddress: "10.10.10.1", 622 EndAddress: "10.10.10.1", 623 }, 624 }), 625 usedIpAddresses: []*types.GatewayUsedIpAddress{}, 626 requiredCount: 1, 627 optionalSubnet: netip.Prefix{}, 628 }, 629 want: []netip.Addr{ 630 netip.MustParseAddr("10.10.10.1"), 631 }, 632 wantErr: false, 633 }, 634 { 635 name: "AvailableIPsFilteredOff", 636 args: args{ 637 uplinks: buildSimpleUplinkStructure([]types.OpenApiIPRangeValues{ 638 { 639 StartAddress: "10.10.10.1", 640 EndAddress: "10.10.10.10", 641 }, 642 }), 643 usedIpAddresses: []*types.GatewayUsedIpAddress{}, 644 requiredCount: 1, 645 optionalSubnet: netip.MustParsePrefix("20.10.10.0/24"), 646 }, 647 want: nil, 648 wantErr: true, 649 }, 650 { 651 name: "AvailableIPsFilteredOffAndUsed", 652 args: args{ 653 uplinks: buildSimpleUplinkStructure([]types.OpenApiIPRangeValues{ 654 { 655 StartAddress: "10.10.10.1", 656 EndAddress: "10.10.10.10", 657 }, 658 }), 659 usedIpAddresses: []*types.GatewayUsedIpAddress{{IPAddress: "10.10.10.1"}}, 660 requiredCount: 1, 661 optionalSubnet: netip.MustParsePrefix("20.10.10.0/24"), 662 }, 663 want: nil, 664 wantErr: true, 665 }, 666 { 667 name: "SingleIpFromMany", 668 args: args{ 669 uplinks: buildSimpleUplinkStructure([]types.OpenApiIPRangeValues{ 670 { 671 StartAddress: "10.10.10.15", 672 EndAddress: "10.10.10.200", 673 }, 674 }), 675 usedIpAddresses: []*types.GatewayUsedIpAddress{}, 676 requiredCount: 1, 677 optionalSubnet: netip.Prefix{}, 678 }, 679 want: []netip.Addr{ 680 netip.MustParseAddr("10.10.10.15"), 681 }, 682 wantErr: false, 683 }, 684 { 685 name: "CrossBoundary", 686 args: args{ 687 uplinks: buildSimpleUplinkStructure([]types.OpenApiIPRangeValues{ 688 { 689 StartAddress: "10.10.10.255", 690 EndAddress: "10.10.11.1", 691 }, 692 }), 693 usedIpAddresses: []*types.GatewayUsedIpAddress{}, 694 requiredCount: 3, 695 optionalSubnet: netip.Prefix{}, 696 }, 697 want: []netip.Addr{ 698 netip.MustParseAddr("10.10.10.255"), 699 netip.MustParseAddr("10.10.11.0"), 700 netip.MustParseAddr("10.10.11.1"), 701 }, 702 wantErr: false, 703 }, 704 { 705 name: "CrossBoundaryPrefix", 706 args: args{ 707 uplinks: buildSimpleUplinkStructure([]types.OpenApiIPRangeValues{ 708 { 709 StartAddress: "10.10.10.255", 710 EndAddress: "10.10.11.1", 711 }, 712 }), 713 usedIpAddresses: []*types.GatewayUsedIpAddress{}, 714 requiredCount: 2, 715 optionalSubnet: netip.MustParsePrefix("10.10.11.0/24"), 716 }, 717 want: []netip.Addr{ 718 netip.MustParseAddr("10.10.11.0"), 719 netip.MustParseAddr("10.10.11.1"), 720 }, 721 wantErr: false, 722 }, 723 { 724 name: "CrossBoundaryPrefixAndUsed", 725 args: args{ 726 uplinks: buildSimpleUplinkStructure([]types.OpenApiIPRangeValues{ 727 { 728 StartAddress: "10.10.10.255", 729 EndAddress: "10.10.11.1", 730 }, 731 }), 732 usedIpAddresses: []*types.GatewayUsedIpAddress{{IPAddress: "10.10.11.0"}}, 733 requiredCount: 1, 734 optionalSubnet: netip.MustParsePrefix("10.10.11.0/24"), 735 }, 736 want: []netip.Addr{ 737 netip.MustParseAddr("10.10.11.1"), 738 }, 739 wantErr: false, 740 }, 741 { 742 name: "IPv6SingleIpAvailable", 743 args: args{ 744 uplinks: buildSimpleUplinkStructure([]types.OpenApiIPRangeValues{ 745 { 746 StartAddress: "4001:0DB8:0000:000b:0000:0000:0000:0001", 747 EndAddress: "4001:0DB8:0000:000b:0000:0000:0000:0001", 748 }, 749 }), 750 usedIpAddresses: []*types.GatewayUsedIpAddress{}, 751 requiredCount: 1, 752 optionalSubnet: netip.Prefix{}, 753 }, 754 want: []netip.Addr{ 755 netip.MustParseAddr("4001:0DB8:0000:000b:0000:0000:0000:0001"), 756 }, 757 wantErr: false, 758 }, 759 { 760 name: "SingleIpAvailableStartOnly", 761 args: args{ 762 uplinks: buildSimpleUplinkStructure([]types.OpenApiIPRangeValues{ 763 { 764 StartAddress: "10.10.10.1", 765 }, 766 }), 767 usedIpAddresses: []*types.GatewayUsedIpAddress{}, 768 requiredCount: 1, 769 optionalSubnet: netip.Prefix{}, 770 }, 771 want: []netip.Addr{ 772 netip.MustParseAddr("10.10.10.1"), 773 }, 774 wantErr: false, 775 }, 776 { 777 name: "IPv6SingleIpAvailableStartOnly", 778 args: args{ 779 uplinks: buildSimpleUplinkStructure([]types.OpenApiIPRangeValues{ 780 { 781 StartAddress: "4001:0DB8:0000:000b:0000:0000:0000:0001", 782 }, 783 }), 784 usedIpAddresses: []*types.GatewayUsedIpAddress{}, 785 requiredCount: 1, 786 optionalSubnet: netip.Prefix{}, 787 }, 788 want: []netip.Addr{ 789 netip.MustParseAddr("4001:0DB8:0000:000b:0000:0000:0000:0001"), 790 }, 791 wantErr: false, 792 }, 793 { 794 name: "InvalidIpRange", 795 args: args{ 796 uplinks: buildSimpleUplinkStructure([]types.OpenApiIPRangeValues{ 797 { 798 // Start Address is higher than end IP address 799 StartAddress: "10.10.10.200", 800 EndAddress: "10.10.10.1", 801 }, 802 }), 803 usedIpAddresses: []*types.GatewayUsedIpAddress{}, 804 requiredCount: 1, 805 optionalSubnet: netip.Prefix{}, 806 }, 807 want: nil, 808 wantErr: true, 809 }, 810 { 811 name: "InsufficientIPs", 812 args: args{ 813 uplinks: buildSimpleUplinkStructure([]types.OpenApiIPRangeValues{ 814 { 815 StartAddress: "10.10.10.1", 816 EndAddress: "10.10.10.6", 817 }, 818 }), 819 usedIpAddresses: []*types.GatewayUsedIpAddress{}, 820 requiredCount: 7, 821 optionalSubnet: netip.Prefix{}, 822 }, 823 want: nil, 824 wantErr: true, 825 }, 826 { 827 name: "InsufficientIPsWithUsed", 828 args: args{ 829 uplinks: buildSimpleUplinkStructure([]types.OpenApiIPRangeValues{ 830 { 831 StartAddress: "10.10.10.1", 832 EndAddress: "10.10.10.6", 833 }, 834 }), 835 usedIpAddresses: []*types.GatewayUsedIpAddress{ 836 {IPAddress: "10.10.10.1"}, 837 }, 838 requiredCount: 6, 839 optionalSubnet: netip.Prefix{}, 840 }, 841 want: nil, 842 wantErr: true, 843 }, 844 { 845 name: "MultipleUplinks", 846 args: args{ 847 uplinks: []types.EdgeGatewayUplinks{ 848 { 849 Subnets: types.OpenAPIEdgeGatewaySubnets{ 850 Values: []types.OpenAPIEdgeGatewaySubnetValue{ 851 { 852 IPRanges: &types.OpenApiIPRanges{ 853 Values: []types.OpenApiIPRangeValues{ 854 { 855 StartAddress: "10.10.10.1", 856 EndAddress: "10.10.10.6", 857 }, 858 }, 859 }, 860 }, 861 }, 862 }, 863 }, 864 { 865 Subnets: types.OpenAPIEdgeGatewaySubnets{ 866 Values: []types.OpenAPIEdgeGatewaySubnetValue{ 867 { 868 IPRanges: &types.OpenApiIPRanges{ 869 Values: []types.OpenApiIPRangeValues{ 870 { 871 StartAddress: "20.10.10.1", 872 EndAddress: "20.10.10.6", 873 }, 874 }, 875 }, 876 }, 877 }, 878 }, 879 }, 880 { 881 Subnets: types.OpenAPIEdgeGatewaySubnets{ 882 Values: []types.OpenAPIEdgeGatewaySubnetValue{ 883 { 884 IPRanges: &types.OpenApiIPRanges{ 885 Values: []types.OpenApiIPRangeValues{ 886 { 887 StartAddress: "30.10.10.1", 888 EndAddress: "30.10.10.6", 889 }, 890 }, 891 }, 892 }, 893 }, 894 }, 895 }, 896 }, 897 usedIpAddresses: []*types.GatewayUsedIpAddress{}, 898 requiredCount: 18, 899 optionalSubnet: netip.Prefix{}, 900 }, 901 want: []netip.Addr{ 902 netip.MustParseAddr("10.10.10.1"), 903 netip.MustParseAddr("10.10.10.2"), 904 netip.MustParseAddr("10.10.10.3"), 905 netip.MustParseAddr("10.10.10.4"), 906 netip.MustParseAddr("10.10.10.5"), 907 netip.MustParseAddr("10.10.10.6"), 908 netip.MustParseAddr("20.10.10.1"), 909 netip.MustParseAddr("20.10.10.2"), 910 netip.MustParseAddr("20.10.10.3"), 911 netip.MustParseAddr("20.10.10.4"), 912 netip.MustParseAddr("20.10.10.5"), 913 netip.MustParseAddr("20.10.10.6"), 914 netip.MustParseAddr("30.10.10.1"), 915 netip.MustParseAddr("30.10.10.2"), 916 netip.MustParseAddr("30.10.10.3"), 917 netip.MustParseAddr("30.10.10.4"), 918 netip.MustParseAddr("30.10.10.5"), 919 netip.MustParseAddr("30.10.10.6"), 920 }, 921 wantErr: false, 922 }, 923 { 924 name: "MultipleUplinksWithUsedIPsInsufficient", 925 args: args{ 926 uplinks: []types.EdgeGatewayUplinks{ 927 { 928 Subnets: types.OpenAPIEdgeGatewaySubnets{ 929 Values: []types.OpenAPIEdgeGatewaySubnetValue{ 930 { 931 IPRanges: &types.OpenApiIPRanges{ 932 Values: []types.OpenApiIPRangeValues{ 933 { 934 StartAddress: "10.10.10.1", 935 EndAddress: "10.10.10.6", 936 }, 937 }, 938 }, 939 }, 940 }, 941 }, 942 }, 943 { 944 Subnets: types.OpenAPIEdgeGatewaySubnets{ 945 Values: []types.OpenAPIEdgeGatewaySubnetValue{ 946 { 947 IPRanges: &types.OpenApiIPRanges{ 948 Values: []types.OpenApiIPRangeValues{ 949 { 950 StartAddress: "20.10.10.1", 951 EndAddress: "20.10.10.6", 952 }, 953 }, 954 }, 955 }, 956 }, 957 }, 958 }, 959 { 960 Subnets: types.OpenAPIEdgeGatewaySubnets{ 961 Values: []types.OpenAPIEdgeGatewaySubnetValue{ 962 { 963 IPRanges: &types.OpenApiIPRanges{ 964 Values: []types.OpenApiIPRangeValues{ 965 { 966 StartAddress: "30.10.10.1", 967 EndAddress: "30.10.10.6", 968 }, 969 }, 970 }, 971 }, 972 }, 973 }, 974 }, 975 }, 976 usedIpAddresses: []*types.GatewayUsedIpAddress{ 977 {IPAddress: "10.10.10.1"}, 978 }, 979 requiredCount: 18, 980 optionalSubnet: netip.Prefix{}, 981 }, 982 want: nil, 983 wantErr: true, 984 }, 985 { 986 name: "MultipleUplinksAndRanges", 987 args: args{ 988 uplinks: []types.EdgeGatewayUplinks{ 989 { 990 Subnets: types.OpenAPIEdgeGatewaySubnets{ 991 Values: []types.OpenAPIEdgeGatewaySubnetValue{ 992 { 993 IPRanges: &types.OpenApiIPRanges{ 994 Values: []types.OpenApiIPRangeValues{ 995 { 996 StartAddress: "10.10.10.1", 997 EndAddress: "10.10.10.2", 998 }, 999 { 1000 StartAddress: "10.10.10.10", 1001 EndAddress: "10.10.10.12", 1002 }, 1003 }, 1004 }, 1005 }, 1006 }, 1007 }, 1008 }, 1009 { 1010 Subnets: types.OpenAPIEdgeGatewaySubnets{ 1011 Values: []types.OpenAPIEdgeGatewaySubnetValue{ 1012 { 1013 IPRanges: &types.OpenApiIPRanges{ 1014 Values: []types.OpenApiIPRangeValues{ 1015 { 1016 StartAddress: "20.10.10.1", 1017 EndAddress: "20.10.10.6", 1018 }, 1019 { 1020 StartAddress: "20.10.10.200", 1021 EndAddress: "20.10.10.201", 1022 }, 1023 { 1024 StartAddress: "20.10.10.251", 1025 EndAddress: "20.10.10.252", 1026 }, 1027 { 1028 StartAddress: "20.10.10.255", 1029 }, 1030 }, 1031 }, 1032 }, 1033 }, 1034 }, 1035 }, 1036 { 1037 Subnets: types.OpenAPIEdgeGatewaySubnets{ 1038 Values: []types.OpenAPIEdgeGatewaySubnetValue{ 1039 { 1040 IPRanges: &types.OpenApiIPRanges{ 1041 Values: []types.OpenApiIPRangeValues{ 1042 { 1043 StartAddress: "30.10.10.1", 1044 EndAddress: "30.10.10.2", 1045 }, 1046 }, 1047 }, 1048 }, 1049 }, 1050 }, 1051 }, 1052 }, 1053 usedIpAddresses: []*types.GatewayUsedIpAddress{}, 1054 requiredCount: 18, 1055 optionalSubnet: netip.Prefix{}, 1056 }, 1057 want: []netip.Addr{ 1058 netip.MustParseAddr("10.10.10.1"), 1059 netip.MustParseAddr("10.10.10.2"), 1060 netip.MustParseAddr("10.10.10.10"), 1061 netip.MustParseAddr("10.10.10.11"), 1062 netip.MustParseAddr("10.10.10.12"), 1063 netip.MustParseAddr("20.10.10.1"), 1064 netip.MustParseAddr("20.10.10.2"), 1065 netip.MustParseAddr("20.10.10.3"), 1066 netip.MustParseAddr("20.10.10.4"), 1067 netip.MustParseAddr("20.10.10.5"), 1068 netip.MustParseAddr("20.10.10.6"), 1069 netip.MustParseAddr("20.10.10.200"), 1070 netip.MustParseAddr("20.10.10.201"), 1071 netip.MustParseAddr("20.10.10.251"), 1072 netip.MustParseAddr("20.10.10.252"), 1073 netip.MustParseAddr("20.10.10.255"), 1074 netip.MustParseAddr("30.10.10.1"), 1075 netip.MustParseAddr("30.10.10.2"), 1076 }, 1077 wantErr: false, 1078 }, 1079 { 1080 name: "MultipleUplinksAndRangesInsufficientIPs", 1081 args: args{ 1082 uplinks: []types.EdgeGatewayUplinks{ 1083 { 1084 Subnets: types.OpenAPIEdgeGatewaySubnets{ 1085 Values: []types.OpenAPIEdgeGatewaySubnetValue{ 1086 { 1087 IPRanges: &types.OpenApiIPRanges{ 1088 Values: []types.OpenApiIPRangeValues{ 1089 { 1090 StartAddress: "10.10.10.1", 1091 EndAddress: "10.10.10.2", 1092 }, 1093 { 1094 StartAddress: "10.10.10.10", 1095 EndAddress: "10.10.10.12", 1096 }, 1097 }, 1098 }, 1099 }, 1100 }, 1101 }, 1102 }, 1103 { 1104 Subnets: types.OpenAPIEdgeGatewaySubnets{ 1105 Values: []types.OpenAPIEdgeGatewaySubnetValue{ 1106 { 1107 IPRanges: &types.OpenApiIPRanges{ 1108 Values: []types.OpenApiIPRangeValues{ 1109 { 1110 StartAddress: "20.10.10.1", 1111 EndAddress: "20.10.10.6", 1112 }, 1113 { 1114 StartAddress: "20.10.10.200", 1115 EndAddress: "20.10.10.201", 1116 }, 1117 { 1118 StartAddress: "20.10.10.251", 1119 EndAddress: "20.10.10.252", 1120 }, 1121 { 1122 StartAddress: "20.10.10.255", 1123 }, 1124 }, 1125 }, 1126 }, 1127 }, 1128 }, 1129 }, 1130 { 1131 Subnets: types.OpenAPIEdgeGatewaySubnets{ 1132 Values: []types.OpenAPIEdgeGatewaySubnetValue{ 1133 { 1134 IPRanges: &types.OpenApiIPRanges{ 1135 Values: []types.OpenApiIPRangeValues{ 1136 { 1137 StartAddress: "30.10.10.1", 1138 EndAddress: "30.10.10.2", 1139 }, 1140 }, 1141 }, 1142 }, 1143 }, 1144 }, 1145 }, 1146 }, 1147 usedIpAddresses: []*types.GatewayUsedIpAddress{}, 1148 requiredCount: 25, 1149 optionalSubnet: netip.Prefix{}, 1150 }, 1151 want: nil, 1152 wantErr: true, 1153 }, 1154 { 1155 name: "MultipleUplinksAndRangesInSubnet24", 1156 args: args{ 1157 uplinks: []types.EdgeGatewayUplinks{ 1158 { 1159 Subnets: types.OpenAPIEdgeGatewaySubnets{ 1160 Values: []types.OpenAPIEdgeGatewaySubnetValue{ 1161 { 1162 IPRanges: &types.OpenApiIPRanges{ 1163 Values: []types.OpenApiIPRangeValues{ 1164 { 1165 StartAddress: "10.10.10.1", 1166 EndAddress: "10.10.10.2", 1167 }, 1168 { 1169 StartAddress: "10.10.10.10", 1170 EndAddress: "10.10.10.12", 1171 }, 1172 }, 1173 }, 1174 }, 1175 }, 1176 }, 1177 }, 1178 { 1179 Subnets: types.OpenAPIEdgeGatewaySubnets{ 1180 Values: []types.OpenAPIEdgeGatewaySubnetValue{ 1181 { 1182 IPRanges: &types.OpenApiIPRanges{ 1183 Values: []types.OpenApiIPRangeValues{ 1184 { 1185 StartAddress: "20.10.10.1", 1186 EndAddress: "20.10.10.6", 1187 }, 1188 { 1189 StartAddress: "20.10.10.200", 1190 EndAddress: "20.10.10.201", 1191 }, 1192 { 1193 StartAddress: "20.10.10.251", 1194 EndAddress: "20.10.10.252", 1195 }, 1196 { 1197 StartAddress: "20.10.10.255", 1198 }, 1199 }, 1200 }, 1201 }, 1202 }, 1203 }, 1204 }, 1205 { 1206 Subnets: types.OpenAPIEdgeGatewaySubnets{ 1207 Values: []types.OpenAPIEdgeGatewaySubnetValue{ 1208 { 1209 IPRanges: &types.OpenApiIPRanges{ 1210 Values: []types.OpenApiIPRangeValues{ 1211 { 1212 StartAddress: "30.10.10.1", 1213 EndAddress: "30.10.10.2", 1214 }, 1215 }, 1216 }, 1217 }, 1218 }, 1219 }, 1220 }, 1221 }, 1222 usedIpAddresses: []*types.GatewayUsedIpAddress{}, 1223 requiredCount: 2, 1224 optionalSubnet: netip.MustParsePrefix("30.10.10.1/24"), 1225 }, 1226 want: []netip.Addr{ 1227 netip.MustParseAddr("30.10.10.1"), 1228 netip.MustParseAddr("30.10.10.2"), 1229 }, 1230 wantErr: false, 1231 }, 1232 { 1233 name: "MultipleUplinksAndRangesInSubnet28", 1234 args: args{ 1235 uplinks: []types.EdgeGatewayUplinks{ 1236 { 1237 Subnets: types.OpenAPIEdgeGatewaySubnets{ 1238 Values: []types.OpenAPIEdgeGatewaySubnetValue{ 1239 { 1240 IPRanges: &types.OpenApiIPRanges{ 1241 Values: []types.OpenApiIPRangeValues{ 1242 { 1243 StartAddress: "10.10.10.1", 1244 EndAddress: "10.10.10.2", 1245 }, 1246 { 1247 StartAddress: "10.10.10.10", 1248 EndAddress: "10.10.10.12", 1249 }, 1250 }, 1251 }, 1252 }, 1253 }, 1254 }, 1255 }, 1256 { 1257 Subnets: types.OpenAPIEdgeGatewaySubnets{ 1258 Values: []types.OpenAPIEdgeGatewaySubnetValue{ 1259 { 1260 IPRanges: &types.OpenApiIPRanges{ 1261 Values: []types.OpenApiIPRangeValues{ 1262 { 1263 StartAddress: "20.10.10.1", 1264 EndAddress: "20.10.10.6", 1265 }, 1266 { 1267 StartAddress: "20.10.10.200", 1268 EndAddress: "20.10.10.201", 1269 }, 1270 { 1271 StartAddress: "20.10.10.251", 1272 EndAddress: "20.10.10.252", 1273 }, 1274 { 1275 StartAddress: "20.10.10.255", 1276 }, 1277 }, 1278 }, 1279 }, 1280 }, 1281 }, 1282 }, 1283 { 1284 Subnets: types.OpenAPIEdgeGatewaySubnets{ 1285 Values: []types.OpenAPIEdgeGatewaySubnetValue{ 1286 { 1287 IPRanges: &types.OpenApiIPRanges{ 1288 Values: []types.OpenApiIPRangeValues{ 1289 { 1290 StartAddress: "30.10.10.1", 1291 EndAddress: "30.10.10.2", 1292 }, 1293 }, 1294 }, 1295 }, 1296 }, 1297 }, 1298 }, 1299 }, 1300 usedIpAddresses: []*types.GatewayUsedIpAddress{}, 1301 requiredCount: 6, 1302 optionalSubnet: netip.MustParsePrefix("20.10.10.1/28"), 1303 }, 1304 want: []netip.Addr{ 1305 netip.MustParseAddr("20.10.10.1"), 1306 netip.MustParseAddr("20.10.10.2"), 1307 netip.MustParseAddr("20.10.10.3"), 1308 netip.MustParseAddr("20.10.10.4"), 1309 netip.MustParseAddr("20.10.10.5"), 1310 netip.MustParseAddr("20.10.10.6"), 1311 }, 1312 wantErr: false, 1313 }, 1314 } 1315 for _, tt := range tests { 1316 t.Run(tt.name, func(t *testing.T) { 1317 got, err := getUnusedExternalIPAddress(tt.args.uplinks, tt.args.usedIpAddresses, tt.args.requiredCount, tt.args.optionalSubnet) 1318 if (err != nil) != tt.wantErr { 1319 t.Errorf("getUnusedExternalIPAddress() error = %v, wantErr %v", err, tt.wantErr) 1320 t.Errorf("getUnusedExternalIPAddress() = %v, want %v", got, tt.want) 1321 return 1322 } 1323 if !reflect.DeepEqual(got, tt.want) { 1324 t.Errorf("getUnusedExternalIPAddress() = %v, want %v", got, tt.want) 1325 } 1326 }) 1327 } 1328 } 1329 1330 func Test_flattenGatewayUsedIpAddressesToIpSlice(t *testing.T) { 1331 type args struct { 1332 usedIpAddresses []*types.GatewayUsedIpAddress 1333 } 1334 tests := []struct { 1335 name string 1336 args args 1337 want []netip.Addr 1338 wantErr bool 1339 }{ 1340 { 1341 name: "SingleIP", 1342 args: args{usedIpAddresses: []*types.GatewayUsedIpAddress{{IPAddress: "10.0.0.1"}}}, 1343 want: []netip.Addr{netip.MustParseAddr("10.0.0.1")}, 1344 wantErr: false, 1345 }, 1346 { 1347 name: "DuplicateIPs", 1348 args: args{ 1349 usedIpAddresses: []*types.GatewayUsedIpAddress{ 1350 {IPAddress: "10.0.0.1"}, 1351 {IPAddress: "10.0.0.1"}, 1352 }, 1353 }, 1354 want: []netip.Addr{ 1355 netip.MustParseAddr("10.0.0.1"), 1356 netip.MustParseAddr("10.0.0.1"), 1357 }, 1358 wantErr: false, 1359 }, 1360 { 1361 name: "NilSlice", 1362 args: args{usedIpAddresses: nil}, 1363 want: []netip.Addr{}, 1364 wantErr: false, 1365 }, 1366 { 1367 name: "InvalidIp", 1368 args: args{usedIpAddresses: []*types.GatewayUsedIpAddress{{IPAddress: "ASD"}}}, 1369 want: nil, 1370 wantErr: true, 1371 }, 1372 { 1373 name: "ManyIPs", 1374 args: args{ 1375 usedIpAddresses: []*types.GatewayUsedIpAddress{ 1376 {IPAddress: "10.0.0.1"}, 1377 {IPAddress: "10.0.0.2"}, 1378 {IPAddress: "10.0.0.3"}, 1379 {IPAddress: "10.0.0.4"}, 1380 }, 1381 }, 1382 want: []netip.Addr{ 1383 netip.MustParseAddr("10.0.0.1"), 1384 netip.MustParseAddr("10.0.0.2"), 1385 netip.MustParseAddr("10.0.0.3"), 1386 netip.MustParseAddr("10.0.0.4"), 1387 }, 1388 wantErr: false, 1389 }, 1390 { 1391 name: "IPv6SingleIP", 1392 args: args{usedIpAddresses: []*types.GatewayUsedIpAddress{{IPAddress: "684D:1111:222:3333:4444:5555:6:77"}}}, 1393 want: []netip.Addr{netip.MustParseAddr("684D:1111:222:3333:4444:5555:6:77")}, 1394 wantErr: false, 1395 }, 1396 { 1397 name: "IPv6ManyIPs", 1398 args: args{ 1399 usedIpAddresses: []*types.GatewayUsedIpAddress{ 1400 {IPAddress: "2001:db8:3333:4444:5555:6666:7777:8888"}, 1401 {IPAddress: "2002:db8:3333:4444:5555:6666:7777:8888"}, 1402 {IPAddress: "2003:db8:3333:4444:5555:6666:7777:8888"}, 1403 {IPAddress: "2004:db8:3333:4444:5555:6666:7777:8888"}, 1404 {IPAddress: "2001:db8::68"}, 1405 }, 1406 }, 1407 want: []netip.Addr{ 1408 netip.MustParseAddr("2001:db8:3333:4444:5555:6666:7777:8888"), 1409 netip.MustParseAddr("2002:db8:3333:4444:5555:6666:7777:8888"), 1410 netip.MustParseAddr("2003:db8:3333:4444:5555:6666:7777:8888"), 1411 netip.MustParseAddr("2004:db8:3333:4444:5555:6666:7777:8888"), 1412 netip.MustParseAddr("2001:db8::68"), 1413 }, 1414 wantErr: false, 1415 }, 1416 } 1417 for _, tt := range tests { 1418 t.Run(tt.name, func(t *testing.T) { 1419 got, err := flattenGatewayUsedIpAddressesToIpSlice(tt.args.usedIpAddresses) 1420 if (err != nil) != tt.wantErr { 1421 t.Errorf("flattenGatewayUsedIpAddressesToIpSlice() error = %v, wantErr %v", err, tt.wantErr) 1422 return 1423 } 1424 if !reflect.DeepEqual(got, tt.want) { 1425 t.Errorf("flattenGatewayUsedIpAddressesToIpSlice() = %v, want %v", got, tt.want) 1426 } 1427 }) 1428 } 1429 } 1430 1431 // TestOpenAPIEdgeGateway_DeallocateIpCount tests that the function 1432 // OpenAPIEdgeGateway.DeallocateIpCount is correctly processing the Edge Gateway uplink structure 1433 func TestOpenAPIEdgeGateway_DeallocateIpCount(t *testing.T) { 1434 type fields struct { 1435 EdgeGatewayUplinks []types.EdgeGatewayUplinks 1436 } 1437 type args struct { 1438 deallocateIpCount int 1439 expectedCount int 1440 } 1441 tests := []struct { 1442 name string 1443 fields fields 1444 args args 1445 wantErr bool 1446 }{ 1447 { 1448 name: "SingleStartAndEndAddresses", 1449 fields: fields{ 1450 EdgeGatewayUplinks: []types.EdgeGatewayUplinks{ 1451 { 1452 Subnets: types.OpenAPIEdgeGatewaySubnets{ 1453 Values: []types.OpenAPIEdgeGatewaySubnetValue{ 1454 { 1455 IPRanges: &types.OpenApiIPRanges{ 1456 Values: []types.OpenApiIPRangeValues{ 1457 { 1458 StartAddress: "10.10.10.1", 1459 EndAddress: "10.10.10.2", 1460 }, 1461 }, 1462 }, 1463 TotalIPCount: addrOf(2), 1464 }, 1465 }, 1466 }, 1467 }, 1468 }, 1469 }, 1470 args: args{ 1471 deallocateIpCount: 1, 1472 expectedCount: 1, 1473 }, 1474 }, 1475 { 1476 // Here we check that the function is able to deallocate exactly one IP address (the 1477 // last). The API will return an error during such operation because Edge Gateway must 1478 // have at least one IP address allocated. 1479 name: "ExactlyOnIp", 1480 fields: fields{ 1481 EdgeGatewayUplinks: []types.EdgeGatewayUplinks{ 1482 { 1483 Subnets: types.OpenAPIEdgeGatewaySubnets{ 1484 Values: []types.OpenAPIEdgeGatewaySubnetValue{ 1485 { 1486 IPRanges: &types.OpenApiIPRanges{ 1487 Values: []types.OpenApiIPRangeValues{ 1488 { 1489 StartAddress: "10.10.10.1", 1490 EndAddress: "10.10.10.1", 1491 }, 1492 }, 1493 }, 1494 TotalIPCount: addrOf(1), 1495 }, 1496 }, 1497 }, 1498 }, 1499 }, 1500 }, 1501 args: args{ 1502 deallocateIpCount: 1, 1503 expectedCount: 0, 1504 }, 1505 }, 1506 { 1507 name: "NegativeAllocationImpossible", 1508 fields: fields{ 1509 EdgeGatewayUplinks: []types.EdgeGatewayUplinks{ 1510 { 1511 Subnets: types.OpenAPIEdgeGatewaySubnets{ 1512 Values: []types.OpenAPIEdgeGatewaySubnetValue{ 1513 { 1514 IPRanges: &types.OpenApiIPRanges{ 1515 Values: []types.OpenApiIPRangeValues{ 1516 { 1517 StartAddress: "10.10.10.1", 1518 EndAddress: "10.10.10.2", 1519 }, 1520 }, 1521 }, 1522 TotalIPCount: addrOf(2), 1523 }, 1524 }, 1525 }, 1526 }, 1527 }, 1528 }, 1529 args: args{ 1530 deallocateIpCount: -1, 1531 }, 1532 wantErr: true, 1533 }, 1534 { 1535 name: "MultipleSubnets", 1536 fields: fields{ 1537 EdgeGatewayUplinks: []types.EdgeGatewayUplinks{ 1538 { 1539 Subnets: types.OpenAPIEdgeGatewaySubnets{ 1540 Values: []types.OpenAPIEdgeGatewaySubnetValue{ 1541 { 1542 IPRanges: &types.OpenApiIPRanges{ 1543 Values: []types.OpenApiIPRangeValues{ 1544 { 1545 StartAddress: "10.10.10.1", 1546 EndAddress: "10.10.10.2", 1547 }, 1548 }, 1549 }, 1550 TotalIPCount: addrOf(2), 1551 }, 1552 { 1553 IPRanges: &types.OpenApiIPRanges{ 1554 Values: []types.OpenApiIPRangeValues{ 1555 { 1556 StartAddress: "10.20.10.1", 1557 EndAddress: "10.20.10.2", 1558 }, 1559 }, 1560 }, 1561 TotalIPCount: addrOf(2), 1562 }, 1563 }, 1564 }, 1565 }, 1566 }, 1567 }, 1568 args: args{ 1569 deallocateIpCount: 3, 1570 expectedCount: 1, 1571 }, 1572 }, 1573 { 1574 name: "RemoveMoreThanAvailable", 1575 fields: fields{ 1576 EdgeGatewayUplinks: []types.EdgeGatewayUplinks{ 1577 { 1578 Subnets: types.OpenAPIEdgeGatewaySubnets{ 1579 Values: []types.OpenAPIEdgeGatewaySubnetValue{ 1580 { 1581 IPRanges: &types.OpenApiIPRanges{ 1582 Values: []types.OpenApiIPRangeValues{ 1583 { 1584 StartAddress: "10.10.10.1", 1585 EndAddress: "10.10.10.2", 1586 }, 1587 }, 1588 }, 1589 TotalIPCount: addrOf(2), 1590 }, 1591 { 1592 IPRanges: &types.OpenApiIPRanges{ 1593 Values: []types.OpenApiIPRangeValues{ 1594 { 1595 StartAddress: "10.20.10.1", 1596 EndAddress: "10.20.10.2", 1597 }, 1598 }, 1599 }, 1600 TotalIPCount: addrOf(2), 1601 }, 1602 }, 1603 }, 1604 }, 1605 }, 1606 }, 1607 args: args{ 1608 deallocateIpCount: 5, // only 4 IPs are available 1609 expectedCount: 1, 1610 }, 1611 wantErr: true, 1612 }, 1613 } 1614 for _, tt := range tests { 1615 t.Run(tt.name, func(t *testing.T) { 1616 egw := &NsxtEdgeGateway{ 1617 EdgeGateway: &types.OpenAPIEdgeGateway{ 1618 EdgeGatewayUplinks: tt.fields.EdgeGatewayUplinks, 1619 }, 1620 } 1621 var err error 1622 if err = egw.DeallocateIpCount(tt.args.deallocateIpCount); (err != nil) != tt.wantErr { 1623 t.Errorf("OpenAPIEdgeGateway.DeallocateIpCount() error = %v, wantErr %v", err, tt.wantErr) 1624 } 1625 1626 // Skip other validations if an error was expected 1627 if err != nil && tt.wantErr { 1628 return 1629 } 1630 1631 allocatedIpCount, err := egw.GetAllocatedIpCount(false) 1632 if err != nil { 1633 t.Errorf("NsxtEdgeGateway.GetAllocatedIpCount() error = %v", err) 1634 } 1635 1636 if allocatedIpCount != tt.args.expectedCount { 1637 t.Errorf("Allocated IP count %d != desired IP count %d", allocatedIpCount, tt.args.expectedCount) 1638 } 1639 1640 }) 1641 } 1642 } 1643 1644 func Test_reorderEdgeGatewayUplinks(t *testing.T) { 1645 type args struct { 1646 edgeGatewayUplinks []types.EdgeGatewayUplinks 1647 } 1648 tests := []struct { 1649 name string 1650 args args 1651 want args 1652 }{ 1653 { 1654 name: "OneT0Uplink", 1655 args: args{edgeGatewayUplinks: []types.EdgeGatewayUplinks{{BackingType: addrOf("NSXT_TIER0"), UplinkID: "1"}}}, 1656 want: args{edgeGatewayUplinks: []types.EdgeGatewayUplinks{{BackingType: addrOf("NSXT_TIER0"), UplinkID: "1"}}}, 1657 }, 1658 { 1659 name: "ImpossibleOneSegmentUplink", 1660 args: args{edgeGatewayUplinks: []types.EdgeGatewayUplinks{{BackingType: addrOf("IMPORTED_T_LOGICAL_SWITCH"), UplinkID: "1"}}}, 1661 want: args{edgeGatewayUplinks: []types.EdgeGatewayUplinks{{BackingType: addrOf("IMPORTED_T_LOGICAL_SWITCH"), UplinkID: "1"}}}, 1662 }, 1663 { 1664 name: "OrderedOneT0OneSegmentUplink", 1665 args: args{edgeGatewayUplinks: []types.EdgeGatewayUplinks{ 1666 {BackingType: addrOf("NSXT_TIER0"), UplinkID: "1"}, 1667 {BackingType: addrOf("IMPORTED_T_LOGICAL_SWITCH"), UplinkID: "2"}, 1668 }}, 1669 want: args{edgeGatewayUplinks: []types.EdgeGatewayUplinks{ 1670 {BackingType: addrOf("NSXT_TIER0"), UplinkID: "1"}, 1671 {BackingType: addrOf("IMPORTED_T_LOGICAL_SWITCH"), UplinkID: "2"}, 1672 }}, 1673 }, 1674 { 1675 name: "OrderedOneT0OneManySegments", 1676 args: args{edgeGatewayUplinks: []types.EdgeGatewayUplinks{ 1677 {BackingType: addrOf("NSXT_TIER0"), UplinkID: "1"}, 1678 {BackingType: addrOf("IMPORTED_T_LOGICAL_SWITCH"), UplinkID: "2"}, 1679 {BackingType: addrOf("IMPORTED_T_LOGICAL_SWITCH"), UplinkID: "3"}, 1680 {BackingType: addrOf("IMPORTED_T_LOGICAL_SWITCH"), UplinkID: "4"}, 1681 {BackingType: addrOf("IMPORTED_T_LOGICAL_SWITCH"), UplinkID: "5"}, 1682 }}, 1683 want: args{edgeGatewayUplinks: []types.EdgeGatewayUplinks{ 1684 {BackingType: addrOf("NSXT_TIER0"), UplinkID: "1"}, 1685 {BackingType: addrOf("IMPORTED_T_LOGICAL_SWITCH"), UplinkID: "2"}, 1686 {BackingType: addrOf("IMPORTED_T_LOGICAL_SWITCH"), UplinkID: "3"}, 1687 {BackingType: addrOf("IMPORTED_T_LOGICAL_SWITCH"), UplinkID: "4"}, 1688 {BackingType: addrOf("IMPORTED_T_LOGICAL_SWITCH"), UplinkID: "5"}, 1689 }}, 1690 }, 1691 { 1692 name: "ReverseOneT0OneSegmentUplink", 1693 args: args{edgeGatewayUplinks: []types.EdgeGatewayUplinks{ 1694 {BackingType: addrOf("IMPORTED_T_LOGICAL_SWITCH"), UplinkID: "1"}, 1695 {BackingType: addrOf("NSXT_TIER0"), UplinkID: "2"}, 1696 }}, 1697 want: args{edgeGatewayUplinks: []types.EdgeGatewayUplinks{ 1698 {BackingType: addrOf("NSXT_TIER0"), UplinkID: "2"}, 1699 {BackingType: addrOf("IMPORTED_T_LOGICAL_SWITCH"), UplinkID: "1"}, 1700 }}, 1701 }, 1702 { 1703 name: "ReverseOneT0ManySegmentUplinks", 1704 args: args{edgeGatewayUplinks: []types.EdgeGatewayUplinks{ 1705 {BackingType: addrOf("IMPORTED_T_LOGICAL_SWITCH"), UplinkID: "1"}, 1706 {BackingType: addrOf("IMPORTED_T_LOGICAL_SWITCH"), UplinkID: "2"}, 1707 {BackingType: addrOf("IMPORTED_T_LOGICAL_SWITCH"), UplinkID: "3"}, 1708 {BackingType: addrOf("IMPORTED_T_LOGICAL_SWITCH"), UplinkID: "4"}, 1709 {BackingType: addrOf("NSXT_TIER0"), UplinkID: "5"}, 1710 }}, 1711 want: args{edgeGatewayUplinks: []types.EdgeGatewayUplinks{ 1712 {BackingType: addrOf("NSXT_TIER0"), UplinkID: "5"}, 1713 {BackingType: addrOf("IMPORTED_T_LOGICAL_SWITCH"), UplinkID: "2"}, 1714 {BackingType: addrOf("IMPORTED_T_LOGICAL_SWITCH"), UplinkID: "3"}, 1715 {BackingType: addrOf("IMPORTED_T_LOGICAL_SWITCH"), UplinkID: "4"}, 1716 {BackingType: addrOf("IMPORTED_T_LOGICAL_SWITCH"), UplinkID: "1"}, 1717 }}, 1718 }, 1719 { 1720 name: "ReverseOneT0ManySegmentUplinksVRF", 1721 args: args{edgeGatewayUplinks: []types.EdgeGatewayUplinks{ 1722 {BackingType: addrOf("IMPORTED_T_LOGICAL_SWITCH"), UplinkID: "1"}, 1723 {BackingType: addrOf("IMPORTED_T_LOGICAL_SWITCH"), UplinkID: "2"}, 1724 {BackingType: addrOf("IMPORTED_T_LOGICAL_SWITCH"), UplinkID: "3"}, 1725 {BackingType: addrOf("IMPORTED_T_LOGICAL_SWITCH"), UplinkID: "4"}, 1726 {BackingType: addrOf("NSXT_VRF_TIER0"), UplinkID: "5"}, 1727 }}, 1728 want: args{edgeGatewayUplinks: []types.EdgeGatewayUplinks{ 1729 {BackingType: addrOf("NSXT_VRF_TIER0"), UplinkID: "5"}, 1730 {BackingType: addrOf("IMPORTED_T_LOGICAL_SWITCH"), UplinkID: "2"}, 1731 {BackingType: addrOf("IMPORTED_T_LOGICAL_SWITCH"), UplinkID: "3"}, 1732 {BackingType: addrOf("IMPORTED_T_LOGICAL_SWITCH"), UplinkID: "4"}, 1733 {BackingType: addrOf("IMPORTED_T_LOGICAL_SWITCH"), UplinkID: "1"}, 1734 }}, 1735 }, 1736 // A failing test example - commented on purpose 1737 // { 1738 // name: "FailingTest", 1739 // args: args{edgeGatewayUplinks: []types.EdgeGatewayUplinks{ 1740 // types.EdgeGatewayUplinks{BackingType: addrOf("IMPORTED_T_LOGICAL_SWITCH")}, 1741 // types.EdgeGatewayUplinks{BackingType: addrOf("NSXT_TIER0")}, 1742 // }}, 1743 // want: args{edgeGatewayUplinks: []types.EdgeGatewayUplinks{ 1744 // types.EdgeGatewayUplinks{BackingType: addrOf("IMPORTED_T_LOGICAL_SWITCH")}, 1745 // types.EdgeGatewayUplinks{BackingType: addrOf("NSXT_TIER0")}, 1746 // }}, 1747 // }, 1748 } 1749 for _, tt := range tests { 1750 t.Run(tt.name, func(t *testing.T) { 1751 reorderedUplinks := reorderEdgeGatewayUplinks(tt.args.edgeGatewayUplinks) 1752 1753 if !reflect.DeepEqual(reorderedUplinks, tt.want.edgeGatewayUplinks) { 1754 t.Errorf("Expected %+v, got %+v", tt.args.edgeGatewayUplinks, tt.want.edgeGatewayUplinks) 1755 } 1756 }) 1757 } 1758 }