Repository: kubernetes-sigs/cloud-provider-azure
Pull request #3729 (2023-04-14 02:10:48)

[release-1.24] [IPv6] Fix CNM not properly handling IPv6 from imds LB
This is an automated cherry-pick of #3726

/assign lzhecheng

```release-note
[IPv6] Fix CNM not properly handling IPv6 from imds LB. imds LB returns IPv6 IPs wrapped by brackets so extra trimming is needed.
```

k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/cloud-provider-azure/pull/3729#" title="Author self-approved">k8s-infra-cherrypick-robot</a>*
**Once this PR has been reviewed and has the lgtm label**, please assign jwtty for approval. For more information see [the Kubernetes Code Review Process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process).

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fcloud-provider-azure).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes-sigs/cloud-provider-azure/blob/release-1.24/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["jwtty"]} -->
coveralls: 
[![Coverage Status](https://coveralls.io/builds/58893120/badge)](https://coveralls.io/builds/58893120)

Coverage: 80.003% (+0.008%) from 79.995% when pulling **62a59334b9db31131c29c781ab9aa92c08286682 on k8s-infra-cherrypick-robot:cherry-pick-3726-to-release-1.24** into **f69db82aab877d0c06987237ff700528784366b7 on kubernetes-sigs:release-1.24**.

k8s-ci-robot: @k8s-infra-cherrypick-robot: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cloud-provider-azure-e2e-capz-1-24 | 62a59334b9db31131c29c781ab9aa92c08286682 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cloud-provider-azure/3729/pull-cloud-provider-azure-e2e-capz-1-24/1646697490122018816) | true | `/test pull-cloud-provider-azure-e2e-capz-1-24`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cloud-provider-azure&pr=3729). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Ak8s-infra-cherrypick-robot). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cloud-provider-azure/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @k8s-infra-cherrypick-robot: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cloud-provider-azure-e2e-capz-1-24 | 62a59334b9db31131c29c781ab9aa92c08286682 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cloud-provider-azure/3729/pull-cloud-provider-azure-e2e-capz-1-24/1646697490122018816) | true | `/test pull-cloud-provider-azure-e2e-capz-1-24`
pull-cloud-provider-azure-e2e-ccm-capz-1-24 | 62a59334b9db31131c29c781ab9aa92c08286682 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cloud-provider-azure/3729/pull-cloud-provider-azure-e2e-ccm-capz-1-24/1646697490184933376) | true | `/test pull-cloud-provider-azure-e2e-ccm-capz-1-24`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cloud-provider-azure&pr=3729). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Ak8s-infra-cherrypick-robot). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cloud-provider-azure/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @k8s-infra-cherrypick-robot: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cloud-provider-azure-e2e-capz-1-24 | 62a59334b9db31131c29c781ab9aa92c08286682 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cloud-provider-azure/3729/pull-cloud-provider-azure-e2e-capz-1-24/1646697490122018816) | true | `/test pull-cloud-provider-azure-e2e-capz-1-24`
pull-cloud-provider-azure-e2e-ccm-capz-1-24 | 62a59334b9db31131c29c781ab9aa92c08286682 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cloud-provider-azure/3729/pull-cloud-provider-azure-e2e-ccm-capz-1-24/1646697490184933376) | true | `/test pull-cloud-provider-azure-e2e-ccm-capz-1-24`
pull-cloud-provider-azure-e2e-ccm-vmss-capz-1-24 | 62a59334b9db31131c29c781ab9aa92c08286682 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cloud-provider-azure/3729/pull-cloud-provider-azure-e2e-ccm-vmss-capz-1-24/1646697490075881472) | true | `/test pull-cloud-provider-azure-e2e-ccm-vmss-capz-1-24`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cloud-provider-azure&pr=3729). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Ak8s-infra-cherrypick-robot). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cloud-provider-azure/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
lzhecheng: /retest
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/cloud-provider-azure/pull/3729#" title="Author self-approved">k8s-infra-cherrypick-robot</a>*, *<a href="https://github.com/kubernetes-sigs/cloud-provider-azure/pull/3729#issuecomment-1508609744" title="Approved">lzhecheng</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fcloud-provider-azure).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/kubernetes-sigs/cloud-provider-azure/blob/release-1.24/OWNERS)~~ [lzhecheng]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->

