Repository: kubernetes-sigs/cloud-provider-azure
Pull request #7496 (2024-11-05 03:50:35)

[release-1.31] Migrate ZoneClient + PLSClient to SDK track2
<!--  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?

<!--
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
-->
/kind feature

#### What this PR does / why we need it:

cherry-pick #7442 and #7456 

#### 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 #

#### 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

```


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/7496#" title="Author self-approved">zarvd</a>*
**Once this PR has been reviewed and has the lgtm label**, please assign [feiskyer](https://github.com/feiskyer) 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.31/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":["feiskyer"]} -->
k8s-ci-robot: Hi @zarvd. Thanks for your PR.

I'm waiting for a [kubernetes-sigs](https://github.com/orgs/kubernetes-sigs/people) member to verify that this patch is reasonable to test. If it is, they should reply with `/ok-to-test` on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should [join the org](https://git.k8s.io/community/community-membership.md#member) to skip this step.

Once the patch is verified, the new status will be reflected by the `ok-to-test` label.

I understand the commands that are listed [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fcloud-provider-azure).

<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-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>

MartinForReal: /ok-to-test
MartinForReal: /lgtm
/approve

Patch:
diff --git a/pkg/azureclients/zoneclient/azure_zoneclient.go b/pkg/azureclients/zoneclient/azure_zoneclient.go
deleted file mode 100644
index 9a226d0b0..000000000
--- a/pkg/azureclients/zoneclient/azure_zoneclient.go
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
-Copyright 2021 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 zoneclient
-
-import (
-	"context"
-	"net/http"
-	"strings"
-
-	"github.com/Azure/go-autorest/autorest"
-	"github.com/Azure/go-autorest/autorest/azure"
-
-	"k8s.io/klog/v2"
-
-	azclients "sigs.k8s.io/cloud-provider-azure/pkg/azureclients"
-	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/armclient"
-	"sigs.k8s.io/cloud-provider-azure/pkg/retry"
-)
-
-var _ Interface = &Client{}
-
-const computeResourceProvider = "Microsoft.Compute"
-
-type resourceTypeMetadata struct {
-	ResourceType string         `json:"resourceType"`
-	ZoneMappings []zoneMappings `json:"zoneMappings"`
-}
-
-type zoneMappings struct {
-	Location string   `json:"location"`
-	Zones    []string `json:"zones"`
-}
-
-type providerListDataProperty struct {
-	ID            string                 `json:"id"`
-	ResourceTypes []resourceTypeMetadata `json:"resourceTypes"`
-}
-
-type providerListData struct {
-	ProviderListDataProperties []providerListDataProperty `json:"value"`
-}
-
-// Client implements zone client Interface.
-type Client struct {
-	armClient      armclient.Interface
-	subscriptionID string
-	cloudName      string
-}
-
-// New creates a new zone client with ratelimiting.
-func New(config *azclients.ClientConfig) *Client {
-	baseURI := config.ResourceManagerEndpoint
-	authorizer := config.Authorizer
-	apiVersion := APIVersion
-	if strings.EqualFold(config.CloudName, AzureStackCloudName) && !config.DisableAzureStackCloud {
-		apiVersion = AzureStackCloudAPIVersion
-	}
-
-	armClient := armclient.New(authorizer, *config, baseURI, apiVersion)
-	client := &Client{
-		armClient:      armClient,
-		subscriptionID: config.SubscriptionID,
-		cloudName:      config.CloudName,
-	}
-
-	return client
-}
-
-// GetZones gets the region-zone map for the subscription specified
-func (c *Client) GetZones(ctx context.Context, subscriptionID string) (map[string][]string, *retry.Error) {
-	result, rerr := c.getZones(ctx, subscriptionID)
-	if rerr != nil {
-
-		return result, rerr
-	}
-
-	return result, nil
-}
-
-// getZones gets the region-zone map for the subscription specified
-func (c *Client) getZones(ctx context.Context, subscriptionID string) (map[string][]string, *retry.Error) {
-	resourceID := armclient.GetProviderResourcesListID(subscriptionID)
-
-	response, rerr := c.armClient.GetResource(ctx, resourceID)
-	defer c.armClient.CloseResponse(ctx, response)
-	if rerr != nil {
-		klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "zone.get.request", resourceID, rerr.Error())
-		return nil, rerr
-	}
-
-	result := providerListData{}
-	err := autorest.Respond(
-		response,
-		azure.WithErrorUnlessStatusCode(http.StatusOK),
-		autorest.ByUnmarshallingJSON(&result))
-	if err != nil {
-		klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "zone.get.respond", resourceID, err)
-		return nil, retry.GetError(response, err)
-	}
-
-	regionZoneMap := make(map[string][]string)
-	expectedID := armclient.GetProviderResourceID(
-		subscriptionID,
-		computeResourceProvider,
-	)
-	if len(result.ProviderListDataProperties) != 0 {
-		for _, property := range result.ProviderListDataProperties {
-			if strings.EqualFold(property.ID, expectedID) {
-				for _, resourceType := range property.ResourceTypes {
-					if strings.EqualFold(resourceType.ResourceType, "virtualMachines") {
-						if len(resourceType.ZoneMappings) != 0 {
-							for _, zoneMapping := range resourceType.ZoneMappings {
-								location := strings.ToLower(strings.ReplaceAll(zoneMapping.Location, " ", ""))
-								regionZoneMap[location] = zoneMapping.Zones
-							}
-						}
-					}
-				}
-			}
-		}
-	}
-
-	return regionZoneMap, nil
-}
diff --git a/pkg/azureclients/zoneclient/doc.go b/pkg/azureclients/zoneclient/doc.go
deleted file mode 100644
index ee1020364..000000000
--- a/pkg/azureclients/zoneclient/doc.go
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
-Copyright 2021 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 zoneclient implements the client for ARM.
-package zoneclient // import "sigs.k8s.io/cloud-provider-azure/pkg/azureclients/zoneclient"
diff --git a/pkg/azureclients/zoneclient/interface.go b/pkg/azureclients/zoneclient/interface.go
deleted file mode 100644
index d9f542afa..000000000
--- a/pkg/azureclients/zoneclient/interface.go
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
-Copyright 2021 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 zoneclient
-
-import (
-	"context"
-
-	"sigs.k8s.io/cloud-provider-azure/pkg/retry"
-)
-
-const (
-	// APIVersion is the API version for provider list api.
-	APIVersion = "2020-06-01"
-	// AzureStackCloudAPIVersion is the API version for Azure Stack
-	AzureStackCloudAPIVersion = "2019-07-01"
-	// AzureStackCloudName is the cloud name of Azure Stack
-	AzureStackCloudName = "AZURESTACKCLOUD"
-)
-
-// Interface is the client interface for ARM.
-// Don't forget to run "hack/update-mock-clients.sh" command to generate the mock client.
-type Interface interface {
-	GetZones(ctx context.Context, subscriptionID string) (map[string][]string, *retry.Error)
-}
diff --git a/pkg/azureclients/zoneclient/mockzoneclient/doc.go b/pkg/azureclients/zoneclient/mockzoneclient/doc.go
deleted file mode 100644
index d74c1ebbb..000000000
--- a/pkg/azureclients/zoneclient/mockzoneclient/doc.go
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
-Copyright 2021 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 mockzonevmclient implements the mock client for zone
-package mockzoneclient // import "sigs.k8s.io/cloud-provider-azure/pkg/azureclients/zoneclient/mockzoneclient"
diff --git a/pkg/azureclients/zoneclient/mockzoneclient/interface.go b/pkg/azureclients/zoneclient/mockzoneclient/interface.go
deleted file mode 100644
index 5a7cf2730..000000000
--- a/pkg/azureclients/zoneclient/mockzoneclient/interface.go
+++ /dev/null
@@ -1,73 +0,0 @@
-// /*
-// Copyright 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.
-// */
-//
-
-// Code generated by MockGen. DO NOT EDIT.
-// Source: pkg/azureclients/zoneclient/interface.go
-//
-// Generated by this command:
-//
-//	mockgen -copyright_file=/home/runner/work/cloud-provider-azure/cloud-provider-azure/hack/boilerplate/boilerplate.generatego.txt -source=pkg/azureclients/zoneclient/interface.go -package=mockzoneclient Interface
-//
-
-// Package mockzoneclient is a generated GoMock package.
-package mockzoneclient
-
-import (
-	context "context"
-	reflect "reflect"
-
-	gomock "go.uber.org/mock/gomock"
-	retry "sigs.k8s.io/cloud-provider-azure/pkg/retry"
-)
-
-// MockInterface is a mock of Interface interface.
-type MockInterface struct {
-	ctrl     *gomock.Controller
-	recorder *MockInterfaceMockRecorder
-}
-
-// MockInterfaceMockRecorder is the mock recorder for MockInterface.
-type MockInterfaceMockRecorder struct {
-	mock *MockInterface
-}
-
-// NewMockInterface creates a new mock instance.
-func NewMockInterface(ctrl *gomock.Controller) *MockInterface {
-	mock := &MockInterface{ctrl: ctrl}
-	mock.recorder = &MockInterfaceMockRecorder{mock}
-	return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use.
-func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder {
-	return m.recorder
-}
-
-// GetZones mocks base method.
-func (m *MockInterface) GetZones(ctx context.Context, subscriptionID string) (map[string][]string, *retry.Error) {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "GetZones", ctx, subscriptionID)
-	ret0, _ := ret[0].(map[string][]string)
-	ret1, _ := ret[1].(*retry.Error)
-	return ret0, ret1
-}
-
-// GetZones indicates an expected call of GetZones.
-func (mr *MockInterfaceMockRecorder) GetZones(ctx, subscriptionID any) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetZones", reflect.TypeOf((*MockInterface)(nil).GetZones), ctx, subscriptionID)
-}
diff --git a/pkg/provider/azure.go b/pkg/provider/azure.go
index 7d85c2a29..742591d78 100644
--- a/pkg/provider/azure.go
+++ b/pkg/provider/azure.go
@@ -53,14 +53,12 @@ import (
 	"sigs.k8s.io/cloud-provider-azure/pkg/azclient/configloader"
 	azclients "sigs.k8s.io/cloud-provider-azure/pkg/azureclients"
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/blobclient"
-	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/deploymentclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/diskclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/fileclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/interfaceclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/loadbalancerclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/privatednszonegroupclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/privateendpointclient"
-	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/privatelinkserviceclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/publicipclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/routetableclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/storageaccountclient"
@@ -70,7 +68,8 @@ import (
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/vmsizeclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/vmssclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/vmssvmclient"
-	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/zoneclient"
+	"sigs.k8s.io/cloud-provider-azure/pkg/provider/privatelinkservice"
+	"sigs.k8s.io/cloud-provider-azure/pkg/provider/zone"
 
 	"sigs.k8s.io/yaml"
 
@@ -370,11 +369,8 @@ type Cloud struct {
 	VirtualMachineScaleSetVMsClient vmssvmclient.Interface
 	VirtualMachineSizesClient       vmsizeclient.Interface
 	AvailabilitySetsClient          vmasclient.Interface
-	ZoneClient                      zoneclient.Interface
 	privateendpointclient           privateendpointclient.Interface
 	privatednszonegroupclient       privatednszonegroupclient.Interface
-	PrivateLinkServiceClient        privatelinkserviceclient.Interface
-	deploymentClient                deploymentclient.Interface
 	ComputeClientFactory            azclient.ClientFactory
 	NetworkClientFactory            azclient.ClientFactory
 	AuthProvider                    *azclient.AuthProvider
@@ -418,16 +414,16 @@ type Cloud struct {
 	routeUpdater       batchProcessor
 	backendPoolUpdater batchProcessor
 
-	vmCache azcache.Resource
-	lbCache azcache.Resource
-	nsgRepo securitygroup.Repository
-	rtCache azcache.Resource
+	vmCache  azcache.Resource
+	lbCache  azcache.Resource
+	nsgRepo  securitygroup.Repository
+	zoneRepo zone.Repository
+	plsRepo  privatelinkservice.Repository
+	rtCache  azcache.Resource
 	// public ip cache
 	// key: [resourceGroupName]
 	// Value: sync.Map of [pipName]*PublicIPAddress
 	pipCache azcache.Resource
-	// use [resourceGroupName*LBFrontEndIpConfigurationID] as the key and search for PLS attached to the frontEnd
-	plsCache azcache.Resource
 	// a timed cache storing storage account properties to avoid querying storage account frequently
 	storageAccountCache azcache.Resource
 	// a timed cache storing storage account file service properties to avoid querying storage account file service properties frequently
@@ -744,6 +740,16 @@ func (az *Cloud) InitializeCloudFromConfig(ctx context.Context, config *Config,
 		if err != nil {
 			return err
 		}
+
+		az.zoneRepo, err = zone.NewRepo(az.ComputeClientFactory.GetProviderClient())
+		if err != nil {
+			return err
+		}
+
+		az.plsRepo, err = privatelinkservice.NewRepo(az.ComputeClientFactory.GetPrivateLinkServiceClient(), time.Duration(az.PlsCacheTTLInSeconds)*time.Second, az.DisableAPICallCache)
+		if err != nil {
+			return err
+		}
 	}
 	err = az.initCaches()
 	if err != nil {
@@ -867,11 +873,6 @@ func (az *Cloud) initCaches() (err error) {
 		return err
 	}
 
-	az.plsCache, err = az.newPLSCache()
-	if err != nil {
-		return err
-	}
-
 	getter := func(_ context.Context, _ string) (interface{}, error) { return nil, nil }
 	if az.storageAccountCache, err = azcache.NewTimedCache(time.Minute, getter, az.Config.DisableAPICallCache); err != nil {
 		return err
@@ -985,15 +986,12 @@ func (az *Cloud) configAzureClients(
 	routeTableClientConfig := azClientConfig.WithRateLimiter(az.Config.RouteTableRateLimit)
 	loadBalancerClientConfig := azClientConfig.WithRateLimiter(az.Config.LoadBalancerRateLimit)
 	publicIPClientConfig := azClientConfig.WithRateLimiter(az.Config.PublicIPAddressRateLimit)
-	deploymentConfig := azClientConfig.WithRateLimiter(az.Config.DeploymentRateLimit)
 	privateDNSZoenGroupConfig := azClientConfig.WithRateLimiter(az.Config.PrivateDNSZoneGroupRateLimit)
 	privateEndpointConfig := azClientConfig.WithRateLimiter(az.Config.PrivateEndpointRateLimit)
-	privateLinkServiceConfig := azClientConfig.WithRateLimiter(az.Config.PrivateLinkServiceRateLimit)
 	// TODO(ZeroMagic): add azurefileRateLimit
 	fileClientConfig := azClientConfig.WithRateLimiter(nil)
 	blobClientConfig := azClientConfig.WithRateLimiter(nil)
 	vmasClientConfig := azClientConfig.WithRateLimiter(az.Config.AvailabilitySetRateLimit)
-	zoneClientConfig := azClientConfig.WithRateLimiter(nil)
 
 	// If uses network resources in different AAD Tenant, update Authorizer for VM/VMSS/VMAS client config
 	if multiTenantOAuthTokenProvider != nil {
@@ -1040,12 +1038,6 @@ func (az *Cloud) configAzureClients(
 	az.AvailabilitySetsClient = vmasclient.New(vmasClientConfig)
 	az.privateendpointclient = privateendpointclient.New(privateEndpointConfig)
 	az.privatednszonegroupclient = privatednszonegroupclient.New(privateDNSZoenGroupConfig)
-	az.PrivateLinkServiceClient = privatelinkserviceclient.New(privateLinkServiceConfig)
-	az.deploymentClient = deploymentclient.New(deploymentConfig)
-
-	if az.ZoneClient == nil {
-		az.ZoneClient = zoneclient.New(zoneClientConfig)
-	}
 }
 
 func (az *Cloud) getAzureClientConfig(servicePrincipalToken *adal.ServicePrincipalToken) *azclients.ClientConfig {
diff --git a/pkg/provider/azure_fakes.go b/pkg/provider/azure_fakes.go
index 127b6713f..07249d8d3 100644
--- a/pkg/provider/azure_fakes.go
+++ b/pkg/provider/azure_fakes.go
@@ -34,7 +34,6 @@ import (
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/diskclient/mockdiskclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/interfaceclient/mockinterfaceclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/loadbalancerclient/mockloadbalancerclient"
-	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/privatelinkserviceclient/mockprivatelinkserviceclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/publicipclient/mockpublicipclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/routetableclient/mockroutetableclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/subnetclient/mocksubnetclient"
@@ -44,6 +43,7 @@ import (
 	azcache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
 	"sigs.k8s.io/cloud-provider-azure/pkg/consts"
 	"sigs.k8s.io/cloud-provider-azure/pkg/provider/config"
+	"sigs.k8s.io/cloud-provider-azure/pkg/provider/privatelinkservice"
 	"sigs.k8s.io/cloud-provider-azure/pkg/provider/securitygroup"
 	utilsets "sigs.k8s.io/cloud-provider-azure/pkg/util/sets"
 )
@@ -119,7 +119,6 @@ func GetTestCloud(ctrl *gomock.Controller) (az *Cloud) {
 	az.VirtualMachineScaleSetsClient = mockvmssclient.NewMockInterface(ctrl)
 	az.VirtualMachineScaleSetVMsClient = mockvmssvmclient.NewMockInterface(ctrl)
 	az.VirtualMachinesClient = mockvmclient.NewMockInterface(ctrl)
-	az.PrivateLinkServiceClient = mockprivatelinkserviceclient.NewMockInterface(ctrl)
 	clientFactory := mock_azclient.NewMockClientFactory(ctrl)
 	az.ComputeClientFactory = clientFactory
 	az.NetworkClientFactory = clientFactory
@@ -138,9 +137,10 @@ func GetTestCloud(ctrl *gomock.Controller) (az *Cloud) {
 	az.nsgRepo, _ = securitygroup.NewSecurityGroupRepo(az.SecurityGroupResourceGroup, az.SecurityGroupName, az.NsgCacheTTLInSeconds, az.Config.DisableAPICallCache, securtyGrouptrack2Client)
 	az.rtCache, _ = az.newRouteTableCache()
 	az.pipCache, _ = az.newPIPCache()
-	az.plsCache, _ = az.newPLSCache()
 	az.LoadBalancerBackendPool = NewMockBackendPool(ctrl)
 
+	az.plsRepo = privatelinkservice.NewMockRepository(ctrl)
+
 	getter := func(_ context.Context, _ string) (interface{}, error) { return nil, nil }
 	az.storageAccountCache, _ = azcache.NewTimedCache(time.Minute, getter, az.Config.DisableAPICallCache)
 	az.fileServicePropertiesCache, _ = azcache.NewTimedCache(5*time.Minute, getter, az.Config.DisableAPICallCache)
diff --git a/pkg/provider/azure_loadbalancer.go b/pkg/provider/azure_loadbalancer.go
index e05cceaf8..c846ff178 100644
--- a/pkg/provider/azure_loadbalancer.go
+++ b/pkg/provider/azure_loadbalancer.go
@@ -890,9 +890,13 @@ func (az *Cloud) getServiceLoadBalancer(
 			}
 		}
 		if az.HasExtendedLocation() {
+			var typ network.ExtendedLocationTypes
+			if getExtendedLocationTypeFromString(az.ExtendedLocationType) == armnetwork.ExtendedLocationTypesEdgeZone {
+				typ = network.EdgeZone
+			}
 			defaultLB.ExtendedLocation = &network.ExtendedLocation{
 				Name: &az.ExtendedLocationName,
-				Type: getExtendedLocationTypeFromString(az.ExtendedLocationType),
+				Type: typ,
 			}
 		}
 	}
@@ -940,9 +944,13 @@ func (az *Cloud) selectLoadBalancer(ctx context.Context, clusterName string, ser
 				LoadBalancerPropertiesFormat: &network.LoadBalancerPropertiesFormat{},
 			}
 			if az.HasExtendedLocation() {
+				var typ network.ExtendedLocationTypes
+				if getExtendedLocationTypeFromString(az.ExtendedLocationType) == armnetwork.ExtendedLocationTypesEdgeZone {
+					typ = network.EdgeZone
+				}
 				selectedLB.ExtendedLocation = &network.ExtendedLocation{
 					Name: &az.ExtendedLocationName,
-					Type: getExtendedLocationTypeFromString(az.ExtendedLocationType),
+					Type: typ,
 				}
 			}
 
@@ -1176,9 +1184,13 @@ func (az *Cloud) ensurePublicIPExists(ctx context.Context, service *v1.Service,
 		pip.Location = ptr.To(az.Location)
 		if az.HasExtendedLocation() {
 			klog.V(2).Infof("Using extended location with name %s, and type %s for PIP", az.ExtendedLocationName, az.ExtendedLocationType)
+			var typ network.ExtendedLocationTypes
+			if getExtendedLocationTypeFromString(az.ExtendedLocationType) == armnetwork.ExtendedLocationTypesEdgeZone {
+				typ = network.EdgeZone
+			}
 			pip.ExtendedLocation = &network.ExtendedLocation{
 				Name: &az.ExtendedLocationName,
-				Type: getExtendedLocationTypeFromString(az.ExtendedLocationType),
+				Type: typ,
 			}
 		}
 		pip.PublicIPAddressPropertiesFormat = &network.PublicIPAddressPropertiesFormat{
diff --git a/pkg/provider/azure_privatelinkservice.go b/pkg/provider/azure_privatelinkservice.go
index 259de24e9..de8fb3a9b 100644
--- a/pkg/provider/azure_privatelinkservice.go
+++ b/pkg/provider/azure_privatelinkservice.go
@@ -22,6 +22,8 @@ import (
 	"net"
 	"strings"
 
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
+	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6"
 	"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2022-07-01/network"
 
 	v1 "k8s.io/api/core/v1"
@@ -31,7 +33,7 @@ import (
 	azcache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
 	"sigs.k8s.io/cloud-provider-azure/pkg/consts"
 	"sigs.k8s.io/cloud-provider-azure/pkg/metrics"
-	"sigs.k8s.io/cloud-provider-azure/pkg/retry"
+	fnutil "sigs.k8s.io/cloud-provider-azure/pkg/util/collectionutil"
 )
 
 // reconcilePrivateLinkService() function makes sure a PLS is created or deleted on
@@ -88,7 +90,7 @@ func (az *Cloud) reconcilePrivateLinkService(
 		}
 
 		// Secondly, check if there is a private link service already created
-		existingPLS, err := az.getPrivateLinkService(ctx, az.getPLSResourceGroup(service), fipConfigID, azcache.CacheReadTypeDefault)
+		existingPLS, err := az.plsRepo.Get(ctx, az.getPLSResourceGroup(service), *fipConfigID, azcache.CacheReadTypeDefault)
 		if err != nil {
 			klog.Errorf("reconcilePrivateLinkService for service(%s): getPrivateLinkService(%s) failed: %v", serviceName, ptr.Deref(fipConfigID, ""), err)
 			return err
@@ -97,7 +99,7 @@ func (az *Cloud) reconcilePrivateLinkService(
 		exists := !strings.EqualFold(ptr.Deref(existingPLS.ID, ""), consts.PrivateLinkServiceNotExistID)
 		if exists {
 			klog.V(4).Infof("reconcilePrivateLinkService for service(%s): found existing private link service attached(%s)", serviceName, ptr.Deref(existingPLS.Name, ""))
-			if !isManagedPrivateLinkSerivce(&existingPLS, clusterName) {
+			if !isManagedPrivateLinkSerivce(existingPLS, clusterName) {
 				return fmt.Errorf(
 					"reconcilePrivateLinkService for service(%s) failed: LB frontend(%s) already has unmanaged private link service(%s)",
 					serviceName,
@@ -106,7 +108,7 @@ func (az *Cloud) reconcilePrivateLinkService(
 				)
 			}
 			// If there is an existing private link service, only owner service can update its properties
-			ownerService := getPrivateLinkServiceOwner(&existingPLS)
+			ownerService := getPrivateLinkServiceOwner(existingPLS)
 			if !strings.EqualFold(ownerService, serviceName) {
 				if serviceHasAdditionalConfigs(service) {
 					return fmt.Errorf(
@@ -128,21 +130,21 @@ func (az *Cloud) reconcilePrivateLinkService(
 		} else {
 			existingPLS.ID = nil
 			existingPLS.Location = &az.Location
-			existingPLS.PrivateLinkServiceProperties = &network.PrivateLinkServiceProperties{}
+			existingPLS.Properties = &armnetwork.PrivateLinkServiceProperties{}
 			if az.HasExtendedLocation() {
-				existingPLS.ExtendedLocation = &network.ExtendedLocation{
+				existingPLS.ExtendedLocation = &armnetwork.ExtendedLocation{
 					Name: &az.ExtendedLocationName,
-					Type: getExtendedLocationTypeFromString(az.ExtendedLocationType),
+					Type: to.Ptr(getExtendedLocationTypeFromString(az.ExtendedLocationType)),
 				}
 			}
 		}
 
-		plsName, err := az.getPrivateLinkServiceName(&existingPLS, service, fipConfig)
+		plsName, err := az.getPrivateLinkServiceName(existingPLS, service, fipConfig)
 		if err != nil {
 			return err
 		}
 
-		dirtyPLS, err := az.getExpectedPrivateLinkService(&existingPLS, &plsName, &clusterName, service, fipConfig)
+		dirtyPLS, err := az.getExpectedPrivateLinkService(existingPLS, &plsName, &clusterName, service, fipConfig)
 		if err != nil {
 			return err
 		}
@@ -155,14 +157,14 @@ func (az *Cloud) reconcilePrivateLinkService(
 				return err
 			}
 			existingPLS.Etag = ptr.To("")
-			err = az.CreateOrUpdatePLS(service, az.getPLSResourceGroup(service), existingPLS)
+			_, err = az.plsRepo.CreateOrUpdate(ctx, az.getPLSResourceGroup(service), *existingPLS)
 			if err != nil {
 				klog.Errorf("reconcilePrivateLinkService for service(%s) abort backoff: pls(%s) - updating: %s", serviceName, plsName, err.Error())
 				return err
 			}
 		}
 	} else if !wantPLS {
-		existingPLS, err := az.getPrivateLinkService(ctx, az.getPLSResourceGroup(service), fipConfigID, azcache.CacheReadTypeDefault)
+		existingPLS, err := az.plsRepo.Get(ctx, az.getPLSResourceGroup(service), *fipConfigID, azcache.CacheReadTypeDefault)
 		if err != nil {
 			klog.Errorf("reconcilePrivateLinkService for service(%s): getPrivateLinkService(%s) failed: %v", serviceName, ptr.Deref(fipConfigID, ""), err)
 			return err
@@ -170,10 +172,10 @@ func (az *Cloud) reconcilePrivateLinkService(
 
 		exists := !strings.EqualFold(ptr.Deref(existingPLS.ID, ""), consts.PrivateLinkServiceNotExistID)
 		if exists {
-			deleteErr := az.safeDeletePLS(&existingPLS, service)
+			deleteErr := az.safeDeletePLS(ctx, existingPLS, service)
 			if deleteErr != nil {
 				klog.Errorf("reconcilePrivateLinkService for service(%s): deletePLS for frontEnd(%s) failed: %v", serviceName, ptr.Deref(fipConfigID, ""), err)
-				return deleteErr.Error()
+				return deleteErr
 			}
 		}
 	}
@@ -222,23 +224,24 @@ func (az *Cloud) disablePLSNetworkPolicy(service *v1.Service) error {
 	return nil
 }
 
-func (az *Cloud) safeDeletePLS(pls *network.PrivateLinkService, service *v1.Service) *retry.Error {
+func (az *Cloud) safeDeletePLS(ctx context.Context, pls *armnetwork.PrivateLinkService, service *v1.Service) error {
 	if pls == nil {
 		return nil
 	}
 
-	peConns := pls.PrivateEndpointConnections
-	if peConns != nil {
-		for _, peConn := range *peConns {
-			klog.V(2).Infof("deletePLS: deleting PEConnection %s", ptr.Deref(peConn.Name, ""))
-			rerr := az.DeletePEConn(service, az.getPLSResourceGroup(service), ptr.Deref(pls.Name, ""), ptr.Deref(peConn.Name, ""))
-			if rerr != nil {
-				return rerr
-			}
+	peConns := pls.Properties.PrivateEndpointConnections
+	for _, peConn := range peConns {
+		klog.V(2).Infof("deletePLS: deleting PEConnection %s", ptr.Deref(peConn.Name, ""))
+		err := az.plsRepo.DeletePEConnection(ctx, az.getPLSResourceGroup(service), ptr.Deref(pls.Name, ""), ptr.Deref(peConn.Name, ""))
+		if err != nil {
+			return err
 		}
 	}
 
-	rerr := az.DeletePLS(service, az.getPLSResourceGroup(service), ptr.Deref(pls.Name, ""), ptr.Deref((*pls.LoadBalancerFrontendIPConfigurations)[0].ID, ""))
+	resourceGroup := az.getPLSResourceGroup(service)
+	plsName := ptr.Deref(pls.Name, "")
+	lbFrontendID := ptr.Deref((pls.Properties.LoadBalancerFrontendIPConfigurations)[0].ID, "")
+	rerr := az.plsRepo.Delete(ctx, resourceGroup, plsName, lbFrontendID)
 	if rerr != nil {
 		return rerr
 	}
@@ -248,7 +251,7 @@ func (az *Cloud) safeDeletePLS(pls *network.PrivateLinkService, service *v1.Serv
 
 // getPrivateLinkServiceName() returns the name of private link service, or any error
 func (az *Cloud) getPrivateLinkServiceName(
-	existingPLS *network.PrivateLinkService,
+	existingPLS *armnetwork.PrivateLinkService,
 	service *v1.Service,
 	fipConfig *network.FrontendIPConfiguration,
 ) (string, error) {
@@ -278,7 +281,7 @@ func (az *Cloud) getPrivateLinkServiceName(
 
 // getExpectedPrivateLinkService builds expected PLS object from service spec
 func (az *Cloud) getExpectedPrivateLinkService(
-	existingPLS *network.PrivateLinkService,
+	existingPLS *armnetwork.PrivateLinkService,
 	plsName *string,
 	clusterName *string,
 	service *v1.Service,
@@ -297,17 +300,17 @@ func (az *Cloud) getExpectedPrivateLinkService(
 	}
 
 	// Set failed PLS as dirty so that provision can be retried
-	if existingPLS.ProvisioningState == network.ProvisioningStateFailed {
+	if existingPLS.Properties.ProvisioningState != nil && *existingPLS.Properties.ProvisioningState == armnetwork.ProvisioningStateFailed {
 		dirtyPLS = true
 	}
 
 	// Shadow copy properties to avoid changing pls cache
-	plsProperties := *existingPLS.PrivateLinkServiceProperties
-	existingPLS.PrivateLinkServiceProperties = &plsProperties
+	plsProperties := *existingPLS.Properties
+	existingPLS.Properties = &plsProperties
 
 	// Set LBFrontendIpConfiguration
-	if existingPLS.LoadBalancerFrontendIPConfigurations == nil {
-		existingPLS.LoadBalancerFrontendIPConfigurations = &[]network.FrontendIPConfiguration{{ID: fipConfig.ID}}
+	if existingPLS.Properties.LoadBalancerFrontendIPConfigurations == nil {
+		existingPLS.Properties.LoadBalancerFrontendIPConfigurations = []*armnetwork.FrontendIPConfiguration{{ID: fipConfig.ID}}
 		dirtyPLS = true
 	}
 
@@ -344,7 +347,7 @@ func (az *Cloud) getExpectedPrivateLinkService(
 
 // reconcile Private link service's IP configurations
 func (az *Cloud) reconcilePLSIpConfigs(
-	existingPLS *network.PrivateLinkService,
+	existingPLS *armnetwork.PrivateLinkService,
 	service *v1.Service,
 ) (bool, error) {
 	changed := false
@@ -376,30 +379,30 @@ func (az *Cloud) reconcilePLSIpConfigs(
 		return false, fmt.Errorf("checkAndUpdatePLSIPConfigs: ipConfigCount(%d) must be no smaller than number of static IPs specified(%d)", ipConfigCount, len(staticIps))
 	}
 
-	if existingPLS.IPConfigurations == nil {
-		existingPLS.IPConfigurations = &[]network.PrivateLinkServiceIPConfiguration{}
+	if existingPLS.Properties.IPConfigurations == nil {
+		existingPLS.Properties.IPConfigurations = []*armnetwork.PrivateLinkServiceIPConfiguration{}
 		changed = true
 	}
 
-	if int32(len(*existingPLS.IPConfigurations)) != ipConfigCount {
+	if int32(len(existingPLS.Properties.IPConfigurations)) != ipConfigCount {
 		changed = true
 	}
 
 	existingStaticIps := make([]string, 0)
-	for _, ipConfig := range *existingPLS.IPConfigurations {
-		if !strings.EqualFold(ptr.Deref(subnet.ID, ""), ptr.Deref(ipConfig.Subnet.ID, "")) {
+	for _, ipConfig := range existingPLS.Properties.IPConfigurations {
+		if !strings.EqualFold(ptr.Deref(subnet.ID, ""), ptr.Deref(ipConfig.Properties.Subnet.ID, "")) {
 			changed = true
 		}
-		if strings.EqualFold(string(ipConfig.PrivateIPAllocationMethod), string(network.Static)) {
-			klog.V(10).Infof("Found static IP: %s", ptr.Deref(ipConfig.PrivateIPAddress, ""))
-			if _, found := staticIps[ptr.Deref(ipConfig.PrivateIPAddress, "")]; !found {
+		if *ipConfig.Properties.PrivateIPAllocationMethod == armnetwork.IPAllocationMethodStatic {
+			klog.V(10).Infof("Found static IP: %s", ptr.Deref(ipConfig.Properties.PrivateIPAddress, ""))
+			if _, found := staticIps[ptr.Deref(ipConfig.Properties.PrivateIPAddress, "")]; !found {
 				changed = true
 			}
-			existingStaticIps = append(existingStaticIps, ptr.Deref(ipConfig.PrivateIPAddress, ""))
+			existingStaticIps = append(existingStaticIps, ptr.Deref(ipConfig.Properties.PrivateIPAddress, ""))
 		}
-		if *ipConfig.Primary {
-			if strings.EqualFold(string(ipConfig.PrivateIPAllocationMethod), string(network.Static)) {
-				if !strings.EqualFold(primaryIP, ptr.Deref(ipConfig.PrivateIPAddress, "")) {
+		if *ipConfig.Properties.Primary {
+			if *ipConfig.Properties.PrivateIPAllocationMethod == armnetwork.IPAllocationMethodStatic {
+				if !strings.EqualFold(primaryIP, ptr.Deref(ipConfig.Properties.PrivateIPAddress, "")) {
 					changed = true
 				}
 			} else {
@@ -428,7 +431,7 @@ func (az *Cloud) reconcilePLSIpConfigs(
 			return prefix + suffix, nil
 		}
 
-		ipConfigs := []network.PrivateLinkServiceIPConfiguration{}
+		var ipConfigs []*armnetwork.PrivateLinkServiceIPConfiguration
 		for k := range staticIps {
 			ip := k
 			isPrimary := strings.EqualFold(ip, primaryIP)
@@ -437,16 +440,16 @@ func (az *Cloud) reconcilePLSIpConfigs(
 			if err != nil {
 				return false, err
 			}
-			ipConfigs = append(ipConfigs, network.PrivateLinkServiceIPConfiguration{
+			ipConfigs = append(ipConfigs, &armnetwork.PrivateLinkServiceIPConfiguration{
 				Name: &configName,
-				PrivateLinkServiceIPConfigurationProperties: &network.PrivateLinkServiceIPConfigurationProperties{
+				Properties: &armnetwork.PrivateLinkServiceIPConfigurationProperties{
 					PrivateIPAddress:          &ip,
-					PrivateIPAllocationMethod: network.Static,
-					Subnet: &network.Subnet{
+					PrivateIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodStatic),
+					Subnet: &armnetwork.Subnet{
 						ID: subnet.ID,
 					},
 					Primary:                 &isPrimary,
-					PrivateIPAddressVersion: network.IPv4,
+					PrivateIPAddressVersion: to.Ptr(armnetwork.IPVersionIPv4),
 				},
 			})
 		}
@@ -457,19 +460,19 @@ func (az *Cloud) reconcilePLSIpConfigs(
 			if err != nil {
 				return false, err
 			}
-			ipConfigs = append(ipConfigs, network.PrivateLinkServiceIPConfiguration{
+			ipConfigs = append(ipConfigs, &armnetwork.PrivateLinkServiceIPConfiguration{
 				Name: &configName,
-				PrivateLinkServiceIPConfigurationProperties: &network.PrivateLinkServiceIPConfigurationProperties{
-					PrivateIPAllocationMethod: network.Dynamic,
-					Subnet: &network.Subnet{
+				Properties: &armnetwork.PrivateLinkServiceIPConfigurationProperties{
+					PrivateIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodDynamic),
+					Subnet: &armnetwork.Subnet{
 						ID: subnet.ID,
 					},
 					Primary:                 &isPrimary,
-					PrivateIPAddressVersion: network.IPv4,
+					PrivateIPAddressVersion: to.Ptr(armnetwork.IPVersionIPv4),
 				},
 			})
 		}
-		existingPLS.IPConfigurations = &ipConfigs
+		existingPLS.Properties.IPConfigurations = ipConfigs
 	}
 	return changed, nil
 }
@@ -479,79 +482,85 @@ func serviceRequiresPLS(service *v1.Service) bool {
 }
 
 func reconcilePLSEnableProxyProtocol(
-	existingPLS *network.PrivateLinkService,
+	existingPLS *armnetwork.PrivateLinkService,
 	service *v1.Service,
 ) bool {
 	changed := false
 	enableProxyProtocol := getBoolValueFromServiceAnnotations(service, consts.ServiceAnnotationPLSProxyProtocol)
-	if enableProxyProtocol && (existingPLS.EnableProxyProtocol == nil || !*existingPLS.EnableProxyProtocol) {
+	if enableProxyProtocol && (existingPLS.Properties.EnableProxyProtocol == nil || !*existingPLS.Properties.EnableProxyProtocol) {
 		changed = true
-	} else if !enableProxyProtocol && (existingPLS.EnableProxyProtocol != nil && *existingPLS.EnableProxyProtocol) {
+	} else if !enableProxyProtocol && (existingPLS.Properties.EnableProxyProtocol != nil && *existingPLS.Properties.EnableProxyProtocol) {
 		changed = true
 	}
 	if changed {
-		existingPLS.EnableProxyProtocol = &enableProxyProtocol
+		existingPLS.Properties.EnableProxyProtocol = &enableProxyProtocol
 	}
 	return changed
 }
 
 func reconcilePLSFqdn(
-	existingPLS *network.PrivateLinkService,
+	existingPLS *armnetwork.PrivateLinkService,
 	service *v1.Service,
 ) bool {
 	changed := false
 	fqdns := getPLSFqdns(service)
-	if existingPLS.Fqdns == nil {
+	if existingPLS.Properties.Fqdns == nil {
 		if len(fqdns) != 0 {
 			changed = true
 		}
-	} else if !sameContentInSlices(fqdns, *existingPLS.Fqdns) {
+	} else if !sameContentInSlices(fqdns, fnutil.Map(func(s *string) string {
+		return *s
+	}, existingPLS.Properties.Fqdns)) {
 		changed = true
 	}
 
 	if changed {
-		existingPLS.Fqdns = &fqdns
+		existingPLS.Properties.Fqdns = fnutil.Map(func(s string) *string { return &s }, fqdns)
 	}
 	return changed
 }
 
 func reconcilePLSVisibility(
-	existingPLS *network.PrivateLinkService,
+	existingPLS *armnetwork.PrivateLinkService,
 	service *v1.Service,
 ) (bool, error) {
 	changed := false
 	visibilitySubs, _ := getPLSVisibility(service)
 	autoApprovalSubs := getPLSAutoApproval(service)
 
-	if existingPLS.Visibility == nil || existingPLS.Visibility.Subscriptions == nil {
+	if existingPLS.Properties.Visibility == nil || existingPLS.Properties.Visibility.Subscriptions == nil {
 		if len(visibilitySubs) != 0 {
 			changed = true
 		}
-	} else if !sameContentInSlices(visibilitySubs, *existingPLS.Visibility.Subscriptions) {
+	} else if !sameContentInSlices(visibilitySubs, fnutil.Map(func(s *string) string {
+		return *s
+	}, existingPLS.Properties.Visibility.Subscriptions)) {
 		changed = true
 	}
 
-	if existingPLS.AutoApproval == nil || existingPLS.AutoApproval.Subscriptions == nil {
+	if existingPLS.Properties.AutoApproval == nil || existingPLS.Properties.AutoApproval.Subscriptions == nil {
 		if len(autoApprovalSubs) != 0 {
 			changed = true
 		}
-	} else if !sameContentInSlices(autoApprovalSubs, *existingPLS.AutoApproval.Subscriptions) {
+	} else if !sameContentInSlices(autoApprovalSubs, fnutil.Map(func(s *string) string {
+		return *s
+	}, existingPLS.Properties.AutoApproval.Subscriptions)) {
 		changed = true
 	}
 
 	if changed {
-		existingPLS.Visibility = &network.PrivateLinkServicePropertiesVisibility{
-			Subscriptions: &visibilitySubs,
+		existingPLS.Properties.Visibility = &armnetwork.PrivateLinkServicePropertiesVisibility{
+			Subscriptions: to.SliceOfPtrs(visibilitySubs...),
 		}
-		existingPLS.AutoApproval = &network.PrivateLinkServicePropertiesAutoApproval{
-			Subscriptions: &autoApprovalSubs,
+		existingPLS.Properties.AutoApproval = &armnetwork.PrivateLinkServicePropertiesAutoApproval{
+			Subscriptions: to.SliceOfPtrs(autoApprovalSubs...),
 		}
 	}
 	return changed, nil
 }
 
 func (az *Cloud) reconcilePLSTags(
-	existingPLS *network.PrivateLinkService,
+	existingPLS *armnetwork.PrivateLinkService,
 	clusterName *string,
 	service *v1.Service,
 ) bool {
@@ -702,14 +711,14 @@ func getPLSStaticIPs(service *v1.Service) (map[string]bool, string, error) {
 	return result, primaryIP, nil
 }
 
-func isManagedPrivateLinkSerivce(existingPLS *network.PrivateLinkService, clusterName string) bool {
+func isManagedPrivateLinkSerivce(existingPLS *armnetwork.PrivateLinkService, clusterName string) bool {
 	tags := existingPLS.Tags
 	v, ok := tags[consts.ClusterNameTagKey]
 	return ok && v != nil && strings.EqualFold(strings.TrimSpace(*v), clusterName)
 }
 
 // find owner service for an existing private link service from its tags
-func getPrivateLinkServiceOwner(existingPLS *network.PrivateLinkService) string {
+func getPrivateLinkServiceOwner(existingPLS *armnetwork.PrivateLinkService) string {
 	tags := existingPLS.Tags
 	v, ok := tags[consts.OwnerServiceTagKey]
 	if ok && v != nil {
diff --git a/pkg/provider/azure_privatelinkservice_repo.go b/pkg/provider/azure_privatelinkservice_repo.go
deleted file mode 100644
index b58278fb0..000000000
--- a/pkg/provider/azure_privatelinkservice_repo.go
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
-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 (
-	"context"
-	"encoding/json"
-	"fmt"
-	"net/http"
-	"strings"
-	"time"
-
-	"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2022-07-01/network"
-
-	v1 "k8s.io/api/core/v1"
-	"k8s.io/klog/v2"
-	"k8s.io/utils/ptr"
-
-	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"
-)
-
-func (az *Cloud) CreateOrUpdatePLS(_ *v1.Service, resourceGroup string, pls network.PrivateLinkService) error {
-	ctx, cancel := getContextWithCancel()
-	defer cancel()
-
-	rerr := az.PrivateLinkServiceClient.CreateOrUpdate(ctx, resourceGroup, ptr.Deref(pls.Name, ""), pls, ptr.Deref(pls.Etag, ""))
-	if rerr == nil {
-		// Invalidate the cache right after updating
-		_ = az.plsCache.Delete(getPLSCacheKey(resourceGroup, ptr.Deref((*pls.LoadBalancerFrontendIPConfigurations)[0].ID, "")))
-		return nil
-	}
-
-	rtJSON, _ := json.Marshal(pls)
-	klog.Warningf("PrivateLinkServiceClient.CreateOrUpdate(%s) failed: %v, PrivateLinkService request: %s", ptr.Deref(pls.Name, ""), rerr.Error(), string(rtJSON))
-
-	// Invalidate the cache because etag mismatch.
-	if rerr.HTTPStatusCode == http.StatusPreconditionFailed {
-		klog.V(3).Infof("Private link service cache for %s is cleanup because of http.StatusPreconditionFailed", ptr.Deref(pls.Name, ""))
-		_ = az.plsCache.Delete(getPLSCacheKey(resourceGroup, ptr.Deref((*pls.LoadBalancerFrontendIPConfigurations)[0].ID, "")))
-	}
-	// Invalidate the cache because another new operation has canceled the current request.
-	if strings.Contains(strings.ToLower(rerr.Error().Error()), consts.OperationCanceledErrorMessage) {
-		klog.V(3).Infof("Private link service for %s is cleanup because CreateOrUpdatePrivateLinkService is canceled by another operation", ptr.Deref(pls.Name, ""))
-		_ = az.plsCache.Delete(getPLSCacheKey(resourceGroup, ptr.Deref((*pls.LoadBalancerFrontendIPConfigurations)[0].ID, "")))
-	}
-	klog.Errorf("PrivateLinkServiceClient.CreateOrUpdate(%s) failed: %v", ptr.Deref(pls.Name, ""), rerr.Error())
-	return rerr.Error()
-}
-
-// DeletePLS invokes az.PrivateLinkServiceClient.Delete with exponential backoff retry
-func (az *Cloud) DeletePLS(service *v1.Service, resourceGroup, plsName, plsLBFrontendID string) *retry.Error {
-	ctx, cancel := getContextWithCancel()
-	defer cancel()
-
-	rerr := az.PrivateLinkServiceClient.Delete(ctx, resourceGroup, plsName)
-	if rerr == nil {
-		// Invalidate the cache right after deleting
-		_ = az.plsCache.Delete(getPLSCacheKey(resourceGroup, plsLBFrontendID))
-		return nil
-	}
-
-	klog.Errorf("PrivateLinkServiceClient.DeletePLS(%s) failed: %s", plsName, rerr.Error().Error())
-	az.Event(service, v1.EventTypeWarning, "DeletePrivateLinkService", rerr.Error().Error())
-	return rerr
-}
-
-// DeletePEConn invokes az.PrivateLinkServiceClient.DeletePEConnection with exponential backoff retry
-func (az *Cloud) DeletePEConn(service *v1.Service, resourceGroup, plsName, peConnName string) *retry.Error {
-	ctx, cancel := getContextWithCancel()
-	defer cancel()
-
-	rerr := az.PrivateLinkServiceClient.DeletePEConnection(ctx, resourceGroup, plsName, peConnName)
-	if rerr == nil {
-		return nil
-	}
-
-	klog.Errorf("PrivateLinkServiceClient.DeletePEConnection(%s-%s) failed: %s", plsName, peConnName, rerr.Error().Error())
-	az.Event(service, v1.EventTypeWarning, "DeletePrivateEndpointConnection", rerr.Error().Error())
-	return rerr
-}
-
-func (az *Cloud) newPLSCache() (azcache.Resource, error) {
-	// for PLS cache, key is LBFrontendIPConfiguration ID
-	getter := func(ctx context.Context, key string) (interface{}, error) {
-		resourceGroup, frontendID := parsePLSCacheKey(key)
-		plsList, err := az.PrivateLinkServiceClient.List(ctx, resourceGroup)
-		exists, rerr := checkResourceExistsFromError(err)
-		if rerr != nil {
-			return nil, rerr.Error()
-		}
-
-		if exists {
-			for i := range plsList {
-				pls := plsList[i]
-				if pls.PrivateLinkServiceProperties == nil {
-					continue
-				}
-				fipConfigs := pls.PrivateLinkServiceProperties.LoadBalancerFrontendIPConfigurations
-				if fipConfigs == nil {
-					continue
-				}
-				for _, fipConfig := range *fipConfigs {
-					if strings.EqualFold(*fipConfig.ID, frontendID) {
-						return &pls, nil
-					}
-				}
-
-			}
-		}
-
-		klog.V(2).Infof("No privateLinkService found for frontendIPConfig %q in rg %q", frontendID, resourceGroup)
-		plsNotExistID := consts.PrivateLinkServiceNotExistID
-		return &network.PrivateLinkService{ID: &plsNotExistID}, nil
-	}
-
-	if az.PlsCacheTTLInSeconds == 0 {
-		az.PlsCacheTTLInSeconds = plsCacheTTLDefaultInSeconds
-	}
-	return azcache.NewTimedCache(time.Duration(az.PlsCacheTTLInSeconds)*time.Second, getter, az.Config.DisableAPICallCache)
-}
-
-func (az *Cloud) getPrivateLinkService(ctx context.Context, resourceGroup string, frontendIPConfigID *string, crt azcache.AzureCacheReadType) (pls network.PrivateLinkService, err error) {
-	cachedPLS, err := az.plsCache.GetWithDeepCopy(ctx, getPLSCacheKey(resourceGroup, *frontendIPConfigID), crt)
-	if err != nil {
-		return pls, err
-	}
-	return *(cachedPLS.(*network.PrivateLinkService)), nil
-}
-
-func getPLSCacheKey(resourceGroup, plsLBFrontendID string) string {
-	return fmt.Sprintf("%s*%s", resourceGroup, plsLBFrontendID)
-}
-
-func parsePLSCacheKey(key string) (string, string) {
-	splits := strings.Split(key, "*")
-	return splits[0], splits[1]
-}
diff --git a/pkg/provider/azure_utils.go b/pkg/provider/azure_utils.go
index 306b76c0c..65fef0f21 100644
--- a/pkg/provider/azure_utils.go
+++ b/pkg/provider/azure_utils.go
@@ -43,8 +43,8 @@ const (
 	IPVersionIPv4 bool = false
 )
 
-var strToExtendedLocationType = map[string]network.ExtendedLocationTypes{
-	"edgezone": network.EdgeZone,
+var strToExtendedLocationType = map[string]armnetwork.ExtendedLocationTypes{
+	"edgezone": armnetwork.ExtendedLocationTypesEdgeZone,
 }
 
 // LockMap used to lock on entries
@@ -205,12 +205,12 @@ func (az *Cloud) reconcileTags(currentTagsOnResource, newTags map[string]*string
 	return currentTagsOnResource, changed
 }
 
-func getExtendedLocationTypeFromString(extendedLocationType string) network.ExtendedLocationTypes {
+func getExtendedLocationTypeFromString(extendedLocationType string) armnetwork.ExtendedLocationTypes {
 	extendedLocationType = strings.ToLower(extendedLocationType)
 	if val, ok := strToExtendedLocationType[extendedLocationType]; ok {
 		return val
 	}
-	return network.EdgeZone
+	return armnetwork.ExtendedLocationTypesEdgeZone
 }
 
 func getNodePrivateIPAddress(node *v1.Node, isIPv6 bool) string {
diff --git a/pkg/provider/azure_wrap.go b/pkg/provider/azure_wrap.go
index 6a819329a..d599ddbd2 100644
--- a/pkg/provider/azure_wrap.go
+++ b/pkg/provider/azure_wrap.go
@@ -31,7 +31,6 @@ var (
 	loadBalancerCacheTTLDefaultInSeconds = 120
 	routeTableCacheTTLDefaultInSeconds   = 120
 	publicIPCacheTTLDefaultInSeconds     = 120
-	plsCacheTTLDefaultInSeconds          = 120
 
 	azureNodeProviderIDRE    = regexp.MustCompile(`^azure:///subscriptions/(?:.*)/resourceGroups/(?:.*)/providers/Microsoft.Compute/(?:.*)`)
 	azureResourceGroupNameRE = regexp.MustCompile(`.*/subscriptions/(?:.*)/resourceGroups/(.+)/providers/(?:.*)`)
