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  }