Repository: kubernetes-sigs/cloud-provider-azure
Pull request #3143 (2023-01-17 03:55:18)

[release-1.23] use pip list in pip cache
<!--  Thanks for sending a pull request!  Here are some tips for you:

1. If this is your first time, please read our contributor guidelines: https://git.k8s.io/community/contributors/guide/first-contribution.md#your-first-contribution and developer guide https://git.k8s.io/community/contributors/devel/development.md#development-guide
2. Please label this pull request according to what type of issue you are addressing, especially if this is a release targeted pull request. For reference on required PR/issue labels, read here:
https://git.k8s.io/community/contributors/devel/sig-release/release.md#issuepr-kind-label
3. Ensure you have added or ran the appropriate tests for your PR: https://git.k8s.io/community/contributors/devel/sig-testing/testing.md
4. If you want *faster* PR reviews, read how: https://git.k8s.io/community/contributors/guide/pull-requests.md#best-practices-for-faster-reviews
5. If the PR is unfinished, see how to mark it: https://git.k8s.io/community/contributors/guide/pull-requests.md#marking-unfinished-pull-requests
-->

#### What type of PR is this?
/kind feature
<!--
Add one of the following kinds:
/kind bug
/kind cleanup
/kind documentation
/kind feature
/kind design

Optionally add one or more of the following kinds if applicable:
/kind api-change
/kind deprecation
/kind failing-test
/kind flake
/kind regression
-->

#### What this PR does / why we need it:
Use list pip ARM call to refresh pipcache, listing pip function queries from cache instead of making direct arm api call.
This can reduce pip list arm calls.
#### Which issue(s) this PR fixes:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
_If PR is about `failing-tests or flakes`, please post the related issues/tests in a comment and do not use `Fixes`_*
-->
Fixes #
related #3088 
#### Special notes for your reviewer:

#### Does this PR introduce a user-facing change?
<!--
If no, just write "NONE" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".

For more information on release notes see: https://git.k8s.io/community/contributors/guide/release-notes.md
-->
```release-note
NONE
```

#### Additional documentation e.g., KEPs (Kubernetes Enhancement Proposals), usage docs, etc.:

<!--
This section can be blank if this pull request does not require a release note.

When adding links which point to resources within git repositories, like
KEPs or supporting documentation, please reference a specific commit and avoid
linking directly to the master branch. This ensures that links reference a
specific point in time, rather than a document that may change over time.

See here for guidance on getting permanent links to files: https://help.github.com/en/articles/getting-permanent-links-to-files

Please use the following format for linking documentation:
- [KEP]: <link>
- [Usage]: <link>
- [Other doc]: <link>
-->
```docs

```



Patch:
diff --git a/pkg/consts/consts.go b/pkg/consts/consts.go
index 28e2fb92f..ff74d3457 100644
--- a/pkg/consts/consts.go
+++ b/pkg/consts/consts.go
@@ -343,9 +343,6 @@ const (
 	// LoadBalancerBackendPoolConfigurationTypePODIP is the lb backend pool config type pod ip
 	// TODO (nilo19): support pod IP in the future
 	LoadBalancerBackendPoolConfigurationTypePODIP = "podIP"
-
-	// To get pip, we need both resource group name and pip name, key in cache has format: pip_rg:pip_name
-	PIPCacheKeySeparator = ":"
 )
 
 // error messages