diff --git a/pkg/provider/azure_zones.go b/pkg/provider/azure_zones.go
index 49d4e3365..8e05dd944 100644
--- a/pkg/provider/azure_zones.go
+++ b/pkg/provider/azure_zones.go
@@ -18,7 +18,6 @@ package provider
 
 import (
 	"context"
-	"errors"
 	"fmt"
 	"os"
 	"strconv"
@@ -31,7 +30,6 @@ import (
 
 	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"
 )
 
 var _ cloudprovider.Zones = (*Cloud)(nil)
@@ -47,11 +45,11 @@ func (az *Cloud) refreshZones(ctx context.Context, refreshFunc func(ctx context.
 
 func (az *Cloud) syncRegionZonesMap(ctx context.Context) error {
 	klog.V(2).Infof("syncRegionZonesMap: starting to fetch all available zones for the subscription %s", az.SubscriptionID)
-	zones, rerr := az.ZoneClient.GetZones(ctx, az.SubscriptionID)
-	if rerr != nil {
-		klog.Warningf("syncRegionZonesMap: error when get zones: %s, will retry after %s", rerr.Error().Error(), consts.ZoneFetchingInterval.String())
-		return rerr.Error()
+	zones, err := az.zoneRepo.ListZones(ctx)
+	if err != nil {
+		return fmt.Errorf("list zones: %w", err)
 	}
+
 	if len(zones) == 0 {
 		klog.Warning("syncRegionZonesMap: empty zone list")
 	}
@@ -92,21 +90,21 @@ func (az *Cloud) getRegionZonesBackoff(ctx context.Context, region string) ([]st
 	klog.V(2).Infof("getRegionZonesMapWrapper: the region-zones map is not initialized successfully, retrying immediately")
 
 	var (
-		zones map[string][]string
-		rerr  *retry.Error
+		zones    map[string][]string
+		innerErr error
 	)
 	err := wait.ExponentialBackoffWithContext(ctx, az.RequestBackoff(), func(ctx context.Context) (done bool, err error) {
-		zones, rerr = az.ZoneClient.GetZones(ctx, az.SubscriptionID)
-		if rerr != nil {
-			klog.Errorf("getRegionZonesMapWrapper: failed to fetch zones information: %v", rerr.Error())
+		zones, innerErr = az.zoneRepo.ListZones(ctx)
+		if innerErr != nil {
+			klog.ErrorS(err, "Failed to list zones")
 			return false, nil
 		}
 
 		return true, nil
 	})
 
-	if errors.Is(err, wait.ErrWaitTimeout) {
-		return []string{}, rerr.Error()
+	if wait.Interrupted(err) {
+		return []string{}, innerErr
 	}
 
 	az.updateRegionZonesMap(zones)
diff --git a/pkg/provider/privatelinkservice/cache.go b/pkg/provider/privatelinkservice/cache.go
new file mode 100644
index 000000000..e13c38349
--- /dev/null
+++ b/pkg/provider/privatelinkservice/cache.go
@@ -0,0 +1,80 @@
+/*
+Copyright 2024 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 privatelinkservice
+
+import (
+	"context"
+	"fmt"
+	"strings"
+	"time"
+
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
+	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6"
+
+	"sigs.k8s.io/cloud-provider-azure/pkg/azclient/privatelinkserviceclient"
+	"sigs.k8s.io/cloud-provider-azure/pkg/cache"
+	"sigs.k8s.io/cloud-provider-azure/pkg/consts"
+)
+
+const (
+	DefaultCacheTTL = 120 * time.Second
+)
+
+func NewCache(
+	client privatelinkserviceclient.Interface,
+	cacheTTL time.Duration,
+	disableAPICallCache bool,
+) (cache.Resource, error) {
+	getter := func(ctx context.Context, key string) (interface{}, error) {
+		resourceGroup, frontendID := parsePLSCacheKey(key)
+		plsList, err := client.List(ctx, resourceGroup)
+		if err != nil {
+			return nil, err
+		}
+
+		for i := range plsList {
+			pls := plsList[i]
+			if pls.Properties == nil {
+				continue
+			}
+			for _, fipConfig := range pls.Properties.LoadBalancerFrontendIPConfigurations {
+				if strings.EqualFold(*fipConfig.ID, frontendID) {
+					return pls, nil
+				}
+			}
+		}
+
+		notFound := &armnetwork.PrivateLinkService{
+			ID: to.Ptr(consts.PrivateLinkServiceNotExistID),
+		}
+		return notFound, nil
+	}
+
+	if cacheTTL == 0 {
+		cacheTTL = DefaultCacheTTL
+	}
+	return cache.NewTimedCache(cacheTTL, getter, disableAPICallCache)
+}
+
+func getPLSCacheKey(resourceGroup, plsLBFrontendID string) string {
+	return fmt.Sprintf("%s*%s", resourceGroup, plsLBFrontendID)
+}
+
+func parsePLSCacheKey(key string) (string, string) {
+	splits := strings.Split(key, "*")
+	return splits[0], splits[1]
+}
diff --git a/pkg/provider/privatelinkservice/mock_repo.go b/pkg/provider/privatelinkservice/mock_repo.go
new file mode 100644
index 000000000..a19bd8a5c
--- /dev/null
+++ b/pkg/provider/privatelinkservice/mock_repo.go
@@ -0,0 +1,131 @@
+// /*
+// Copyright 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.
+// */
+//
+
+// Code generated by MockGen. DO NOT EDIT.
+// Source: repo.go
+//
+// Generated by this command:
+//
+//	mockgen -destination=./mock_repo.go -package=privatelinkservice -copyright_file ../../../hack/boilerplate/boilerplate.generatego.txt -source=repo.go Repository
+//
+// Package privatelinkservice is a generated GoMock package.
+package privatelinkservice
+
+import (
+	context "context"
+	reflect "reflect"
+
+	armnetwork "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6"
+	gomock "go.uber.org/mock/gomock"
+	cache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
+)
+
+// MockRepository is a mock of Repository interface.
+type MockRepository struct {
+	ctrl     *gomock.Controller
+	recorder *MockRepositoryMockRecorder
+}
+
+// MockRepositoryMockRecorder is the mock recorder for MockRepository.
+type MockRepositoryMockRecorder struct {
+	mock *MockRepository
+}
+
+// NewMockRepository creates a new mock instance.
+func NewMockRepository(ctrl *gomock.Controller) *MockRepository {
+	mock := &MockRepository{ctrl: ctrl}
+	mock.recorder = &MockRepositoryMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder {
+	return m.recorder
+}
+
+// CreateOrUpdate mocks base method.
+func (m *MockRepository) CreateOrUpdate(ctx context.Context, resourceGroup string, pls armnetwork.PrivateLinkService) (*armnetwork.PrivateLinkService, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "CreateOrUpdate", ctx, resourceGroup, pls)
+	ret0, _ := ret[0].(*armnetwork.PrivateLinkService)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// CreateOrUpdate indicates an expected call of CreateOrUpdate.
+func (mr *MockRepositoryMockRecorder) CreateOrUpdate(ctx, resourceGroup, pls any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOrUpdate", reflect.TypeOf((*MockRepository)(nil).CreateOrUpdate), ctx, resourceGroup, pls)
+}
+
+// Delete mocks base method.
+func (m *MockRepository) Delete(ctx context.Context, resourceGroup, plsName, lbFrontendID string) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Delete", ctx, resourceGroup, plsName, lbFrontendID)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// Delete indicates an expected call of Delete.
+func (mr *MockRepositoryMockRecorder) Delete(ctx, resourceGroup, plsName, lbFrontendID any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockRepository)(nil).Delete), ctx, resourceGroup, plsName, lbFrontendID)
+}
+
+// DeletePEConnection mocks base method.
+func (m *MockRepository) DeletePEConnection(ctx context.Context, resourceGroup, plsName, peConnName string) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "DeletePEConnection", ctx, resourceGroup, plsName, peConnName)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// DeletePEConnection indicates an expected call of DeletePEConnection.
+func (mr *MockRepositoryMockRecorder) DeletePEConnection(ctx, resourceGroup, plsName, peConnName any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePEConnection", reflect.TypeOf((*MockRepository)(nil).DeletePEConnection), ctx, resourceGroup, plsName, peConnName)
+}
+
+// Get mocks base method.
+func (m *MockRepository) Get(ctx context.Context, resourceGroup, frontendIPConfigID string, crt cache.AzureCacheReadType) (*armnetwork.PrivateLinkService, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Get", ctx, resourceGroup, frontendIPConfigID, crt)
+	ret0, _ := ret[0].(*armnetwork.PrivateLinkService)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// Get indicates an expected call of Get.
+func (mr *MockRepositoryMockRecorder) Get(ctx, resourceGroup, frontendIPConfigID, crt any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockRepository)(nil).Get), ctx, resourceGroup, frontendIPConfigID, crt)
+}
+
+// List mocks base method.
+func (m *MockRepository) List(ctx context.Context, resourceGroup string) ([]*armnetwork.PrivateLinkService, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "List", ctx, resourceGroup)
+	ret0, _ := ret[0].([]*armnetwork.PrivateLinkService)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// List indicates an expected call of List.
+func (mr *MockRepositoryMockRecorder) List(ctx, resourceGroup any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockRepository)(nil).List), ctx, resourceGroup)
+}
diff --git a/pkg/provider/privatelinkservice/repo.go b/pkg/provider/privatelinkservice/repo.go
new file mode 100644
index 000000000..0b3cea734
--- /dev/null
+++ b/pkg/provider/privatelinkservice/repo.go
@@ -0,0 +1,128 @@
+/*
+Copyright 2024 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 privatelinkservice
+
+// Generate mocks for the repository interface
+//go:generate mockgen -destination=./mock_repo.go -package=privatelinkservice -copyright_file ../../../hack/boilerplate/boilerplate.generatego.txt -source=repo.go Repository
+
+import (
+	"context"
+	"fmt"
+	"time"
+
+	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6"
+
+	"sigs.k8s.io/cloud-provider-azure/pkg/azclient/privatelinkserviceclient"
+	"sigs.k8s.io/cloud-provider-azure/pkg/cache"
+)
+
+type Repository interface {
+	Get(ctx context.Context, resourceGroup, frontendIPConfigID string, crt cache.AzureCacheReadType) (*armnetwork.PrivateLinkService, error)
+	List(ctx context.Context, resourceGroup string) ([]*armnetwork.PrivateLinkService, error)
+	CreateOrUpdate(ctx context.Context, resourceGroup string, pls armnetwork.PrivateLinkService) (*armnetwork.PrivateLinkService, error)
+	Delete(ctx context.Context, resourceGroup, plsName, lbFrontendID string) error
+	DeletePEConnection(ctx context.Context, resourceGroup, plsName, peConnName string) error
+}
+
+type repo struct {
+	client privatelinkserviceclient.Interface
+	cache  cache.Resource
+}
+
+func NewRepo(
+	client privatelinkserviceclient.Interface,
+	cacheTTL time.Duration,
+	disableAPICallCache bool,
+) (Repository, error) {
+	c, err := NewCache(client, cacheTTL, disableAPICallCache)
+	if err != nil {
+		return nil, fmt.Errorf("new PLS cache: %w", err)
+	}
+
+	return &repo{
+		client: client,
+		cache:  c,
+	}, nil
+}
+
+var (
+	ErrMissingPLSName                               = fmt.Errorf("missing PLS name")
+	ErrLoadBalancerFrontendIPConfigurationsNotFound = fmt.Errorf("load balancer frontend IP configurations not found")
+)
+
+func (r *repo) Get(ctx context.Context, resourceGroup, frontendIPConfigID string, crt cache.AzureCacheReadType) (*armnetwork.PrivateLinkService, error) {
+	cacheKey := getPLSCacheKey(resourceGroup, frontendIPConfigID)
+
+	cachedPLS, err := r.cache.GetWithDeepCopy(ctx, cacheKey, crt)
+	if err != nil {
+		return nil, fmt.Errorf("get PLS: %w", err)
+	}
+
+	return cachedPLS.(*armnetwork.PrivateLinkService), nil
+}
+
+func (r *repo) List(ctx context.Context, resourceGroup string) ([]*armnetwork.PrivateLinkService, error) {
+	return r.client.List(ctx, resourceGroup)
+}
+
+func (r *repo) CreateOrUpdate(ctx context.Context, resourceGroup string, pls armnetwork.PrivateLinkService) (*armnetwork.PrivateLinkService, error) {
+	if pls.Name == nil {
+		return nil, ErrMissingPLSName
+	}
+	if pls.Properties == nil ||
+		len(pls.Properties.LoadBalancerFrontendIPConfigurations) == 0 ||
+		pls.Properties.LoadBalancerFrontendIPConfigurations[0].ID == nil {
+		return nil, ErrLoadBalancerFrontendIPConfigurationsNotFound
+	}
+	cacheKey := getPLSCacheKey(resourceGroup, *pls.Properties.LoadBalancerFrontendIPConfigurations[0].ID)
+
+	resp, err := r.client.CreateOrUpdate(ctx, resourceGroup, *pls.Name, pls)
+	if err != nil {
+		return nil, fmt.Errorf("create or update PLS: %w", err)
+	}
+	// clear cache
+	_ = r.cache.Delete(cacheKey)
+
+	return resp, nil
+}
+
+func (r *repo) Delete(ctx context.Context, resourceGroup, plsName, lbFrontendID string) error {
+	if plsName == "" {
+		return ErrMissingPLSName
+	}
+	cacheKey := getPLSCacheKey(resourceGroup, lbFrontendID)
+
+	if err := r.client.Delete(ctx, resourceGroup, plsName); err != nil {
+		return fmt.Errorf("delete PLS: %w", err)
+	}
+	// clear cache
+	_ = r.cache.Delete(cacheKey)
+
+	return nil
+}
+
+func (r *repo) DeletePEConnection(ctx context.Context, resourceGroup, plsName, peConnName string) error {
+	if plsName == "" {
+		return ErrMissingPLSName
+	}
+
+	if err := r.client.DeletePrivateEndpointConnection(ctx, resourceGroup, plsName, peConnName); err != nil {
+		return fmt.Errorf("delete PLS PE connection: %w", err)
+	}
+
+	return nil
+}
diff --git a/pkg/provider/zone/mock_repo.go b/pkg/provider/zone/mock_repo.go
new file mode 100644
index 000000000..5a446d893
--- /dev/null
+++ b/pkg/provider/zone/mock_repo.go
@@ -0,0 +1,71 @@
+// /*
+// Copyright 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.
+// */
+//
+
+// Code generated by MockGen. DO NOT EDIT.
+// Source: repo.go
+//
+// Generated by this command:
+//
+//	mockgen -destination=./mock_repo.go -package=zone -copyright_file ../../../hack/boilerplate/boilerplate.generatego.txt -source=repo.go Repository
+//
+// Package zone is a generated GoMock package.
+package zone
+
+import (
+	context "context"
+	reflect "reflect"
+
+	gomock "go.uber.org/mock/gomock"
+)
+
+// MockRepository is a mock of Repository interface.
+type MockRepository struct {
+	ctrl     *gomock.Controller
+	recorder *MockRepositoryMockRecorder
+}
+
+// MockRepositoryMockRecorder is the mock recorder for MockRepository.
+type MockRepositoryMockRecorder struct {
+	mock *MockRepository
+}
+
+// NewMockRepository creates a new mock instance.
+func NewMockRepository(ctrl *gomock.Controller) *MockRepository {
+	mock := &MockRepository{ctrl: ctrl}
+	mock.recorder = &MockRepositoryMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder {
+	return m.recorder
+}
+
+// ListZones mocks base method.
+func (m *MockRepository) ListZones(ctx context.Context) (map[string][]string, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "ListZones", ctx)
+	ret0, _ := ret[0].(map[string][]string)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// ListZones indicates an expected call of ListZones.
+func (mr *MockRepositoryMockRecorder) ListZones(ctx any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListZones", reflect.TypeOf((*MockRepository)(nil).ListZones), ctx)
+}
diff --git a/pkg/provider/zone/repo.go b/pkg/provider/zone/repo.go
new file mode 100644
index 000000000..ad3284b3a
--- /dev/null
+++ b/pkg/provider/zone/repo.go
@@ -0,0 +1,53 @@
+/*
+Copyright 2024 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 zone
+
+// Generate mocks for the repository interface
+//go:generate mockgen -destination=./mock_repo.go -package=zone -copyright_file ../../../hack/boilerplate/boilerplate.generatego.txt -source=repo.go Repository
+
+import (
+	"context"
+
+	"sigs.k8s.io/cloud-provider-azure/pkg/azclient/providerclient"
+	fnutil "sigs.k8s.io/cloud-provider-azure/pkg/util/collectionutil"
+)
+
+type Repository interface {
+	ListZones(ctx context.Context) (map[string][]string, error)
+}
+
+type repo struct {
+	client providerclient.Interface
+}
+
+func NewRepo(client providerclient.Interface) (Repository, error) {
+	return &repo{client: client}, nil
+}
+
+func (r *repo) ListZones(ctx context.Context) (map[string][]string, error) {
+	zones, err := r.client.GetVirtualMachineSupportedZones(ctx)
+	if err != nil {
+		return nil, err
+	}
+
+	var rv = make(map[string][]string, len(zones))
+	for region, z := range zones {
+		rv[region] = fnutil.Map(func(s *string) string { return *s }, z)
+	}
+
+	return rv, nil
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index a958b619e..6e1969d89 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -1559,9 +1559,11 @@ sigs.k8s.io/cloud-provider-azure/pkg/azclient/policy/retryrepectthrottled
 sigs.k8s.io/cloud-provider-azure/pkg/azclient/policy/useragent
 sigs.k8s.io/cloud-provider-azure/pkg/azclient/privateendpointclient
 sigs.k8s.io/cloud-provider-azure/pkg/azclient/privatelinkserviceclient
+sigs.k8s.io/cloud-provider-azure/pkg/azclient/privatelinkserviceclient/mock_privatelinkserviceclient
 sigs.k8s.io/cloud-provider-azure/pkg/azclient/privatezoneclient
 sigs.k8s.io/cloud-provider-azure/pkg/azclient/privatezoneclient/mock_privatezoneclient
 sigs.k8s.io/cloud-provider-azure/pkg/azclient/providerclient
+sigs.k8s.io/cloud-provider-azure/pkg/azclient/providerclient/mock_providerclient
 sigs.k8s.io/cloud-provider-azure/pkg/azclient/publicipaddressclient
 sigs.k8s.io/cloud-provider-azure/pkg/azclient/publicipprefixclient
 sigs.k8s.io/cloud-provider-azure/pkg/azclient/registryclient
diff --git a/vendor/sigs.k8s.io/cloud-provider-azure/pkg/azclient/privatelinkserviceclient/mock_privatelinkserviceclient/interface.go b/vendor/sigs.k8s.io/cloud-provider-azure/pkg/azclient/privatelinkserviceclient/mock_privatelinkserviceclient/interface.go
new file mode 100644
index 000000000..630b2663e
--- /dev/null
+++ b/vendor/sigs.k8s.io/cloud-provider-azure/pkg/azclient/privatelinkserviceclient/mock_privatelinkserviceclient/interface.go
@@ -0,0 +1,131 @@
+// /*
+// Copyright 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.
+// */
+
+// Code generated by MockGen. DO NOT EDIT.
+// Source: privatelinkserviceclient/interface.go
+//
+// Generated by this command:
+//
+//	mockgen -package mock_privatelinkserviceclient -source privatelinkserviceclient/interface.go
+//
+
+// Package mock_privatelinkserviceclient is a generated GoMock package.
+package mock_privatelinkserviceclient
+
+import (
+	context "context"
+	reflect "reflect"
+
+	armnetwork "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6"
+	gomock "go.uber.org/mock/gomock"
+)
+
+// MockInterface is a mock of Interface interface.
+type MockInterface struct {
+	ctrl     *gomock.Controller
+	recorder *MockInterfaceMockRecorder
+	isgomock struct{}
+}
+
+// MockInterfaceMockRecorder is the mock recorder for MockInterface.
+type MockInterfaceMockRecorder struct {
+	mock *MockInterface
+}
+
+// NewMockInterface creates a new mock instance.
+func NewMockInterface(ctrl *gomock.Controller) *MockInterface {
+	mock := &MockInterface{ctrl: ctrl}
+	mock.recorder = &MockInterfaceMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder {
+	return m.recorder
+}
+
+// CreateOrUpdate mocks base method.
+func (m *MockInterface) CreateOrUpdate(ctx context.Context, resourceGroupName, resourceName string, resourceParam armnetwork.PrivateLinkService) (*armnetwork.PrivateLinkService, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "CreateOrUpdate", ctx, resourceGroupName, resourceName, resourceParam)
+	ret0, _ := ret[0].(*armnetwork.PrivateLinkService)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// CreateOrUpdate indicates an expected call of CreateOrUpdate.
+func (mr *MockInterfaceMockRecorder) CreateOrUpdate(ctx, resourceGroupName, resourceName, resourceParam any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOrUpdate", reflect.TypeOf((*MockInterface)(nil).CreateOrUpdate), ctx, resourceGroupName, resourceName, resourceParam)
+}
+
+// Delete mocks base method.
+func (m *MockInterface) Delete(ctx context.Context, resourceGroupName, resourceName string) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Delete", ctx, resourceGroupName, resourceName)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// Delete indicates an expected call of Delete.
+func (mr *MockInterfaceMockRecorder) Delete(ctx, resourceGroupName, resourceName any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockInterface)(nil).Delete), ctx, resourceGroupName, resourceName)
+}
+
+// DeletePrivateEndpointConnection mocks base method.
+func (m *MockInterface) DeletePrivateEndpointConnection(ctx context.Context, resourceGroupName, serviceName, peConnectionName string) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "DeletePrivateEndpointConnection", ctx, resourceGroupName, serviceName, peConnectionName)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// DeletePrivateEndpointConnection indicates an expected call of DeletePrivateEndpointConnection.
+func (mr *MockInterfaceMockRecorder) DeletePrivateEndpointConnection(ctx, resourceGroupName, serviceName, peConnectionName any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePrivateEndpointConnection", reflect.TypeOf((*MockInterface)(nil).DeletePrivateEndpointConnection), ctx, resourceGroupName, serviceName, peConnectionName)
+}
+
+// Get mocks base method.
+func (m *MockInterface) Get(ctx context.Context, resourceGroupName, resourceName string, expand *string) (*armnetwork.PrivateLinkService, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Get", ctx, resourceGroupName, resourceName, expand)
+	ret0, _ := ret[0].(*armnetwork.PrivateLinkService)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// Get indicates an expected call of Get.
+func (mr *MockInterfaceMockRecorder) Get(ctx, resourceGroupName, resourceName, expand any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockInterface)(nil).Get), ctx, resourceGroupName, resourceName, expand)
+}
+
+// List mocks base method.
+func (m *MockInterface) List(ctx context.Context, resourceGroupName string) ([]*armnetwork.PrivateLinkService, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "List", ctx, resourceGroupName)
+	ret0, _ := ret[0].([]*armnetwork.PrivateLinkService)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// List indicates an expected call of List.
+func (mr *MockInterfaceMockRecorder) List(ctx, resourceGroupName any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockInterface)(nil).List), ctx, resourceGroupName)
+}
diff --git a/vendor/sigs.k8s.io/cloud-provider-azure/pkg/azclient/providerclient/mock_providerclient/interface.go b/vendor/sigs.k8s.io/cloud-provider-azure/pkg/azclient/providerclient/mock_providerclient/interface.go
new file mode 100644
index 000000000..0fe5952a4
--- /dev/null
+++ b/vendor/sigs.k8s.io/cloud-provider-azure/pkg/azclient/providerclient/mock_providerclient/interface.go
@@ -0,0 +1,102 @@
+// /*
+// Copyright 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.
+// */
+
+// Code generated by MockGen. DO NOT EDIT.
+// Source: providerclient/interface.go
+//
+// Generated by this command:
+//
+//	mockgen -package mock_providerclient -source providerclient/interface.go
+//
+
+// Package mock_providerclient is a generated GoMock package.
+package mock_providerclient
+
+import (
+	context "context"
+	reflect "reflect"
+
+	armresources "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources"
+	gomock "go.uber.org/mock/gomock"
+)
+
+// MockInterface is a mock of Interface interface.
+type MockInterface struct {
+	ctrl     *gomock.Controller
+	recorder *MockInterfaceMockRecorder
+}
+
+// MockInterfaceMockRecorder is the mock recorder for MockInterface.
+type MockInterfaceMockRecorder struct {
+	mock *MockInterface
+}
+
+// NewMockInterface creates a new mock instance.
+func NewMockInterface(ctrl *gomock.Controller) *MockInterface {
+	mock := &MockInterface{ctrl: ctrl}
+	mock.recorder = &MockInterfaceMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder {
+	return m.recorder
+}
+
+// GetProvider mocks base method.
+func (m *MockInterface) GetProvider(ctx context.Context, resourceProviderNamespace string) (*armresources.Provider, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetProvider", ctx, resourceProviderNamespace)
+	ret0, _ := ret[0].(*armresources.Provider)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetProvider indicates an expected call of GetProvider.
+func (mr *MockInterfaceMockRecorder) GetProvider(ctx, resourceProviderNamespace any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProvider", reflect.TypeOf((*MockInterface)(nil).GetProvider), ctx, resourceProviderNamespace)
+}
+
+// GetVirtualMachineSupportedZones mocks base method.
+func (m *MockInterface) GetVirtualMachineSupportedZones(ctx context.Context) (map[string][]*string, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetVirtualMachineSupportedZones", ctx)
+	ret0, _ := ret[0].(map[string][]*string)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetVirtualMachineSupportedZones indicates an expected call of GetVirtualMachineSupportedZones.
+func (mr *MockInterfaceMockRecorder) GetVirtualMachineSupportedZones(ctx any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVirtualMachineSupportedZones", reflect.TypeOf((*MockInterface)(nil).GetVirtualMachineSupportedZones), ctx)
+}
+
+// ListProviders mocks base method.
+func (m *MockInterface) ListProviders(ctx context.Context) ([]*armresources.Provider, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "ListProviders", ctx)
+	ret0, _ := ret[0].([]*armresources.Provider)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// ListProviders indicates an expected call of ListProviders.
+func (mr *MockInterfaceMockRecorder) ListProviders(ctx any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListProviders", reflect.TypeOf((*MockInterface)(nil).ListProviders), ctx)
+}


Test patch:
diff --git a/pkg/azureclients/zoneclient/azure_zoneclient_test.go b/pkg/azureclients/zoneclient/azure_zoneclient_test.go
deleted file mode 100644
index 03b5d2fde..000000000
--- a/pkg/azureclients/zoneclient/azure_zoneclient_test.go
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
-Copyright 2021 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 zoneclient
-
-import (
-	"bytes"
-	"context"
-	"io"
-	"net/http"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-	"go.uber.org/mock/gomock"
-
-	azclients "sigs.k8s.io/cloud-provider-azure/pkg/azureclients"
-	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/armclient"
-	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/armclient/mockarmclient"
-)
-
-const (
-	responseString = `
-{
-	"value": [
-		{
-			"id": "/subscriptions/subscriptionID/providers/Microsoft.Network",
-			"resourceTypes": [
-				{
-					"resourceType": "foo",
-					"zoneMappings": [
-						{
-							"location": "East US 2",
-							"zones": [
-								"1",
-								"2",
-								"3"
-							]
-						}
-					]
-				},
-				{
-					"resourceType": "virtualMachines",
-					"zoneMappings": [
-						{
-							"location": "East US 2",
-							"zones": [
-								"1",
-								"2",
-								"3"
-							]
-						}
-					]
-				}
-			]
-		},
-		{
-			"id": "/subscriptions/subscriptionID/providers/Microsoft.Compute",
-			"resourceTypes": [
-				{
-					"resourceType": "foo",
-					"zoneMappings": [
-						{
-							"location": "East US 2",
-							"zones": [
-								"1",
-								"2",
-								"3"
-							]
-						}
-					]
-				},
-				{
-					"resourceType": "virtualMachines",
-					"zoneMappings": [
-						{
-							"location": "East US 2",
-							"zones": [
-								"1",
-								"2",
-								"3"
-							]
-						}
-					]
-				}
-			]
-		}
-	]
-}
-`
-	testProviderResourcesListID = "/subscriptions/subscriptionID/providers"
-)
-
-func TestNew(t *testing.T) {
-	config := &azclients.ClientConfig{
-		SubscriptionID:          "sub",
-		ResourceManagerEndpoint: "endpoint",
-		Location:                "eastus",
-	}
-
-	zoneClient := New(config)
-	assert.Equal(t, "sub", zoneClient.subscriptionID)
-}
-
-func TestGetZones(t *testing.T) {
-	ctrl := gomock.NewController(t)
-	defer ctrl.Finish()
-
-	response := &http.Response{
-		StatusCode: http.StatusOK,
-		Body:       io.NopCloser(bytes.NewReader([]byte(responseString))),
-	}
-	armClient := mockarmclient.NewMockInterface(ctrl)
-	armClient.EXPECT().GetResource(gomock.Any(), testProviderResourcesListID).Return(response, nil).Times(1)
-	armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1)
-
-	zoneClient := getTestZoneClient(armClient)
-	expected := map[string][]string{"eastus2": {"1", "2", "3"}}
-	zones, rerr := zoneClient.GetZones(context.TODO(), zoneClient.subscriptionID)
-	assert.Nil(t, rerr)
-	assert.Equal(t, expected, zones)
-}
-
-func getTestZoneClient(armClient armclient.Interface) *Client {
-	return &Client{
-		armClient:      armClient,
-		subscriptionID: "subscriptionID",
-	}
-}
-
-func TestGetNotFound(t *testing.T) {
-	ctrl := gomock.NewController(t)
-	defer ctrl.Finish()
-
-	response := &http.Response{
-		StatusCode: http.StatusNotFound,
-		Body:       io.NopCloser(bytes.NewReader([]byte("{}"))),
-	}
-	armClient := mockarmclient.NewMockInterface(ctrl)
-	armClient.EXPECT().GetResource(gomock.Any(), testProviderResourcesListID).Return(response, nil).Times(1)
-	armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1)
-
-	zoneClient := getTestZoneClient(armClient)
-	result, rerr := zoneClient.GetZones(context.TODO(), zoneClient.subscriptionID)
-	assert.Nil(t, result)
-	assert.NotNil(t, rerr)
-	assert.Equal(t, http.StatusNotFound, rerr.HTTPStatusCode)
-}
-
-func TestGetInternalError(t *testing.T) {
-	ctrl := gomock.NewController(t)
-	defer ctrl.Finish()
-
-	response := &http.Response{
-		StatusCode: http.StatusInternalServerError,
-		Body:       io.NopCloser(bytes.NewReader([]byte("{}"))),
-	}
-	armClient := mockarmclient.NewMockInterface(ctrl)
-	armClient.EXPECT().GetResource(gomock.Any(), testProviderResourcesListID).Return(response, nil).Times(1)
-	armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1)
-
-	zoneClient := getTestZoneClient(armClient)
-	result, rerr := zoneClient.GetZones(context.TODO(), zoneClient.subscriptionID)
-	assert.Nil(t, result)
-	assert.NotNil(t, rerr)
-	assert.Equal(t, http.StatusInternalServerError, rerr.HTTPStatusCode)
-}
diff --git a/pkg/provider/azure_loadbalancer_test.go b/pkg/provider/azure_loadbalancer_test.go
index 204cb9734..ca8b3ffb0 100644
--- a/pkg/provider/azure_loadbalancer_test.go
+++ b/pkg/provider/azure_loadbalancer_test.go
@@ -28,15 +28,14 @@ import (
 	"testing"
 	"time"
 
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
+	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6"
 	"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2022-08-01/compute"
 	"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2022-07-01/network"
 	"github.com/stretchr/testify/assert"
-
 	"go.uber.org/mock/gomock"
-
 	"golang.org/x/text/cases"
 	"golang.org/x/text/language"
-
 	coordinationv1 "k8s.io/api/coordination/v1"
 	v1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -48,12 +47,12 @@ import (
 	"k8s.io/utils/ptr"
 
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/loadbalancerclient/mockloadbalancerclient"
-	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/privatelinkserviceclient/mockprivatelinkserviceclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/publicipclient/mockpublicipclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/subnetclient/mocksubnetclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/vmssclient/mockvmssclient"
-	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/zoneclient/mockzoneclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/consts"
+	"sigs.k8s.io/cloud-provider-azure/pkg/provider/privatelinkservice"
+	"sigs.k8s.io/cloud-provider-azure/pkg/provider/zone"
 	"sigs.k8s.io/cloud-provider-azure/pkg/retry"
 	utilsets "sigs.k8s.io/cloud-provider-azure/pkg/util/sets"
 )
@@ -756,9 +755,9 @@ 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
+		mockPLSRepo := privatelinkservice.NewMockRepository(ctrl)
+		mockPLSRepo.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&armnetwork.PrivateLinkService{ID: to.Ptr(consts.PrivateLinkServiceNotExistID)}, nil).AnyTimes()
+		az.plsRepo = mockPLSRepo
 
 		// create the service first.
 		lbStatus, err := az.EnsureLoadBalancer(context.TODO(), testClusterName, &service, clusterResources.nodes)
