Repository: kubernetes-sigs/cloud-provider-azure
Pull request #7456 (2024-10-31 03:23:28)

Migrate PLSClient to SDK track v2
<!--  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:

Migrate PLSClient to SDK track v2

#### 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/7456#" title="Author self-approved">zarvd</a>*
**Once this PR has been reviewed and has the lgtm label**, please assign [lzhecheng](https://github.com/lzhecheng) 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/master/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":["lzhecheng"]} -->
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
k8s-ci-robot: @zarvd: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cloud-provider-azure-check | 62b8032a58a9ceb6435c57b6b7ec49880ee2dd84 | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cloud-provider-azure/7456/pull-cloud-provider-azure-check/1851828069120086016) | true | `/test pull-cloud-provider-azure-check`

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

<details>

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

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/cloud-provider-azure/pull/7456#issuecomment-2451186646" title="Approved">MartinForReal</a>*, *<a href="https://github.com/kubernetes-sigs/cloud-provider-azure/pull/7456#" title="Author self-approved">zarvd</a>*

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

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

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

- ~~[OWNERS](https://github.com/kubernetes-sigs/cloud-provider-azure/blob/master/OWNERS)~~ [MartinForReal]

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":[]} -->
k8s-ci-robot: @zarvd: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cloud-provider-azure-e2e-ccm-vmss-shared-probe-capz | f81dedb5c30b88c240bcca7ec9872fe2e6a95119 | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cloud-provider-azure/7456/pull-cloud-provider-azure-e2e-ccm-vmss-shared-probe-capz/1852136393581531136) | true | `/test pull-cloud-provider-azure-e2e-ccm-vmss-shared-probe-capz`

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

<details>

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

Patch:
diff --git a/pkg/provider/azure.go b/pkg/provider/azure.go
index ae8a2fdd4..0b3ab1e97 100644
--- a/pkg/provider/azure.go
+++ b/pkg/provider/azure.go
@@ -59,7 +59,6 @@ import (
 	"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"
@@ -69,6 +68,7 @@ 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/provider/privatelinkservice"
 	"sigs.k8s.io/cloud-provider-azure/pkg/provider/zone"
 
 	"sigs.k8s.io/yaml"
@@ -371,7 +371,6 @@ type Cloud struct {
 	AvailabilitySetsClient          vmasclient.Interface
 	privateendpointclient           privateendpointclient.Interface
 	privatednszonegroupclient       privatednszonegroupclient.Interface
-	PrivateLinkServiceClient        privatelinkserviceclient.Interface
 	ComputeClientFactory            azclient.ClientFactory
 	NetworkClientFactory            azclient.ClientFactory
 	AuthProvider                    *azclient.AuthProvider
@@ -419,13 +418,12 @@ type Cloud struct {
 	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
@@ -747,6 +745,11 @@ func (az *Cloud) InitializeCloudFromConfig(ctx context.Context, config *Config,
 		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 {
@@ -870,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
@@ -989,7 +987,6 @@ func (az *Cloud) configAzureClients(
 	publicIPClientConfig := azClientConfig.WithRateLimiter(az.Config.PublicIPAddressRateLimit)
 	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)
@@ -1038,7 +1035,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)
 }
 
 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/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/vendor/modules.txt b/vendor/modules.txt
index d3bfae79c..8ef0216df 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -1558,6 +1558,7 @@ 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
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)
+}


Test patch:
diff --git a/pkg/provider/azure_loadbalancer_test.go b/pkg/provider/azure_loadbalancer_test.go
index 83eb8acb6..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,11 +47,11 @@ 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/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)
 	})
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 2eed65922..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,12 +49,12 @@ 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/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"
@@ -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) {
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)
+	})
+}