diff --git a/pkg/provider/azure.go b/pkg/provider/azure.go
index c7c2eb97c..32d62978b 100644
--- a/pkg/provider/azure.go
+++ b/pkg/provider/azure.go
@@ -345,6 +345,9 @@ type Cloud struct {
 	lbCache  *azcache.TimedCache
 	nsgCache *azcache.TimedCache
 	rtCache  *azcache.TimedCache
+	// public ip cache
+	// key: [resourceGroupName]
+	// Value: sync.Map of [pipName]*PublicIPAddress
 	pipCache *azcache.TimedCache
 	// use LB frontEndIpConfiguration ID as the key and search for PLS attached to the frontEnd
 	plsCache *azcache.TimedCache
diff --git a/pkg/provider/azure_backoff.go b/pkg/provider/azure_backoff.go
index 6620a82e2..b33ad6030 100644
--- a/pkg/provider/azure_backoff.go
+++ b/pkg/provider/azure_backoff.go
@@ -35,6 +35,7 @@ import (
 	"k8s.io/apimachinery/pkg/util/wait"
 	cloudprovider "k8s.io/cloud-provider"
 	"k8s.io/klog/v2"
+	"k8s.io/utils/pointer"
 
 	azcache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
 	"sigs.k8s.io/cloud-provider-azure/pkg/consts"
@@ -385,25 +386,6 @@ func (az *Cloud) ListLB(service *v1.Service) ([]network.LoadBalancer, error) {
 	return allLBs, nil
 }
 
-// ListPIP list the PIP resources in the given resource group
-func (az *Cloud) ListPIP(service *v1.Service, pipResourceGroup string) ([]network.PublicIPAddress, error) {
-	ctx, cancel := getContextWithCancel()
-	defer cancel()
-
-	allPIPs, rerr := az.PublicIPAddressesClient.List(ctx, pipResourceGroup)
-	if rerr != nil {
-		if rerr.IsNotFound() {
-			return nil, nil
-		}
-		az.Event(service, v1.EventTypeWarning, "ListPublicIPs", rerr.Error().Error())
-		klog.Errorf("PublicIPAddressesClient.List(%v) failure with err=%v", pipResourceGroup, rerr)
-		return nil, rerr.Error()
-	}
-
-	klog.V(2).Infof("PublicIPAddressesClient.List(%v) success", pipResourceGroup)
-	return allPIPs, nil
-}
-
 // CreateOrUpdatePIP invokes az.PublicIPAddressesClient.CreateOrUpdate with exponential backoff retry
 func (az *Cloud) CreateOrUpdatePIP(service *v1.Service, pipResourceGroup string, pip network.PublicIPAddress) error {
 	ctx, cancel := getContextWithCancel()
@@ -413,7 +395,7 @@ func (az *Cloud) CreateOrUpdatePIP(service *v1.Service, pipResourceGroup string,
 	klog.V(10).Infof("PublicIPAddressesClient.CreateOrUpdate(%s, %s): end", pipResourceGroup, to.String(pip.Name))
 	if rerr == nil {
 		// Invalidate the cache right after updating
-		_ = az.pipCache.Delete(az.getPIPCacheKey(pipResourceGroup, to.String(pip.Name)))
+		_ = az.pipCache.Delete(pipResourceGroup)
 		return nil
 	}
 
@@ -423,15 +405,15 @@ func (az *Cloud) CreateOrUpdatePIP(service *v1.Service, pipResourceGroup string,
 
 	// Invalidate the cache because ETAG precondition mismatch.
 	if rerr.HTTPStatusCode == http.StatusPreconditionFailed {
-		klog.V(3).Infof("PublicIP cache for (%s, %s) is cleanup because of http.StatusPreconditionFailed", pipResourceGroup, to.String(pip.Name))
-		_ = az.pipCache.Delete(az.getPIPCacheKey(pipResourceGroup, to.String(pip.Name)))
+		klog.V(3).Infof("PublicIP cache for (%s, %s) is cleanup because of http.StatusPreconditionFailed", pipResourceGroup, pointer.StringDeref(pip.Name, ""))
+		_ = az.pipCache.Delete(pipResourceGroup)
 	}
 
 	retryErrorMessage := rerr.Error().Error()
 	// Invalidate the cache because another new operation has canceled the current request.
 	if strings.Contains(strings.ToLower(retryErrorMessage), consts.OperationCanceledErrorMessage) {
-		klog.V(3).Infof("PublicIP cache for (%s, %s) is cleanup because CreateOrUpdate is canceled by another operation", pipResourceGroup, to.String(pip.Name))
-		_ = az.pipCache.Delete(az.getPIPCacheKey(pipResourceGroup, to.String(pip.Name)))
+		klog.V(3).Infof("PublicIP cache for (%s, %s) is cleanup because CreateOrUpdate is canceled by another operation", pipResourceGroup, pointer.StringDeref(pip.Name, ""))
+		_ = az.pipCache.Delete(pipResourceGroup)
 	}
 
 	return rerr.Error()
@@ -471,7 +453,7 @@ func (az *Cloud) DeletePublicIP(service *v1.Service, pipResourceGroup string, pi
 	}
 
 	// Invalidate the cache right after deleting
-	_ = az.pipCache.Delete(az.getPIPCacheKey(pipResourceGroup, pipName))
+	_ = az.pipCache.Delete(pipResourceGroup)
 	return nil
 }
 
diff --git a/pkg/provider/azure_loadbalancer.go b/pkg/provider/azure_loadbalancer.go
index da4ed3fe8..529f491f9 100644
--- a/pkg/provider/azure_loadbalancer.go
+++ b/pkg/provider/azure_loadbalancer.go
@@ -1021,7 +1021,7 @@ func (az *Cloud) findMatchedPIPByLoadBalancerIP(service *v1.Service, loadBalance
 		// this should not happen
 		return nil, fmt.Errorf("findMatchedPIPByLoadBalancerIP: nil pip list passed")
 	} else if *pips == nil {
-		pipList, err := az.ListPIP(service, pipResourceGroup)
+		pipList, err := az.listPIP(pipResourceGroup)
 		if err != nil {
 			return nil, err
 		}
@@ -3034,7 +3034,7 @@ func (az *Cloud) reconcilePublicIP(clusterName string, service *v1.Service, lbNa
 
 	pipResourceGroup := az.getPublicIPAddressResourceGroup(service)
 
-	pips, err := az.ListPIP(service, pipResourceGroup)
+	pips, err := az.listPIP(pipResourceGroup)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/provider/azure_wrap.go b/pkg/provider/azure_wrap.go
index c004556c1..950209f38 100644
--- a/pkg/provider/azure_wrap.go
+++ b/pkg/provider/azure_wrap.go
@@ -21,6 +21,7 @@ import (
 	"net/http"
 	"regexp"
 	"strings"
+	"sync"
 	"time"
 
 	"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2022-03-01/compute"
@@ -30,10 +31,12 @@ import (
 	"k8s.io/apimachinery/pkg/types"
 	cloudprovider "k8s.io/cloud-provider"
 	"k8s.io/klog/v2"
+	"k8s.io/utils/pointer"
 
 	azcache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
 	"sigs.k8s.io/cloud-provider-azure/pkg/consts"
 	"sigs.k8s.io/cloud-provider-azure/pkg/retry"
+	"sigs.k8s.io/cloud-provider-azure/pkg/util/deepcopy"
 )
 
 var (
@@ -98,27 +101,44 @@ func (az *Cloud) getRouteTable(crt azcache.AzureCacheReadType) (routeTable netwo
 	return *(cachedRt.(*network.RouteTable)), true, nil
 }
 
-func (az *Cloud) getPIPCacheKey(pipResourceGroup string, pipName string) string {
-	resourceGroup := az.ResourceGroup
-	if pipResourceGroup != "" {
-		resourceGroup = pipResourceGroup
-	}
-	return fmt.Sprintf("%s%s%s", resourceGroup, consts.PIPCacheKeySeparator, pipName)
-}
-
 func (az *Cloud) getPublicIPAddress(pipResourceGroup string, pipName string, crt azcache.AzureCacheReadType) (network.PublicIPAddress, bool, error) {
-	pip := network.PublicIPAddress{}
-	cacheKey := az.getPIPCacheKey(pipResourceGroup, pipName)
-	cachedPIP, err := az.pipCache.GetWithDeepCopy(cacheKey, crt)
+	cached, err := az.pipCache.Get(pipResourceGroup, crt)
 	if err != nil {
-		return pip, false, err
+		return network.PublicIPAddress{}, false, err
 	}
 
-	if cachedPIP == nil {
-		return pip, false, nil
+	pips := cached.(*sync.Map)
+	pip, ok := pips.Load(pipName)
+	if !ok {
+		// pip not found, refresh cache and retry
+		cached, err = az.pipCache.Get(pipResourceGroup, azcache.CacheReadTypeForceRefresh)
+		if err != nil {
+			return network.PublicIPAddress{}, false, err
+		}
+		pips = cached.(*sync.Map)
+		pip, ok = pips.Load(pipName)
+		if !ok {
+			return network.PublicIPAddress{}, false, nil
+		}
 	}
 
-	return *(cachedPIP.(*network.PublicIPAddress)), true, nil
+	pip = pip.(*network.PublicIPAddress)
+	return *(deepcopy.Copy(pip).(*network.PublicIPAddress)), true, nil
+}
+
+func (az *Cloud) listPIP(pipResourceGroup string) ([]network.PublicIPAddress, error) {
+	cached, err := az.pipCache.Get(pipResourceGroup, azcache.CacheReadTypeDefault)
+	if err != nil {
+		return nil, err
+	}
+	pips := cached.(*sync.Map)
+	var ret []network.PublicIPAddress
+	pips.Range(func(key, value interface{}) bool {
+		pip := value.(*network.PublicIPAddress)
+		ret = append(ret, *pip)
+		return true
+	})
+	return ret, nil
 }
 
 func (az *Cloud) getSubnet(virtualNetworkName string, subnetName string) (network.Subnet, bool, error) {
@@ -304,24 +324,18 @@ func (az *Cloud) newPIPCache() (*azcache.TimedCache, error) {
 		ctx, cancel := getContextWithCancel()
 		defer cancel()
 
-		parsedKey := strings.Split(strings.TrimSpace(key), consts.PIPCacheKeySeparator)
-		if len(parsedKey) != 2 {
-			return nil, fmt.Errorf("failed to parse public ip rg and name from cache key %q", key)
-		}
-		pipResourceGroup, pipName := strings.TrimSpace(parsedKey[0]), strings.TrimSpace(parsedKey[1])
-
-		pip, err := az.PublicIPAddressesClient.Get(ctx, pipResourceGroup, pipName, "")
-		exists, rerr := checkResourceExistsFromError(err)
+		pipResourceGroup := key
+		pipList, rerr := az.PublicIPAddressesClient.List(ctx, pipResourceGroup)
 		if rerr != nil {
 			return nil, rerr.Error()
 		}
 
-		if !exists {
-			klog.V(2).Infof("Public IP %q in rg %q not found", pipName, pipResourceGroup)
-			return nil, nil
+		pipMap := &sync.Map{}
+		for _, pip := range pipList {
+			pip := pip
+			pipMap.Store(pointer.StringDeref(pip.Name, ""), &pip)
 		}
-
-		return &pip, nil
+		return pipMap, nil
 	}
 
 	if az.PublicIPCacheTTLInSeconds == 0 {


Test patch:
diff --git a/pkg/provider/azure_backoff_test.go b/pkg/provider/azure_backoff_test.go
index 76db3e1b7..e084bddbd 100644
--- a/pkg/provider/azure_backoff_test.go
+++ b/pkg/provider/azure_backoff_test.go
@@ -25,6 +25,7 @@ import (
 	v1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/util/wait"
 	cloudprovider "k8s.io/cloud-provider"
+	"k8s.io/utils/pointer"
 
 	"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2022-03-01/compute"
 	"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-08-01/network"
@@ -194,6 +195,7 @@ func TestGetIPForMachineWithRetry(t *testing.T) {
 	}
 
 	expectedPIP := network.PublicIPAddress{
+		Name: pointer.String("pip"),
 		PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
 			IPAddress: to.StringPtr("5.6.7.8"),
 		},
@@ -208,7 +210,7 @@ func TestGetIPForMachineWithRetry(t *testing.T) {
 		mockInterfaceClient.EXPECT().Get(gomock.Any(), az.ResourceGroup, "nic", gomock.Any()).Return(expectedInterface, nil).MaxTimes(1)
 
 		mockPIPClient := az.PublicIPAddressesClient.(*mockpublicipclient.MockInterface)
-		mockPIPClient.EXPECT().Get(gomock.Any(), az.ResourceGroup, "pip", gomock.Any()).Return(expectedPIP, nil).MaxTimes(1)
+		mockPIPClient.EXPECT().List(gomock.Any(), az.ResourceGroup).Return([]network.PublicIPAddress{expectedPIP}, nil).MaxTimes(1)
 
 		privateIP, publicIP, err := az.GetIPForMachineWithRetry("vm")
 		assert.Equal(t, test.expectedErr, err)
@@ -272,13 +274,13 @@ func TestCreateOrUpdateLB(t *testing.T) {
 		mockLBClient.EXPECT().Get(gomock.Any(), az.ResourceGroup, "lb", gomock.Any()).Return(network.LoadBalancer{}, nil)
 
 		mockPIPClient := az.PublicIPAddressesClient.(*mockpublicipclient.MockInterface)
-		mockPIPClient.EXPECT().CreateOrUpdate(gomock.Any(), az.ResourceGroup, "pip", gomock.Any()).Return(nil).AnyTimes()
-		mockPIPClient.EXPECT().Get(gomock.Any(), az.ResourceGroup, "pip", gomock.Any()).Return(network.PublicIPAddress{
-			Name: to.StringPtr("pip"),
+		mockPIPClient.EXPECT().CreateOrUpdate(gomock.Any(), az.ResourceGroup, "pip", gomock.Any()).Return(nil).MaxTimes(1)
+		mockPIPClient.EXPECT().List(gomock.Any(), az.ResourceGroup).Return([]network.PublicIPAddress{{
+			Name: pointer.String("pip"),
 			PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
 				ProvisioningState: network.ProvisioningStateSucceeded,
 			},
-		}, nil).AnyTimes()
+		}}, nil).MaxTimes(2)
 
 		err := az.CreateOrUpdateLB(&v1.Service{}, network.LoadBalancer{
 			Name: to.StringPtr("lb"),
@@ -292,7 +294,7 @@ func TestCreateOrUpdateLB(t *testing.T) {
 		assert.Empty(t, shouldBeEmpty)
 
 		// public ip cache should be populated since there's GetPIP
-		shouldNotBeEmpty, err := az.pipCache.GetWithDeepCopy(az.getPIPCacheKey(az.ResourceGroup, "pip"), cache.CacheReadTypeDefault)
+		shouldNotBeEmpty, err := az.pipCache.Get(az.ResourceGroup, cache.CacheReadTypeDefault)
 		assert.NoError(t, err)
 		assert.NotEmpty(t, shouldNotBeEmpty)
 	}
@@ -350,34 +352,6 @@ func TestListAgentPoolLBs(t *testing.T) {
 	}
 }
 
-func TestListPIP(t *testing.T) {
-	ctrl := gomock.NewController(t)
-	defer ctrl.Finish()
-
-	tests := []struct {
-		clientErr   *retry.Error
-		expectedErr error
-	}{
-		{
-			clientErr:   &retry.Error{HTTPStatusCode: http.StatusInternalServerError},
-			expectedErr: fmt.Errorf("Retriable: false, RetryAfter: 0s, HTTPStatusCode: 500, RawError: %w", error(nil)),
-		},
-		{
-			clientErr:   &retry.Error{HTTPStatusCode: http.StatusNotFound},
-			expectedErr: nil,
-		},
-	}
-	for _, test := range tests {
-		az := GetTestCloud(ctrl)
-		mockPIPClient := az.PublicIPAddressesClient.(*mockpublicipclient.MockInterface)
-		mockPIPClient.EXPECT().List(gomock.Any(), az.ResourceGroup).Return(nil, test.clientErr)
-
-		pips, err := az.ListPIP(&v1.Service{}, az.ResourceGroup)
-		assert.Equal(t, test.expectedErr, err)
-		assert.Empty(t, pips)
-	}
-}
-
 func TestCreateOrUpdatePIP(t *testing.T) {
 	ctrl := gomock.NewController(t)
 	defer ctrl.Finish()
@@ -406,18 +380,17 @@ func TestCreateOrUpdatePIP(t *testing.T) {
 
 	for _, test := range tests {
 		az := GetTestCloud(ctrl)
-		cacheKey := az.getPIPCacheKey(az.ResourceGroup, "nic")
-		az.pipCache.Set(cacheKey, "test")
+		az.pipCache.Set(az.ResourceGroup, []network.PublicIPAddress{{Name: pointer.String("test")}})
 		mockPIPClient := az.PublicIPAddressesClient.(*mockpublicipclient.MockInterface)
 		mockPIPClient.EXPECT().CreateOrUpdate(gomock.Any(), az.ResourceGroup, "nic", gomock.Any()).Return(test.clientErr)
 		if test.cacheExpectedEmpty {
-			mockPIPClient.EXPECT().Get(gomock.Any(), az.ResourceGroup, "nic", gomock.Any()).Return(network.PublicIPAddress{}, nil)
+			mockPIPClient.EXPECT().List(gomock.Any(), az.ResourceGroup).Return([]network.PublicIPAddress{}, nil)
 		}
 
 		err := az.CreateOrUpdatePIP(&v1.Service{}, az.ResourceGroup, network.PublicIPAddress{Name: to.StringPtr("nic")})
 		assert.EqualError(t, test.expectedErr, err.Error())
 
-		cachedPIP, err := az.pipCache.GetWithDeepCopy(az.getPIPCacheKey(az.ResourceGroup, "nic"), cache.CacheReadTypeDefault)
+		cachedPIP, err := az.pipCache.GetWithDeepCopy(az.ResourceGroup, cache.CacheReadTypeDefault)
 		assert.NoError(t, err)
 		if test.cacheExpectedEmpty {
 			assert.Empty(t, cachedPIP)
diff --git a/pkg/provider/azure_instances_test.go b/pkg/provider/azure_instances_test.go
index 65fcbdf60..731b1fbaf 100644
--- a/pkg/provider/azure_instances_test.go
+++ b/pkg/provider/azure_instances_test.go
@@ -34,6 +34,7 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/types"
 	cloudprovider "k8s.io/cloud-provider"
+	"k8s.io/utils/pointer"
 
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/interfaceclient/mockinterfaceclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/publicipclient/mockpublicipclient"
@@ -401,7 +402,8 @@ func TestNodeAddresses(t *testing.T) {
 	}
 
 	expectedPIP := network.PublicIPAddress{
-		ID: to.StringPtr("/subscriptions/subscriptionID/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/pip1"),
+		Name: pointer.String("pip1"),
+		ID:   pointer.String("/subscriptions/subscriptionID/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/pip1"),
 		PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
 			IPAddress: to.StringPtr("192.168.1.12"),
 		},
@@ -644,7 +646,7 @@ func TestNodeAddresses(t *testing.T) {
 		mockVMClient.EXPECT().Get(gomock.Any(), cloud.ResourceGroup, "vm2", gomock.Any()).Return(compute.VirtualMachine{}, &retry.Error{HTTPStatusCode: http.StatusNotFound, RawError: cloudprovider.InstanceNotFound}).AnyTimes()
 
 		mockPublicIPAddressesClient := cloud.PublicIPAddressesClient.(*mockpublicipclient.MockInterface)
-		mockPublicIPAddressesClient.EXPECT().Get(gomock.Any(), cloud.ResourceGroup, "pip1", gomock.Any()).Return(expectedPIP, nil).AnyTimes()
+		mockPublicIPAddressesClient.EXPECT().List(gomock.Any(), cloud.ResourceGroup).Return([]network.PublicIPAddress{expectedPIP}, nil).AnyTimes()
 
 		mockInterfaceClient := cloud.InterfacesClient.(*mockinterfaceclient.MockInterface)
 		mockInterfaceClient.EXPECT().Get(gomock.Any(), cloud.ResourceGroup, "nic", gomock.Any()).Return(expectedInterface, nil).AnyTimes()
@@ -894,12 +896,13 @@ func TestInstanceMetadata(t *testing.T) {
 		mockNICClient := cloud.InterfacesClient.(*mockinterfaceclient.MockInterface)
 		mockNICClient.EXPECT().Get(gomock.Any(), cloud.ResourceGroup, "k8s-agentpool1-00000000-nic-1", gomock.Any()).Return(expectedNIC, nil)
 		expectedPIP := network.PublicIPAddress{
+			Name: pointer.String("pip"),
 			PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
 				IPAddress: to.StringPtr("5.6.7.8"),
 			},
 		}
 		mockPIPClient := cloud.PublicIPAddressesClient.(*mockpublicipclient.MockInterface)
-		mockPIPClient.EXPECT().Get(gomock.Any(), cloud.ResourceGroup, "pip", gomock.Any()).Return(expectedPIP, nil)
+		mockPIPClient.EXPECT().List(gomock.Any(), cloud.ResourceGroup).Return([]network.PublicIPAddress{expectedPIP}, nil)
 
 		expectedMetadata := cloudprovider.InstanceMetadata{
 			ProviderID:   "azure:///subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Compute/VirtualMachines/vm",
diff --git a/pkg/provider/azure_loadbalancer_test.go b/pkg/provider/azure_loadbalancer_test.go
index 9ad9227b8..58b84722d 100644
--- a/pkg/provider/azure_loadbalancer_test.go
+++ b/pkg/provider/azure_loadbalancer_test.go
@@ -38,6 +38,7 @@ import (
 
 	v1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/utils/pointer"
 
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/loadbalancerclient/mockloadbalancerclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/privatelinkserviceclient/mockprivatelinkserviceclient"
@@ -168,12 +169,16 @@ func TestGetLoadBalancer(t *testing.T) {
 		az := GetTestCloud(ctrl)
 		mockPIPsClient := az.PublicIPAddressesClient.(*mockpublicipclient.MockInterface)
 		if c.pipExists {
-			mockPIPsClient.EXPECT().Get(gomock.Any(), "rg", gomock.Any(), gomock.Any()).Return(network.PublicIPAddress{
-				PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-					IPAddress: to.StringPtr("1.2.3.4"),
-				}}, nil)
+			mockPIPsClient.EXPECT().List(gomock.Any(), "rg").Return([]network.PublicIPAddress{
+				{
+					Name: pointer.String("testCluster-aservice"),
+					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
+						IPAddress: pointer.String("1.2.3.4"),
+					},
+				},
+			}, nil)
 		} else {
-			mockPIPsClient.EXPECT().Get(gomock.Any(), "rg", gomock.Any(), gomock.Any()).Return(network.PublicIPAddress{}, &retry.Error{HTTPStatusCode: http.StatusNotFound})
+			mockPIPsClient.EXPECT().List(gomock.Any(), "rg").Return([]network.PublicIPAddress{}, nil).MaxTimes(2)
 		}
 		mockLBsClient := az.LoadBalancerClient.(*mockloadbalancerclient.MockInterface)
 		mockLBsClient.EXPECT().List(gomock.Any(), az.Config.ResourceGroup).Return(c.existingLBs, nil)
@@ -746,6 +751,10 @@ func TestEnsureLoadBalancerDeleted(t *testing.T) {
 		expectedLBs := make([]network.LoadBalancer, 0)
 		setMockLBs(az, ctrl, &expectedLBs, "service", 1, i+1, c.isInternalSvc)
 
+		mockPLSClient := mockprivatelinkserviceclient.NewMockInterface(ctrl)
+		mockPLSClient.EXPECT().List(gomock.Any(), az.Config.ResourceGroup).Return([]network.PrivateLinkService{}, nil).MaxTimes(2)
+		az.PrivateLinkServiceClient = mockPLSClient
+
 		// create the service first.
 		lbStatus, err := az.EnsureLoadBalancer(context.TODO(), testClusterName, &c.service, clusterResources.nodes)
 		if c.expectCreateError {
@@ -771,11 +780,6 @@ func TestEnsureLoadBalancerDeleted(t *testing.T) {
 		expectedLBs = make([]network.LoadBalancer, 0)
 		setMockLBs(az, ctrl, &expectedLBs, "service", 1, i+1, c.isInternalSvc)
 
-		expectedPLS := make([]network.PrivateLinkService, 0)
-		mockPLSClient := mockprivatelinkserviceclient.NewMockInterface(ctrl)
-		mockPLSClient.EXPECT().List(gomock.Any(), az.Config.ResourceGroup).Return(expectedPLS, nil).MaxTimes(1)
-		az.PrivateLinkServiceClient = mockPLSClient
-
 		err = az.EnsureLoadBalancerDeleted(context.TODO(), testClusterName, &c.service)
 		expectedLBs = make([]network.LoadBalancer, 0)
 		mockLBsClient := mockloadbalancerclient.NewMockInterface(ctrl)
@@ -2022,12 +2026,12 @@ func TestIsFrontendIPChanged(t *testing.T) {
 			mockPIPsClient := az.PublicIPAddressesClient.(*mockpublicipclient.MockInterface)
 			mockPIPsClient.EXPECT().CreateOrUpdate(gomock.Any(), "rg", gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
 			for _, existingPIP := range test.existingPIPs {
-				mockPIPsClient.EXPECT().Get(gomock.Any(), "rg", *existingPIP.Name, gomock.Any()).Return(existingPIP, nil).AnyTimes()
 				err := az.PublicIPAddressesClient.CreateOrUpdate(context.TODO(), "rg", *existingPIP.Name, existingPIP)
 				if err != nil {
 					t.Fatal(err)
 				}
 			}
+			mockPIPsClient.EXPECT().List(gomock.Any(), "rg").Return(test.existingPIPs, nil).MaxTimes(2)
 			setServiceLoadBalancerIP(&test.service, test.loadBalancerIP)
 			test.service.Annotations[consts.ServiceAnnotationLoadBalancerInternalSubnet] = test.annotations
 			flag, rerr := az.isFrontendIPChanged("testCluster", test.config,
@@ -3748,8 +3752,7 @@ func TestReconcilePublicIP(t *testing.T) {
 	defer ctrl.Finish()
 
 	deleteUnwantedPIPsAndCreateANewOneclientGet := func(client *mockpublicipclient.MockInterface) {
-		client.EXPECT().Get(gomock.Any(), "rg", "testCluster-atest1", gomock.Any()).Return(network.PublicIPAddress{}, &retry.Error{HTTPStatusCode: http.StatusNotFound}).Times(1)
-		client.EXPECT().Get(gomock.Any(), "rg", "testCluster-atest1", gomock.Any()).Return(network.PublicIPAddress{ID: to.StringPtr("/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/testCluster-atest1")}, nil).Times(1)
+		client.EXPECT().Get(gomock.Any(), "rg", "testCluster-atest1", gomock.Any()).Return(network.PublicIPAddress{ID: pointer.String("/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/testCluster-atest1")}, nil).Times(1)
 	}
 
 	testCases := []struct {
@@ -4056,7 +4059,6 @@ func TestReconcilePublicIP(t *testing.T) {
 				return nil
 			})
 
-			mockPIPsClient.EXPECT().List(gomock.Any(), "rg").Return(test.existingPIPs, nil)
 			if test.expectedClientGet != nil {
 				(*test.expectedClientGet)(mockPIPsClient)
 			}
@@ -4092,6 +4094,18 @@ func TestReconcilePublicIP(t *testing.T) {
 				// Clear create or update count to prepare for main execution
 				createOrUpdateCount = 0
 			}
+			lister := mockPIPsClient.EXPECT().List(gomock.Any(), "rg").AnyTimes()
+			lister.DoAndReturn(func(ctx context.Context, resourceGroupName string) (result []network.PublicIPAddress, rerr *retry.Error) {
+				m.Lock()
+				for pipName, pip := range savedPips {
+					deleted, deletedContains := deletedPips[pipName]
+					if !deletedContains || !deleted {
+						result = append(result, pip)
+					}
+				}
+				m.Unlock()
+				return
+			})
 			pip, err := az.reconcilePublicIP("testCluster", &service, "", test.wantLb)
 			if !test.expectedError {
 				assert.NoError(t, err)
@@ -4376,7 +4390,7 @@ func TestEnsurePublicIPExists(t *testing.T) {
 					"/providers/Microsoft.Network/publicIPAddresses/pip1"),
 				PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
 					PublicIPAddressVersion:   network.IPVersionIPv6,
-					PublicIPAllocationMethod: network.IPAllocationMethodDynamic,
+					PublicIPAllocationMethod: network.IPAllocationMethodStatic,
 				},
 				Tags: map[string]*string{},
 			},
@@ -4416,25 +4430,30 @@ func TestEnsurePublicIPExists(t *testing.T) {
 				mockPIPsClient.EXPECT().CreateOrUpdate(gomock.Any(), "rg", gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, resourceGroupName string, publicIPAddressName string, parameters network.PublicIPAddress) *retry.Error {
 					if len(test.existingPIPs) != 0 {
 						test.existingPIPs[0] = parameters
+					} else {
+						test.existingPIPs = append(test.existingPIPs, parameters)
 					}
 					return nil
 				}).AnyTimes()
 			}
 			mockPIPsClient.EXPECT().Get(gomock.Any(), "rg", "pip1", gomock.Any()).DoAndReturn(func(ctx context.Context, resourceGroupName string, publicIPAddressName string, expand string) (network.PublicIPAddress, *retry.Error) {
-				var basicPIP network.PublicIPAddress
+				return test.existingPIPs[0], nil
+			}).MaxTimes(1)
+			mockPIPsClient.EXPECT().List(gomock.Any(), "rg").DoAndReturn(func(ctx context.Context, resourceGroupName string) ([]network.PublicIPAddress, *retry.Error) {
+				var basicPIP *network.PublicIPAddress
 				if len(test.existingPIPs) == 0 {
-					basicPIP = network.PublicIPAddress{
-						Name: to.StringPtr("pip1"),
+					basicPIP = &network.PublicIPAddress{
+						Name: pointer.String("pip1"),
 					}
 				} else {
-					basicPIP = test.existingPIPs[0]
+					basicPIP = &test.existingPIPs[0]
 				}
 
 				basicPIP.ID = to.StringPtr("/subscriptions/subscription/resourceGroups/rg" +
 					"/providers/Microsoft.Network/publicIPAddresses/pip1")
 
 				if basicPIP.PublicIPAddressPropertiesFormat == nil {
-					return basicPIP, nil
+					return []network.PublicIPAddress{*basicPIP}, nil
 				}
 
 				if test.isIPv6 {
@@ -4444,7 +4463,7 @@ func TestEnsurePublicIPExists(t *testing.T) {
 					basicPIP.PublicIPAddressPropertiesFormat.PublicIPAddressVersion = network.IPVersionIPv4
 				}
 
-				return basicPIP, nil
+				return []network.PublicIPAddress{*basicPIP}, nil
 			}).AnyTimes()
 
 			pip, err := az.ensurePublicIPExists(&service, "pip1", test.inputDNSLabel, "", false, test.foundDNSLabelAnnotation)
@@ -4484,9 +4503,7 @@ func TestEnsurePublicIPExistsWithExtendedLocation(t *testing.T) {
 		},
 	}
 	mockPIPsClient := az.PublicIPAddressesClient.(*mockpublicipclient.MockInterface)
-	first := mockPIPsClient.EXPECT().Get(gomock.Any(), "rg", "pip1", gomock.Any()).Return(network.PublicIPAddress{}, &retry.Error{
-		HTTPStatusCode: 404,
-	})
+	first := mockPIPsClient.EXPECT().List(gomock.Any(), "rg").Return([]network.PublicIPAddress{}, nil).Times(2)
 	mockPIPsClient.EXPECT().Get(gomock.Any(), "rg", "pip1", gomock.Any()).Return(*expectedPIP, nil).After(first)
 
 	mockPIPsClient.EXPECT().CreateOrUpdate(gomock.Any(), "rg", "pip1", gomock.Any()).
@@ -5315,7 +5332,6 @@ func TestReconcileZonesForFrontendIPConfigs(t *testing.T) {
 		existingFrontendIPConfigs []network.FrontendIPConfiguration
 		existingPIP               network.PublicIPAddress
 		status                    *v1.LoadBalancerStatus
-		getPIPError               *retry.Error
 		getZoneError              *retry.Error
 		regionZonesMap            map[string][]string
 		expectedZones             *[]string
@@ -5327,8 +5343,7 @@ func TestReconcileZonesForFrontendIPConfigs(t *testing.T) {
 			description:               "reconcileFrontendIPConfigs should reconcile the zones for the new fip config",
 			service:                   getTestService("test", v1.ProtocolTCP, nil, false, 80),
 			existingFrontendIPConfigs: []network.FrontendIPConfiguration{},
-			existingPIP:               network.PublicIPAddress{Location: to.StringPtr("eastus")},
-			getPIPError:               &retry.Error{HTTPStatusCode: http.StatusNotFound},
+			existingPIP:               network.PublicIPAddress{Location: pointer.String("eastus")},
 			regionZonesMap:            map[string][]string{"westus": {"1", "2", "3"}, "eastus": {"1", "2"}},
 			expectedDirty:             true,
 		},
@@ -5336,8 +5351,7 @@ func TestReconcileZonesForFrontendIPConfigs(t *testing.T) {
 			description:               "reconcileFrontendIPConfigs should reconcile the zones for the new internal fip config",
 			service:                   getInternalTestService("test", 80),
 			existingFrontendIPConfigs: []network.FrontendIPConfiguration{},
-			existingPIP:               network.PublicIPAddress{Location: to.StringPtr("eastus")},
-			getPIPError:               &retry.Error{HTTPStatusCode: http.StatusNotFound},
+			existingPIP:               network.PublicIPAddress{Location: pointer.String("eastus")},
 			regionZonesMap:            map[string][]string{"westus": {"1", "2", "3"}, "eastus": {"1", "2"}},
 			expectedZones:             &[]string{"1", "2", "3"},
 			expectedDirty:             true,
@@ -5425,8 +5439,8 @@ func TestReconcileZonesForFrontendIPConfigs(t *testing.T) {
 			lb.FrontendIPConfigurations = &tc.existingFrontendIPConfigs
 
 			mockPIPClient := cloud.PublicIPAddressesClient.(*mockpublicipclient.MockInterface)
-			mockPIPClient.EXPECT().Get(gomock.Any(), "rg", gomock.Any(), gomock.Any()).Return(tc.existingPIP, tc.getPIPError).MaxTimes(1)
-			mockPIPClient.EXPECT().Get(gomock.Any(), "rg", gomock.Any(), gomock.Any()).Return(tc.existingPIP, nil).MaxTimes(1)
+			first := mockPIPClient.EXPECT().List(gomock.Any(), "rg").Return([]network.PublicIPAddress{}, nil).MaxTimes(2)
+			mockPIPClient.EXPECT().Get(gomock.Any(), "rg", gomock.Any(), gomock.Any()).Return(tc.existingPIP, nil).MaxTimes(1).After(first)
 			mockPIPClient.EXPECT().CreateOrUpdate(gomock.Any(), "rg", gomock.Any(), gomock.Any()).Return(nil).MaxTimes(1)
 
 			subnetClient := cloud.SubnetsClient.(*mocksubnetclient.MockInterface)
diff --git a/pkg/provider/azure_test.go b/pkg/provider/azure_test.go
index 711c5e96f..a10280056 100644
--- a/pkg/provider/azure_test.go
+++ b/pkg/provider/azure_test.go
@@ -40,6 +40,7 @@ import (
 	cloudprovider "k8s.io/cloud-provider"
 	cloudproviderapi "k8s.io/cloud-provider/api"
 	servicehelpers "k8s.io/cloud-provider/service/helpers"
+	"k8s.io/utils/pointer"
 
 	"sigs.k8s.io/cloud-provider-azure/pkg/auth"
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/interfaceclient/mockinterfaceclient"
@@ -164,45 +165,46 @@ func setMockEnv(az *Cloud, ctrl *gomock.Controller, expectedInterfaces []network
 }
 
 func setMockPublicIPs(az *Cloud, ctrl *gomock.Controller, serviceCount int) {
-	expectedPIPs := []network.PublicIPAddress{
-		{
-			Name:     to.StringPtr("testCluster-aservicea"),
-			Location: &az.Location,
-			PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-				PublicIPAllocationMethod: network.IPAllocationMethodStatic,
-				PublicIPAddressVersion:   network.IPVersionIPv4,
-				IPAddress:                to.StringPtr("1.2.3.4"),
-			},
-			Tags: map[string]*string{
-				consts.ServiceTagKey:  to.StringPtr("default/servicea"),
-				consts.ClusterNameKey: to.StringPtr(testClusterName),
-			},
-			Sku: &network.PublicIPAddressSku{
-				Name: network.PublicIPAddressSkuNameStandard,
-			},
-			ID: to.StringPtr("testCluster-aservice1"),
+	expectedPIP := network.PublicIPAddress{
+		Name:     pointer.String("testCluster-aservicea"),
+		Location: &az.Location,
+		PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
+			PublicIPAllocationMethod: network.IPAllocationMethodStatic,
+			PublicIPAddressVersion:   network.IPVersionIPv4,
+			IPAddress:                pointer.String("1.2.3.4"),
+		},
+		Tags: map[string]*string{
+			consts.ServiceTagKey:  pointer.String("default/servicea"),
+			consts.ClusterNameKey: pointer.String(testClusterName),
+		},
+		Sku: &network.PublicIPAddressSku{
+			Name: network.PublicIPAddressSkuNameStandard,
 		},
+		ID: pointer.String("testCluster-aservice1"),
 	}
 
 	mockPIPsClient := mockpublicipclient.NewMockInterface(ctrl)
 	az.PublicIPAddressesClient = mockPIPsClient
 	mockPIPsClient.EXPECT().CreateOrUpdate(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
-	mockPIPsClient.EXPECT().List(gomock.Any(), az.ResourceGroup).Return(expectedPIPs, nil).AnyTimes()
 	mockPIPsClient.EXPECT().List(gomock.Any(), gomock.Not(az.ResourceGroup)).Return(nil, nil).AnyTimes()
 	mockPIPsClient.EXPECT().Get(gomock.Any(), gomock.Not(az.ResourceGroup), gomock.Any(), gomock.Any()).Return(network.PublicIPAddress{}, &retry.Error{HTTPStatusCode: http.StatusNotFound, RawError: cloudprovider.InstanceNotFound}).AnyTimes()
 
 	a := 'a'
+	var expectedPIPs []network.PublicIPAddress
 	for i := 1; i <= serviceCount; i++ {
-		expectedPIPs[0].Name = to.StringPtr(fmt.Sprintf("testCluster-aservice%d", i))
-		expectedPIPs[0].Tags[consts.ServiceTagKey] = to.StringPtr(fmt.Sprintf("default/service%d", i))
-		mockPIPsClient.EXPECT().Get(gomock.Any(), az.ResourceGroup, fmt.Sprintf("testCluster-aservice%d", i), gomock.Any()).Return(expectedPIPs[0], nil).AnyTimes()
+		expectedPIP.Name = pointer.String(fmt.Sprintf("testCluster-aservice%d", i))
+		expectedPIP.Tags[consts.ServiceTagKey] = pointer.String(fmt.Sprintf("default/service%d", i))
+		mockPIPsClient.EXPECT().Get(gomock.Any(), az.ResourceGroup, fmt.Sprintf("testCluster-aservice%d", i), gomock.Any()).Return(expectedPIP, nil).AnyTimes()
 		mockPIPsClient.EXPECT().Delete(gomock.Any(), az.ResourceGroup, fmt.Sprintf("testCluster-aservice%d", i)).Return(nil).AnyTimes()
-		expectedPIPs[0].Name = to.StringPtr(fmt.Sprintf("testCluster-aservice%c", a))
-		expectedPIPs[0].Tags[consts.ServiceTagKey] = to.StringPtr(fmt.Sprintf("default/service%c", a))
-		mockPIPsClient.EXPECT().Get(gomock.Any(), az.ResourceGroup, fmt.Sprintf("testCluster-aservice%c", a), gomock.Any()).Return(expectedPIPs[0], nil).AnyTimes()
+		expectedPIPs = append(expectedPIPs, expectedPIP)
+		expectedPIP.Name = pointer.String(fmt.Sprintf("testCluster-aservice%c", a))
+		expectedPIP.Tags[consts.ServiceTagKey] = pointer.String(fmt.Sprintf("default/service%c", a))
+		mockPIPsClient.EXPECT().Get(gomock.Any(), az.ResourceGroup, fmt.Sprintf("testCluster-aservice%c", a), gomock.Any()).Return(expectedPIP, nil).AnyTimes()
 		mockPIPsClient.EXPECT().Delete(gomock.Any(), az.ResourceGroup, fmt.Sprintf("testCluster-aservice%c", a)).Return(nil).AnyTimes()
+		expectedPIPs = append(expectedPIPs, expectedPIP)
 		a++
 	}
+	mockPIPsClient.EXPECT().List(gomock.Any(), az.ResourceGroup).Return(expectedPIPs, nil).AnyTimes()
 }
 
 func setMockSecurityGroup(az *Cloud, ctrl *gomock.Controller, sgs ...*network.SecurityGroup) {
@@ -1087,7 +1089,7 @@ func TestServiceDefaultsToNoSessionPersistence(t *testing.T) {
 	mockPIPsClient := mockpublicipclient.NewMockInterface(ctrl)
 	az.PublicIPAddressesClient = mockPIPsClient
 	mockPIPsClient.EXPECT().CreateOrUpdate(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
-	mockPIPsClient.EXPECT().Get(gomock.Any(), az.ResourceGroup, "testCluster-aservicesaomitted1", gomock.Any()).Return(expectedPIP, nil).AnyTimes()
+	mockPIPsClient.EXPECT().List(gomock.Any(), az.ResourceGroup).Return([]network.PublicIPAddress{expectedPIP}, nil).AnyTimes()
 
 	mockLBBackendPool := az.LoadBalancerBackendPool.(*MockBackendPool)
 	mockLBBackendPool.EXPECT().ReconcileBackendPools(gomock.Any(), gomock.Any(), gomock.Any()).Return(false, false, nil).AnyTimes()
@@ -1141,7 +1143,8 @@ func TestServiceRespectsNoSessionAffinity(t *testing.T) {
 	mockPIPsClient := mockpublicipclient.NewMockInterface(ctrl)
 	az.PublicIPAddressesClient = mockPIPsClient
 	mockPIPsClient.EXPECT().CreateOrUpdate(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
-	mockPIPsClient.EXPECT().Get(gomock.Any(), az.ResourceGroup, gomock.Any(), gomock.Any()).Return(expectedPIP, nil).AnyTimes()
+	mockPIPsClient.EXPECT().List(gomock.Any(), az.ResourceGroup).Return([]network.PublicIPAddress{expectedPIP}, nil).AnyTimes()
+	mockPIPsClient.EXPECT().Get(gomock.Any(), az.ResourceGroup, "testCluster-aservicesanone", gomock.Any()).Return(expectedPIP, nil).AnyTimes()
 
 	expectedPLS := make([]network.PrivateLinkService, 0)
 	mockPLSClient := az.PrivateLinkServiceClient.(*mockprivatelinkserviceclient.MockInterface)
@@ -1201,7 +1204,8 @@ func TestServiceRespectsClientIPSessionAffinity(t *testing.T) {
 	mockPIPsClient := mockpublicipclient.NewMockInterface(ctrl)
 	az.PublicIPAddressesClient = mockPIPsClient
 	mockPIPsClient.EXPECT().CreateOrUpdate(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
-	mockPIPsClient.EXPECT().Get(gomock.Any(), az.ResourceGroup, gomock.Any(), gomock.Any()).Return(expectedPIP, nil).AnyTimes()
+	mockPIPsClient.EXPECT().Get(gomock.Any(), az.ResourceGroup, "testCluster-aservicesaclientip", gomock.Any()).Return(expectedPIP, nil).AnyTimes()
+	mockPIPsClient.EXPECT().List(gomock.Any(), az.ResourceGroup).Return([]network.PublicIPAddress{expectedPIP}, nil).AnyTimes()
 
 	expectedPLS := make([]network.PrivateLinkService, 0)
 	mockPLSClient := az.PrivateLinkServiceClient.(*mockprivatelinkserviceclient.MockInterface)
diff --git a/pkg/provider/azure_wrap_test.go b/pkg/provider/azure_wrap_test.go
index b294d9439..ce99e6cf2 100644
--- a/pkg/provider/azure_wrap_test.go
+++ b/pkg/provider/azure_wrap_test.go
@@ -19,13 +19,18 @@ package provider
 import (
 	"net/http"
 	"reflect"
+	"sync"
 	"testing"
 
+	"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-08-01/network"
 	"github.com/golang/mock/gomock"
 	"github.com/stretchr/testify/assert"
 
 	"k8s.io/apimachinery/pkg/util/sets"
+	"k8s.io/utils/pointer"
 
+	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/publicipclient/mockpublicipclient"
+	azcache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
 	"sigs.k8s.io/cloud-provider-azure/pkg/consts"
 	"sigs.k8s.io/cloud-provider-azure/pkg/retry"
 )
@@ -290,3 +295,114 @@ func TestIsBackendPoolOnSameLB(t *testing.T) {
 		assert.Equal(t, test.expectedLBName, lbName)
 	}
 }
+
+func TestGetPublicIPAddress(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
+
+	tests := []struct {
+		desc          string
+		pipCache      []network.PublicIPAddress
+		expectPIPList bool
+		existingPIPs  []network.PublicIPAddress
+		expectExists  bool
+		expectedPIP   network.PublicIPAddress
+	}{
+		{
+			desc:         "getPublicIPAddress should return pip from cache when it exists",
+			pipCache:     []network.PublicIPAddress{{Name: pointer.String("pip")}},
+			expectExists: true,
+			expectedPIP:  network.PublicIPAddress{Name: pointer.String("pip")},
+		},
+		{
+			desc:          "getPublicIPAddress should from list call when cache is empty",
+			expectPIPList: true,
+			existingPIPs: []network.PublicIPAddress{
+				{Name: pointer.String("pip")},
+				{Name: pointer.String("pip1")},
+			},
+			expectExists: true,
+			expectedPIP:  network.PublicIPAddress{Name: pointer.String("pip")},
+		},
+		{
+			desc:          "getPublicIPAddress should try listing when pip does not exist",
+			pipCache:      []network.PublicIPAddress{{Name: pointer.String("pip1")}},
+			expectPIPList: true,
+			existingPIPs:  []network.PublicIPAddress{{Name: pointer.String("pip1")}},
+			expectExists:  false,
+			expectedPIP:   network.PublicIPAddress{},
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.desc, func(t *testing.T) {
+			pipCache := &sync.Map{}
+			for _, pip := range test.pipCache {
+				pip := pip
+				pipCache.Store(pointer.StringDeref(pip.Name, ""), &pip)
+			}
+			az := GetTestCloud(ctrl)
+			az.pipCache.Set(az.ResourceGroup, pipCache)
+			mockPIPsClient := az.PublicIPAddressesClient.(*mockpublicipclient.MockInterface)
+			if test.expectPIPList {
+				mockPIPsClient.EXPECT().List(gomock.Any(), az.ResourceGroup).Return(test.existingPIPs, nil).MaxTimes(2)
+			}
+			pip, pipExists, err := az.getPublicIPAddress(az.ResourceGroup, "pip", azcache.CacheReadTypeDefault)
+			assert.Equal(t, test.expectedPIP, pip)
+			assert.Equal(t, test.expectExists, pipExists)
+			assert.NoError(t, err)
+		})
+	}
+}
+
+func TestListPIP(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
+
+	tests := []struct {
+		desc          string
+		pipCache      []network.PublicIPAddress
+		expectPIPList bool
+		existingPIPs  []network.PublicIPAddress
+	}{
+		{
+			desc:     "listPIP should return data from cache, when data is empty slice",
+			pipCache: []network.PublicIPAddress{},
+		},
+		{
+			desc: "listPIP should return data from cache",
+			pipCache: []network.PublicIPAddress{
+				{Name: pointer.String("pip1")},
+				{Name: pointer.String("pip2")},
+			},
+		},
+		{
+			desc:          "listPIP should return data from arm list call",
+			expectPIPList: true,
+			existingPIPs:  []network.PublicIPAddress{{Name: pointer.String("pip")}},
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.desc, func(t *testing.T) {
+			az := GetTestCloud(ctrl)
+			if test.pipCache != nil {
+				pipCache := &sync.Map{}
+				for _, pip := range test.pipCache {
+					pip := pip
+					pipCache.Store(pointer.StringDeref(pip.Name, ""), &pip)
+				}
+				az.pipCache.Set(az.ResourceGroup, pipCache)
+			}
+			mockPIPsClient := az.PublicIPAddressesClient.(*mockpublicipclient.MockInterface)
+			if test.expectPIPList {
+				mockPIPsClient.EXPECT().List(gomock.Any(), az.ResourceGroup).Return(test.existingPIPs, nil).MaxTimes(2)
+			}
+			pips, err := az.listPIP(az.ResourceGroup)
+			if test.expectPIPList {
+				assert.ElementsMatch(t, test.existingPIPs, pips)
+			} else {
+				assert.ElementsMatch(t, test.pipCache, pips)
+			}
+			assert.NoError(t, err)
+		})
+	}
+}