Patch:
diff --git a/pkg/provider/azure_instance_metadata.go b/pkg/provider/azure_instance_metadata.go
index 014f900fb..2d0b58066 100644
--- a/pkg/provider/azure_instance_metadata.go
+++ b/pkg/provider/azure_instance_metadata.go
@@ -21,6 +21,7 @@ import (
 	"fmt"
 	"io/ioutil"
 	"net/http"
+	"strings"
 
 	"k8s.io/klog/v2"
 
@@ -118,6 +119,33 @@ func NewInstanceMetadataService(imdsServer string) (*InstanceMetadataService, er
 	return ims, nil
 }
 
+// fillNetInterfacePublicIPs finds PIPs from imds load balancer and fills them into net interface config.
+func fillNetInterfacePublicIPs(publicIPs []PublicIPMetadata, netInterface *NetworkInterface) {
+	// IPv6 IPs from imds load balancer are wrapped by brackets while those from imds are not.
+	trimIP := func(ip string) string {
+		return strings.Trim(strings.Trim(ip, "["), "]")
+	}
+
+	if len(netInterface.IPV4.IPAddress) > 0 && len(netInterface.IPV4.IPAddress[0].PrivateIP) > 0 {
+		for _, pip := range publicIPs {
+			if pip.PrivateIPAddress == netInterface.IPV4.IPAddress[0].PrivateIP {
+				netInterface.IPV4.IPAddress[0].PublicIP = pip.FrontendIPAddress
+				break
+			}
+		}
+	}
+	if len(netInterface.IPV6.IPAddress) > 0 && len(netInterface.IPV6.IPAddress[0].PrivateIP) > 0 {
+		for _, pip := range publicIPs {
+			privateIP := trimIP(pip.PrivateIPAddress)
+			frontendIP := trimIP(pip.FrontendIPAddress)
+			if privateIP == netInterface.IPV6.IPAddress[0].PrivateIP {
+				netInterface.IPV6.IPAddress[0].PublicIP = frontendIP
+				break
+			}
+		}
+	}
+}
+
 func (ims *InstanceMetadataService) getMetadata(key string) (interface{}, error) {
 	instanceMetadata, err := ims.getInstanceMetadata(key)
 	if err != nil {
@@ -141,22 +169,7 @@ func (ims *InstanceMetadataService) getMetadata(key string) (interface{}, error)
 		}
 
 		publicIPs := loadBalancerMetadata.LoadBalancer.PublicIPAddresses
-		if len(netInterface.IPV4.IPAddress) > 0 && len(netInterface.IPV4.IPAddress[0].PrivateIP) > 0 {
-			for _, pip := range publicIPs {
-				if pip.PrivateIPAddress == netInterface.IPV4.IPAddress[0].PrivateIP {
-					netInterface.IPV4.IPAddress[0].PublicIP = pip.FrontendIPAddress
-					break
-				}
-			}
-		}
-		if len(netInterface.IPV6.IPAddress) > 0 && len(netInterface.IPV6.IPAddress[0].PrivateIP) > 0 {
-			for _, pip := range publicIPs {
-				if pip.PrivateIPAddress == netInterface.IPV6.IPAddress[0].PrivateIP {
-					netInterface.IPV6.IPAddress[0].PublicIP = pip.FrontendIPAddress
-					break
-				}
-			}
-		}
+		fillNetInterfacePublicIPs(publicIPs, &netInterface)
 	}
 
 	return instanceMetadata, nil


Test patch:
diff --git a/pkg/provider/azure_instance_metadata_test.go b/pkg/provider/azure_instance_metadata_test.go
new file mode 100644
index 000000000..f924670e4
--- /dev/null
+++ b/pkg/provider/azure_instance_metadata_test.go
@@ -0,0 +1,89 @@
+/*
+Copyright 2023 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package provider
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+// TestFillNetInterfacePublicIPs tests if IPv6 IPs from imds load balancer are
+// properly handled.
+func TestFillNetInterfacePublicIPs(t *testing.T) {
+	testcases := []struct {
+		desc                 string
+		publicIPs            []PublicIPMetadata
+		netInterface         *NetworkInterface
+		expectedNetInterface *NetworkInterface
+	}{
+		{
+			desc: "IPv6/DualStack",
+			publicIPs: []PublicIPMetadata{
+				{
+					FrontendIPAddress: "20.0.0.0",
+					PrivateIPAddress:  "10.244.0.0",
+				},
+				{
+					FrontendIPAddress: "[2001::1]",
+					PrivateIPAddress:  "[fd00::1]",
+				},
+			},
+			netInterface: &NetworkInterface{
+				IPV4: NetworkData{
+					IPAddress: []IPAddress{
+						{
+							PrivateIP: "10.244.0.0",
+						},
+					},
+				},
+				IPV6: NetworkData{
+					IPAddress: []IPAddress{
+						{
+							PrivateIP: "fd00::1",
+						},
+					},
+				},
+			},
+			expectedNetInterface: &NetworkInterface{
+				IPV4: NetworkData{
+					IPAddress: []IPAddress{
+						{
+							PrivateIP: "10.244.0.0",
+							PublicIP:  "20.0.0.0",
+						},
+					},
+				},
+				IPV6: NetworkData{
+					IPAddress: []IPAddress{
+						{
+							PrivateIP: "fd00::1",
+							PublicIP:  "2001::1",
+						},
+					},
+				},
+			},
+		},
+	}
+
+	for _, tc := range testcases {
+		t.Run(tc.desc, func(t *testing.T) {
+			fillNetInterfacePublicIPs(tc.publicIPs, tc.netInterface)
+			assert.Equal(t, tc.expectedNetInterface, tc.netInterface)
+		})
+	}
+}