@@ -1888,7 +1887,6 @@ func TestGetServiceLoadBalancerMultiSLB(t *testing.T) {
 			cloud := GetTestCloud(ctrl)
 			cloud.LoadBalancerSku = "Standard"
 			cloud.MultipleStandardLoadBalancerConfigurations = tc.multiSLBConfigs
-			cloud.plsCache, _ = cloud.newPLSCache()
 			lbClient := mockloadbalancerclient.NewMockInterface(ctrl)
 			lbClient.EXPECT().Delete(gomock.Any(), gomock.Any(), "lb1-internal").MaxTimes(1)
 			lbClient.EXPECT().DeleteLBBackendPool(gomock.Any(), gomock.Any(), "lb1", "default-test1").Return(nil).MaxTimes(1)
@@ -1896,9 +1894,8 @@ func TestGetServiceLoadBalancerMultiSLB(t *testing.T) {
 			lbClient.EXPECT().CreateOrUpdate(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).MaxTimes(1)
 			cloud.LoadBalancerClient = lbClient
 
-			expectedPLS := make([]network.PrivateLinkService, 0)
-			mockPLSClient := cloud.PrivateLinkServiceClient.(*mockprivatelinkserviceclient.MockInterface)
-			mockPLSClient.EXPECT().List(gomock.Any(), gomock.Any()).Return(expectedPLS, nil)
+			mockPLSRepo := cloud.plsRepo.(*privatelinkservice.MockRepository)
+			mockPLSRepo.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&armnetwork.PrivateLinkService{ID: to.Ptr(consts.PrivateLinkServiceNotExistID)}, nil)
 
 			mockPIPClient := mockpublicipclient.NewMockInterface(ctrl)
 			mockPIPClient.EXPECT().List(gomock.Any(), gomock.Any()).Return([]network.PublicIPAddress{}, nil).MaxTimes(2)
@@ -2039,11 +2036,6 @@ func TestGetServiceLoadBalancerCommon(t *testing.T) {
 			mockLBsClient.EXPECT().Delete(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
 			az.LoadBalancerClient = mockLBsClient
 
-			expectedPLS := make([]network.PrivateLinkService, 0)
-			mockPLSClient := mockprivatelinkserviceclient.NewMockInterface(ctrl)
-			mockPLSClient.EXPECT().List(gomock.Any(), "rg").Return(expectedPLS, nil).MaxTimes(1)
-			az.PrivateLinkServiceClient = mockPLSClient
-
 			for _, existingLB := range test.existingLBs {
 				err := az.LoadBalancerClient.CreateOrUpdate(context.TODO(), "rg", *existingLB.Name, existingLB, "")
 				assert.NoError(t, err.Error())
@@ -6188,9 +6180,8 @@ func TestRemoveFrontendIPConfigurationFromLoadBalancerDelete(t *testing.T) {
 		cloud := GetTestCloud(ctrl)
 		mockLBClient := cloud.LoadBalancerClient.(*mockloadbalancerclient.MockInterface)
 		mockLBClient.EXPECT().Delete(gomock.Any(), "rg", "lb").Return(nil)
-		expectedPLS := make([]network.PrivateLinkService, 0)
-		mockPLSClient := cloud.PrivateLinkServiceClient.(*mockprivatelinkserviceclient.MockInterface)
-		mockPLSClient.EXPECT().List(gomock.Any(), "rg").Return(expectedPLS, nil).MaxTimes(1)
+		mockPLSRepo := cloud.plsRepo.(*privatelinkservice.MockRepository)
+		mockPLSRepo.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&armnetwork.PrivateLinkService{ID: to.Ptr(consts.PrivateLinkServiceNotExistID)}, nil)
 		existingLBs := []network.LoadBalancer{{Name: ptr.To("lb")}}
 		_, err := cloud.removeFrontendIPConfigurationFromLoadBalancer(context.TODO(), &lb, &existingLBs, []*network.FrontendIPConfiguration{fip}, "testCluster", &service)
 		assert.NoError(t, err)
@@ -6220,9 +6211,8 @@ func TestRemoveFrontendIPConfigurationFromLoadBalancerUpdate(t *testing.T) {
 		cloud := GetTestCloud(ctrl)
 		mockLBClient := cloud.LoadBalancerClient.(*mockloadbalancerclient.MockInterface)
 		mockLBClient.EXPECT().CreateOrUpdate(gomock.Any(), "rg", "lb", gomock.Any(), gomock.Any()).Return(nil)
-		expectedPLS := make([]network.PrivateLinkService, 0)
-		mockPLSClient := cloud.PrivateLinkServiceClient.(*mockprivatelinkserviceclient.MockInterface)
-		mockPLSClient.EXPECT().List(gomock.Any(), "rg").Return(expectedPLS, nil).MaxTimes(1)
+		mockPLSRepo := cloud.plsRepo.(*privatelinkservice.MockRepository)
+		mockPLSRepo.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&armnetwork.PrivateLinkService{ID: to.Ptr(consts.PrivateLinkServiceNotExistID)}, nil)
 		_, err := cloud.removeFrontendIPConfigurationFromLoadBalancer(context.TODO(), &lb, &[]network.LoadBalancer{}, []*network.FrontendIPConfiguration{fip}, "testCluster", &service)
 		assert.NoError(t, err)
 	})
@@ -6286,7 +6276,7 @@ func TestReconcileZonesForFrontendIPConfigs(t *testing.T) {
 		existingPIPV4             network.PublicIPAddress
 		existingPIPV6             network.PublicIPAddress
 		status                    *v1.LoadBalancerStatus
-		getZoneError              *retry.Error
+		getZoneError              error
 		regionZonesMap            map[string][]string
 		expectedZones             *[]string
 		expectedDirty             bool
@@ -6316,7 +6306,7 @@ func TestReconcileZonesForFrontendIPConfigs(t *testing.T) {
 		{
 			description:  "reconcileFrontendIPConfigs should report an error if failed to get zones",
 			service:      getInternalTestServiceDualStack("test", 80),
-			getZoneError: retry.NewError(false, errors.New("get zone failed")),
+			getZoneError: errors.New("get zone failed"),
 			expectedErr:  errors.New("get zone failed"),
 		},
 		{
@@ -6413,9 +6403,9 @@ func TestReconcileZonesForFrontendIPConfigs(t *testing.T) {
 			subnetClient.EXPECT().Get(gomock.Any(), "rg", "vnet", "subnet", gomock.Any()).Return(
 				network.Subnet{ID: ptr.To("subnet0"), SubnetPropertiesFormat: &network.SubnetPropertiesFormat{AddressPrefixes: &[]string{"1.2.3.4/31", "2001::1/127"}}}, nil).MaxTimes(1)
 
-			zoneClient := mockzoneclient.NewMockInterface(ctrl)
-			zoneClient.EXPECT().GetZones(gomock.Any(), gomock.Any()).Return(map[string][]string{}, tc.getZoneError).MaxTimes(2)
-			cloud.ZoneClient = zoneClient
+			zoneMock := zone.NewMockRepository(ctrl)
+			zoneMock.EXPECT().ListZones(gomock.Any()).Return(map[string][]string{}, tc.getZoneError).MaxTimes(2)
+			cloud.zoneRepo = zoneMock
 
 			service := tc.service
 			isDualStack := isServiceDualStack(&service)
diff --git a/pkg/provider/azure_privatelinkservice_repo_test.go b/pkg/provider/azure_privatelinkservice_repo_test.go
deleted file mode 100644
index 22b337cd2..000000000
--- a/pkg/provider/azure_privatelinkservice_repo_test.go
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
-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 (
-	"context"
-	"fmt"
-	"net/http"
-	"testing"
-
-	"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2022-07-01/network"
-	"github.com/stretchr/testify/assert"
-
-	"go.uber.org/mock/gomock"
-
-	v1 "k8s.io/api/core/v1"
-	"k8s.io/utils/ptr"
-
-	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/privatelinkserviceclient/mockprivatelinkserviceclient"
-	"sigs.k8s.io/cloud-provider-azure/pkg/cache"
-	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"
-)
-
-func TestCreateOrUpdatePLS(t *testing.T) {
-	ctrl := gomock.NewController(t)
-	defer ctrl.Finish()
-
-	tests := []struct {
-		clientErr   *retry.Error
-		expectedErr error
-	}{
-		{
-			clientErr:   &retry.Error{HTTPStatusCode: http.StatusPreconditionFailed},
-			expectedErr: fmt.Errorf("Retriable: false, RetryAfter: 0s, HTTPStatusCode: 412, RawError: %w", error(nil)),
-		},
-		{
-			clientErr:   &retry.Error{RawError: fmt.Errorf(consts.OperationCanceledErrorMessage)},
-			expectedErr: fmt.Errorf("Retriable: false, RetryAfter: 0s, HTTPStatusCode: 0, RawError: %w", fmt.Errorf("canceledandsupersededduetoanotheroperation")),
-		},
-	}
-
-	for _, test := range tests {
-		az := GetTestCloud(ctrl)
-		az.pipCache.Set("rg*frontendID", "test")
-
-		mockPLSClient := az.PrivateLinkServiceClient.(*mockprivatelinkserviceclient.MockInterface)
-		mockPLSClient.EXPECT().CreateOrUpdate(gomock.Any(), "rg", gomock.Any(), gomock.Any(), gomock.Any()).Return(test.clientErr)
-		mockPLSClient.EXPECT().List(gomock.Any(), az.ResourceGroup).Return([]network.PrivateLinkService{}, nil)
-
-		err := az.CreateOrUpdatePLS(&v1.Service{}, "rg", network.PrivateLinkService{
-			Name: ptr.To("pls"),
-			Etag: ptr.To("etag"),
-			PrivateLinkServiceProperties: &network.PrivateLinkServiceProperties{
-				LoadBalancerFrontendIPConfigurations: &[]network.FrontendIPConfiguration{
-					{
-						ID: ptr.To("frontendID"),
-					},
-				},
-			},
-		})
-		assert.EqualError(t, test.expectedErr, err.Error())
-
-		// loadbalancer should be removed from cache if the etag is mismatch or the operation is canceled
-		pls, err := az.plsCache.GetWithDeepCopy(context.TODO(), "rg*frontendID", cache.CacheReadTypeDefault)
-		assert.NoError(t, err)
-		assert.Equal(t, consts.PrivateLinkServiceNotExistID, *pls.(*network.PrivateLinkService).ID)
-	}
-}
-
-func TestDeletePLS(t *testing.T) {
-	ctrl := gomock.NewController(t)
-	defer ctrl.Finish()
-
-	az := GetTestCloud(ctrl)
-	mockPLSClient := az.PrivateLinkServiceClient.(*mockprivatelinkserviceclient.MockInterface)
-	mockPLSClient.EXPECT().Delete(gomock.Any(), "rg", "pls").Return(&retry.Error{HTTPStatusCode: http.StatusInternalServerError})
-
-	err := az.DeletePLS(&v1.Service{}, "rg", "pls", "frontendID")
-	assert.EqualError(t, fmt.Errorf("Retriable: false, RetryAfter: 0s, HTTPStatusCode: 500, RawError: %w", error(nil)), fmt.Sprintf("%s", err.Error()))
-}
-
-func TestDeletePEConn(t *testing.T) {
-	ctrl := gomock.NewController(t)
-	defer ctrl.Finish()
-
-	az := GetTestCloud(ctrl)
-	mockPLSClient := az.PrivateLinkServiceClient.(*mockprivatelinkserviceclient.MockInterface)
-	mockPLSClient.EXPECT().DeletePEConnection(gomock.Any(), "rg", "pls", "peConn").Return(&retry.Error{HTTPStatusCode: http.StatusInternalServerError})
-
-	err := az.DeletePEConn(&v1.Service{}, "rg", "pls", "peConn")
-	assert.EqualError(t, fmt.Errorf("Retriable: false, RetryAfter: 0s, HTTPStatusCode: 500, RawError: %w", error(nil)), fmt.Sprintf("%s", err.Error()))
-}
-
-func TestGetPrivateLinkService(t *testing.T) {
-	ctrl := gomock.NewController(t)
-	defer ctrl.Finish()
-
-	az := GetTestCloud(ctrl)
-	az.plsCache.Set("rg*frontendID", &network.PrivateLinkService{Name: ptr.To("pls")})
-
-	// cache hit
-	pls, err := az.getPrivateLinkService(context.TODO(), "rg", ptr.To("frontendID"), azcache.CacheReadTypeDefault)
-	assert.NoError(t, err)
-	assert.Equal(t, "pls", *pls.Name)
-
-	// cache miss
-	mockPLSClient := az.PrivateLinkServiceClient.(*mockprivatelinkserviceclient.MockInterface)
-	mockPLSClient.EXPECT().List(gomock.Any(), "rg1").Return([]network.PrivateLinkService{
-		{
-			Name: ptr.To("pls1"),
-			PrivateLinkServiceProperties: &network.PrivateLinkServiceProperties{
-				LoadBalancerFrontendIPConfigurations: &[]network.FrontendIPConfiguration{
-					{
-						ID: ptr.To("frontendID1"),
-					},
-				},
-			},
-		},
-	}, nil)
-	pls, err = az.getPrivateLinkService(context.TODO(), "rg1", ptr.To("frontendID1"), azcache.CacheReadTypeDefault)
-	assert.NoError(t, err)
-	assert.Equal(t, "pls1", *pls.Name)
-}
-
-func TestGetPLSCacheKey(t *testing.T) {
-	rg, frontendID := "rg", "/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Network/loadBalancers/lb/frontendIPConfigurations/ipconfig"
-	assert.Equal(t, "rg*/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Network/loadBalancers/lb/frontendIPConfigurations/ipconfig", getPLSCacheKey(rg, frontendID))
-}
-
-func TestParsePLSCacheKey(t *testing.T) {
-	key := "rg*/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Network/loadBalancers/lb/frontendIPConfigurations/ipconfig"
-	rg, frontendID := parsePLSCacheKey(key)
-	assert.Equal(t, "rg", rg)
-	assert.Equal(t, "/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Network/loadBalancers/lb/frontendIPConfigurations/ipconfig", frontendID)
-}
diff --git a/pkg/provider/azure_privatelinkservice_test.go b/pkg/provider/azure_privatelinkservice_test.go
index 15c518280..3de34e9e2 100644
--- a/pkg/provider/azure_privatelinkservice_test.go
+++ b/pkg/provider/azure_privatelinkservice_test.go
@@ -22,18 +22,19 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
+	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6"
 	"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2022-07-01/network"
 	"github.com/stretchr/testify/assert"
-
 	"go.uber.org/mock/gomock"
-
 	v1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/utils/ptr"
 
-	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/privatelinkserviceclient/mockprivatelinkserviceclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/subnetclient/mocksubnetclient"
+	"sigs.k8s.io/cloud-provider-azure/pkg/cache"
 	"sigs.k8s.io/cloud-provider-azure/pkg/consts"
+	"sigs.k8s.io/cloud-provider-azure/pkg/provider/privatelinkservice"
 	"sigs.k8s.io/cloud-provider-azure/pkg/retry"
 )
 
@@ -48,9 +49,9 @@ func TestReconcilePrivateLinkService(t *testing.T) {
 		expectedSubnetGet bool
 		existingSubnet    *network.Subnet
 		expectedPLSList   bool
-		existingPLSList   []network.PrivateLinkService
+		existingPLSList   []*armnetwork.PrivateLinkService
 		expectedPLSCreate bool
-		expectedPLS       *network.PrivateLinkService
+		expectedPLS       *armnetwork.PrivateLinkService
 		expectedPLSDelete bool
 		expectedError     bool
 	}{
@@ -82,9 +83,9 @@ func TestReconcilePrivateLinkService(t *testing.T) {
 				},
 			},
 			expectedPLSList:   true,
-			existingPLSList:   []network.PrivateLinkService{},
+			existingPLSList:   []*armnetwork.PrivateLinkService{},
 			expectedPLSCreate: true,
-			expectedPLS:       &network.PrivateLinkService{Name: ptr.To("pls-fipConfig")},
+			expectedPLS:       &armnetwork.PrivateLinkService{Name: ptr.To("pls-fipConfig")},
 		},
 		{
 			desc: "reconcilePrivateLinkService should create a new PLS if no existing PLS attached to the LB frontend",
@@ -103,9 +104,9 @@ func TestReconcilePrivateLinkService(t *testing.T) {
 				},
 			},
 			expectedPLSList:   true,
-			existingPLSList:   []network.PrivateLinkService{},
+			existingPLSList:   []*armnetwork.PrivateLinkService{},
 			expectedPLSCreate: true,
-			expectedPLS:       &network.PrivateLinkService{Name: ptr.To("testpls")},
+			expectedPLS:       &armnetwork.PrivateLinkService{Name: ptr.To("testpls")},
 		},
 		{
 			desc: "reconcilePrivateLinkService should report error if existing PLS attached to LB frontEnd is unmanaged",
@@ -116,18 +117,18 @@ func TestReconcilePrivateLinkService(t *testing.T) {
 			},
 			wantPLS:         true,
 			expectedPLSList: true,
-			existingPLSList: []network.PrivateLinkService{
+			existingPLSList: []*armnetwork.PrivateLinkService{
 				{
 					Name: ptr.To("testpls"),
-					PrivateLinkServiceProperties: &network.PrivateLinkServiceProperties{
-						LoadBalancerFrontendIPConfigurations: &[]network.FrontendIPConfiguration{{ID: ptr.To("fipConfigID")}},
-						IPConfigurations: &[]network.PrivateLinkServiceIPConfiguration{
+					Properties: &armnetwork.PrivateLinkServiceProperties{
+						LoadBalancerFrontendIPConfigurations: []*armnetwork.FrontendIPConfiguration{{ID: ptr.To("fipConfigID")}},
+						IPConfigurations: []*armnetwork.PrivateLinkServiceIPConfiguration{
 							{
-								PrivateLinkServiceIPConfigurationProperties: &network.PrivateLinkServiceIPConfigurationProperties{
-									PrivateIPAllocationMethod: network.Dynamic,
-									Subnet:                    &network.Subnet{ID: ptr.To("subnetID")},
+								Properties: &armnetwork.PrivateLinkServiceIPConfigurationProperties{
+									PrivateIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodDynamic),
+									Subnet:                    &armnetwork.Subnet{ID: ptr.To("subnetID")},
 									Primary:                   ptr.To(true),
-									PrivateIPAddressVersion:   network.IPv4,
+									PrivateIPAddressVersion:   to.Ptr(armnetwork.IPVersionIPv4),
 								},
 							},
 						},
@@ -145,18 +146,18 @@ func TestReconcilePrivateLinkService(t *testing.T) {
 			},
 			wantPLS:         true,
 			expectedPLSList: true,
-			existingPLSList: []network.PrivateLinkService{
+			existingPLSList: []*armnetwork.PrivateLinkService{
 				{
 					Name: ptr.To("testpls"),
-					PrivateLinkServiceProperties: &network.PrivateLinkServiceProperties{
-						LoadBalancerFrontendIPConfigurations: &[]network.FrontendIPConfiguration{{ID: ptr.To("fipConfigID")}},
-						IPConfigurations: &[]network.PrivateLinkServiceIPConfiguration{
+					Properties: &armnetwork.PrivateLinkServiceProperties{
+						LoadBalancerFrontendIPConfigurations: []*armnetwork.FrontendIPConfiguration{{ID: ptr.To("fipConfigID")}},
+						IPConfigurations: []*armnetwork.PrivateLinkServiceIPConfiguration{
 							{
-								PrivateLinkServiceIPConfigurationProperties: &network.PrivateLinkServiceIPConfigurationProperties{
-									PrivateIPAllocationMethod: network.Dynamic,
-									Subnet:                    &network.Subnet{ID: ptr.To("subnetID")},
+								Properties: &armnetwork.PrivateLinkServiceIPConfigurationProperties{
+									PrivateIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodDynamic),
+									Subnet:                    &armnetwork.Subnet{ID: ptr.To("subnetID")},
 									Primary:                   ptr.To(true),
-									PrivateIPAddressVersion:   network.IPv4,
+									PrivateIPAddressVersion:   to.Ptr(armnetwork.IPVersionIPv4),
 								},
 							},
 						},
@@ -177,19 +178,19 @@ func TestReconcilePrivateLinkService(t *testing.T) {
 			},
 			wantPLS:         true,
 			expectedPLSList: true,
-			existingPLSList: []network.PrivateLinkService{
+			existingPLSList: []*armnetwork.PrivateLinkService{
 				{
 					Name: ptr.To("testpls"),
-					PrivateLinkServiceProperties: &network.PrivateLinkServiceProperties{
-						LoadBalancerFrontendIPConfigurations: &[]network.FrontendIPConfiguration{{ID: ptr.To("fipConfigID")}},
-						IPConfigurations: &[]network.PrivateLinkServiceIPConfiguration{
+					Properties: &armnetwork.PrivateLinkServiceProperties{
+						LoadBalancerFrontendIPConfigurations: []*armnetwork.FrontendIPConfiguration{{ID: ptr.To("fipConfigID")}},
+						IPConfigurations: []*armnetwork.PrivateLinkServiceIPConfiguration{
 							{
-								PrivateLinkServiceIPConfigurationProperties: &network.PrivateLinkServiceIPConfigurationProperties{
-									PrivateIPAllocationMethod: network.Static,
+								Properties: &armnetwork.PrivateLinkServiceIPConfigurationProperties{
+									PrivateIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodStatic),
 									PrivateIPAddress:          ptr.To("10.2.0.4"),
-									Subnet:                    &network.Subnet{ID: ptr.To("subnetID")},
+									Subnet:                    &armnetwork.Subnet{ID: ptr.To("subnetID")},
 									Primary:                   ptr.To(true),
-									PrivateIPAddressVersion:   network.IPv4,
+									PrivateIPAddressVersion:   to.Ptr(armnetwork.IPVersionIPv4),
 								},
 							},
 						},
@@ -218,18 +219,18 @@ func TestReconcilePrivateLinkService(t *testing.T) {
 				},
 			},
 			expectedPLSList: true,
-			existingPLSList: []network.PrivateLinkService{
+			existingPLSList: []*armnetwork.PrivateLinkService{
 				{
 					Name: ptr.To("testpls"),
-					PrivateLinkServiceProperties: &network.PrivateLinkServiceProperties{
-						LoadBalancerFrontendIPConfigurations: &[]network.FrontendIPConfiguration{{ID: ptr.To("fipConfigID")}},
-						IPConfigurations: &[]network.PrivateLinkServiceIPConfiguration{
+					Properties: &armnetwork.PrivateLinkServiceProperties{
+						LoadBalancerFrontendIPConfigurations: []*armnetwork.FrontendIPConfiguration{{ID: ptr.To("fipConfigID")}},
+						IPConfigurations: []*armnetwork.PrivateLinkServiceIPConfiguration{
 							{
-								PrivateLinkServiceIPConfigurationProperties: &network.PrivateLinkServiceIPConfigurationProperties{
-									PrivateIPAllocationMethod: network.Dynamic,
-									Subnet:                    &network.Subnet{ID: ptr.To("subnetID")},
+								Properties: &armnetwork.PrivateLinkServiceIPConfigurationProperties{
+									PrivateIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodDynamic),
+									Subnet:                    &armnetwork.Subnet{ID: ptr.To("subnetID")},
 									Primary:                   ptr.To(true),
-									PrivateIPAddressVersion:   network.IPv4,
+									PrivateIPAddressVersion:   to.Ptr(armnetwork.IPVersionIPv4),
 								},
 							},
 						},
@@ -259,18 +260,18 @@ func TestReconcilePrivateLinkService(t *testing.T) {
 				},
 			},
 			expectedPLSList: true,
-			existingPLSList: []network.PrivateLinkService{
+			existingPLSList: []*armnetwork.PrivateLinkService{
 				{
 					Name: ptr.To("testpls"),
-					PrivateLinkServiceProperties: &network.PrivateLinkServiceProperties{
-						LoadBalancerFrontendIPConfigurations: &[]network.FrontendIPConfiguration{{ID: ptr.To("fipConfigID")}},
-						IPConfigurations: &[]network.PrivateLinkServiceIPConfiguration{
+					Properties: &armnetwork.PrivateLinkServiceProperties{
+						LoadBalancerFrontendIPConfigurations: []*armnetwork.FrontendIPConfiguration{{ID: ptr.To("fipConfigID")}},
+						IPConfigurations: []*armnetwork.PrivateLinkServiceIPConfiguration{
 							{
-								PrivateLinkServiceIPConfigurationProperties: &network.PrivateLinkServiceIPConfigurationProperties{
-									PrivateIPAllocationMethod: network.Dynamic,
-									Subnet:                    &network.Subnet{ID: ptr.To("subnetID")},
+								Properties: &armnetwork.PrivateLinkServiceIPConfigurationProperties{
+									PrivateIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodDynamic),
+									Subnet:                    &armnetwork.Subnet{ID: ptr.To("subnetID")},
 									Primary:                   ptr.To(true),
-									PrivateIPAddressVersion:   network.IPv4,
+									PrivateIPAddressVersion:   to.Ptr(armnetwork.IPVersionIPv4),
 								},
 							},
 						},
@@ -282,7 +283,7 @@ func TestReconcilePrivateLinkService(t *testing.T) {
 				},
 			},
 			expectedPLSCreate: true,
-			expectedPLS:       &network.PrivateLinkService{Name: ptr.To("testpls")},
+			expectedPLS:       &armnetwork.PrivateLinkService{Name: ptr.To("testpls")},
 		},
 		{
 			desc: "reconcilePrivateLinkService should not do anything if no existing PLS attached to the LB frontend when deleting",
@@ -292,11 +293,11 @@ func TestReconcilePrivateLinkService(t *testing.T) {
 				consts.ServiceAnnotationPLSName:              "testpls",
 			},
 			expectedPLSList: true,
-			existingPLSList: []network.PrivateLinkService{
+			existingPLSList: []*armnetwork.PrivateLinkService{
 				{
 					Name: ptr.To("testpls"),
-					PrivateLinkServiceProperties: &network.PrivateLinkServiceProperties{
-						LoadBalancerFrontendIPConfigurations: &[]network.FrontendIPConfiguration{{ID: ptr.To("fipConfigID2")}},
+					Properties: &armnetwork.PrivateLinkServiceProperties{
+						LoadBalancerFrontendIPConfigurations: []*armnetwork.FrontendIPConfiguration{{ID: ptr.To("fipConfigID2")}},
 					},
 				},
 			},
@@ -309,11 +310,11 @@ func TestReconcilePrivateLinkService(t *testing.T) {
 				consts.ServiceAnnotationPLSName:              "testpls",
 			},
 			expectedPLSList: true,
-			existingPLSList: []network.PrivateLinkService{
+			existingPLSList: []*armnetwork.PrivateLinkService{
 				{
 					Name: ptr.To("testpls"),
-					PrivateLinkServiceProperties: &network.PrivateLinkServiceProperties{
-						LoadBalancerFrontendIPConfigurations: &[]network.FrontendIPConfiguration{{ID: ptr.To("fipConfigID")}},
+					Properties: &armnetwork.PrivateLinkServiceProperties{
+						LoadBalancerFrontendIPConfigurations: []*armnetwork.FrontendIPConfiguration{{ID: ptr.To("fipConfigID")}},
 					},
 				},
 			},
@@ -340,21 +341,29 @@ func TestReconcilePrivateLinkService(t *testing.T) {
 			clusterName := testClusterName
 
 			mockSubnetsClient := az.SubnetsClient.(*mocksubnetclient.MockInterface)
-			mockPLSsClient := az.PrivateLinkServiceClient.(*mockprivatelinkserviceclient.MockInterface)
+			mockPLSRepo := az.plsRepo.(*privatelinkservice.MockRepository)
 			if test.expectedSubnetGet {
 				mockSubnetsClient.EXPECT().Get(gomock.Any(), "rg", "vnet", "subnet", "").Return(*test.existingSubnet, nil).MaxTimes(2)
 			}
 			if test.expectedPLSList {
-				mockPLSsClient.EXPECT().List(gomock.Any(), "rg").Return(test.existingPLSList, nil).MaxTimes(1)
+				mockPLSRepo.EXPECT().Get(gomock.Any(), "rg", *fipConfig.ID, gomock.Any()).DoAndReturn(func(_ context.Context, _, frontendIPConfigID string, _ cache.AzureCacheReadType) (*armnetwork.PrivateLinkService, error) {
+					for _, pls := range test.existingPLSList {
+						if strings.EqualFold(*pls.Properties.LoadBalancerFrontendIPConfigurations[0].ID, frontendIPConfigID) {
+							return pls, nil
+						}
+					}
+					return &armnetwork.PrivateLinkService{ID: to.Ptr(consts.PrivateLinkServiceNotExistID)}, nil
+				}).MaxTimes(1)
+				mockPLSRepo.EXPECT().List(gomock.Any(), "rg").Return(test.existingPLSList, nil).MaxTimes(1)
 			}
 			if test.expectedPLSCreate {
-				mockPLSsClient.EXPECT().CreateOrUpdate(gomock.Any(), "rg", ptr.Deref(test.expectedPLS.Name, ""), gomock.Any(), gomock.Any()).Return(nil).Times(1)
+				mockPLSRepo.EXPECT().CreateOrUpdate(gomock.Any(), "rg", gomock.Any()).Return(nil, nil).Times(1)
 			}
 			if test.expectedPLSDelete {
-				mockPLSsClient.EXPECT().Delete(gomock.Any(), "rg", "testpls").Return(nil).Times(1)
+				mockPLSRepo.EXPECT().Delete(gomock.Any(), "rg", "testpls", *fipConfig.ID).Return(nil).Times(1)
 			}
 			err := az.reconcilePrivateLinkService(context.TODO(), clusterName, &service, fipConfig, test.wantPLS)
-			assert.Equal(t, test.expectedError, err != nil)
+			assert.Equal(t, test.expectedError, err != nil, "error: %v", err)
 		})
 	}
 }
@@ -447,16 +456,16 @@ func TestSafeDeletePLS(t *testing.T) {
 
 	testCases := []struct {
 		desc          string
-		pls           *network.PrivateLinkService
+		pls           *armnetwork.PrivateLinkService
 		expectedError bool
 	}{
 		{
 			desc: "safeDeletePLS shall delete all PE connections and pls itself",
-			pls: &network.PrivateLinkService{
+			pls: &armnetwork.PrivateLinkService{
 				Name: ptr.To("testpls"),
-				PrivateLinkServiceProperties: &network.PrivateLinkServiceProperties{
-					LoadBalancerFrontendIPConfigurations: &[]network.FrontendIPConfiguration{{ID: ptr.To("FipConfigID")}},
-					PrivateEndpointConnections: &[]network.PrivateEndpointConnection{
+				Properties: &armnetwork.PrivateLinkServiceProperties{
+					LoadBalancerFrontendIPConfigurations: []*armnetwork.FrontendIPConfiguration{{ID: ptr.To("FipConfigID")}},
+					PrivateEndpointConnections: []*armnetwork.PrivateEndpointConnection{
 						{Name: ptr.To("pe1")},
 						{Name: ptr.To("pe2")},
 					},
@@ -467,12 +476,12 @@ func TestSafeDeletePLS(t *testing.T) {
 
 	for i, test := range testCases {
 		az := GetTestCloud(ctrl)
-		mockPLSsClient := az.PrivateLinkServiceClient.(*mockprivatelinkserviceclient.MockInterface)
-		mockPLSsClient.EXPECT().DeletePEConnection(gomock.Any(), "rg", "testpls", "pe1").Return(nil).Times(1)
-		mockPLSsClient.EXPECT().DeletePEConnection(gomock.Any(), "rg", "testpls", "pe2").Return(nil).Times(1)
-		mockPLSsClient.EXPECT().Delete(gomock.Any(), "rg", "testpls").Return(nil).Times(1)
+		mockPLSRepo := az.plsRepo.(*privatelinkservice.MockRepository)
+		mockPLSRepo.EXPECT().DeletePEConnection(gomock.Any(), "rg", "testpls", "pe1").Return(nil).Times(1)
+		mockPLSRepo.EXPECT().DeletePEConnection(gomock.Any(), "rg", "testpls", "pe2").Return(nil).Times(1)
+		mockPLSRepo.EXPECT().Delete(gomock.Any(), "rg", "testpls", gomock.Any()).Return(nil).Times(1)
 		service := getTestService("test1", v1.ProtocolTCP, nil, false, 80)
-		rerr := az.safeDeletePLS(test.pls, &service)
+		rerr := az.safeDeletePLS(context.Background(), test.pls, &service)
 		assert.Equal(t, test.expectedError, rerr != nil, "TestCase[%d]: %s", i, test.desc)
 	}
 }
@@ -485,14 +494,14 @@ func TestGetPrivateLinkServiceName(t *testing.T) {
 	tests := []struct {
 		desc         string
 		annotations  map[string]string
-		pls          *network.PrivateLinkService
+		pls          *armnetwork.PrivateLinkService
 		fipConfig    *network.FrontendIPConfiguration
 		expectedName string
 		expectedErr  bool
 	}{
 		{
 			desc: "If pls name does not set, sets it as what service configures",
-			pls:  &network.PrivateLinkService{},
+			pls:  &armnetwork.PrivateLinkService{},
 			annotations: map[string]string{
 				consts.ServiceAnnotationPLSName: "testpls",
 			},
@@ -500,7 +509,7 @@ func TestGetPrivateLinkServiceName(t *testing.T) {
 		},
 		{
 			desc: "If pls name does not set, and service does not configure, sets it as default(pls-fipConfigName)",
-			pls:  &network.PrivateLinkService{},
+			pls:  &armnetwork.PrivateLinkService{},
 			fipConfig: &network.FrontendIPConfiguration{
 				Name: ptr.To("fipname"),
 			},
@@ -508,7 +517,7 @@ func TestGetPrivateLinkServiceName(t *testing.T) {
 		},
 		{
 			desc: "If pls name is not equal to service configuration, error should be reported",
-			pls: &network.PrivateLinkService{
+			pls: &armnetwork.PrivateLinkService{
 				Name: ptr.To("testpls"),
 			},
 			annotations: map[string]string{
@@ -518,7 +527,7 @@ func TestGetPrivateLinkServiceName(t *testing.T) {
 		},
 		{
 			desc: "If pls name is same as service configuration, simply return it",
-			pls: &network.PrivateLinkService{
+			pls: &armnetwork.PrivateLinkService{
 				Name: ptr.To("testpls"),
 			},
 			annotations: map[string]string{
@@ -564,7 +573,7 @@ func TestGetExpectedPrivateLinkService(t *testing.T) {
 		plsName := "testPLS"
 		clusterName := testClusterName
 		fipConfig := &network.FrontendIPConfiguration{ID: ptr.To("fipConfigID")}
-		pls := &network.PrivateLinkService{PrivateLinkServiceProperties: &network.PrivateLinkServiceProperties{}}
+		pls := &armnetwork.PrivateLinkService{Properties: &armnetwork.PrivateLinkServiceProperties{}}
 		subnetClient := cloud.SubnetsClient.(*mocksubnetclient.MockInterface)
 		subnetClient.EXPECT().Get(gomock.Any(), "rg", "vnet", "subnet", gomock.Any()).Return(
 			network.Subnet{
@@ -578,53 +587,53 @@ func TestGetExpectedPrivateLinkService(t *testing.T) {
 
 		assert.Equal(t, pls.Name, &plsName)
 
-		expectedLBFrontendConfig := &[]network.FrontendIPConfiguration{{ID: fipConfig.ID}}
-		assert.Equal(t, pls.LoadBalancerFrontendIPConfigurations, expectedLBFrontendConfig)
+		expectedLBFrontendConfig := []*armnetwork.FrontendIPConfiguration{{ID: fipConfig.ID}}
+		assert.Equal(t, pls.Properties.LoadBalancerFrontendIPConfigurations, expectedLBFrontendConfig)
 
-		expectedConfigs := []network.PrivateLinkServiceIPConfiguration{
+		expectedConfigs := []*armnetwork.PrivateLinkServiceIPConfiguration{
 			{
 				Name: ptr.To("subnet-testPLS-static-10.2.0.4"),
-				PrivateLinkServiceIPConfigurationProperties: &network.PrivateLinkServiceIPConfigurationProperties{
-					PrivateIPAllocationMethod: network.Static,
+				Properties: &armnetwork.PrivateLinkServiceIPConfigurationProperties{
+					PrivateIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodStatic),
 					PrivateIPAddress:          ptr.To("10.2.0.4"),
-					Subnet:                    &network.Subnet{ID: ptr.To("subnetID")},
+					Subnet:                    &armnetwork.Subnet{ID: ptr.To("subnetID")},
 					Primary:                   ptr.To(true),
-					PrivateIPAddressVersion:   network.IPv4,
+					PrivateIPAddressVersion:   to.Ptr(armnetwork.IPVersionIPv4),
 				},
 			},
 			{
 				Name: ptr.To("subnet-testPLS-static-10.2.0.5"),
-				PrivateLinkServiceIPConfigurationProperties: &network.PrivateLinkServiceIPConfigurationProperties{
-					PrivateIPAllocationMethod: network.Static,
+				Properties: &armnetwork.PrivateLinkServiceIPConfigurationProperties{
+					PrivateIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodStatic),
 					PrivateIPAddress:          ptr.To("10.2.0.5"),
-					Subnet:                    &network.Subnet{ID: ptr.To("subnetID")},
+					Subnet:                    &armnetwork.Subnet{ID: ptr.To("subnetID")},
 					Primary:                   ptr.To(false),
-					PrivateIPAddressVersion:   network.IPv4,
+					PrivateIPAddressVersion:   to.Ptr(armnetwork.IPVersionIPv4),
 				},
 			},
 			{
 				Name: ptr.To("subnet-testPLS-dynamic-0"),
-				PrivateLinkServiceIPConfigurationProperties: &network.PrivateLinkServiceIPConfigurationProperties{
-					PrivateIPAllocationMethod: network.Dynamic,
-					Subnet:                    &network.Subnet{ID: ptr.To("subnetID")},
+				Properties: &armnetwork.PrivateLinkServiceIPConfigurationProperties{
+					PrivateIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodDynamic),
+					Subnet:                    &armnetwork.Subnet{ID: ptr.To("subnetID")},
 					Primary:                   ptr.To(false),
-					PrivateIPAddressVersion:   network.IPv4,
+					PrivateIPAddressVersion:   to.Ptr(armnetwork.IPVersionIPv4),
 				},
 			},
 		}
 
-		testSamePLSIpConfigs(t, *pls.IPConfigurations, expectedConfigs)
+		testSamePLSIpConfigs(t, pls.Properties.IPConfigurations, expectedConfigs)
 
-		expectedFqdns := []string{"fqdns1", "fqdns2", "fqdns3"}
-		assert.Equal(t, *pls.Fqdns, expectedFqdns)
+		expectedFqdns := to.SliceOfPtrs("fqdns1", "fqdns2", "fqdns3")
+		assert.Equal(t, expectedFqdns, pls.Properties.Fqdns)
 
-		assert.True(t, *pls.EnableProxyProtocol)
+		assert.True(t, *pls.Properties.EnableProxyProtocol)
 
-		expectedVisibility := []string{"*"}
-		assert.Equal(t, *pls.Visibility.Subscriptions, expectedVisibility)
+		expectedVisibility := to.SliceOfPtrs("*")
+		assert.Equal(t, expectedVisibility, pls.Properties.Visibility.Subscriptions)
 
-		expectedAutoApproval := []string{"sub1", "sub2", "sub3"}
-		assert.Equal(t, *pls.AutoApproval.Subscriptions, expectedAutoApproval)
+		expectedAutoApproval := to.SliceOfPtrs("sub1", "sub2", "sub3")
+		assert.Equal(t, expectedAutoApproval, pls.Properties.AutoApproval.Subscriptions)
 	})
 }
 
@@ -636,8 +645,8 @@ func TestReconcilePLSIpConfigs(t *testing.T) {
 		desc              string
 		annotations       map[string]string
 		plsName           string
-		existingIPConfigs *[]network.PrivateLinkServiceIPConfiguration
-		expectedIPConfigs *[]network.PrivateLinkServiceIPConfiguration
+		existingIPConfigs []*armnetwork.PrivateLinkServiceIPConfiguration
+		expectedIPConfigs []*armnetwork.PrivateLinkServiceIPConfiguration
 		getSubnetError    *retry.Error
 		expectedChanged   bool
 		expectedErr       bool
@@ -664,14 +673,14 @@ func TestReconcilePLSIpConfigs(t *testing.T) {
 		{
 			desc:    "reconcilePLSIpConfigs should change existingPLS if its ipConfig is nil",
 			plsName: "testpls",
-			expectedIPConfigs: &[]network.PrivateLinkServiceIPConfiguration{
+			expectedIPConfigs: []*armnetwork.PrivateLinkServiceIPConfiguration{
 				{
 					Name: ptr.To("subnet-testpls-dynamic-0"),
-					PrivateLinkServiceIPConfigurationProperties: &network.PrivateLinkServiceIPConfigurationProperties{
-						PrivateIPAllocationMethod: network.Dynamic,
-						Subnet:                    &network.Subnet{ID: ptr.To("subnetID")},
+					Properties: &armnetwork.PrivateLinkServiceIPConfigurationProperties{
+						PrivateIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodDynamic),
+						Subnet:                    &armnetwork.Subnet{ID: ptr.To("subnetID")},
 						Primary:                   ptr.To(true),
-						PrivateIPAddressVersion:   network.IPv4,
+						PrivateIPAddressVersion:   to.Ptr(armnetwork.IPVersionIPv4),
 					},
 				},
 			},
@@ -683,34 +692,34 @@ func TestReconcilePLSIpConfigs(t *testing.T) {
 			annotations: map[string]string{
 				consts.ServiceAnnotationPLSIpConfigurationIPAddressCount: "2",
 			},
-			existingIPConfigs: &[]network.PrivateLinkServiceIPConfiguration{
+			existingIPConfigs: []*armnetwork.PrivateLinkServiceIPConfiguration{
 				{
 					Name: ptr.To("subnet-testpls-dynamic-0"),
-					PrivateLinkServiceIPConfigurationProperties: &network.PrivateLinkServiceIPConfigurationProperties{
-						PrivateIPAllocationMethod: network.Dynamic,
-						Subnet:                    &network.Subnet{ID: ptr.To("subnetID")},
+					Properties: &armnetwork.PrivateLinkServiceIPConfigurationProperties{
+						PrivateIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodDynamic),
+						Subnet:                    &armnetwork.Subnet{ID: ptr.To("subnetID")},
 						Primary:                   ptr.To(true),
-						PrivateIPAddressVersion:   network.IPv4,
+						PrivateIPAddressVersion:   to.Ptr(armnetwork.IPVersionIPv4),
 					},
 				},
 			},
-			expectedIPConfigs: &[]network.PrivateLinkServiceIPConfiguration{
+			expectedIPConfigs: []*armnetwork.PrivateLinkServiceIPConfiguration{
 				{
 					Name: ptr.To("subnet-testpls-dynamic-0"),
-					PrivateLinkServiceIPConfigurationProperties: &network.PrivateLinkServiceIPConfigurationProperties{
-						PrivateIPAllocationMethod: network.Dynamic,
-						Subnet:                    &network.Subnet{ID: ptr.To("subnetID")},
+					Properties: &armnetwork.PrivateLinkServiceIPConfigurationProperties{
+						PrivateIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodDynamic),
+						Subnet:                    &armnetwork.Subnet{ID: ptr.To("subnetID")},
 						Primary:                   ptr.To(true),
-						PrivateIPAddressVersion:   network.IPv4,
+						PrivateIPAddressVersion:   to.Ptr(armnetwork.IPVersionIPv4),
 					},
 				},
 				{
 					Name: ptr.To("subnet-testpls-dynamic-1"),
-					PrivateLinkServiceIPConfigurationProperties: &network.PrivateLinkServiceIPConfigurationProperties{
-						PrivateIPAllocationMethod: network.Dynamic,
-						Subnet:                    &network.Subnet{ID: ptr.To("subnetID")},
+					Properties: &armnetwork.PrivateLinkServiceIPConfigurationProperties{
+						PrivateIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodDynamic),
+						Subnet:                    &armnetwork.Subnet{ID: ptr.To("subnetID")},
 						Primary:                   ptr.To(false),
-						PrivateIPAddressVersion:   network.IPv4,
+						PrivateIPAddressVersion:   to.Ptr(armnetwork.IPVersionIPv4),
 					},
 				},
 			},
@@ -719,25 +728,25 @@ func TestReconcilePLSIpConfigs(t *testing.T) {
 		{
 			desc:    "reconcilePLSIpConfigs should change existingPLS if its subnetID is different",
 			plsName: "testpls",
-			existingIPConfigs: &[]network.PrivateLinkServiceIPConfiguration{
+			existingIPConfigs: []*armnetwork.PrivateLinkServiceIPConfiguration{
 				{
 					Name: ptr.To("subnet-testpls-dynamic-0"),
-					PrivateLinkServiceIPConfigurationProperties: &network.PrivateLinkServiceIPConfigurationProperties{
-						PrivateIPAllocationMethod: network.Dynamic,
-						Subnet:                    &network.Subnet{ID: ptr.To("subnetID1")},
+					Properties: &armnetwork.PrivateLinkServiceIPConfigurationProperties{
+						PrivateIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodDynamic),
+						Subnet:                    &armnetwork.Subnet{ID: ptr.To("subnetID1")},
 						Primary:                   ptr.To(true),
-						PrivateIPAddressVersion:   network.IPv4,
+						PrivateIPAddressVersion:   to.Ptr(armnetwork.IPVersionIPv4),
 					},
 				},
 			},
-			expectedIPConfigs: &[]network.PrivateLinkServiceIPConfiguration{
+			expectedIPConfigs: []*armnetwork.PrivateLinkServiceIPConfiguration{
 				{
 					Name: ptr.To("subnet-testpls-dynamic-0"),
-					PrivateLinkServiceIPConfigurationProperties: &network.PrivateLinkServiceIPConfigurationProperties{
-						PrivateIPAllocationMethod: network.Dynamic,
-						Subnet:                    &network.Subnet{ID: ptr.To("subnetID")},
+					Properties: &armnetwork.PrivateLinkServiceIPConfigurationProperties{
+						PrivateIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodDynamic),
+						Subnet:                    &armnetwork.Subnet{ID: ptr.To("subnetID")},
 						Primary:                   ptr.To(true),
-						PrivateIPAddressVersion:   network.IPv4,
+						PrivateIPAddressVersion:   to.Ptr(armnetwork.IPVersionIPv4),
 					},
 				},
 			},
@@ -750,35 +759,35 @@ func TestReconcilePLSIpConfigs(t *testing.T) {
 				consts.ServiceAnnotationPLSIpConfigurationIPAddressCount: "2",
 				consts.ServiceAnnotationPLSIpConfigurationIPAddress:      "10.2.0.4",
 			},
-			existingIPConfigs: &[]network.PrivateLinkServiceIPConfiguration{
+			existingIPConfigs: []*armnetwork.PrivateLinkServiceIPConfiguration{
 				{
 					Name: ptr.To("subnet-testpls-dynamic-0"),
-					PrivateLinkServiceIPConfigurationProperties: &network.PrivateLinkServiceIPConfigurationProperties{
-						PrivateIPAllocationMethod: network.Dynamic,
-						Subnet:                    &network.Subnet{ID: ptr.To("subnetID")},
+					Properties: &armnetwork.PrivateLinkServiceIPConfigurationProperties{
+						PrivateIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodDynamic),
+						Subnet:                    &armnetwork.Subnet{ID: ptr.To("subnetID")},
 						Primary:                   ptr.To(true),
-						PrivateIPAddressVersion:   network.IPv4,
+						PrivateIPAddressVersion:   to.Ptr(armnetwork.IPVersionIPv4),
 					},
 				},
 			},
-			expectedIPConfigs: &[]network.PrivateLinkServiceIPConfiguration{
+			expectedIPConfigs: []*armnetwork.PrivateLinkServiceIPConfiguration{
 				{
 					Name: ptr.To("subnet-testpls-static-10.2.0.4"),
-					PrivateLinkServiceIPConfigurationProperties: &network.PrivateLinkServiceIPConfigurationProperties{
-						PrivateIPAllocationMethod: network.Static,
+					Properties: &armnetwork.PrivateLinkServiceIPConfigurationProperties{
+						PrivateIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodStatic),
 						PrivateIPAddress:          ptr.To("10.2.0.4"),
-						Subnet:                    &network.Subnet{ID: ptr.To("subnetID")},
+						Subnet:                    &armnetwork.Subnet{ID: ptr.To("subnetID")},
 						Primary:                   ptr.To(true),
-						PrivateIPAddressVersion:   network.IPv4,
+						PrivateIPAddressVersion:   to.Ptr(armnetwork.IPVersionIPv4),
 					},
 				},
 				{
 					Name: ptr.To("subnet-testpls-dynamic-0"),
-					PrivateLinkServiceIPConfigurationProperties: &network.PrivateLinkServiceIPConfigurationProperties{
-						PrivateIPAllocationMethod: network.Dynamic,
-						Subnet:                    &network.Subnet{ID: ptr.To("subnetID")},
+					Properties: &armnetwork.PrivateLinkServiceIPConfigurationProperties{
+						PrivateIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodDynamic),
+						Subnet:                    &armnetwork.Subnet{ID: ptr.To("subnetID")},
 						Primary:                   ptr.To(false),
-						PrivateIPAddressVersion:   network.IPv4,
+						PrivateIPAddressVersion:   to.Ptr(armnetwork.IPVersionIPv4),
 					},
 				},
 			},
@@ -787,26 +796,26 @@ func TestReconcilePLSIpConfigs(t *testing.T) {
 		{
 			desc:    "reconcilePLSIpConfigs should change existingPLS if ip allocation type is changed from static to dynamic",
 			plsName: "testpls",
-			existingIPConfigs: &[]network.PrivateLinkServiceIPConfiguration{
+			existingIPConfigs: []*armnetwork.PrivateLinkServiceIPConfiguration{
 				{
 					Name: ptr.To("subnet-testpls-static-10.2.0.4"),
-					PrivateLinkServiceIPConfigurationProperties: &network.PrivateLinkServiceIPConfigurationProperties{
-						PrivateIPAllocationMethod: network.Static,
+					Properties: &armnetwork.PrivateLinkServiceIPConfigurationProperties{
+						PrivateIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodStatic),
+						Subnet:                    &armnetwork.Subnet{ID: ptr.To("subnetID")},
 						PrivateIPAddress:          ptr.To("10.2.0.4"),
-						Subnet:                    &network.Subnet{ID: ptr.To("subnetID")},
 						Primary:                   ptr.To(true),
-						PrivateIPAddressVersion:   network.IPv4,
+						PrivateIPAddressVersion:   to.Ptr(armnetwork.IPVersionIPv4),
 					},
 				},
 			},
-			expectedIPConfigs: &[]network.PrivateLinkServiceIPConfiguration{
+			expectedIPConfigs: []*armnetwork.PrivateLinkServiceIPConfiguration{
 				{
 					Name: ptr.To("subnet-testpls-dynamic-0"),
-					PrivateLinkServiceIPConfigurationProperties: &network.PrivateLinkServiceIPConfigurationProperties{
-						PrivateIPAllocationMethod: network.Dynamic,
-						Subnet:                    &network.Subnet{ID: ptr.To("subnetID")},
+					Properties: &armnetwork.PrivateLinkServiceIPConfigurationProperties{
+						PrivateIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodDynamic),
+						Subnet:                    &armnetwork.Subnet{ID: ptr.To("subnetID")},
 						Primary:                   ptr.To(true),
-						PrivateIPAddressVersion:   network.IPv4,
+						PrivateIPAddressVersion:   to.Ptr(armnetwork.IPVersionIPv4),
 					},
 				},
 			},
@@ -818,27 +827,27 @@ func TestReconcilePLSIpConfigs(t *testing.T) {
 			annotations: map[string]string{
 				consts.ServiceAnnotationPLSIpConfigurationIPAddress: "10.2.0.5",
 			},
-			existingIPConfigs: &[]network.PrivateLinkServiceIPConfiguration{
+			existingIPConfigs: []*armnetwork.PrivateLinkServiceIPConfiguration{
 				{
 					Name: ptr.To("subnet-testpls-static-10.2.0.4"),
-					PrivateLinkServiceIPConfigurationProperties: &network.PrivateLinkServiceIPConfigurationProperties{
-						PrivateIPAllocationMethod: network.Static,
+					Properties: &armnetwork.PrivateLinkServiceIPConfigurationProperties{
+						PrivateIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodStatic),
 						PrivateIPAddress:          ptr.To("10.2.0.4"),
-						Subnet:                    &network.Subnet{ID: ptr.To("subnetID")},
+						Subnet:                    &armnetwork.Subnet{ID: ptr.To("subnetID")},
 						Primary:                   ptr.To(true),
-						PrivateIPAddressVersion:   network.IPv4,
+						PrivateIPAddressVersion:   to.Ptr(armnetwork.IPVersionIPv4),
 					},
 				},
 			},
-			expectedIPConfigs: &[]network.PrivateLinkServiceIPConfiguration{
+			expectedIPConfigs: []*armnetwork.PrivateLinkServiceIPConfiguration{
 				{
 					Name: ptr.To("subnet-testpls-static-10.2.0.5"),
-					PrivateLinkServiceIPConfigurationProperties: &network.PrivateLinkServiceIPConfigurationProperties{
-						PrivateIPAllocationMethod: network.Static,
+					Properties: &armnetwork.PrivateLinkServiceIPConfigurationProperties{
+						PrivateIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodStatic),
 						PrivateIPAddress:          ptr.To("10.2.0.5"),
-						Subnet:                    &network.Subnet{ID: ptr.To("subnetID")},
+						Subnet:                    &armnetwork.Subnet{ID: ptr.To("subnetID")},
 						Primary:                   ptr.To(true),
-						PrivateIPAddressVersion:   network.IPv4,
+						PrivateIPAddressVersion:   to.Ptr(armnetwork.IPVersionIPv4),
 					},
 				},
 			},
@@ -847,25 +856,25 @@ func TestReconcilePLSIpConfigs(t *testing.T) {
 		{
 			desc:    "reconcilePLSIpConfigs should not change existingPLS if ip allocation type is dynamic only",
 			plsName: "testpls",
-			existingIPConfigs: &[]network.PrivateLinkServiceIPConfiguration{
+			existingIPConfigs: []*armnetwork.PrivateLinkServiceIPConfiguration{
 				{
 					Name: ptr.To("subnet-testpls-dynamic-0"),
-					PrivateLinkServiceIPConfigurationProperties: &network.PrivateLinkServiceIPConfigurationProperties{
-						PrivateIPAllocationMethod: network.Dynamic,
-						Subnet:                    &network.Subnet{ID: ptr.To("subnetID")},
+					Properties: &armnetwork.PrivateLinkServiceIPConfigurationProperties{
+						PrivateIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodDynamic),
+						Subnet:                    &armnetwork.Subnet{ID: ptr.To("subnetID")},
 						Primary:                   ptr.To(true),
-						PrivateIPAddressVersion:   network.IPv4,
+						PrivateIPAddressVersion:   to.Ptr(armnetwork.IPVersionIPv4),
 					},
 				},
 			},
-			expectedIPConfigs: &[]network.PrivateLinkServiceIPConfiguration{
+			expectedIPConfigs: []*armnetwork.PrivateLinkServiceIPConfiguration{
 				{
 					Name: ptr.To("subnet-testpls-dynamic-0"),
-					PrivateLinkServiceIPConfigurationProperties: &network.PrivateLinkServiceIPConfigurationProperties{
-						PrivateIPAllocationMethod: network.Dynamic,
-						Subnet:                    &network.Subnet{ID: ptr.To("subnetID")},
+					Properties: &armnetwork.PrivateLinkServiceIPConfigurationProperties{
+						PrivateIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodDynamic),
+						Subnet:                    &armnetwork.Subnet{ID: ptr.To("subnetID")},
 						Primary:                   ptr.To(true),
-						PrivateIPAddressVersion:   network.IPv4,
+						PrivateIPAddressVersion:   to.Ptr(armnetwork.IPVersionIPv4),
 					},
 				},
 			},
@@ -876,27 +885,27 @@ func TestReconcilePLSIpConfigs(t *testing.T) {
 			annotations: map[string]string{
 				consts.ServiceAnnotationPLSIpConfigurationIPAddress: "10.2.0.5",
 			},
-			existingIPConfigs: &[]network.PrivateLinkServiceIPConfiguration{
+			existingIPConfigs: []*armnetwork.PrivateLinkServiceIPConfiguration{
 				{
 					Name: ptr.To("subnet-testpls-static-10.2.0.5"),
-					PrivateLinkServiceIPConfigurationProperties: &network.PrivateLinkServiceIPConfigurationProperties{
-						PrivateIPAllocationMethod: network.Static,
+					Properties: &armnetwork.PrivateLinkServiceIPConfigurationProperties{
+						PrivateIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodStatic),
 						PrivateIPAddress:          ptr.To("10.2.0.5"),
-						Subnet:                    &network.Subnet{ID: ptr.To("subnetID")},
+						Subnet:                    &armnetwork.Subnet{ID: ptr.To("subnetID")},
 						Primary:                   ptr.To(true),
-						PrivateIPAddressVersion:   network.IPv4,
+						PrivateIPAddressVersion:   to.Ptr(armnetwork.IPVersionIPv4),
 					},
 				},
 			},
-			expectedIPConfigs: &[]network.PrivateLinkServiceIPConfiguration{
+			expectedIPConfigs: []*armnetwork.PrivateLinkServiceIPConfiguration{
 				{
 					Name: ptr.To("subnet-testpls-static-10.2.0.5"),
-					PrivateLinkServiceIPConfigurationProperties: &network.PrivateLinkServiceIPConfigurationProperties{
-						PrivateIPAllocationMethod: network.Static,
+					Properties: &armnetwork.PrivateLinkServiceIPConfigurationProperties{
+						PrivateIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodStatic),
 						PrivateIPAddress:          ptr.To("10.2.0.5"),
-						Subnet:                    &network.Subnet{ID: ptr.To("subnetID")},
+						Subnet:                    &armnetwork.Subnet{ID: ptr.To("subnetID")},
 						Primary:                   ptr.To(true),
-						PrivateIPAddressVersion:   network.IPv4,
+						PrivateIPAddressVersion:   to.Ptr(armnetwork.IPVersionIPv4),
 					},
 				},
 			},
@@ -908,25 +917,25 @@ func TestReconcilePLSIpConfigs(t *testing.T) {
 				consts.ServiceAnnotationPLSIpConfigurationIPAddress:      "10.2.0.5",
 				consts.ServiceAnnotationPLSIpConfigurationIPAddressCount: "2",
 			},
-			existingIPConfigs: &[]network.PrivateLinkServiceIPConfiguration{},
-			expectedIPConfigs: &[]network.PrivateLinkServiceIPConfiguration{
+			existingIPConfigs: []*armnetwork.PrivateLinkServiceIPConfiguration{},
+			expectedIPConfigs: []*armnetwork.PrivateLinkServiceIPConfiguration{
 				{
 					Name: ptr.To("subnet-" + strings.Repeat("12345678", 7) + "1" + "-static-10.2.0.5"),
-					PrivateLinkServiceIPConfigurationProperties: &network.PrivateLinkServiceIPConfigurationProperties{
-						PrivateIPAllocationMethod: network.Static,
+					Properties: &armnetwork.PrivateLinkServiceIPConfigurationProperties{
+						PrivateIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodStatic),
 						PrivateIPAddress:          ptr.To("10.2.0.5"),
-						Subnet:                    &network.Subnet{ID: ptr.To("subnetID")},
+						Subnet:                    &armnetwork.Subnet{ID: ptr.To("subnetID")},
 						Primary:                   ptr.To(true),
-						PrivateIPAddressVersion:   network.IPv4,
+						PrivateIPAddressVersion:   to.Ptr(armnetwork.IPVersionIPv4),
 					},
 				},
 				{
 					Name: ptr.To("subnet-" + strings.Repeat("12345678", 7) + "1234567" + "-dynamic-0"),
-					PrivateLinkServiceIPConfigurationProperties: &network.PrivateLinkServiceIPConfigurationProperties{
-						PrivateIPAllocationMethod: network.Dynamic,
-						Subnet:                    &network.Subnet{ID: ptr.To("subnetID")},
+					Properties: &armnetwork.PrivateLinkServiceIPConfigurationProperties{
+						PrivateIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodDynamic),
+						Subnet:                    &armnetwork.Subnet{ID: ptr.To("subnetID")},
 						Primary:                   ptr.To(false),
-						PrivateIPAddressVersion:   network.IPv4,
+						PrivateIPAddressVersion:   to.Ptr(armnetwork.IPVersionIPv4),
 					},
 				},
 			},
@@ -939,45 +948,45 @@ func TestReconcilePLSIpConfigs(t *testing.T) {
 				consts.ServiceAnnotationPLSIpConfigurationIPAddress:      "10.2.0.5",
 				consts.ServiceAnnotationPLSIpConfigurationIPAddressCount: "2",
 			},
-			existingIPConfigs: &[]network.PrivateLinkServiceIPConfiguration{
+			existingIPConfigs: []*armnetwork.PrivateLinkServiceIPConfiguration{
 				{
 					Name: ptr.To("subnet-" + strings.Repeat("12345678", 7) + "1" + "-static-10.2.0.5"),
-					PrivateLinkServiceIPConfigurationProperties: &network.PrivateLinkServiceIPConfigurationProperties{
-						PrivateIPAllocationMethod: network.Static,
+					Properties: &armnetwork.PrivateLinkServiceIPConfigurationProperties{
+						PrivateIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodStatic),
 						PrivateIPAddress:          ptr.To("10.2.0.5"),
-						Subnet:                    &network.Subnet{ID: ptr.To("subnetID")},
+						Subnet:                    &armnetwork.Subnet{ID: ptr.To("subnetID")},
 						Primary:                   ptr.To(true),
-						PrivateIPAddressVersion:   network.IPv4,
+						PrivateIPAddressVersion:   to.Ptr(armnetwork.IPVersionIPv4),
 					},
 				},
 				{
 					Name: ptr.To("subnet-" + strings.Repeat("12345678", 7) + "1234567" + "-dynamic-0"),
-					PrivateLinkServiceIPConfigurationProperties: &network.PrivateLinkServiceIPConfigurationProperties{
-						PrivateIPAllocationMethod: network.Dynamic,
-						Subnet:                    &network.Subnet{ID: ptr.To("subnetID")},
+					Properties: &armnetwork.PrivateLinkServiceIPConfigurationProperties{
+						PrivateIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodDynamic),
+						Subnet:                    &armnetwork.Subnet{ID: ptr.To("subnetID")},
 						Primary:                   ptr.To(false),
-						PrivateIPAddressVersion:   network.IPv4,
+						PrivateIPAddressVersion:   to.Ptr(armnetwork.IPVersionIPv4),
 					},
 				},
 			},
-			expectedIPConfigs: &[]network.PrivateLinkServiceIPConfiguration{
+			expectedIPConfigs: []*armnetwork.PrivateLinkServiceIPConfiguration{
 				{
 					Name: ptr.To("subnet-" + strings.Repeat("12345678", 7) + "1" + "-static-10.2.0.5"),
-					PrivateLinkServiceIPConfigurationProperties: &network.PrivateLinkServiceIPConfigurationProperties{
-						PrivateIPAllocationMethod: network.Static,
+					Properties: &armnetwork.PrivateLinkServiceIPConfigurationProperties{
+						PrivateIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodStatic),
 						PrivateIPAddress:          ptr.To("10.2.0.5"),
-						Subnet:                    &network.Subnet{ID: ptr.To("subnetID")},
+						Subnet:                    &armnetwork.Subnet{ID: ptr.To("subnetID")},
 						Primary:                   ptr.To(true),
-						PrivateIPAddressVersion:   network.IPv4,
+						PrivateIPAddressVersion:   to.Ptr(armnetwork.IPVersionIPv4),
 					},
 				},
 				{
 					Name: ptr.To("subnet-" + strings.Repeat("12345678", 7) + "1234567" + "-dynamic-0"),
-					PrivateLinkServiceIPConfigurationProperties: &network.PrivateLinkServiceIPConfigurationProperties{
-						PrivateIPAllocationMethod: network.Dynamic,
-						Subnet:                    &network.Subnet{ID: ptr.To("subnetID")},
+					Properties: &armnetwork.PrivateLinkServiceIPConfigurationProperties{
+						PrivateIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodDynamic),
+						Subnet:                    &armnetwork.Subnet{ID: ptr.To("subnetID")},
 						Primary:                   ptr.To(false),
-						PrivateIPAddressVersion:   network.IPv4,
+						PrivateIPAddressVersion:   to.Ptr(armnetwork.IPVersionIPv4),
 					},
 				},
 			},
@@ -991,9 +1000,9 @@ func TestReconcilePLSIpConfigs(t *testing.T) {
 				Annotations: test.annotations,
 			},
 		}
-		pls := &network.PrivateLinkService{
+		pls := &armnetwork.PrivateLinkService{
 			Name: ptr.To(test.plsName),
-			PrivateLinkServiceProperties: &network.PrivateLinkServiceProperties{
+			Properties: &armnetwork.PrivateLinkServiceProperties{
 				IPConfigurations: test.existingIPConfigs,
 			},
 		}
@@ -1009,21 +1018,30 @@ func TestReconcilePLSIpConfigs(t *testing.T) {
 			assert.Error(t, err, "TestCase[%d]: %s", i, test.desc)
 		} else {
 			assert.Equal(t, test.expectedChanged, changed, "TestCase[%d]: %s", i, test.desc)
-			testSamePLSIpConfigs(t, *pls.IPConfigurations, *test.expectedIPConfigs)
+			testSamePLSIpConfigs(t, pls.Properties.IPConfigurations, test.expectedIPConfigs)
 		}
 	}
 }
 
-func testSamePLSIpConfigs(t *testing.T, actual []network.PrivateLinkServiceIPConfiguration, expected []network.PrivateLinkServiceIPConfiguration) {
-	actualIPConfigs := make(map[string]network.PrivateLinkServiceIPConfiguration)
-	expectedIPConfigs := make(map[string]network.PrivateLinkServiceIPConfiguration)
+func testSamePLSIpConfigs(t *testing.T, actual []*armnetwork.PrivateLinkServiceIPConfiguration, expected []*armnetwork.PrivateLinkServiceIPConfiguration) {
+	t.Helper()
+	actualIPConfigs := make(map[string]armnetwork.PrivateLinkServiceIPConfiguration)
+	expectedIPConfigs := make(map[string]armnetwork.PrivateLinkServiceIPConfiguration)
 	for _, ipConfig := range actual {
-		actualIPConfigs[ptr.Deref(ipConfig.Name, "")] = ipConfig
+		actualIPConfigs[ptr.Deref(ipConfig.Name, "")] = *ipConfig
 	}
 	for _, ipConfig := range expected {
-		expectedIPConfigs[ptr.Deref(ipConfig.Name, "")] = ipConfig
+		expectedIPConfigs[ptr.Deref(ipConfig.Name, "")] = *ipConfig
 	}
-	assert.Equal(t, actualIPConfigs, expectedIPConfigs)
+
+	for name, expectedIPConfig := range expectedIPConfigs {
+		actualIPConfig, ok := actualIPConfigs[name]
+		assert.True(t, ok, "Expected IPConfig %s not found", name)
+		assert.Equal(t, expectedIPConfig, actualIPConfig, "IPConfig %s not equal", name)
+		delete(actualIPConfigs, name)
+	}
+
+	assert.Empty(t, actualIPConfigs)
 }
 
 func TestServiceRequiresPLS(t *testing.T) {
@@ -1065,15 +1083,15 @@ func TestReconcilePLSEnableProxyProtocol(t *testing.T) {
 	tests := []struct {
 		desc            string
 		annotations     map[string]string
-		pls             *network.PrivateLinkService
+		pls             *armnetwork.PrivateLinkService
 		expectedChanged bool
 		expectedEnabled *bool
 	}{
 		{
 			desc:        "empty service enableProxyProto and empty pls enableProxyProto should not trigger any change",
 			annotations: map[string]string{},
-			pls: &network.PrivateLinkService{
-				PrivateLinkServiceProperties: &network.PrivateLinkServiceProperties{},
+			pls: &armnetwork.PrivateLinkService{
+				Properties: &armnetwork.PrivateLinkServiceProperties{},
 			},
 		},
 		{
@@ -1081,8 +1099,8 @@ func TestReconcilePLSEnableProxyProtocol(t *testing.T) {
 			annotations: map[string]string{
 				consts.ServiceAnnotationPLSProxyProtocol: "False",
 			},
-			pls: &network.PrivateLinkService{
-				PrivateLinkServiceProperties: &network.PrivateLinkServiceProperties{},
+			pls: &armnetwork.PrivateLinkService{
+				Properties: &armnetwork.PrivateLinkServiceProperties{},
 			},
 		},
 		{
@@ -1090,8 +1108,8 @@ func TestReconcilePLSEnableProxyProtocol(t *testing.T) {
 			annotations: map[string]string{
 				consts.ServiceAnnotationPLSProxyProtocol: "False",
 			},
-			pls: &network.PrivateLinkService{
-				PrivateLinkServiceProperties: &network.PrivateLinkServiceProperties{
+			pls: &armnetwork.PrivateLinkService{
+				Properties: &armnetwork.PrivateLinkServiceProperties{
 					EnableProxyProtocol: ptr.To(false),
 				},
 			},
@@ -1102,8 +1120,8 @@ func TestReconcilePLSEnableProxyProtocol(t *testing.T) {
 			annotations: map[string]string{
 				consts.ServiceAnnotationPLSProxyProtocol: "True",
 			},
-			pls: &network.PrivateLinkService{
-				PrivateLinkServiceProperties: &network.PrivateLinkServiceProperties{
+			pls: &armnetwork.PrivateLinkService{
+				Properties: &armnetwork.PrivateLinkServiceProperties{
 					EnableProxyProtocol: ptr.To(true),
 				},
 			},
@@ -1114,8 +1132,8 @@ func TestReconcilePLSEnableProxyProtocol(t *testing.T) {
 			annotations: map[string]string{
 				consts.ServiceAnnotationPLSProxyProtocol: "True",
 			},
-			pls: &network.PrivateLinkService{
-				PrivateLinkServiceProperties: &network.PrivateLinkServiceProperties{},
+			pls: &armnetwork.PrivateLinkService{
+				Properties: &armnetwork.PrivateLinkServiceProperties{},
 			},
 			expectedEnabled: ptr.To(true),
 			expectedChanged: true,
@@ -1125,8 +1143,8 @@ func TestReconcilePLSEnableProxyProtocol(t *testing.T) {
 			annotations: map[string]string{
 				consts.ServiceAnnotationPLSProxyProtocol: "False",
 			},
-			pls: &network.PrivateLinkService{
-				PrivateLinkServiceProperties: &network.PrivateLinkServiceProperties{
+			pls: &armnetwork.PrivateLinkService{
+				Properties: &armnetwork.PrivateLinkServiceProperties{
 					EnableProxyProtocol: ptr.To(true),
 				},
 			},
@@ -1139,7 +1157,7 @@ func TestReconcilePLSEnableProxyProtocol(t *testing.T) {
 		s.Annotations = test.annotations
 		changed := reconcilePLSEnableProxyProtocol(test.pls, s)
 		assert.Equal(t, test.expectedChanged, changed, "TestCase[%d]: %s", i, test.desc)
-		assert.Equal(t, test.expectedEnabled, test.pls.EnableProxyProtocol, "TestCase[%d]: %s", i, test.desc)
+		assert.Equal(t, test.expectedEnabled, test.pls.Properties.EnableProxyProtocol, "TestCase[%d]: %s", i, test.desc)
 	}
 }
 
@@ -1147,15 +1165,15 @@ func TestReconcilePLSFqdn(t *testing.T) {
 	tests := []struct {
 		desc            string
 		annotations     map[string]string
-		pls             *network.PrivateLinkService
+		pls             *armnetwork.PrivateLinkService
 		expectedChanged bool
-		expectedfqdns   *[]string
+		expectedFQDNs   []*string
 	}{
 		{
 			desc:        "Empty service fqdns + empty pls fqdns should not trigger any change",
 			annotations: map[string]string{},
-			pls: &network.PrivateLinkService{
-				PrivateLinkServiceProperties: &network.PrivateLinkServiceProperties{},
+			pls: &armnetwork.PrivateLinkService{
+				Properties: &armnetwork.PrivateLinkServiceProperties{},
 			},
 		},
 		{
@@ -1163,49 +1181,49 @@ func TestReconcilePLSFqdn(t *testing.T) {
 			annotations: map[string]string{
 				consts.ServiceAnnotationPLSFqdns: "fqdns1 fqdns2 fqdns3",
 			},
-			pls: &network.PrivateLinkService{
-				PrivateLinkServiceProperties: &network.PrivateLinkServiceProperties{
-					Fqdns: &[]string{"fqdns2", "fqdns3", "fqdns1"},
+			pls: &armnetwork.PrivateLinkService{
+				Properties: &armnetwork.PrivateLinkServiceProperties{
+					Fqdns: to.SliceOfPtrs("fqdns2", "fqdns3", "fqdns1"),
 				},
 			},
-			expectedfqdns: &[]string{"fqdns2", "fqdns3", "fqdns1"},
+			expectedFQDNs: to.SliceOfPtrs("fqdns2", "fqdns3", "fqdns1"),
 		},
 		{
 			desc: "fqdns should be changed according to service - 0",
 			annotations: map[string]string{
 				consts.ServiceAnnotationPLSFqdns: "",
 			},
-			pls: &network.PrivateLinkService{
-				PrivateLinkServiceProperties: &network.PrivateLinkServiceProperties{
-					Fqdns: &[]string{"fqdns2", "fqdns3", "fqdns1"},
+			pls: &armnetwork.PrivateLinkService{
+				Properties: &armnetwork.PrivateLinkServiceProperties{
+					Fqdns: to.SliceOfPtrs("fqdns2", "fqdns3", "fqdns1"),
 				},
 			},
 			expectedChanged: true,
-			expectedfqdns:   &[]string{},
+			expectedFQDNs:   []*string{},
 		},
 		{
 			desc: "fqdns should be changed according to service - 1",
 			annotations: map[string]string{
 				consts.ServiceAnnotationPLSFqdns: "fqdns1 fqdns2",
 			},
-			pls: &network.PrivateLinkService{
-				PrivateLinkServiceProperties: &network.PrivateLinkServiceProperties{},
+			pls: &armnetwork.PrivateLinkService{
+				Properties: &armnetwork.PrivateLinkServiceProperties{},
 			},
 			expectedChanged: true,
-			expectedfqdns:   &[]string{"fqdns1", "fqdns2"},
+			expectedFQDNs:   to.SliceOfPtrs("fqdns1", "fqdns2"),
 		},
 		{
 			desc: "fqdns should be changed according to service - 2",
 			annotations: map[string]string{
 				consts.ServiceAnnotationPLSFqdns: "fqdns1 fqdns2",
 			},
-			pls: &network.PrivateLinkService{
-				PrivateLinkServiceProperties: &network.PrivateLinkServiceProperties{
-					Fqdns: &[]string{"fqdns2", "fqdns3", "fqdns1"},
+			pls: &armnetwork.PrivateLinkService{
+				Properties: &armnetwork.PrivateLinkServiceProperties{
+					Fqdns: to.SliceOfPtrs("fqdns2", "fqdns3", "fqdns1"),
 				},
 			},
 			expectedChanged: true,
-			expectedfqdns:   &[]string{"fqdns1", "fqdns2"},
+			expectedFQDNs:   to.SliceOfPtrs("fqdns1", "fqdns2"),
 		},
 	}
 	for i, test := range tests {
@@ -1213,7 +1231,7 @@ func TestReconcilePLSFqdn(t *testing.T) {
 		s.Annotations = test.annotations
 		changed := reconcilePLSFqdn(test.pls, s)
 		assert.Equal(t, test.expectedChanged, changed, "TestCase[%d]: %s", i, test.desc)
-		assert.Equal(t, test.expectedfqdns, test.pls.Fqdns, "TestCase[%d]: %s", i, test.desc)
+		assert.Equal(t, test.expectedFQDNs, test.pls.Properties.Fqdns, "TestCase[%d]: %s", i, test.desc)
 	}
 }
 
@@ -1223,8 +1241,8 @@ func TestReconcilePLSVisibility(t *testing.T) {
 			Annotations: map[string]string{},
 		},
 	}
-	pls := network.PrivateLinkService{
-		PrivateLinkServiceProperties: &network.PrivateLinkServiceProperties{},
+	pls := armnetwork.PrivateLinkService{
+		Properties: &armnetwork.PrivateLinkServiceProperties{},
 	}
 
 	t.Run("reconcilePLSVisibility should change nothing when both visibility and auto-approval are nil", func(t *testing.T) {
@@ -1238,8 +1256,8 @@ func TestReconcilePLSVisibility(t *testing.T) {
 			consts.ServiceAnnotationPLSVisibility: "sub1 sub2",
 		}
 		service.Annotations = annotations
-		pls.Visibility = &network.PrivateLinkServicePropertiesVisibility{
-			Subscriptions: &[]string{"sub2", "sub1"},
+		pls.Properties.Visibility = &armnetwork.PrivateLinkServicePropertiesVisibility{
+			Subscriptions: to.SliceOfPtrs("sub2", "sub1"),
 		}
 		changed, err := reconcilePLSVisibility(&pls, &service)
 		assert.NoError(t, err)
@@ -1252,11 +1270,11 @@ func TestReconcilePLSVisibility(t *testing.T) {
 			consts.ServiceAnnotationPLSAutoApproval: "sub1 sub2",
 		}
 		service.Annotations = annotations
-		pls.Visibility = &network.PrivateLinkServicePropertiesVisibility{
-			Subscriptions: &[]string{"*"},
+		pls.Properties.Visibility = &armnetwork.PrivateLinkServicePropertiesVisibility{
+			Subscriptions: to.SliceOfPtrs("*"),
 		}
-		pls.AutoApproval = &network.PrivateLinkServicePropertiesAutoApproval{
-			Subscriptions: &[]string{"sub1", "sub2"},
+		pls.Properties.AutoApproval = &armnetwork.PrivateLinkServicePropertiesAutoApproval{
+			Subscriptions: to.SliceOfPtrs("sub1", "sub2"),
 		}
 		changed, err := reconcilePLSVisibility(&pls, &service)
 		assert.NoError(t, err)
@@ -1268,15 +1286,15 @@ func TestReconcilePLSVisibility(t *testing.T) {
 			consts.ServiceAnnotationPLSVisibility:   "*",
 			consts.ServiceAnnotationPLSAutoApproval: "sub1 sub2",
 		}
-		pls.Visibility = nil
-		pls.AutoApproval = nil
-		expectedPLS := network.PrivateLinkService{
-			PrivateLinkServiceProperties: &network.PrivateLinkServiceProperties{
-				Visibility: &network.PrivateLinkServicePropertiesVisibility{
-					Subscriptions: &[]string{"*"},
+		pls.Properties.Visibility = nil
+		pls.Properties.AutoApproval = nil
+		expectedPLS := armnetwork.PrivateLinkService{
+			Properties: &armnetwork.PrivateLinkServiceProperties{
+				Visibility: &armnetwork.PrivateLinkServicePropertiesVisibility{
+					Subscriptions: to.SliceOfPtrs("*"),
 				},
-				AutoApproval: &network.PrivateLinkServicePropertiesAutoApproval{
-					Subscriptions: &[]string{"sub1", "sub2"},
+				AutoApproval: &armnetwork.PrivateLinkServicePropertiesAutoApproval{
+					Subscriptions: to.SliceOfPtrs("sub1", "sub2"),
 				},
 			},
 		}
@@ -1291,15 +1309,15 @@ func TestReconcilePLSVisibility(t *testing.T) {
 		annotations := map[string]string{
 			consts.ServiceAnnotationPLSVisibility: "sub1 sub2",
 		}
-		pls.Visibility = nil
-		pls.AutoApproval = nil
-		expectedPLS := network.PrivateLinkService{
-			PrivateLinkServiceProperties: &network.PrivateLinkServiceProperties{
-				Visibility: &network.PrivateLinkServicePropertiesVisibility{
-					Subscriptions: &[]string{"sub1", "sub2"},
+		pls.Properties.Visibility = nil
+		pls.Properties.AutoApproval = nil
+		expectedPLS := armnetwork.PrivateLinkService{
+			Properties: &armnetwork.PrivateLinkServiceProperties{
+				Visibility: &armnetwork.PrivateLinkServicePropertiesVisibility{
+					Subscriptions: to.SliceOfPtrs("sub1", "sub2"),
 				},
-				AutoApproval: &network.PrivateLinkServicePropertiesAutoApproval{
-					Subscriptions: &[]string{},
+				AutoApproval: &armnetwork.PrivateLinkServicePropertiesAutoApproval{
+					Subscriptions: []*string{},
 				},
 			},
 		}
@@ -1314,23 +1332,23 @@ func TestReconcilePLSVisibility(t *testing.T) {
 		annotations := map[string]string{
 			consts.ServiceAnnotationPLSVisibility: "sub1 sub2",
 		}
-		pls = network.PrivateLinkService{
-			PrivateLinkServiceProperties: &network.PrivateLinkServiceProperties{
-				Visibility: &network.PrivateLinkServicePropertiesVisibility{
-					Subscriptions: &[]string{"*"},
+		pls = armnetwork.PrivateLinkService{
+			Properties: &armnetwork.PrivateLinkServiceProperties{
+				Visibility: &armnetwork.PrivateLinkServicePropertiesVisibility{
+					Subscriptions: to.SliceOfPtrs("*"),
 				},
-				AutoApproval: &network.PrivateLinkServicePropertiesAutoApproval{
-					Subscriptions: &[]string{"sub1"},
+				AutoApproval: &armnetwork.PrivateLinkServicePropertiesAutoApproval{
+					Subscriptions: to.SliceOfPtrs("sub1"),
 				},
 			},
 		}
-		expectedPLS := network.PrivateLinkService{
-			PrivateLinkServiceProperties: &network.PrivateLinkServiceProperties{
-				Visibility: &network.PrivateLinkServicePropertiesVisibility{
-					Subscriptions: &[]string{"sub1", "sub2"},
+		expectedPLS := armnetwork.PrivateLinkService{
+			Properties: &armnetwork.PrivateLinkServiceProperties{
+				Visibility: &armnetwork.PrivateLinkServicePropertiesVisibility{
+					Subscriptions: to.SliceOfPtrs("sub1", "sub2"),
 				},
-				AutoApproval: &network.PrivateLinkServicePropertiesAutoApproval{
-					Subscriptions: &[]string{},
+				AutoApproval: &armnetwork.PrivateLinkServicePropertiesAutoApproval{
+					Subscriptions: []*string{},
 				},
 			},
 		}
@@ -1346,23 +1364,23 @@ func TestReconcilePLSVisibility(t *testing.T) {
 			consts.ServiceAnnotationPLSVisibility:   "*",
 			consts.ServiceAnnotationPLSAutoApproval: "sub1 sub2",
 		}
-		pls = network.PrivateLinkService{
-			PrivateLinkServiceProperties: &network.PrivateLinkServiceProperties{
-				Visibility: &network.PrivateLinkServicePropertiesVisibility{
-					Subscriptions: &[]string{"*"},
+		pls = armnetwork.PrivateLinkService{
+			Properties: &armnetwork.PrivateLinkServiceProperties{
+				Visibility: &armnetwork.PrivateLinkServicePropertiesVisibility{
+					Subscriptions: to.SliceOfPtrs("*"),
 				},
-				AutoApproval: &network.PrivateLinkServicePropertiesAutoApproval{
-					Subscriptions: &[]string{"sub3"},
+				AutoApproval: &armnetwork.PrivateLinkServicePropertiesAutoApproval{
+					Subscriptions: to.SliceOfPtrs("sub3"),
 				},
 			},
 		}
-		expectedPLS := network.PrivateLinkService{
-			PrivateLinkServiceProperties: &network.PrivateLinkServiceProperties{
-				Visibility: &network.PrivateLinkServicePropertiesVisibility{
-					Subscriptions: &[]string{"*"},
+		expectedPLS := armnetwork.PrivateLinkService{
+			Properties: &armnetwork.PrivateLinkServiceProperties{
+				Visibility: &armnetwork.PrivateLinkServicePropertiesVisibility{
+					Subscriptions: to.SliceOfPtrs("*"),
 				},
-				AutoApproval: &network.PrivateLinkServicePropertiesAutoApproval{
-					Subscriptions: &[]string{"sub1", "sub2"},
+				AutoApproval: &armnetwork.PrivateLinkServicePropertiesAutoApproval{
+					Subscriptions: to.SliceOfPtrs("sub1", "sub2"),
 				},
 			},
 		}
@@ -1387,7 +1405,7 @@ func TestReconcilePLSTags(t *testing.T) {
 			Name:      "test-svc",
 		},
 	}
-	pls := network.PrivateLinkService{
+	pls := armnetwork.PrivateLinkService{
 		Tags: map[string]*string{
 			"foo": ptr.To("bar"),
 			"a":   ptr.To("j"),
@@ -1397,7 +1415,7 @@ func TestReconcilePLSTags(t *testing.T) {
 	clusterName := testClusterName
 
 	t.Run("reconcilePLSTags should ensure the pls is tagged as configured", func(t *testing.T) {
-		expectedPLS := network.PrivateLinkService{
+		expectedPLS := armnetwork.PrivateLinkService{
 			Tags: map[string]*string{
 				consts.ClusterNameTagKey:  ptr.To(testClusterName),
 				consts.OwnerServiceTagKey: ptr.To("test-ns/test-svc"),
@@ -1414,7 +1432,7 @@ func TestReconcilePLSTags(t *testing.T) {
 
 	t.Run("reconcilePLSTags should delete the old tags if the SystemTags is set", func(t *testing.T) {
 		cloud.SystemTags = "a,foo,b"
-		expectedPLS := network.PrivateLinkService{
+		expectedPLS := armnetwork.PrivateLinkService{
 			Tags: map[string]*string{
 				consts.ClusterNameTagKey:  ptr.To(testClusterName),
 				consts.OwnerServiceTagKey: ptr.To("test-ns/test-svc"),
@@ -1431,7 +1449,7 @@ func TestReconcilePLSTags(t *testing.T) {
 	t.Run("reconcilePLSTags should support TagsMap", func(t *testing.T) {
 		cloud.SystemTags = "a,foo,b"
 		cloud.TagsMap = map[string]string{"a": "c", "a=b": "c=d", "Y": "zz"}
-		expectedPLS := network.PrivateLinkService{
+		expectedPLS := armnetwork.PrivateLinkService{
 			Tags: map[string]*string{
 				consts.ClusterNameTagKey:  ptr.To(testClusterName),
 				consts.OwnerServiceTagKey: ptr.To("test-ns/test-svc"),
@@ -1449,7 +1467,7 @@ func TestReconcilePLSTags(t *testing.T) {
 	pls.Tags[consts.OwnerServiceTagKey] = ptr.To("default/svc")
 
 	t.Run("reconcilePLSTags should respect cluster and owner service tag keys", func(t *testing.T) {
-		expectedPLS := network.PrivateLinkService{
+		expectedPLS := armnetwork.PrivateLinkService{
 			Tags: map[string]*string{
 				consts.ClusterNameTagKey:  ptr.To("testCluster1"),
 				consts.OwnerServiceTagKey: ptr.To("default/svc"),
@@ -1819,23 +1837,23 @@ func TestGetPLSStaticIPs(t *testing.T) {
 func TestIsManagedPrivateLinkSerivce(t *testing.T) {
 	tests := []struct {
 		desc        string
-		pls         *network.PrivateLinkService
+		pls         *armnetwork.PrivateLinkService
 		clusterName string
 		expected    bool
 	}{
 		{
 			desc: "Private link service with nil tag should return false",
-			pls:  &network.PrivateLinkService{},
+			pls:  &armnetwork.PrivateLinkService{},
 		},
 		{
 			desc: "Private link service with empty tag should return false",
-			pls: &network.PrivateLinkService{
+			pls: &armnetwork.PrivateLinkService{
 				Tags: map[string]*string{},
 			},
 		},
 		{
 			desc: "Private link service with different cluster name should return false",
-			pls: &network.PrivateLinkService{
+			pls: &armnetwork.PrivateLinkService{
 				Tags: map[string]*string{
 					"k8s-azure-cluster-name": ptr.To("test-cluster1"),
 				},
@@ -1844,7 +1862,7 @@ func TestIsManagedPrivateLinkSerivce(t *testing.T) {
 		},
 		{
 			desc: "Private link service with same cluster name should return true",
-			pls: &network.PrivateLinkService{
+			pls: &armnetwork.PrivateLinkService{
 				Tags: map[string]*string{
 					"k8s-azure-cluster-name": ptr.To("test-cluster"),
 				},
@@ -1862,22 +1880,22 @@ func TestIsManagedPrivateLinkSerivce(t *testing.T) {
 func TestGetPrivateLinkServiceOwner(t *testing.T) {
 	tests := []struct {
 		desc     string
-		pls      *network.PrivateLinkService
+		pls      *armnetwork.PrivateLinkService
 		expected string
 	}{
 		{
 			desc: "Private link service with nil tag should return empty string",
-			pls:  &network.PrivateLinkService{},
+			pls:  &armnetwork.PrivateLinkService{},
 		},
 		{
 			desc: "Private link service with empty tag should return empty string",
-			pls: &network.PrivateLinkService{
+			pls: &armnetwork.PrivateLinkService{
 				Tags: map[string]*string{},
 			},
 		},
 		{
 			desc: "Private link service with service owner tag should return service owner",
-			pls: &network.PrivateLinkService{
+			pls: &armnetwork.PrivateLinkService{
 				Tags: map[string]*string{"k8s-azure-owner-service": ptr.To("test-service")},
 			},
 			expected: "test-service",
diff --git a/pkg/provider/azure_test.go b/pkg/provider/azure_test.go
index 615693364..69ed7018c 100644
--- a/pkg/provider/azure_test.go
+++ b/pkg/provider/azure_test.go
@@ -27,6 +27,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
 	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6"
 	"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2022-08-01/compute"
 	"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2022-07-01/network"
@@ -48,13 +49,13 @@ import (
 	"sigs.k8s.io/cloud-provider-azure/pkg/azclient/securitygroupclient/mock_securitygroupclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/interfaceclient/mockinterfaceclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/loadbalancerclient/mockloadbalancerclient"
-	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/privatelinkserviceclient/mockprivatelinkserviceclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/publicipclient/mockpublicipclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/subnetclient/mocksubnetclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/vmclient/mockvmclient"
-	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/zoneclient/mockzoneclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/consts"
 	providerconfig "sigs.k8s.io/cloud-provider-azure/pkg/provider/config"
+	"sigs.k8s.io/cloud-provider-azure/pkg/provider/privatelinkservice"
+	"sigs.k8s.io/cloud-provider-azure/pkg/provider/zone"
 	"sigs.k8s.io/cloud-provider-azure/pkg/retry"
 	utilsets "sigs.k8s.io/cloud-provider-azure/pkg/util/sets"
 	"sigs.k8s.io/cloud-provider-azure/pkg/util/taints"
@@ -522,10 +523,9 @@ func testLoadBalancerServiceDefaultModeSelection(t *testing.T, isInternal bool)
 
 		expectedLBName := setMockLBs(az, ctrl, &expectedLBs, "service", 1, index, isInternal)
 
-		expectedPLS := make([]network.PrivateLinkService, 0)
-		mockPLSClient := mockprivatelinkserviceclient.NewMockInterface(ctrl)
-		mockPLSClient.EXPECT().List(gomock.Any(), az.Config.ResourceGroup).Return(expectedPLS, nil).MinTimes(1).MaxTimes(1)
-		az.PrivateLinkServiceClient = mockPLSClient
+		mockPLSRepo := privatelinkservice.NewMockRepository(ctrl)
+		mockPLSRepo.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&armnetwork.PrivateLinkService{ID: to.Ptr(consts.PrivateLinkServiceNotExistID)}, nil).AnyTimes()
+		az.plsRepo = mockPLSRepo
 
 		lbStatus, err := az.EnsureLoadBalancer(context.TODO(), testClusterName, &svc, clusterResources.nodes)
 		if err != nil {
@@ -592,10 +592,9 @@ func testLoadBalancerServiceAutoModeSelection(t *testing.T, isInternal bool) {
 
 		setMockLBs(az, ctrl, &expectedLBs, "service", availabilitySetCount, index, isInternal)
 
-		expectedPLS := make([]network.PrivateLinkService, 0)
-		mockPLSClient := mockprivatelinkserviceclient.NewMockInterface(ctrl)
-		mockPLSClient.EXPECT().List(gomock.Any(), az.Config.ResourceGroup).Return(expectedPLS, nil).MinTimes(1).MaxTimes(1)
-		az.PrivateLinkServiceClient = mockPLSClient
+		mockPLSRepo := privatelinkservice.NewMockRepository(ctrl)
+		mockPLSRepo.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&armnetwork.PrivateLinkService{ID: to.Ptr(consts.PrivateLinkServiceNotExistID)}, nil).AnyTimes()
+		az.plsRepo = mockPLSRepo
 
 		lbStatus, err := az.EnsureLoadBalancer(context.TODO(), testClusterName, &svc, clusterResources.nodes)
 		assert.Nil(t, err)
@@ -670,10 +669,10 @@ func testLoadBalancerServicesSpecifiedSelection(t *testing.T, isInternal bool) {
 
 		setMockLBs(az, ctrl, &expectedLBs, "service", 1, index, isInternal)
 
-		expectedPLS := make([]network.PrivateLinkService, 0)
-		mockPLSClient := mockprivatelinkserviceclient.NewMockInterface(ctrl)
-		mockPLSClient.EXPECT().List(gomock.Any(), az.Config.ResourceGroup).Return(expectedPLS, nil).MinTimes(1).MaxTimes(1)
-		az.PrivateLinkServiceClient = mockPLSClient
+		expectedPLS := make([]*armnetwork.PrivateLinkService, 0)
+		mockPLSRepo := privatelinkservice.NewMockRepository(ctrl)
+		mockPLSRepo.EXPECT().List(gomock.Any(), az.Config.ResourceGroup).Return(expectedPLS, nil).MinTimes(1).MaxTimes(1)
+		az.plsRepo = mockPLSRepo
 
 		lbStatus, err := az.EnsureLoadBalancer(context.TODO(), testClusterName, &svc, clusterResources.nodes)
 		if err != nil {
@@ -729,10 +728,10 @@ func testLoadBalancerMaxRulesServices(t *testing.T, isInternal bool) {
 
 		setMockLBs(az, ctrl, &expectedLBs, "service", az.Config.MaximumLoadBalancerRuleCount, index, isInternal)
 
-		expectedPLS := make([]network.PrivateLinkService, 0)
-		mockPLSClient := mockprivatelinkserviceclient.NewMockInterface(ctrl)
-		mockPLSClient.EXPECT().List(gomock.Any(), az.Config.ResourceGroup).Return(expectedPLS, nil).MinTimes(1).MaxTimes(1)
-		az.PrivateLinkServiceClient = mockPLSClient
+		expectedPLS := make([]*armnetwork.PrivateLinkService, 0)
+		mockPLSRepo := privatelinkservice.NewMockRepository(ctrl)
+		mockPLSRepo.EXPECT().List(gomock.Any(), az.Config.ResourceGroup).Return(expectedPLS, nil).MinTimes(1).MaxTimes(1)
+		az.plsRepo = mockPLSRepo
 
 		lbStatus, err := az.EnsureLoadBalancer(context.TODO(), testClusterName, &svc, clusterResources.nodes)
 		if err != nil {
@@ -817,10 +816,9 @@ func testLoadBalancerServiceAutoModeDeleteSelection(t *testing.T, isInternal boo
 
 		setMockLBs(az, ctrl, &expectedLBs, "service", availabilitySetCount, index, isInternal)
 
-		expectedPLS := make([]network.PrivateLinkService, 0)
-		mockPLSClient := mockprivatelinkserviceclient.NewMockInterface(ctrl)
-		mockPLSClient.EXPECT().List(gomock.Any(), az.Config.ResourceGroup).Return(expectedPLS, nil).MinTimes(1).MaxTimes(1)
-		az.PrivateLinkServiceClient = mockPLSClient
+		mockPLSRepo := privatelinkservice.NewMockRepository(ctrl)
+		mockPLSRepo.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&armnetwork.PrivateLinkService{ID: to.Ptr(consts.PrivateLinkServiceNotExistID)}, nil).AnyTimes()
+		az.plsRepo = mockPLSRepo
 
 		lbStatus, err := az.EnsureLoadBalancer(context.TODO(), testClusterName, &svc, clusterResources.nodes)
 		if err != nil {
@@ -862,10 +860,9 @@ func testLoadBalancerServiceAutoModeDeleteSelection(t *testing.T, isInternal boo
 			t.Errorf("Unexpected number of LB's: Expected (%d) Found (%d)", expectedNumOfLB, lbCount)
 		}
 
-		expectedPLS := make([]network.PrivateLinkService, 0)
-		mockPLSClient := mockprivatelinkserviceclient.NewMockInterface(ctrl)
-		mockPLSClient.EXPECT().List(gomock.Any(), az.Config.ResourceGroup).Return(expectedPLS, nil).MinTimes(1).MaxTimes(1)
-		az.PrivateLinkServiceClient = mockPLSClient
+		mockPLSRepo := privatelinkservice.NewMockRepository(ctrl)
+		mockPLSRepo.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&armnetwork.PrivateLinkService{ID: to.Ptr(consts.PrivateLinkServiceNotExistID)}, nil).AnyTimes()
+		az.plsRepo = mockPLSRepo
 
 		err := az.EnsureLoadBalancerDeleted(context.TODO(), testClusterName, &svc)
 		if err != nil {
@@ -978,9 +975,9 @@ func TestReconcileLoadBalancerEditServiceSubnet(t *testing.T) {
 	}).AnyTimes()
 	mockLBBackendPool.EXPECT().EnsureHostsInPool(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
 
-	expectedPLS := make([]network.PrivateLinkService, 0)
-	mockPLSClient := az.PrivateLinkServiceClient.(*mockprivatelinkserviceclient.MockInterface)
-	mockPLSClient.EXPECT().List(gomock.Any(), az.Config.ResourceGroup).Return(expectedPLS, nil).MinTimes(1).MaxTimes(1)
+	mockPLSRepo := privatelinkservice.NewMockRepository(ctrl)
+	mockPLSRepo.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&armnetwork.PrivateLinkService{ID: to.Ptr(consts.PrivateLinkServiceNotExistID)}, nil).AnyTimes()
+	az.plsRepo = mockPLSRepo
 
 	lb, err := az.reconcileLoadBalancer(context.TODO(), testClusterName, &svc, clusterResources.nodes, true /* wantLb */)
 	if err != nil {
@@ -1170,9 +1167,10 @@ func TestReconcileLoadBalancerMultipleServices(t *testing.T) {
 	}).AnyTimes()
 	mockLBBackendPool.EXPECT().EnsureHostsInPool(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
 
-	expectedPLS := make([]network.PrivateLinkService, 0)
-	mockPLSClient := az.PrivateLinkServiceClient.(*mockprivatelinkserviceclient.MockInterface)
-	mockPLSClient.EXPECT().List(gomock.Any(), az.Config.ResourceGroup).Return(expectedPLS, nil).MinTimes(1).MaxTimes(1)
+	expectedPLS := make([]*armnetwork.PrivateLinkService, 0)
+	mockPLSRepo := privatelinkservice.NewMockRepository(ctrl)
+	mockPLSRepo.EXPECT().List(gomock.Any(), az.Config.ResourceGroup).Return(expectedPLS, nil).MinTimes(1).MaxTimes(1)
+	az.plsRepo = mockPLSRepo
 
 	_, err := az.reconcileLoadBalancer(context.TODO(), testClusterName, &svc1, clusterResources.nodes, true /* wantLb */)
 	assert.Nil(t, err)
@@ -1285,9 +1283,9 @@ func TestServiceRespectsNoSessionAffinity(t *testing.T) {
 	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)
-	mockPLSClient.EXPECT().List(gomock.Any(), az.Config.ResourceGroup).Return(expectedPLS, nil).MinTimes(1).MaxTimes(1)
+	mockPLSRepo := privatelinkservice.NewMockRepository(ctrl)
+	mockPLSRepo.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&armnetwork.PrivateLinkService{ID: to.Ptr(consts.PrivateLinkServiceNotExistID)}, nil).AnyTimes()
+	az.plsRepo = mockPLSRepo
 
 	mockLBBackendPool := az.LoadBalancerBackendPool.(*MockBackendPool)
 	mockLBBackendPool.EXPECT().ReconcileBackendPools(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(_ context.Context, _ string, _ *v1.Service, lb *network.LoadBalancer) (bool, bool, *network.LoadBalancer, error) {
@@ -1348,9 +1346,9 @@ func TestServiceRespectsClientIPSessionAffinity(t *testing.T) {
 	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)
-	mockPLSClient.EXPECT().List(gomock.Any(), az.Config.ResourceGroup).Return(expectedPLS, nil).MinTimes(1).MaxTimes(1)
+	mockPLSRepo := privatelinkservice.NewMockRepository(ctrl)
+	mockPLSRepo.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&armnetwork.PrivateLinkService{ID: to.Ptr(consts.PrivateLinkServiceNotExistID)}, nil).AnyTimes()
+	az.plsRepo = mockPLSRepo
 
 	mockLBBackendPool := az.LoadBalancerBackendPool.(*MockBackendPool)
 	mockLBBackendPool.EXPECT().ReconcileBackendPools(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(_ context.Context, _ string, _ *v1.Service, lb *network.LoadBalancer) (bool, bool, *network.LoadBalancer, error) {
@@ -2264,6 +2262,7 @@ func getCloudFromConfig(t *testing.T, config string) *Cloud {
 	c, err := ParseConfig(configReader)
 	assert.NoError(t, err)
 
+	zoneMock := zone.NewMockRepository(ctrl)
 	az := &Cloud{
 		nodeNames:                utilsets.NewString(),
 		nodeZones:                map[string]*utilsets.IgnoreCaseSet{},
@@ -2271,12 +2270,11 @@ func getCloudFromConfig(t *testing.T, config string) *Cloud {
 		unmanagedNodes:           utilsets.NewString(),
 		excludeLoadBalancerNodes: utilsets.NewString(),
 		routeCIDRs:               map[string]string{},
-		ZoneClient:               mockzoneclient.NewMockInterface(ctrl),
+		zoneRepo:                 zoneMock,
 		ComputeClientFactory:     mock_azclient.NewMockClientFactory(ctrl),
 		NetworkClientFactory:     mock_azclient.NewMockClientFactory(ctrl),
 	}
-	mockZoneClient := az.ZoneClient.(*mockzoneclient.MockInterface)
-	mockZoneClient.EXPECT().GetZones(gomock.Any(), gomock.Any()).Return(map[string][]string{"eastus": {"1", "2", "3"}}, nil)
+	zoneMock.EXPECT().ListZones(gomock.Any()).Return(map[string][]string{"eastus": {"1", "2", "3"}}, nil)
 
 	// Skip AAD client cert path validation since it will read the file from the path
 	aadCertPath := c.AADClientCertPath
@@ -2782,9 +2780,9 @@ func TestInitializeCloudFromConfig(t *testing.T) {
 	defer ctrl.Finish()
 	az := GetTestCloud(ctrl)
 
-	mockZoneClient := mockzoneclient.NewMockInterface(ctrl)
-	mockZoneClient.EXPECT().GetZones(gomock.Any(), gomock.Any()).Return(map[string][]string{"eastus": {"1", "2", "3"}}, nil).AnyTimes()
-	az.ZoneClient = mockZoneClient
+	zoneMock := zone.NewMockRepository(ctrl)
+	zoneMock.EXPECT().ListZones(gomock.Any()).Return(map[string][]string{"eastus": {"1", "2", "3"}}, nil).AnyTimes()
+	az.zoneRepo = zoneMock
 
 	err := az.InitializeCloudFromConfig(context.Background(), nil, false, true)
 	assert.Equal(t, fmt.Errorf("InitializeCloudFromConfig: cannot initialize from nil config"), err)
diff --git a/pkg/provider/azure_zones_test.go b/pkg/provider/azure_zones_test.go
index a1a5e476a..6d9eea2f9 100644
--- a/pkg/provider/azure_zones_test.go
+++ b/pkg/provider/azure_zones_test.go
@@ -33,8 +33,7 @@ import (
 	"k8s.io/utils/ptr"
 
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/vmclient/mockvmclient"
-	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/zoneclient/mockzoneclient"
-	"sigs.k8s.io/cloud-provider-azure/pkg/retry"
+	"sigs.k8s.io/cloud-provider-azure/pkg/provider/zone"
 	utilsets "sigs.k8s.io/cloud-provider-azure/pkg/util/sets"
 )
 
@@ -253,14 +252,12 @@ func TestSyncRegionZonesMap(t *testing.T) {
 	for _, testCase := range []struct {
 		description   string
 		zones         map[string][]string
-		getZonesErr   *retry.Error
+		getZonesErr   error
 		expectedZones map[string][]string
-		expectedErr   error
 	}{
 		{
 			description: "syncRegionZonesMap should report an error if failed to get zones",
-			getZonesErr: retry.NewError(false, fmt.Errorf("error")),
-			expectedErr: fmt.Errorf("Retriable: false, RetryAfter: 0s, HTTPStatusCode: 0, RawError: error"),
+			getZonesErr: fmt.Errorf("error"),
 		},
 		{
 			description:   "syncRegionZonesMap should not report an error if the given zones map is empty",
@@ -268,15 +265,15 @@ func TestSyncRegionZonesMap(t *testing.T) {
 		},
 	} {
 		t.Run(testCase.description, func(t *testing.T) {
+			m := zone.NewMockRepository(ctrl)
 			az := &Cloud{
-				ZoneClient: mockzoneclient.NewMockInterface(ctrl),
+				zoneRepo: m,
 			}
-			mockZoneClient := az.ZoneClient.(*mockzoneclient.MockInterface)
-			mockZoneClient.EXPECT().GetZones(gomock.Any(), gomock.Any()).Return(testCase.expectedZones, testCase.getZonesErr)
+			m.EXPECT().ListZones(gomock.Any()).Return(testCase.expectedZones, testCase.getZonesErr)
 
 			err := az.syncRegionZonesMap(context.TODO())
 			if err != nil {
-				assert.EqualError(t, testCase.expectedErr, err.Error())
+				assert.ErrorIs(t, err, testCase.getZonesErr)
 			}
 			assert.Equal(t, testCase.expectedZones, az.regionZonesMap)
 		})
@@ -290,14 +287,14 @@ func TestGetRegionZonesBackoff(t *testing.T) {
 	for _, testCase := range []struct {
 		description   string
 		zones         map[string][]string
-		getZonesErr   *retry.Error
+		getZonesErr   error
 		expectedZones map[string][]string
 		expectedErr   error
 	}{
 		{
 			description: "syncRegionZonesMap should report an error if failed to get zones",
-			getZonesErr: retry.NewError(false, fmt.Errorf("error")),
-			expectedErr: fmt.Errorf("Retriable: false, RetryAfter: 0s, HTTPStatusCode: 0, RawError: error"),
+			getZonesErr: fmt.Errorf("error"),
+			expectedErr: fmt.Errorf("error"),
 		},
 		{
 			description:   "syncRegionZonesMap should not report an error if the given zones map is empty",
@@ -305,11 +302,11 @@ func TestGetRegionZonesBackoff(t *testing.T) {
 		},
 	} {
 		t.Run(testCase.description, func(t *testing.T) {
+			m := zone.NewMockRepository(ctrl)
 			az := &Cloud{
-				ZoneClient: mockzoneclient.NewMockInterface(ctrl),
+				zoneRepo: m,
 			}
-			mockZoneClient := az.ZoneClient.(*mockzoneclient.MockInterface)
-			mockZoneClient.EXPECT().GetZones(gomock.Any(), gomock.Any()).Return(testCase.expectedZones, testCase.getZonesErr)
+			m.EXPECT().ListZones(gomock.Any()).Return(testCase.expectedZones, testCase.getZonesErr)
 
 			_, err := az.getRegionZonesBackoff(context.TODO(), "eastus2")
 			if err != nil {
@@ -324,10 +321,10 @@ func TestRefreshZones(t *testing.T) {
 	ctrl := gomock.NewController(t)
 	defer ctrl.Finish()
 
+	m := zone.NewMockRepository(ctrl)
 	az := GetTestCloud(ctrl)
-	mockZoneClient := mockzoneclient.NewMockInterface(ctrl)
-	mockZoneClient.EXPECT().GetZones(gomock.Any(), gomock.Any()).Return(map[string][]string{}, nil).Times(0)
-	az.ZoneClient = mockZoneClient
+	az.zoneRepo = m
+	m.EXPECT().ListZones(gomock.Any()).Return(map[string][]string{}, nil).Times(0)
 
 	ctx, cancel := context.WithCancel(context.Background())
 	go func() {
diff --git a/pkg/provider/privatelinkservice/repo_test.go b/pkg/provider/privatelinkservice/repo_test.go
new file mode 100644
index 000000000..b6b8bee9c
--- /dev/null
+++ b/pkg/provider/privatelinkservice/repo_test.go
@@ -0,0 +1,331 @@
+/*
+Copyright 2024 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 privatelinkservice
+
+import (
+	"context"
+	"fmt"
+	"testing"
+	"time"
+
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
+	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6"
+	"github.com/stretchr/testify/assert"
+	"go.uber.org/mock/gomock"
+
+	"sigs.k8s.io/cloud-provider-azure/pkg/azclient/privatelinkserviceclient/mock_privatelinkserviceclient"
+	"sigs.k8s.io/cloud-provider-azure/pkg/cache"
+	"sigs.k8s.io/cloud-provider-azure/pkg/consts"
+)
+
+func TestRepo_Get(t *testing.T) {
+	t.Run("refresh cache", func(t *testing.T) {
+		ctrl := gomock.NewController(t)
+		defer ctrl.Finish()
+		cli := mock_privatelinkserviceclient.NewMockInterface(ctrl)
+		repo, err := NewRepo(cli, 60*time.Second, false)
+		assert.NoError(t, err)
+		ctx := context.Background()
+
+		const (
+			rg                 = "resource-group"
+			frontendIPConfigID = "frontend-ip-config-id"
+			plsID              = "pls-id"
+		)
+
+		cli.EXPECT().List(gomock.Any(), rg).Return([]*armnetwork.PrivateLinkService{
+			{
+				ID: to.Ptr(plsID),
+				Properties: &armnetwork.PrivateLinkServiceProperties{
+					LoadBalancerFrontendIPConfigurations: []*armnetwork.FrontendIPConfiguration{
+						{ID: to.Ptr(frontendIPConfigID)},
+					},
+				},
+			},
+		}, nil).Times(1)
+
+		v, err := repo.Get(ctx, rg, frontendIPConfigID, cache.CacheReadTypeDefault)
+		assert.NoError(t, err)
+		assert.Equal(t, plsID, *v.ID)
+
+		// cache hit
+		v, err = repo.Get(ctx, rg, frontendIPConfigID, cache.CacheReadTypeDefault)
+		assert.NoError(t, err)
+		assert.Equal(t, plsID, *v.ID)
+	})
+
+	t.Run("refresh cache with not found", func(t *testing.T) {
+		ctrl := gomock.NewController(t)
+		defer ctrl.Finish()
+		cli := mock_privatelinkserviceclient.NewMockInterface(ctrl)
+		repo, err := NewRepo(cli, 60*time.Second, false)
+		assert.NoError(t, err)
+		ctx := context.Background()
+
+		const (
+			rg                 = "resource-group"
+			frontendIPConfigID = "frontend-ip-config-id"
+		)
+
+		cli.EXPECT().List(gomock.Any(), rg).Return([]*armnetwork.PrivateLinkService{}, nil).Times(1)
+
+		v, err := repo.Get(ctx, rg, frontendIPConfigID, cache.CacheReadTypeDefault)
+		assert.NoError(t, err)
+		assert.Equal(t, consts.PrivateLinkServiceNotExistID, *v.ID)
+	})
+
+	t.Run("API error", func(t *testing.T) {
+		ctrl := gomock.NewController(t)
+		defer ctrl.Finish()
+		cli := mock_privatelinkserviceclient.NewMockInterface(ctrl)
+		repo, err := NewRepo(cli, 60*time.Second, false)
+		assert.NoError(t, err)
+		ctx := context.Background()
+
+		const (
+			rg                 = "resource-group"
+			frontendIPConfigID = "frontend-ip-config-id"
+		)
+
+		expectedErr := fmt.Errorf("API error")
+
+		cli.EXPECT().List(gomock.Any(), rg).Return(nil, expectedErr).Times(1)
+
+		_, err = repo.Get(ctx, rg, frontendIPConfigID, cache.CacheReadTypeDefault)
+		assert.Error(t, err)
+		assert.ErrorIs(t, err, expectedErr)
+	})
+}
+
+func TestRepo_CreateOrUpdate(t *testing.T) {
+	t.Run("create one", func(t *testing.T) {
+		ctrl := gomock.NewController(t)
+		defer ctrl.Finish()
+		cli := mock_privatelinkserviceclient.NewMockInterface(ctrl)
+		repo, err := NewRepo(cli, 60*time.Second, false)
+		assert.NoError(t, err)
+		ctx := context.Background()
+
+		const (
+			rg                 = "resource-group"
+			plsName            = "pls-name"
+			frontendIPConfigID = "frontend-ip-config-id"
+		)
+
+		cli.EXPECT().CreateOrUpdate(gomock.Any(), rg, plsName, gomock.Any()).Return(&armnetwork.PrivateLinkService{
+			Name: to.Ptr(plsName),
+			Properties: &armnetwork.PrivateLinkServiceProperties{
+				LoadBalancerFrontendIPConfigurations: []*armnetwork.FrontendIPConfiguration{
+					{ID: to.Ptr(frontendIPConfigID)},
+				},
+			},
+		}, nil).Times(1)
+
+		v, err := repo.CreateOrUpdate(ctx, rg, armnetwork.PrivateLinkService{
+			Name: to.Ptr(plsName),
+			Properties: &armnetwork.PrivateLinkServiceProperties{
+				LoadBalancerFrontendIPConfigurations: []*armnetwork.FrontendIPConfiguration{
+					{ID: to.Ptr(frontendIPConfigID)},
+				},
+			},
+		})
+		assert.NoError(t, err)
+		assert.Equal(t, plsName, *v.Name)
+	})
+
+	t.Run("create one with missing PLS name", func(t *testing.T) {
+		ctrl := gomock.NewController(t)
+		defer ctrl.Finish()
+		cli := mock_privatelinkserviceclient.NewMockInterface(ctrl)
+		repo, err := NewRepo(cli, 60*time.Second, false)
+		assert.NoError(t, err)
+		ctx := context.Background()
+
+		const (
+			rg = "resource-group"
+		)
+
+		_, err = repo.CreateOrUpdate(ctx, rg, armnetwork.PrivateLinkService{})
+		assert.Error(t, err)
+		assert.ErrorIs(t, err, ErrMissingPLSName)
+	})
+
+	t.Run("create one with missing LoadBalancerFrontendIPConfigurations", func(t *testing.T) {
+		ctrl := gomock.NewController(t)
+		defer ctrl.Finish()
+		cli := mock_privatelinkserviceclient.NewMockInterface(ctrl)
+		repo, err := NewRepo(cli, 60*time.Second, false)
+		assert.NoError(t, err)
+		ctx := context.Background()
+
+		const (
+			rg      = "resource-group"
+			plsName = "pls-name"
+		)
+
+		_, err = repo.CreateOrUpdate(ctx, rg, armnetwork.PrivateLinkService{
+			Name: to.Ptr(plsName),
+		})
+		assert.Error(t, err)
+		assert.ErrorIs(t, err, ErrLoadBalancerFrontendIPConfigurationsNotFound)
+	})
+
+	t.Run("should clear cache", func(t *testing.T) {
+		ctrl := gomock.NewController(t)
+		defer ctrl.Finish()
+		cli := mock_privatelinkserviceclient.NewMockInterface(ctrl)
+		repo, err := NewRepo(cli, 60*time.Second, false)
+		assert.NoError(t, err)
+		ctx := context.Background()
+
+		const (
+			rg                 = "resource-group"
+			plsName            = "pls-name"
+			frontendIPConfigID = "frontend-ip-config-id"
+		)
+
+		cli.EXPECT().List(gomock.Any(), rg).Return([]*armnetwork.PrivateLinkService{
+			{
+				Name: to.Ptr(plsName),
+				Properties: &armnetwork.PrivateLinkServiceProperties{
+					LoadBalancerFrontendIPConfigurations: []*armnetwork.FrontendIPConfiguration{
+						{ID: to.Ptr(frontendIPConfigID)},
+					},
+				},
+			},
+		}, nil).Times(2)
+
+		cli.EXPECT().CreateOrUpdate(gomock.Any(), rg, plsName, gomock.Any()).Return(&armnetwork.PrivateLinkService{
+			Name: to.Ptr(plsName),
+			Properties: &armnetwork.PrivateLinkServiceProperties{
+				LoadBalancerFrontendIPConfigurations: []*armnetwork.FrontendIPConfiguration{
+					{ID: to.Ptr(frontendIPConfigID)},
+				},
+			},
+		}, nil).Times(1)
+
+		v, err := repo.Get(ctx, rg, frontendIPConfigID, cache.CacheReadTypeDefault)
+		assert.NoError(t, err)
+		assert.Equal(t, plsName, *v.Name)
+
+		v, err = repo.CreateOrUpdate(ctx, rg, armnetwork.PrivateLinkService{
+			Name: to.Ptr(plsName),
+			Properties: &armnetwork.PrivateLinkServiceProperties{
+				LoadBalancerFrontendIPConfigurations: []*armnetwork.FrontendIPConfiguration{
+					{ID: to.Ptr(frontendIPConfigID)},
+				},
+			},
+		})
+		assert.NoError(t, err)
+		assert.Equal(t, plsName, *v.Name)
+
+		// cache miss
+		v, err = repo.Get(ctx, rg, frontendIPConfigID, cache.CacheReadTypeDefault)
+		assert.NoError(t, err)
+		assert.Equal(t, plsName, *v.Name)
+
+	})
+}
+
+func TestRepo_Delete(t *testing.T) {
+	t.Run("delete one", func(t *testing.T) {
+		ctrl := gomock.NewController(t)
+		defer ctrl.Finish()
+		cli := mock_privatelinkserviceclient.NewMockInterface(ctrl)
+		repo, err := NewRepo(cli, 60*time.Second, false)
+		assert.NoError(t, err)
+		ctx := context.Background()
+
+		const (
+			rg                 = "resource-group"
+			plsName            = "pls-name"
+			frontendIPConfigID = "frontend-ip-config-id"
+		)
+
+		cli.EXPECT().Delete(gomock.Any(), rg, plsName).Return(nil).Times(1)
+
+		err = repo.Delete(ctx, rg, plsName, frontendIPConfigID)
+		assert.NoError(t, err)
+	})
+
+	t.Run("API error", func(t *testing.T) {
+		ctrl := gomock.NewController(t)
+		defer ctrl.Finish()
+		cli := mock_privatelinkserviceclient.NewMockInterface(ctrl)
+		repo, err := NewRepo(cli, 60*time.Second, false)
+		assert.NoError(t, err)
+		ctx := context.Background()
+
+		const (
+			rg      = "resource-group"
+			plsName = "pls-name"
+		)
+
+		expectedErr := fmt.Errorf("API error")
+
+		cli.EXPECT().Delete(gomock.Any(), rg, plsName).Return(expectedErr).Times(1)
+
+		err = repo.Delete(ctx, rg, plsName, "")
+		assert.Error(t, err)
+		assert.ErrorIs(t, err, expectedErr)
+	})
+}
+
+func TestRepo_DeletePEConnection(t *testing.T) {
+	t.Run("delete one", func(t *testing.T) {
+		ctrl := gomock.NewController(t)
+		defer ctrl.Finish()
+		cli := mock_privatelinkserviceclient.NewMockInterface(ctrl)
+		repo, err := NewRepo(cli, 60*time.Second, false)
+		assert.NoError(t, err)
+		ctx := context.Background()
+
+		const (
+			rg         = "resource-group"
+			plsName    = "pls-name"
+			peConnName = "pe-conn-name"
+		)
+
+		cli.EXPECT().DeletePrivateEndpointConnection(gomock.Any(), rg, plsName, peConnName).Return(nil).Times(1)
+
+		err = repo.DeletePEConnection(ctx, rg, plsName, peConnName)
+		assert.NoError(t, err)
+	})
+
+	t.Run("API error", func(t *testing.T) {
+		ctrl := gomock.NewController(t)
+		defer ctrl.Finish()
+		cli := mock_privatelinkserviceclient.NewMockInterface(ctrl)
+		repo, err := NewRepo(cli, 60*time.Second, false)
+		assert.NoError(t, err)
+		ctx := context.Background()
+
+		const (
+			rg         = "resource-group"
+			plsName    = "pls-name"
+			peConnName = "pe-conn-name"
+		)
+
+		expectedErr := fmt.Errorf("API error")
+
+		cli.EXPECT().DeletePrivateEndpointConnection(gomock.Any(), rg, plsName, peConnName).Return(expectedErr).Times(1)
+
+		err = repo.DeletePEConnection(ctx, rg, plsName, peConnName)
+		assert.Error(t, err)
+		assert.ErrorIs(t, err, expectedErr)
+	})
+}
diff --git a/pkg/provider/zone/repo_test.go b/pkg/provider/zone/repo_test.go
new file mode 100644
index 000000000..e9d269b99
--- /dev/null
+++ b/pkg/provider/zone/repo_test.go
@@ -0,0 +1,68 @@
+/*
+Copyright 2024 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 zone
+
+import (
+	"context"
+	"fmt"
+	"testing"
+
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
+	"github.com/stretchr/testify/assert"
+	"go.uber.org/mock/gomock"
+
+	"sigs.k8s.io/cloud-provider-azure/pkg/azclient/providerclient/mock_providerclient"
+)
+
+func TestRepo_ListZones(t *testing.T) {
+	t.Run("return zones", func(t *testing.T) {
+		ctrl := gomock.NewController(t)
+		defer ctrl.Finish()
+
+		client := mock_providerclient.NewMockInterface(ctrl)
+		repo, err := NewRepo(client)
+		assert.NoError(t, err)
+
+		expectedZones := map[string][]*string{
+			"k1": {to.Ptr("zone1"), to.Ptr("zone2")},
+			"k2": {to.Ptr("zone3"), to.Ptr("zone4")},
+		}
+		client.EXPECT().GetVirtualMachineSupportedZones(gomock.Any()).Return(expectedZones, nil).Times(1)
+
+		zones, err := repo.ListZones(context.Background())
+		assert.NoError(t, err)
+		assert.Equal(t, map[string][]string{
+			"k1": {"zone1", "zone2"},
+			"k2": {"zone3", "zone4"},
+		}, zones)
+	})
+
+	t.Run("return error", func(t *testing.T) {
+		ctrl := gomock.NewController(t)
+		defer ctrl.Finish()
+
+		client := mock_providerclient.NewMockInterface(ctrl)
+		repo, err := NewRepo(client)
+		assert.NoError(t, err)
+
+		expectedErr := fmt.Errorf("error")
+		client.EXPECT().GetVirtualMachineSupportedZones(gomock.Any()).Return(nil, expectedErr).Times(1)
+
+		_, err = repo.ListZones(context.Background())
+		assert.ErrorIs(t, err, expectedErr)
+	})
+}