diff --git a/pkg/provider/azure_loadbalancer.go b/pkg/provider/azure_loadbalancer.go index f1c74ea84d..247294c078 100644 --- a/pkg/provider/azure_loadbalancer.go +++ b/pkg/provider/azure_loadbalancer.go @@ -1703,7 +1703,7 @@ func (az *Cloud) findFrontendIPConfigsOfService( var fipIsIPv6 bool var err error if fipIPVersion != nil { - fipIsIPv6 = fipIPVersion == to.Ptr(armnetwork.IPVersionIPv6) + fipIsIPv6 = *fipIPVersion == armnetwork.IPVersionIPv6 } else { if fipIsIPv6, err = az.isFIPIPv6(service, config); err != nil { return nil, err @@ -1902,7 +1902,7 @@ func (az *Cloud) reconcileLoadBalancer(ctx context.Context, clusterName string, var err error _, _, fipIPVersion := az.serviceOwnsFrontendIP(ctx, ownedFIPConfig, service) if fipIPVersion != nil { - isIPv6 = fipIPVersion == to.Ptr(armnetwork.IPVersionIPv6) + isIPv6 = *fipIPVersion == armnetwork.IPVersionIPv6 } else { if isIPv6, err = az.isFIPIPv6(service, ownedFIPConfig); err != nil { return nil, false, err @@ -2608,7 +2608,7 @@ func (az *Cloud) reconcileFrontendIPConfigs( var isIPv6 bool var err error if fipIPVersion != nil { - isIPv6 = fipIPVersion == to.Ptr(armnetwork.IPVersionIPv6) + isIPv6 = *fipIPVersion == armnetwork.IPVersionIPv6 } else { if isIPv6, err = az.isFIPIPv6(service, config); err != nil { return nil, toDeleteConfigs, false, err diff --git a/pkg/provider/azure_loadbalancer_test.go b/pkg/provider/azure_loadbalancer_test.go index de22121a15..2c8cfa6c84 100644 --- a/pkg/provider/azure_loadbalancer_test.go +++ b/pkg/provider/azure_loadbalancer_test.go @@ -2453,6 +2453,8 @@ func TestDeterminePublicIPName(t *testing.T) { expectedPIPName string expectedError bool isIPv6 bool + serviceIPv6 bool + annotations map[string]string }{ { desc: "determinePublicIpName shall get public IP from az.getPublicIPName if no specific " + @@ -2481,12 +2483,22 @@ func TestDeterminePublicIPName(t *testing.T) { expectedPIPName: "pipName", expectedError: false, }, + { + desc: "determinePublicIpName shall use IPv6 pip annotation for IPv6 single stack service", + annotations: map[string]string{ + consts.ServiceAnnotationPIPNameDualStack[true]: "service-lb-public-IP3dbe-v6", + }, + expectedPIPName: "service-lb-public-IP3dbe-v6", + isIPv6: true, + serviceIPv6: true, + expectedError: false, + }, } for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { az := GetTestCloud(ctrl) - service := getTestService("test1", v1.ProtocolTCP, nil, false, 80) + service := getTestService("test1", v1.ProtocolTCP, test.annotations, test.serviceIPv6, 80) setServiceLoadBalancerIP(&service, test.loadBalancerIP) mockPIPsClient := az.NetworkClientFactory.GetPublicIPAddressClient().(*mock_publicipaddressclient.MockInterface) @@ -8517,6 +8529,93 @@ func TestServiceOwnsFrontendIP(t *testing.T) { } } +func TestFindFrontendIPConfigsOfService(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + testCases := []struct { + desc string + existingPIPs []*armnetwork.PublicIPAddress + fip *armnetwork.FrontendIPConfiguration + service *v1.Service + isIPv6 bool + }{ + { + desc: "config works for ipv6 service", + existingPIPs: []*armnetwork.PublicIPAddress{ + { + Name: ptr.To("pip1"), + ID: ptr.To("pip1"), + Properties: &armnetwork.PublicIPAddressPropertiesFormat{ + IPAddress: ptr.To("fd00::eef0"), + PublicIPAddressVersion: to.Ptr(armnetwork.IPVersionIPv6), + }, + }, + }, + fip: &armnetwork.FrontendIPConfiguration{ + Name: ptr.To("auid"), + Properties: &armnetwork.FrontendIPConfigurationPropertiesFormat{ + PublicIPAddress: &armnetwork.PublicIPAddress{ + ID: ptr.To("pip1"), + }, + }, + }, + service: &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID("secondary"), + Annotations: map[string]string{consts.ServiceAnnotationPIPNameDualStack[false]: "pip1"}, + }, + }, + isIPv6: true, + }, + { + desc: "config works for ipv4 service", + existingPIPs: []*armnetwork.PublicIPAddress{ + { + Name: ptr.To("pip1"), + ID: ptr.To("pip1"), + Properties: &armnetwork.PublicIPAddressPropertiesFormat{ + IPAddress: ptr.To("4.3.2.1"), + PublicIPAddressVersion: to.Ptr(armnetwork.IPVersionIPv4), + }, + }, + }, + fip: &armnetwork.FrontendIPConfiguration{ + Name: ptr.To("auid"), + Properties: &armnetwork.FrontendIPConfigurationPropertiesFormat{ + PublicIPAddress: &armnetwork.PublicIPAddress{ + ID: ptr.To("pip1"), + }, + }, + }, + service: &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID("secondary"), + Annotations: map[string]string{consts.ServiceAnnotationPIPNameDualStack[false]: "pip1"}, + }, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + cloud := GetTestCloud(ctrl) + if test.existingPIPs != nil { + mockPIPsClient := cloud.NetworkClientFactory.GetPublicIPAddressClient().(*mock_publicipaddressclient.MockInterface) + mockPIPsClient.EXPECT().List(gomock.Any(), "rg").Return(test.existingPIPs, nil).MaxTimes(2) + } + configs, err := cloud.findFrontendIPConfigsOfService(context.TODO(), []*armnetwork.FrontendIPConfiguration{test.fip}, test.service) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + assert.Equal(t, 1, len(configs)) + assert.NotNil(t, configs[test.isIPv6]) + assert.Equal(t, test.fip, configs[test.isIPv6]) + }) + } +} + func TestReconcileMultipleStandardLoadBalancerNodes(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() diff --git a/pkg/provider/azure_utils.go b/pkg/provider/azure_utils.go index 5f2e11fd36..92608e2e9b 100644 --- a/pkg/provider/azure_utils.go +++ b/pkg/provider/azure_utils.go @@ -378,6 +378,12 @@ func getServicePIPName(service *v1.Service, isIPv6 bool) string { } if !isServiceDualStack(service) { + v4Enabled, v6Enabled := getIPFamiliesEnabled(service) + if isIPv6 && v6Enabled && !v4Enabled { + if name := service.Annotations[consts.ServiceAnnotationPIPNameDualStack[true]]; name != "" { + return name + } + } return service.Annotations[consts.ServiceAnnotationPIPNameDualStack[false]] } @@ -398,6 +404,12 @@ func getServicePIPPrefixID(service *v1.Service, isIPv6 bool) string { } if !isServiceDualStack(service) { + v4Enabled, v6Enabled := getIPFamiliesEnabled(service) + if isIPv6 && v6Enabled && !v4Enabled { + if id := service.Annotations[consts.ServiceAnnotationPIPPrefixIDDualStack[true]]; id != "" { + return id + } + } return service.Annotations[consts.ServiceAnnotationPIPPrefixIDDualStack[false]] } diff --git a/pkg/provider/azure_utils_test.go b/pkg/provider/azure_utils_test.go index 4dc145c467..861edebc12 100644 --- a/pkg/provider/azure_utils_test.go +++ b/pkg/provider/azure_utils_test.go @@ -794,7 +794,7 @@ func TestGetServicePIPName(t *testing.T) { &v1.Service{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - consts.ServiceAnnotationPIPNameDualStack[false]: "pip-name-ipv6", + consts.ServiceAnnotationPIPNameDualStack[true]: "pip-name-ipv6", }, }, Spec: v1.ServiceSpec{ @@ -836,6 +836,21 @@ func TestGetServicePIPName(t *testing.T) { true, "pip-name-ipv6", }, + { + "From ServiceAnnotationPIPName IPv6 single stack fallback", + &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + consts.ServiceAnnotationPIPNameDualStack[false]: "pip-name-ipv6", + }, + }, + Spec: v1.ServiceSpec{ + IPFamilies: []v1.IPFamily{v1.IPv6Protocol}, + }, + }, + true, + "pip-name-ipv6", + }, } for _, tc := range testcases { t.Run(tc.desc, func(t *testing.T) { @@ -872,7 +887,7 @@ func TestGetServicePIPPrefixID(t *testing.T) { &v1.Service{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - consts.ServiceAnnotationPIPPrefixIDDualStack[false]: "pip-prefix-id-ipv6", + consts.ServiceAnnotationPIPPrefixIDDualStack[true]: "pip-prefix-id-ipv6", }, }, Spec: v1.ServiceSpec{ @@ -914,6 +929,21 @@ func TestGetServicePIPPrefixID(t *testing.T) { true, "pip-prefix-id-ipv6", }, + { + "From ServiceAnnotationPIPPrefixIDDualStack IPv6 single stack fallback", + &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + consts.ServiceAnnotationPIPPrefixIDDualStack[false]: "pip-prefix-id-ipv6", + }, + }, + Spec: v1.ServiceSpec{ + IPFamilies: []v1.IPFamily{v1.IPv6Protocol}, + }, + }, + true, + "pip-prefix-id-ipv6", + }, } for _, tc := range testcases { t.Run(tc.desc, func(t *testing.T) { @@ -923,6 +953,22 @@ func TestGetServicePIPPrefixID(t *testing.T) { } } +func TestGetServicePIPNames(t *testing.T) { + svc := &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + consts.ServiceAnnotationPIPNameDualStack[true]: "pip-name-ipv6", + }, + }, + Spec: v1.ServiceSpec{ + IPFamilies: []v1.IPFamily{v1.IPv6Protocol}, + }, + } + + names := getServicePIPNames(svc) + assert.Equal(t, []string{"", "pip-name-ipv6"}, names) +} + func TestGetResourceByIPFamily(t *testing.T) { testcases := []struct { desc string