Repository: kubernetes-sigs/cloud-provider-azure
Pull request #7344 (2024-10-17 03:43:19)

[release-1.28] feat: Lock updates on azure resources when other component is doing t…
…he same thing.

This PR utilizes a lease in each service reconciliation to prevent race conditions where cloud provider and others are updating the same azure resources.

<!--  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:

This PR utilizes a lease in each service reconciliation to prevent race conditions where cloud provider and others are updating the same azure resources. If the lease has not expired yet and is held by other component, the service reconciliation will be aborted and retried exponentially.

#### 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
feat: Lock updates on azure resources when other component is doing the same thing.

This PR utilizes a lease in each service reconciliation to prevent race conditions where cloud provider and others are updating the same azure resources.
```

#### 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: @nilo19: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cloud-provider-azure-check-1-28 | cf33ae2a6940cb30dd60fae7a2a44d45950f3c76 | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cloud-provider-azure/7344/pull-cloud-provider-azure-check-1-28/1846758874393088000) | true | `/test pull-cloud-provider-azure-check-1-28`
pull-cloud-provider-azure-unit-1-28 | cf33ae2a6940cb30dd60fae7a2a44d45950f3c76 | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cloud-provider-azure/7344/pull-cloud-provider-azure-unit-1-28/1846758874778963968) | true | `/test pull-cloud-provider-azure-unit-1-28`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cloud-provider-azure&pr=7344). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Anilo19). 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 -->
k8s-ci-robot: @nilo19: 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-unit-1-28 | 26ccb118057e942aacdf0c5668bf6430bc7dc968 | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cloud-provider-azure/7344/pull-cloud-provider-azure-unit-1-28/1846759757814173696) | true | `/test pull-cloud-provider-azure-unit-1-28`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cloud-provider-azure&pr=7344). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Anilo19). 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 -->
k8s-ci-robot: @nilo19: 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-unit-1-28 | e886c22802eb1f25078f854523f80b5569c8debb | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cloud-provider-azure/7344/pull-cloud-provider-azure-unit-1-28/1846760276028821504) | true | `/test pull-cloud-provider-azure-unit-1-28`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cloud-provider-azure&pr=7344). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Anilo19). 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 -->
k8s-ci-robot: @nilo19: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cloud-provider-azure-unit-1-28 | e886c22802eb1f25078f854523f80b5569c8debb | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cloud-provider-azure/7344/pull-cloud-provider-azure-unit-1-28/1846760276028821504) | true | `/test pull-cloud-provider-azure-unit-1-28`
pull-cloud-provider-azure-e2e-ccm-vmss-ip-lb-capz-1-28 | e886c22802eb1f25078f854523f80b5569c8debb | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cloud-provider-azure/7344/pull-cloud-provider-azure-e2e-ccm-vmss-ip-lb-capz-1-28/1846760275819106304) | true | `/test pull-cloud-provider-azure-e2e-ccm-vmss-ip-lb-capz-1-28`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cloud-provider-azure&pr=7344). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Anilo19). 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 -->
k8s-ci-robot: @nilo19: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cloud-provider-azure-unit-1-28 | e886c22802eb1f25078f854523f80b5569c8debb | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cloud-provider-azure/7344/pull-cloud-provider-azure-unit-1-28/1846760276028821504) | true | `/test pull-cloud-provider-azure-unit-1-28`
pull-cloud-provider-azure-e2e-ccm-vmss-ip-lb-capz-1-28 | e886c22802eb1f25078f854523f80b5569c8debb | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cloud-provider-azure/7344/pull-cloud-provider-azure-e2e-ccm-vmss-ip-lb-capz-1-28/1846760275819106304) | true | `/test pull-cloud-provider-azure-e2e-ccm-vmss-ip-lb-capz-1-28`
pull-cloud-provider-azure-e2e-ccm-vmss-multi-slb-capz-1-28 | e886c22802eb1f25078f854523f80b5569c8debb | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cloud-provider-azure/7344/pull-cloud-provider-azure-e2e-ccm-vmss-multi-slb-capz-1-28/1846760275869437952) | true | `/test pull-cloud-provider-azure-e2e-ccm-vmss-multi-slb-capz-1-28`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cloud-provider-azure&pr=7344). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Anilo19). 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 -->
k8s-ci-robot: @nilo19: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cloud-provider-azure-unit-1-28 | e886c22802eb1f25078f854523f80b5569c8debb | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cloud-provider-azure/7344/pull-cloud-provider-azure-unit-1-28/1846760276028821504) | true | `/test pull-cloud-provider-azure-unit-1-28`
pull-cloud-provider-azure-e2e-ccm-vmss-ip-lb-capz-1-28 | e886c22802eb1f25078f854523f80b5569c8debb | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cloud-provider-azure/7344/pull-cloud-provider-azure-e2e-ccm-vmss-ip-lb-capz-1-28/1846760275819106304) | true | `/test pull-cloud-provider-azure-e2e-ccm-vmss-ip-lb-capz-1-28`
pull-cloud-provider-azure-e2e-ccm-vmss-multi-slb-capz-1-28 | e886c22802eb1f25078f854523f80b5569c8debb | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cloud-provider-azure/7344/pull-cloud-provider-azure-e2e-ccm-vmss-multi-slb-capz-1-28/1846760275869437952) | true | `/test pull-cloud-provider-azure-e2e-ccm-vmss-multi-slb-capz-1-28`
pull-cloud-provider-azure-e2e-ccm-vmss-capz-1-28 | e886c22802eb1f25078f854523f80b5569c8debb | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cloud-provider-azure/7344/pull-cloud-provider-azure-e2e-ccm-vmss-capz-1-28/1846760275651334144) | true | `/test pull-cloud-provider-azure-e2e-ccm-vmss-capz-1-28`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cloud-provider-azure&pr=7344). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Anilo19). 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 -->
k8s-ci-robot: @nilo19: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cloud-provider-azure-unit-1-28 | e886c22802eb1f25078f854523f80b5569c8debb | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cloud-provider-azure/7344/pull-cloud-provider-azure-unit-1-28/1846760276028821504) | true | `/test pull-cloud-provider-azure-unit-1-28`
pull-cloud-provider-azure-e2e-ccm-vmss-ip-lb-capz-1-28 | e886c22802eb1f25078f854523f80b5569c8debb | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cloud-provider-azure/7344/pull-cloud-provider-azure-e2e-ccm-vmss-ip-lb-capz-1-28/1846760275819106304) | true | `/test pull-cloud-provider-azure-e2e-ccm-vmss-ip-lb-capz-1-28`
pull-cloud-provider-azure-e2e-ccm-vmss-multi-slb-capz-1-28 | e886c22802eb1f25078f854523f80b5569c8debb | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cloud-provider-azure/7344/pull-cloud-provider-azure-e2e-ccm-vmss-multi-slb-capz-1-28/1846760275869437952) | true | `/test pull-cloud-provider-azure-e2e-ccm-vmss-multi-slb-capz-1-28`
pull-cloud-provider-azure-e2e-ccm-vmss-capz-1-28 | e886c22802eb1f25078f854523f80b5569c8debb | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cloud-provider-azure/7344/pull-cloud-provider-azure-e2e-ccm-vmss-capz-1-28/1846760275651334144) | true | `/test pull-cloud-provider-azure-e2e-ccm-vmss-capz-1-28`
pull-cloud-provider-azure-e2e-ccm-capz-1-28 | e886c22802eb1f25078f854523f80b5569c8debb | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cloud-provider-azure/7344/pull-cloud-provider-azure-e2e-ccm-capz-1-28/1846760275756191744) | true | `/test pull-cloud-provider-azure-e2e-ccm-capz-1-28`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cloud-provider-azure&pr=7344). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Anilo19). 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 -->

Patch:
diff --git a/cmd/cloud-controller-manager/app/controllermanager.go b/cmd/cloud-controller-manager/app/controllermanager.go
index 61600b2da..07b69022c 100644
--- a/cmd/cloud-controller-manager/app/controllermanager.go
+++ b/cmd/cloud-controller-manager/app/controllermanager.go
@@ -312,7 +312,7 @@ func Run(ctx context.Context, c *cloudcontrollerconfig.CompletedConfig, h *contr
 	)
 
 	if c.ComponentConfig.KubeCloudShared.CloudProvider.CloudConfigFile != "" {
-		cloud, err = provider.NewCloudFromConfigFile(ctx, c.ComponentConfig.KubeCloudShared.CloudProvider.CloudConfigFile, true)
+		cloud, err = provider.NewCloudFromConfigFile(ctx, c.ClientBuilder, c.ComponentConfig.KubeCloudShared.CloudProvider.CloudConfigFile, true)
 		if err != nil {
 			klog.Fatalf("Cloud provider azure could not be initialized: %v", err)
 		}
@@ -358,7 +358,8 @@ func Run(ctx context.Context, c *cloudcontrollerconfig.CompletedConfig, h *contr
 
 // startControllers starts the cloud specific controller loops.
 func startControllers(ctx context.Context, controllerContext genericcontrollermanager.ControllerContext, completedConfig *cloudcontrollerconfig.CompletedConfig, stopCh <-chan struct{},
-	cloud cloudprovider.Interface, controllers map[string]initFunc, healthzHandler *controllerhealthz.MutableHealthzHandler) error {
+	cloud cloudprovider.Interface, controllers map[string]initFunc, healthzHandler *controllerhealthz.MutableHealthzHandler,
+) error {
 	// Initialize the cloud provider with a reference to the clientBuilder
 	cloud.Initialize(completedConfig.ClientBuilder, stopCh)
 	// Set the informer on the user cloud object
diff --git a/go.mod b/go.mod
index c0d2ac262..7125d4877 100644
--- a/go.mod
+++ b/go.mod
@@ -5,6 +5,7 @@ go 1.23.1
 require (
 	github.com/Azure/azure-kusto-go v0.16.1
 	github.com/Azure/azure-sdk-for-go v68.0.0+incompatible
+	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.2
 	github.com/Azure/go-autorest/autorest v0.11.29
 	github.com/Azure/go-autorest/autorest/adal v0.9.24
 	github.com/Azure/go-autorest/autorest/date v0.3.0
@@ -32,13 +33,12 @@ require (
 	k8s.io/controller-manager v0.28.14
 	k8s.io/klog/v2 v2.100.1
 	k8s.io/kubelet v0.28.14
-	k8s.io/utils v0.0.0-20230406110748-d93618cff8a2
+	k8s.io/utils v0.0.0-20240921022957-49e7df575cb6
 	sigs.k8s.io/yaml v1.3.0
 )
 
 require (
 	github.com/Azure/azure-pipeline-go v0.2.3 // indirect
-	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.2 // indirect
 	github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1 // indirect
 	github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
 	github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0 // indirect
diff --git a/go.sum b/go.sum
index 7c509861f..3577b4f63 100644
--- a/go.sum
+++ b/go.sum
@@ -440,8 +440,8 @@ k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5Ohx
 k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM=
 k8s.io/kubelet v0.28.14 h1:nKLJsbVZVBbe9i5VYhWWxv3rZ/63C/tTrweHTDmZBjY=
 k8s.io/kubelet v0.28.14/go.mod h1:wGsgJm3PsckoUMbs5r8gCOc7PPXxQcyzNEHbF8U/L8k=
-k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk=
-k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
+k8s.io/utils v0.0.0-20240921022957-49e7df575cb6 h1:MDF6h2H/h4tbzmtIKTuctcwZmY0tY9mD9fNT47QO6HI=
+k8s.io/utils v0.0.0-20240921022957-49e7df575cb6/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
 sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.1.2 h1:trsWhjU5jZrx6UvFu4WzQDrN7Pga4a7Qg+zcfcj64PA=
 sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.1.2/go.mod h1:+qG7ISXqCDVVcyO8hLn12AKVYYUjM7ftlqsqmrhMZE0=
 sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
diff --git a/pkg/consts/consts.go b/pkg/consts/consts.go
index 2227d8a96..55b14c69b 100644
--- a/pkg/consts/consts.go
+++ b/pkg/consts/consts.go
@@ -594,3 +594,15 @@ const (
 	VMPowerStateDeallocating = "deallocating"
 	VMPowerStateUnknown      = "unknown"
 )
+
+// Azure resource lock
+const (
+	AzureResourceLockHolderNameCloudControllerManager         = "cloud-controller-manager"
+	AzureResourceLockLeaseName                                = "aks-managed-resource-locker"
+	AzureResourceLockLeaseNamespace                           = "kube-system"
+	AzureResourceLockLeaseDuration                            = int32(15 * 60)
+	AzureResourceLockPreviousHolderNameAnnotation             = "aks-managed-resource-locker-previous-holder"
+	AzureResourceLockFailedToLockErrorTemplate                = "%s failed due to fail to lock azure resources. This may because another component is trying to update azure resources, e.g., load balancers. This will be automatically retried by cloud provider exponentially: %w"
+	AzureResourceLockFailedToUnlockErrorTemplate              = "%s failed due to fail to unlock azure resources. This will be automatically retried by cloud provider exponentially, but can also be manually unlocked by removing the holder of the lease '%s/%s'. Before doing this, please be aware that this could lead to unexpected issues: %w"
+	AzureResourceLockFailedToReconcileWithUnlockErrorTemplate = "%s failed due to %s, and when unlocking azure resources another error happened: %w"
+)
diff --git a/pkg/node/node.go b/pkg/node/node.go
index 11256e3dc..da441394e 100644
--- a/pkg/node/node.go
+++ b/pkg/node/node.go
@@ -35,7 +35,7 @@ type IMDSNodeProvider struct {
 
 // NewIMDSNodeProvider creates a new IMDSNodeProvider.
 func NewIMDSNodeProvider(ctx context.Context) *IMDSNodeProvider {
-	az, err := azureprovider.NewCloud(ctx, bytes.NewBuffer([]byte(`{
+	az, err := azureprovider.NewCloud(ctx, nil, bytes.NewBuffer([]byte(`{
 			"useInstanceMetadata": true,
 			"vmType": "vmss"
 		}`)), false)
diff --git a/pkg/node/nodearm.go b/pkg/node/nodearm.go
index 58dbc5f78..ac48322b8 100644
--- a/pkg/node/nodearm.go
+++ b/pkg/node/nodearm.go
@@ -47,8 +47,7 @@ func NewARMNodeProvider(ctx context.Context, cloudConfigFilePath string) *ARMNod
 		}
 		defer configFile.Close()
 
-		az, err = azureprovider.NewCloud(ctx, configFile, false)
-
+		az, err = azureprovider.NewCloud(ctx, nil, configFile, false)
 		if err != nil {
 			klog.Fatalf("Failed to initialize Azure cloud provider: %v", err)
 		}
diff --git a/pkg/provider/azure.go b/pkg/provider/azure.go
index 32bf5f150..02e9b948f 100644
--- a/pkg/provider/azure.go
+++ b/pkg/provider/azure.go
@@ -466,20 +466,33 @@ type Cloud struct {
 	multipleStandardLoadBalancersActiveNodesLock    sync.Mutex
 	localServiceNameToServiceInfoMap                sync.Map
 	endpointSlicesCache                             sync.Map
+
+	azureResourceLocker *AzureResourceLocker
 }
 
 // NewCloud returns a Cloud with initialized clients
-func NewCloud(ctx context.Context, configReader io.Reader, callFromCCM bool) (cloudprovider.Interface, error) {
+func NewCloud(ctx context.Context, clientBuilder cloudprovider.ControllerClientBuilder, configReader io.Reader, callFromCCM bool) (cloudprovider.Interface, error) {
 	az, err := NewCloudWithoutFeatureGates(ctx, configReader, callFromCCM)
 	if err != nil {
 		return nil, err
 	}
 	az.ipv6DualStackEnabled = true
 
+	if clientBuilder != nil {
+		az.KubeClient = clientBuilder.ClientOrDie("azure-cloud-provider")
+	}
+	az.azureResourceLocker = NewAzureResourceLocker(
+		az,
+		consts.AzureResourceLockHolderNameCloudControllerManager,
+		consts.AzureResourceLockLeaseName,
+		consts.AzureResourceLockLeaseNamespace,
+		consts.AzureResourceLockLeaseDuration,
+	)
+
 	return az, nil
 }
 
-func NewCloudFromConfigFile(ctx context.Context, configFilePath string, calFromCCM bool) (cloudprovider.Interface, error) {
+func NewCloudFromConfigFile(ctx context.Context, clientBuilder cloudprovider.ControllerClientBuilder, configFilePath string, calFromCCM bool) (cloudprovider.Interface, error) {
 	var (
 		cloud cloudprovider.Interface
 		err   error
@@ -494,11 +507,11 @@ func NewCloudFromConfigFile(ctx context.Context, configFilePath string, calFromC
 		}
 
 		defer config.Close()
-		cloud, err = NewCloud(ctx, config, calFromCCM)
+		cloud, err = NewCloud(ctx, clientBuilder, config, calFromCCM)
 	} else {
 		// Pass explicit nil so plugins can actually check for nil. See
 		// "Why is my nil error value not equal to nil?" in golang.org/doc/faq.
-		cloud, err = NewCloud(ctx, nil, false)
+		cloud, err = NewCloud(ctx, clientBuilder, nil, false)
 	}
 
 	if err != nil {
@@ -965,7 +978,8 @@ func (az *Cloud) setCloudProviderBackoffDefaults(config *Config) wait.Backoff {
 func (az *Cloud) configAzureClients(
 	servicePrincipalToken *adal.ServicePrincipalToken,
 	multiTenantServicePrincipalToken *adal.MultiTenantServicePrincipalToken,
-	networkResourceServicePrincipalToken *adal.ServicePrincipalToken) {
+	networkResourceServicePrincipalToken *adal.ServicePrincipalToken,
+) {
 	azClientConfig := az.getAzureClientConfig(servicePrincipalToken)
 
 	// Prepare AzureClientConfig for all azure clients
diff --git a/pkg/provider/azure_loadbalancer.go b/pkg/provider/azure_loadbalancer.go
index 05df40d13..932bb51c0 100644
--- a/pkg/provider/azure_loadbalancer.go
+++ b/pkg/provider/azure_loadbalancer.go
@@ -181,7 +181,7 @@ func (az *Cloud) reconcileService(ctx context.Context, clusterName string, servi
 }
 
 // EnsureLoadBalancer creates a new load balancer 'name', or updates the existing one. Returns the status of the balancer
-func (az *Cloud) EnsureLoadBalancer(ctx context.Context, clusterName string, service *v1.Service, nodes []*v1.Node) (*v1.LoadBalancerStatus, error) {
+func (az *Cloud) EnsureLoadBalancer(ctx context.Context, clusterName string, service *v1.Service, nodes []*v1.Node) (lbStatus *v1.LoadBalancerStatus, err error) {
 	// When a client updates the internal load balancer annotation,
 	// the service may be switched from an internal LB to a public one, or vice versa.
 	// Here we'll firstly ensure service do not lie in the opposite LB.
@@ -190,7 +190,41 @@ func (az *Cloud) EnsureLoadBalancer(ctx context.Context, clusterName string, ser
 	az.serviceReconcileLock.Lock()
 	defer az.serviceReconcileLock.Unlock()
 
-	var err error
+	if az.azureResourceLocker != nil {
+		err = az.azureResourceLocker.Lock(ctx)
+		if err != nil {
+			klog.Error(err, "EnsureLoadBalancer: failed to lock azure resources")
+			return nil, fmt.Errorf(
+				consts.AzureResourceLockFailedToLockErrorTemplate,
+				"EnsureLoadBalancer",
+				err,
+			)
+		}
+
+		defer func() {
+			unlockErr := az.azureResourceLocker.Unlock(ctx)
+			if unlockErr != nil {
+				unlockErr = fmt.Errorf(
+					consts.AzureResourceLockFailedToUnlockErrorTemplate,
+					"EnsureLoadBalancer",
+					consts.AzureResourceLockLeaseNamespace,
+					consts.AzureResourceLockLeaseName,
+					unlockErr,
+				)
+			}
+			if err == nil {
+				err = unlockErr
+			} else if unlockErr != nil {
+				err = fmt.Errorf(
+					consts.AzureResourceLockFailedToReconcileWithUnlockErrorTemplate,
+					"EnsureLoadBalancer",
+					err,
+					unlockErr,
+				)
+			}
+		}()
+	}
+
 	serviceName := getServiceName(service)
 	mc := metrics.NewMetricContext("services", "ensure_loadbalancer", az.ResourceGroup, az.getNetworkResourceSubscriptionID(), serviceName)
 	klog.V(5).InfoS("EnsureLoadBalancer Start", "service", serviceName, "cluster", clusterName, "service_spec", service)
@@ -201,7 +235,7 @@ func (az *Cloud) EnsureLoadBalancer(ctx context.Context, clusterName string, ser
 		klog.V(5).InfoS("EnsureLoadBalancer Finish", "service", serviceName, "cluster", clusterName, "service_spec", service, "error", err)
 	}()
 
-	lbStatus, err := az.reconcileService(ctx, clusterName, service, nodes)
+	lbStatus, err = az.reconcileService(ctx, clusterName, service, nodes)
 	if err != nil {
 		return nil, err
 	}
@@ -266,6 +300,41 @@ func (az *Cloud) UpdateLoadBalancer(ctx context.Context, clusterName string, ser
 		return nil
 	}
 
+	if az.azureResourceLocker != nil {
+		err = az.azureResourceLocker.Lock(ctx)
+		if err != nil {
+			klog.Error(err, "UpdateLoadBalancer: failed to lock azure resources")
+			return fmt.Errorf(
+				consts.AzureResourceLockFailedToLockErrorTemplate,
+				"UpdateLoadBalancer",
+				err,
+			)
+		}
+
+		defer func() {
+			unlockErr := az.azureResourceLocker.Unlock(ctx)
+			if unlockErr != nil {
+				unlockErr = fmt.Errorf(
+					consts.AzureResourceLockFailedToUnlockErrorTemplate,
+					"UpdateLoadBalancer",
+					consts.AzureResourceLockLeaseNamespace,
+					consts.AzureResourceLockLeaseName,
+					unlockErr,
+				)
+			}
+			if err == nil {
+				err = unlockErr
+			} else if unlockErr != nil {
+				err = fmt.Errorf(
+					consts.AzureResourceLockFailedToReconcileWithUnlockErrorTemplate,
+					"UpdateLoadBalancer",
+					err,
+					unlockErr,
+				)
+			}
+		}()
+	}
+
 	_, err = az.reconcileService(ctx, clusterName, service, nodes)
 	if err != nil {
 		return err
@@ -281,12 +350,11 @@ func (az *Cloud) UpdateLoadBalancer(ctx context.Context, clusterName string, ser
 // This construction is useful because many cloud providers' load balancers
 // have multiple underlying components, meaning a Get could say that the LB
 // doesn't exist even if some part of it is still laying around.
-func (az *Cloud) EnsureLoadBalancerDeleted(ctx context.Context, clusterName string, service *v1.Service) error {
+func (az *Cloud) EnsureLoadBalancerDeleted(ctx context.Context, clusterName string, service *v1.Service) (err error) {
 	// Serialize service reconcile process
 	az.serviceReconcileLock.Lock()
 	defer az.serviceReconcileLock.Unlock()
 
-	var err error
 	serviceName := getServiceName(service)
 	mc := metrics.NewMetricContext("services", "ensure_loadbalancer_deleted", az.ResourceGroup, az.getNetworkResourceSubscriptionID(), serviceName)
 	klog.V(5).InfoS("EnsureLoadBalancerDeleted Start", "service", serviceName, "cluster", clusterName, "service_spec", service)
@@ -296,6 +364,41 @@ func (az *Cloud) EnsureLoadBalancerDeleted(ctx context.Context, clusterName stri
 		klog.V(5).InfoS("EnsureLoadBalancerDeleted Finish", "service", serviceName, "cluster", clusterName, "service_spec", service, "error", err)
 	}()
 
+	if az.azureResourceLocker != nil {
+		err = az.azureResourceLocker.Lock(ctx)
+		if err != nil {
+			klog.Error(err, "EnsureLoadBalancerDeleted: failed to lock azure resources")
+			return fmt.Errorf(
+				consts.AzureResourceLockFailedToLockErrorTemplate,
+				"EnsureLoadBalancerDeleted",
+				err,
+			)
+		}
+
+		defer func() {
+			unlockErr := az.azureResourceLocker.Unlock(ctx)
+			if unlockErr != nil {
+				unlockErr = fmt.Errorf(
+					consts.AzureResourceLockFailedToUnlockErrorTemplate,
+					"EnsureLoadBalancerDeleted",
+					consts.AzureResourceLockLeaseNamespace,
+					consts.AzureResourceLockLeaseName,
+					unlockErr,
+				)
+			}
+			if err == nil {
+				err = unlockErr
+			} else if unlockErr != nil {
+				err = fmt.Errorf(
+					consts.AzureResourceLockFailedToReconcileWithUnlockErrorTemplate,
+					"EnsureLoadBalancerDeleted",
+					err,
+					unlockErr,
+				)
+			}
+		}()
+	}
+
 	_, _, _, lbIPsPrimaryPIPs, _, err := az.getServiceLoadBalancer(service, clusterName, nil, false, &[]network.LoadBalancer{})
 	if err != nil && !retry.HasStatusForbiddenOrIgnoredError(err) {
 		return err
@@ -2142,8 +2245,7 @@ func (az *Cloud) recordExistingNodesOnLoadBalancers(clusterName string, lbs *[]n
 					if strings.EqualFold(trimSuffixIgnoreCase(
 						lbName, consts.InternalLoadBalancerNameSuffix,
 					), multiSLBConfig.Name) {
-						az.MultipleStandardLoadBalancerConfigurations[i].ActiveNodes =
-							utilsets.SafeInsert(multiSLBConfig.ActiveNodes, nodeNames...)
+						az.MultipleStandardLoadBalancerConfigurations[i].ActiveNodes = utilsets.SafeInsert(multiSLBConfig.ActiveNodes, nodeNames...)
 					}
 				}
 			}
@@ -2268,7 +2370,8 @@ func (az *Cloud) reconcileFrontendIPConfigs(clusterName string,
 	lb *network.LoadBalancer,
 	status *v1.LoadBalancerStatus,
 	wantLb bool,
-	lbFrontendIPConfigNames map[bool]string) ([]*network.FrontendIPConfiguration, []network.FrontendIPConfiguration, bool, error) {
+	lbFrontendIPConfigNames map[bool]string,
+) ([]*network.FrontendIPConfiguration, []network.FrontendIPConfiguration, bool, error) {
 	var err error
 	lbName := *lb.Name
 	serviceName := getServiceName(service)
@@ -2474,7 +2577,8 @@ func (az *Cloud) getFrontendZones(
 	fipConfig *network.FrontendIPConfiguration,
 	previousZone *[]string,
 	isFipChanged bool,
-	serviceName, lbFrontendIPConfigName string) error {
+	serviceName, lbFrontendIPConfigName string,
+) error {
 	if !isFipChanged { // fetch zone information from API for new frontends
 		// only add zone information for new internal frontend IP configurations for standard load balancer not deployed to an edge zone.
 		location := az.Location
@@ -2606,8 +2710,8 @@ func (az *Cloud) getExpectedLBRules(
 	lbFrontendIPConfigID string,
 	lbBackendPoolID string,
 	lbName string,
-	isIPv6 bool) ([]network.Probe, []network.LoadBalancingRule, error) {
-
+	isIPv6 bool,
+) ([]network.Probe, []network.LoadBalancingRule, error) {
 	var expectedRules []network.LoadBalancingRule
 	var expectedProbes []network.Probe
 
@@ -2656,7 +2760,7 @@ func (az *Cloud) getExpectedLBRules(
 		if err != nil {
 			return nil, nil, fmt.Errorf("error generate lb rule for ha mod loadbalancer. err: %w", err)
 		}
-		//Here we need to find one health probe rule for the HA lb rule.
+		// Here we need to find one health probe rule for the HA lb rule.
 		if nodeEndpointHealthprobe == nil {
 			// use user customized health probe rule if any
 			for _, port := range service.Spec.Ports {
@@ -2664,7 +2768,7 @@ func (az *Cloud) getExpectedLBRules(
 				if err != nil {
 					klog.V(2).ErrorS(err, "error occurred when buildHealthProbeRulesForPort", "service", service.Name, "namespace", service.Namespace,
 						"rule-name", lbRuleName, "port", port.Port)
-					//ignore error because we only need one correct rule
+					// ignore error because we only need one correct rule
 				}
 				if portprobe != nil {
 					props.Probe = &network.SubResource{
@@ -2761,7 +2865,8 @@ func (az *Cloud) getExpectedLBRules(
 func (az *Cloud) getExpectedLoadBalancingRulePropertiesForPort(
 	service *v1.Service,
 	lbFrontendIPConfigID string,
-	lbBackendPoolID string, servicePort v1.ServicePort, transportProto network.TransportProtocol) (*network.LoadBalancingRulePropertiesFormat, error) {
+	lbBackendPoolID string, servicePort v1.ServicePort, transportProto network.TransportProtocol,
+) (*network.LoadBalancingRulePropertiesFormat, error) {
 	var err error
 
 	loadDistribution := network.LoadDistributionDefault
@@ -2817,7 +2922,8 @@ func (az *Cloud) getExpectedLoadBalancingRulePropertiesForPort(
 func (az *Cloud) getExpectedHAModeLoadBalancingRuleProperties(
 	service *v1.Service,
 	lbFrontendIPConfigID string,
-	lbBackendPoolID string) (*network.LoadBalancingRulePropertiesFormat, error) {
+	lbBackendPoolID string,
+) (*network.LoadBalancingRulePropertiesFormat, error) {
 	props, err := az.getExpectedLoadBalancingRulePropertiesForPort(service, lbFrontendIPConfigID, lbBackendPoolID, v1.ServicePort{}, network.TransportProtocolAll)
 	if err != nil {
 		return nil, fmt.Errorf("error generate lb rule for ha mod loadbalancer. err: %w", err)
@@ -3053,7 +3159,7 @@ func (az *Cloud) reconcileSecurityRules(sg network.SecurityGroup,
 							}
 							if len(existingPrefixes) == 1 {
 								shouldDeleteNSGRule = true
-								break //shared nsg rule has only one entry and entry owned by deleted svc has been found. skip the rest of the entries
+								break // shared nsg rule has only one entry and entry owned by deleted svc has been found. skip the rest of the entries
 							} else {
 								newDestinations := append(existingPrefixes[:addressIndex], existingPrefixes[addressIndex+1:]...)
 								sharedRule.DestinationAddressPrefixes = &newDestinations
diff --git a/pkg/provider/azure_lock.go b/pkg/provider/azure_lock.go
new file mode 100644
index 000000000..12a325584
--- /dev/null
+++ b/pkg/provider/azure_lock.go
@@ -0,0 +1,244 @@
+/*
+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 provider
+
+import (
+	"context"
+	"errors"
+	"strings"
+	"time"
+
+	v1 "k8s.io/api/coordination/v1"
+	k8sapierrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/kubernetes"
+	"k8s.io/klog/v2"
+	"k8s.io/utils/ptr"
+
+	"sigs.k8s.io/cloud-provider-azure/pkg/consts"
+)
+
+type AzureResourceLocker struct {
+	*Cloud
+	holder               string
+	leaseName            string
+	leaseNamespace       string
+	leaseDurationSeconds int32
+}
+
+func NewAzureResourceLocker(
+	cloud *Cloud,
+	holder, leaseName, leaseNamespace string,
+	leaseDurationSeconds int32,
+) *AzureResourceLocker {
+	return &AzureResourceLocker{
+		Cloud:                cloud,
+		holder:               holder,
+		leaseName:            leaseName,
+		leaseNamespace:       leaseNamespace,
+		leaseDurationSeconds: leaseDurationSeconds,
+	}
+}
+
+// Lock creates a lease if it does not exist and acquires the lease.
+// If the lease has not expired yet and is held by another holder, it will return an error.
+func (l *AzureResourceLocker) Lock(ctx context.Context) error {
+	if err := createLeaseIfNotExists(
+		ctx, l.leaseNamespace, l.leaseName, l.leaseDurationSeconds, l.KubeClient,
+	); err != nil {
+		return err
+	}
+	if err := l.acquireLease(
+		ctx, l.KubeClient, l.holder, l.leaseNamespace, l.leaseName); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// Unlock releases the lease if needed.
+func (l *AzureResourceLocker) Unlock(ctx context.Context) error {
+	if err := releaseLease(
+		ctx, l.KubeClient, l.leaseNamespace, l.leaseName, l.holder,
+	); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func createLeaseIfNotExists(
+	ctx context.Context,
+	leaseNamespace, leaseName string,
+	leaseDurationSeconds int32,
+	clientset kubernetes.Interface,
+) error {
+	logger := klog.Background().WithName("createLeaseIfNotExists").
+		WithValues("leaseNamespace", leaseNamespace, "leaseName", leaseName,
+			"leaseDurationSeconds", leaseDurationSeconds).V(4)
+
+	lease := &v1.Lease{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      leaseName,
+			Namespace: leaseNamespace,
+		},
+		Spec: v1.LeaseSpec{
+			LeaseDurationSeconds: ptr.To(leaseDurationSeconds),
+		},
+	}
+
+	_, err := clientset.CoordinationV1().Leases(leaseNamespace).Get(ctx, leaseName, metav1.GetOptions{})
+	if err != nil {
+		if !k8sapierrors.IsNotFound(err) {
+			logger.Error(err, "failed to get lease")
+			return err
+		}
+		// Lease does not exist, create it
+		_, err = clientset.CoordinationV1().Leases(leaseNamespace).Create(ctx, lease, metav1.CreateOptions{})
+		if err != nil {
+			logger.Error(err, "failed to create lease")
+			return err
+		}
+		logger.Info("Successfully created lease.")
+	} else {
+		logger.Info("Lease already exists.")
+	}
+
+	return nil
+}
+
+func (l *AzureResourceLocker) acquireLease(
+	ctx context.Context,
+	clientset kubernetes.Interface,
+	holder, leaseNamespace, leaseName string,
+) error {
+	logger := klog.Background().WithName("acquireLease").
+		WithValues("holder", holder, "leaseNamespace", leaseNamespace, "leaseName", leaseName).V(4)
+
+	lease, err := clientset.CoordinationV1().Leases(leaseNamespace).Get(ctx, leaseName, metav1.GetOptions{})
+	if err != nil {
+		logger.Error(err, "failed to get lease")
+		return err
+	}
+
+	if !hasExpired(lease) {
+		// If the lease has not expired, but the previous holder is the same,
+		// it means the same previous operation failed and the releaseLease
+		// also failed. In this case we should continue acquiring the lease.
+		// This should be a rare case, otherwise we should not acquire the lease.
+		prevHolder := ptr.Deref(lease.Spec.HolderIdentity, "")
+		if !strings.EqualFold(prevHolder, holder) {
+			errMsg := "Lease has not expired yet, another component such as aks rp may be processing another request. This would be automatically recovered after the lease expires."
+			err := errors.New(errMsg)
+			logger.Error(err, "failed to acquire lease")
+			return err
+		}
+	}
+
+	lease.Spec.HolderIdentity = ptr.To(holder)
+	now := &metav1.MicroTime{Time: time.Now()}
+	lease.Spec.AcquireTime = now
+	lease.Spec.RenewTime = now
+	_, err = clientset.CoordinationV1().
+		Leases(leaseNamespace).Update(ctx, lease, metav1.UpdateOptions{})
+	if err != nil {
+		logger.Error(err, "failed to acquire lease")
+		return err
+	}
+
+	// invalidate caches if the previous holder was not the cloud controller manager
+	if lease.Annotations != nil &&
+		!strings.EqualFold(
+			lease.Annotations[consts.AzureResourceLockPreviousHolderNameAnnotation],
+			consts.AzureResourceLockHolderNameCloudControllerManager,
+		) {
+		l.lbCache, err = l.newLBCache()
+		if err != nil {
+			return err
+		}
+		if err := l.VMSet.RefreshCaches(ctx); err != nil {
+			return err
+		}
+	}
+
+	logger.Info("Successfully acquired lease.")
+	return nil
+}
+
+func hasExpired(lease *v1.Lease) bool {
+	if ptr.Deref(lease.Spec.HolderIdentity, "") == "" {
+		return true // No holder, hence expired
+	}
+
+	// Calculate the expiration time
+	leaseDuration := time.Duration(ptr.Deref(lease.Spec.LeaseDurationSeconds, 0)) * time.Second
+	var expirationTime time.Time
+	if lease.Spec.RenewTime != nil {
+		expirationTime = lease.Spec.RenewTime.Add(leaseDuration)
+	}
+
+	return time.Now().After(expirationTime)
+}
+
+func releaseLease(
+	ctx context.Context,
+	clientset kubernetes.Interface,
+	leaseNamespace, leaseName, holder string,
+) error {
+	logger := klog.Background().WithName("releaseLease").
+		WithValues("leaseNamespace", leaseNamespace, "leaseName", leaseName, "holder", holder).V(4)
+
+	lease, err := clientset.CoordinationV1().
+		Leases(leaseNamespace).Get(ctx, leaseName, metav1.GetOptions{})
+	if err != nil {
+		if k8sapierrors.IsNotFound(err) {
+			logger.Info("lease does not exist, no need to release it.")
+			return nil
+		}
+		logger.Error(err, "failed to get lease")
+		return err
+	}
+
+	prevHolder := ptr.Deref(lease.Spec.HolderIdentity, "")
+	if !strings.EqualFold(prevHolder, holder) {
+		logger.Info(
+			"%s is holding the lease instead of %s, no need to release it.",
+			prevHolder, holder,
+		)
+		return nil
+	}
+
+	if hasExpired(lease) {
+		logger.Info("lease has already expired, no need to release it.")
+		return nil
+	}
+
+	lease.Spec.HolderIdentity = ptr.To("")
+	if lease.Annotations == nil {
+		lease.Annotations = make(map[string]string)
+	}
+	lease.Annotations[consts.AzureResourceLockPreviousHolderNameAnnotation] = consts.AzureResourceLockHolderNameCloudControllerManager
+	_, err = clientset.CoordinationV1().
+		Leases(leaseNamespace).Update(ctx, lease, metav1.UpdateOptions{})
+	if err != nil {
+		logger.Error(err, "failed to release lease")
+		return err
+	}
+
+	logger.Info("Successfully released lease.")
+	return nil
+}
diff --git a/pkg/provider/azure_mock_vmsets.go b/pkg/provider/azure_mock_vmsets.go
index c4102e2e9..05dc3345d 100644
--- a/pkg/provider/azure_mock_vmsets.go
+++ b/pkg/provider/azure_mock_vmsets.go
@@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
+// Package provider is a generated GoMock package.
 package provider
 
 import (
@@ -27,7 +28,7 @@ import (
 
 	v1 "k8s.io/api/core/v1"
 	types "k8s.io/apimachinery/pkg/types"
-	cloud_provider "k8s.io/cloud-provider"
+	cloudprovider "k8s.io/cloud-provider"
 
 	cache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
 )
@@ -65,7 +66,7 @@ func (m *MockVMSet) AttachDisk(ctx context.Context, nodeName types.NodeName, dis
 }
 
 // AttachDisk indicates an expected call of AttachDisk.
-func (mr *MockVMSetMockRecorder) AttachDisk(ctx, nodeName, diskMap interface{}) *gomock.Call {
+func (mr *MockVMSetMockRecorder) AttachDisk(ctx, nodeName, diskMap any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AttachDisk", reflect.TypeOf((*MockVMSet)(nil).AttachDisk), ctx, nodeName, diskMap)
 }
@@ -79,7 +80,7 @@ func (m *MockVMSet) DeleteCacheForNode(nodeName string) error {
 }
 
 // DeleteCacheForNode indicates an expected call of DeleteCacheForNode.
-func (mr *MockVMSetMockRecorder) DeleteCacheForNode(nodeName interface{}) *gomock.Call {
+func (mr *MockVMSetMockRecorder) DeleteCacheForNode(nodeName any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteCacheForNode", reflect.TypeOf((*MockVMSet)(nil).DeleteCacheForNode), nodeName)
 }
@@ -93,7 +94,7 @@ func (m *MockVMSet) DetachDisk(ctx context.Context, nodeName types.NodeName, dis
 }
 
 // DetachDisk indicates an expected call of DetachDisk.
-func (mr *MockVMSetMockRecorder) DetachDisk(ctx, nodeName, diskMap interface{}) *gomock.Call {
+func (mr *MockVMSetMockRecorder) DetachDisk(ctx, nodeName, diskMap any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DetachDisk", reflect.TypeOf((*MockVMSet)(nil).DetachDisk), ctx, nodeName, diskMap)
 }
@@ -108,7 +109,7 @@ func (m *MockVMSet) EnsureBackendPoolDeleted(service *v1.Service, backendPoolIDs
 }
 
 // EnsureBackendPoolDeleted indicates an expected call of EnsureBackendPoolDeleted.
-func (mr *MockVMSetMockRecorder) EnsureBackendPoolDeleted(service, backendPoolIDs, vmSetName, backendAddressPools, deleteFromVMSet interface{}) *gomock.Call {
+func (mr *MockVMSetMockRecorder) EnsureBackendPoolDeleted(service, backendPoolIDs, vmSetName, backendAddressPools, deleteFromVMSet any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnsureBackendPoolDeleted", reflect.TypeOf((*MockVMSet)(nil).EnsureBackendPoolDeleted), service, backendPoolIDs, vmSetName, backendAddressPools, deleteFromVMSet)
 }
@@ -122,7 +123,7 @@ func (m *MockVMSet) EnsureBackendPoolDeletedFromVMSets(vmSetNamesMap map[string]
 }
 
 // EnsureBackendPoolDeletedFromVMSets indicates an expected call of EnsureBackendPoolDeletedFromVMSets.
-func (mr *MockVMSetMockRecorder) EnsureBackendPoolDeletedFromVMSets(vmSetNamesMap, backendPoolIDs interface{}) *gomock.Call {
+func (mr *MockVMSetMockRecorder) EnsureBackendPoolDeletedFromVMSets(vmSetNamesMap, backendPoolIDs any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnsureBackendPoolDeletedFromVMSets", reflect.TypeOf((*MockVMSet)(nil).EnsureBackendPoolDeletedFromVMSets), vmSetNamesMap, backendPoolIDs)
 }
@@ -140,7 +141,7 @@ func (m *MockVMSet) EnsureHostInPool(service *v1.Service, nodeName types.NodeNam
 }
 
 // EnsureHostInPool indicates an expected call of EnsureHostInPool.
-func (mr *MockVMSetMockRecorder) EnsureHostInPool(service, nodeName, backendPoolID, vmSetName interface{}) *gomock.Call {
+func (mr *MockVMSetMockRecorder) EnsureHostInPool(service, nodeName, backendPoolID, vmSetName any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnsureHostInPool", reflect.TypeOf((*MockVMSet)(nil).EnsureHostInPool), service, nodeName, backendPoolID, vmSetName)
 }
@@ -154,7 +155,7 @@ func (m *MockVMSet) EnsureHostsInPool(service *v1.Service, nodes []*v1.Node, bac
 }
 
 // EnsureHostsInPool indicates an expected call of EnsureHostsInPool.
-func (mr *MockVMSetMockRecorder) EnsureHostsInPool(service, nodes, backendPoolID, vmSetName interface{}) *gomock.Call {
+func (mr *MockVMSetMockRecorder) EnsureHostsInPool(service, nodes, backendPoolID, vmSetName any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnsureHostsInPool", reflect.TypeOf((*MockVMSet)(nil).EnsureHostsInPool), service, nodes, backendPoolID, vmSetName)
 }
@@ -169,7 +170,7 @@ func (m *MockVMSet) GetAgentPoolVMSetNames(nodes []*v1.Node) (*[]string, error)
 }
 
 // GetAgentPoolVMSetNames indicates an expected call of GetAgentPoolVMSetNames.
-func (mr *MockVMSetMockRecorder) GetAgentPoolVMSetNames(nodes interface{}) *gomock.Call {
+func (mr *MockVMSetMockRecorder) GetAgentPoolVMSetNames(nodes any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAgentPoolVMSetNames", reflect.TypeOf((*MockVMSet)(nil).GetAgentPoolVMSetNames), nodes)
 }
@@ -185,7 +186,7 @@ func (m *MockVMSet) GetDataDisks(nodeName types.NodeName, crt cache.AzureCacheRe
 }
 
 // GetDataDisks indicates an expected call of GetDataDisks.
-func (mr *MockVMSetMockRecorder) GetDataDisks(nodeName, crt interface{}) *gomock.Call {
+func (mr *MockVMSetMockRecorder) GetDataDisks(nodeName, crt any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDataDisks", reflect.TypeOf((*MockVMSet)(nil).GetDataDisks), nodeName, crt)
 }
@@ -201,7 +202,7 @@ func (m *MockVMSet) GetIPByNodeName(name string) (string, string, error) {
 }
 
 // GetIPByNodeName indicates an expected call of GetIPByNodeName.
-func (mr *MockVMSetMockRecorder) GetIPByNodeName(name interface{}) *gomock.Call {
+func (mr *MockVMSetMockRecorder) GetIPByNodeName(name any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetIPByNodeName", reflect.TypeOf((*MockVMSet)(nil).GetIPByNodeName), name)
 }
@@ -216,7 +217,7 @@ func (m *MockVMSet) GetInstanceIDByNodeName(name string) (string, error) {
 }
 
 // GetInstanceIDByNodeName indicates an expected call of GetInstanceIDByNodeName.
-func (mr *MockVMSetMockRecorder) GetInstanceIDByNodeName(name interface{}) *gomock.Call {
+func (mr *MockVMSetMockRecorder) GetInstanceIDByNodeName(name any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInstanceIDByNodeName", reflect.TypeOf((*MockVMSet)(nil).GetInstanceIDByNodeName), name)
 }
@@ -231,7 +232,7 @@ func (m *MockVMSet) GetInstanceTypeByNodeName(name string) (string, error) {
 }
 
 // GetInstanceTypeByNodeName indicates an expected call of GetInstanceTypeByNodeName.
-func (mr *MockVMSetMockRecorder) GetInstanceTypeByNodeName(name interface{}) *gomock.Call {
+func (mr *MockVMSetMockRecorder) GetInstanceTypeByNodeName(name any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInstanceTypeByNodeName", reflect.TypeOf((*MockVMSet)(nil).GetInstanceTypeByNodeName), name)
 }
@@ -247,7 +248,7 @@ func (m *MockVMSet) GetNodeCIDRMasksByProviderID(providerID string) (int, int, e
 }
 
 // GetNodeCIDRMasksByProviderID indicates an expected call of GetNodeCIDRMasksByProviderID.
-func (mr *MockVMSetMockRecorder) GetNodeCIDRMasksByProviderID(providerID interface{}) *gomock.Call {
+func (mr *MockVMSetMockRecorder) GetNodeCIDRMasksByProviderID(providerID any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNodeCIDRMasksByProviderID", reflect.TypeOf((*MockVMSet)(nil).GetNodeCIDRMasksByProviderID), providerID)
 }
@@ -263,7 +264,7 @@ func (m *MockVMSet) GetNodeNameByIPConfigurationID(ipConfigurationID string) (st
 }
 
 // GetNodeNameByIPConfigurationID indicates an expected call of GetNodeNameByIPConfigurationID.
-func (mr *MockVMSetMockRecorder) GetNodeNameByIPConfigurationID(ipConfigurationID interface{}) *gomock.Call {
+func (mr *MockVMSetMockRecorder) GetNodeNameByIPConfigurationID(ipConfigurationID any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNodeNameByIPConfigurationID", reflect.TypeOf((*MockVMSet)(nil).GetNodeNameByIPConfigurationID), ipConfigurationID)
 }
@@ -278,7 +279,7 @@ func (m *MockVMSet) GetNodeNameByProviderID(providerID string) (types.NodeName,
 }
 
 // GetNodeNameByProviderID indicates an expected call of GetNodeNameByProviderID.
-func (mr *MockVMSetMockRecorder) GetNodeNameByProviderID(providerID interface{}) *gomock.Call {
+func (mr *MockVMSetMockRecorder) GetNodeNameByProviderID(providerID any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNodeNameByProviderID", reflect.TypeOf((*MockVMSet)(nil).GetNodeNameByProviderID), providerID)
 }
@@ -293,7 +294,7 @@ func (m *MockVMSet) GetNodeVMSetName(node *v1.Node) (string, error) {
 }
 
 // GetNodeVMSetName indicates an expected call of GetNodeVMSetName.
-func (mr *MockVMSetMockRecorder) GetNodeVMSetName(node interface{}) *gomock.Call {
+func (mr *MockVMSetMockRecorder) GetNodeVMSetName(node any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNodeVMSetName", reflect.TypeOf((*MockVMSet)(nil).GetNodeVMSetName), node)
 }
@@ -308,7 +309,7 @@ func (m *MockVMSet) GetPowerStatusByNodeName(name string) (string, error) {
 }
 
 // GetPowerStatusByNodeName indicates an expected call of GetPowerStatusByNodeName.
-func (mr *MockVMSetMockRecorder) GetPowerStatusByNodeName(name interface{}) *gomock.Call {
+func (mr *MockVMSetMockRecorder) GetPowerStatusByNodeName(name any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPowerStatusByNodeName", reflect.TypeOf((*MockVMSet)(nil).GetPowerStatusByNodeName), name)
 }
@@ -323,7 +324,7 @@ func (m *MockVMSet) GetPrimaryInterface(nodeName string) (network.Interface, err
 }
 
 // GetPrimaryInterface indicates an expected call of GetPrimaryInterface.
-func (mr *MockVMSetMockRecorder) GetPrimaryInterface(nodeName interface{}) *gomock.Call {
+func (mr *MockVMSetMockRecorder) GetPrimaryInterface(nodeName any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPrimaryInterface", reflect.TypeOf((*MockVMSet)(nil).GetPrimaryInterface), nodeName)
 }
@@ -352,7 +353,7 @@ func (m *MockVMSet) GetPrivateIPsByNodeName(name string) ([]string, error) {
 }
 
 // GetPrivateIPsByNodeName indicates an expected call of GetPrivateIPsByNodeName.
-func (mr *MockVMSetMockRecorder) GetPrivateIPsByNodeName(name interface{}) *gomock.Call {
+func (mr *MockVMSetMockRecorder) GetPrivateIPsByNodeName(name any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPrivateIPsByNodeName", reflect.TypeOf((*MockVMSet)(nil).GetPrivateIPsByNodeName), name)
 }
@@ -367,7 +368,7 @@ func (m *MockVMSet) GetProvisioningStateByNodeName(name string) (string, error)
 }
 
 // GetProvisioningStateByNodeName indicates an expected call of GetProvisioningStateByNodeName.
-func (mr *MockVMSetMockRecorder) GetProvisioningStateByNodeName(name interface{}) *gomock.Call {
+func (mr *MockVMSetMockRecorder) GetProvisioningStateByNodeName(name any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProvisioningStateByNodeName", reflect.TypeOf((*MockVMSet)(nil).GetProvisioningStateByNodeName), name)
 }
@@ -382,26 +383,40 @@ func (m *MockVMSet) GetVMSetNames(service *v1.Service, nodes []*v1.Node) (*[]str
 }
 
 // GetVMSetNames indicates an expected call of GetVMSetNames.
-func (mr *MockVMSetMockRecorder) GetVMSetNames(service, nodes interface{}) *gomock.Call {
+func (mr *MockVMSetMockRecorder) GetVMSetNames(service, nodes any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVMSetNames", reflect.TypeOf((*MockVMSet)(nil).GetVMSetNames), service, nodes)
 }
 
 // GetZoneByNodeName mocks base method.
-func (m *MockVMSet) GetZoneByNodeName(name string) (cloud_provider.Zone, error) {
+func (m *MockVMSet) GetZoneByNodeName(name string) (cloudprovider.Zone, error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "GetZoneByNodeName", name)
-	ret0, _ := ret[0].(cloud_provider.Zone)
+	ret0, _ := ret[0].(cloudprovider.Zone)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
 
 // GetZoneByNodeName indicates an expected call of GetZoneByNodeName.
-func (mr *MockVMSetMockRecorder) GetZoneByNodeName(name interface{}) *gomock.Call {
+func (mr *MockVMSetMockRecorder) GetZoneByNodeName(name any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetZoneByNodeName", reflect.TypeOf((*MockVMSet)(nil).GetZoneByNodeName), name)
 }
 
+// RefreshCaches mocks base method.
+func (m *MockVMSet) RefreshCaches(ctx context.Context) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "RefreshCaches", ctx)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// RefreshCaches indicates an expected call of RefreshCaches.
+func (mr *MockVMSetMockRecorder) RefreshCaches(ctx any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RefreshCaches", reflect.TypeOf((*MockVMSet)(nil).RefreshCaches), ctx)
+}
+
 // UpdateVM mocks base method.
 func (m *MockVMSet) UpdateVM(ctx context.Context, nodeName types.NodeName) error {
 	m.ctrl.T.Helper()
@@ -411,7 +426,7 @@ func (m *MockVMSet) UpdateVM(ctx context.Context, nodeName types.NodeName) error
 }
 
 // UpdateVM indicates an expected call of UpdateVM.
-func (mr *MockVMSetMockRecorder) UpdateVM(ctx, nodeName interface{}) *gomock.Call {
+func (mr *MockVMSetMockRecorder) UpdateVM(ctx, nodeName any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateVM", reflect.TypeOf((*MockVMSet)(nil).UpdateVM), ctx, nodeName)
 }
@@ -426,7 +441,7 @@ func (m *MockVMSet) UpdateVMAsync(ctx context.Context, nodeName types.NodeName)
 }
 
 // UpdateVMAsync indicates an expected call of UpdateVMAsync.
-func (mr *MockVMSetMockRecorder) UpdateVMAsync(ctx, nodeName interface{}) *gomock.Call {
+func (mr *MockVMSetMockRecorder) UpdateVMAsync(ctx, nodeName any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateVMAsync", reflect.TypeOf((*MockVMSet)(nil).UpdateVMAsync), ctx, nodeName)
 }
@@ -440,7 +455,7 @@ func (m *MockVMSet) WaitForUpdateResult(ctx context.Context, future *azure.Futur
 }
 
 // WaitForUpdateResult indicates an expected call of WaitForUpdateResult.
-func (mr *MockVMSetMockRecorder) WaitForUpdateResult(ctx, future, nodeName, source interface{}) *gomock.Call {
+func (mr *MockVMSetMockRecorder) WaitForUpdateResult(ctx, future, nodeName, source any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitForUpdateResult", reflect.TypeOf((*MockVMSet)(nil).WaitForUpdateResult), ctx, future, nodeName, source)
 }
diff --git a/pkg/provider/azure_standard.go b/pkg/provider/azure_standard.go
index a302bca48..6772a711a 100644
--- a/pkg/provider/azure_standard.go
+++ b/pkg/provider/azure_standard.go
@@ -189,7 +189,6 @@ func getProtocolsFromKubernetesProtocol(protocol v1.Protocol) (*network.Transpor
 	default:
 		return &transportProto, &securityProto, &probeProto, fmt.Errorf("only TCP, UDP and SCTP are supported for Azure LoadBalancers")
 	}
-
 }
 
 // This returns the full identifier of the primary NIC for the given VM.
@@ -446,15 +445,24 @@ func (as *availabilitySet) newVMASCache() (azcache.Resource, error) {
 	return azcache.NewTimedCache(time.Duration(as.Config.AvailabilitySetsCacheTTLInSeconds)*time.Second, getter, as.Cloud.Config.DisableAPICallCache)
 }
 
+// RefreshCaches invalidates and renew all related caches.
+func (as *availabilitySet) RefreshCaches(_ context.Context) error {
+	var err error
+	as.vmasCache, err = as.newVMASCache()
+	if err != nil {
+		klog.Errorf("as.RefreshCaches: failed to create or refresh VMAS cache: %s", err)
+		return err
+	}
+	return nil
+}
+
 // newStandardSet creates a new availabilitySet.
 func newAvailabilitySet(az *Cloud) (VMSet, error) {
 	as := &availabilitySet{
 		Cloud: az,
 	}
 
-	var err error
-	as.vmasCache, err = as.newVMASCache()
-	if err != nil {
+	if err := as.RefreshCaches(context.Background()); err != nil {
 		return nil, err
 	}
 
@@ -1010,7 +1018,6 @@ func (as *availabilitySet) EnsureBackendPoolDeleted(service *v1.Service, backend
 				}
 			}
 		}
-
 	}
 	nicUpdaters := make([]func() error, 0)
 	allErrs := make([]error, 0)
diff --git a/pkg/provider/azure_vmsets.go b/pkg/provider/azure_vmsets.go
index e405ef3a7..10fa7f3cd 100644
--- a/pkg/provider/azure_vmsets.go
+++ b/pkg/provider/azure_vmsets.go
@@ -72,7 +72,7 @@ type VMSet interface {
 	EnsureHostInPool(service *v1.Service, nodeName types.NodeName, backendPoolID string, vmSetName string) (string, string, string, *compute.VirtualMachineScaleSetVM, error)
 	// EnsureBackendPoolDeleted ensures the loadBalancer backendAddressPools deleted from the specified nodes.
 	EnsureBackendPoolDeleted(service *v1.Service, backendPoolIDs []string, vmSetName string, backendAddressPools *[]network.BackendAddressPool, deleteFromVMSet bool) (bool, error)
-	//EnsureBackendPoolDeletedFromVMSets ensures the loadBalancer backendAddressPools deleted from the specified VMSS/VMAS
+	// EnsureBackendPoolDeletedFromVMSets ensures the loadBalancer backendAddressPools deleted from the specified VMSS/VMAS
 	EnsureBackendPoolDeletedFromVMSets(vmSetNamesMap map[string]bool, backendPoolIDs []string) error
 
 	// AttachDisk attaches a disk to vm
@@ -111,4 +111,7 @@ type VMSet interface {
 
 	// DeleteCacheForNode removes the node entry from cache.
 	DeleteCacheForNode(nodeName string) error
+
+	// RefreshCaches invalidates and renew all related caches.
+	RefreshCaches(ctx context.Context) error
 }
diff --git a/pkg/provider/azure_vmss.go b/pkg/provider/azure_vmss.go
index 27895f237..b59167b80 100644
--- a/pkg/provider/azure_vmss.go
+++ b/pkg/provider/azure_vmss.go
@@ -105,6 +105,34 @@ type ScaleSet struct {
 	lockMap *lockMap
 }
 
+// RefreshCaches invalidates and renew all related caches.
+func (ss *ScaleSet) RefreshCaches(ctx context.Context) error {
+	logger := klog.Background().WithName("ss.RefreshCaches")
+
+	var err error
+	ss.vmssCache, err = ss.newVMSSCache(ctx)
+	if err != nil {
+		logger.Error(err, "failed to create or refresh vmss cache")
+		return err
+	}
+
+	if !ss.DisableAvailabilitySetNodes || ss.EnableVmssFlexNodes {
+		ss.nonVmssUniformNodesCache, err = ss.newNonVmssUniformNodesCache()
+		if err != nil {
+			logger.Error(err, "failed to create or refresh nonVmssUniformNodes cache")
+			return err
+		}
+	}
+
+	ss.vmssVMCache, err = ss.newVMSSVirtualMachinesCache()
+	if err != nil {
+		logger.Error(err, "failed to create or refresh vmssVM cache")
+		return err
+	}
+
+	return nil
+}
+
 // newScaleSet creates a new ScaleSet.
 func newScaleSet(ctx context.Context, az *Cloud) (VMSet, error) {
 	if az.Config.VmssVirtualMachinesCacheTTLInSeconds == 0 {
@@ -128,20 +156,7 @@ func newScaleSet(ctx context.Context, az *Cloud) (VMSet, error) {
 		lockMap:         newLockMap(),
 	}
 
-	if !ss.DisableAvailabilitySetNodes || ss.EnableVmssFlexNodes {
-		ss.nonVmssUniformNodesCache, err = ss.newNonVmssUniformNodesCache()
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	ss.vmssCache, err = ss.newVMSSCache(ctx)
-	if err != nil {
-		return nil, err
-	}
-
-	ss.vmssVMCache, err = ss.newVMSSVirtualMachinesCache()
-	if err != nil {
+	if err := ss.RefreshCaches(ctx); err != nil {
 		return nil, err
 	}
 
@@ -424,7 +439,6 @@ func (ss *ScaleSet) GetInstanceIDByNodeName(name string) (string, error) {
 // azure:///subscriptions/subsid/resourceGroups/rg/providers/Microsoft.Compute/virtualMachineScaleSets/aks-agentpool-22126781-vmss/virtualMachines/1
 // /subscriptions/subsid/resourceGroups/rg/providers/Microsoft.Compute/virtualMachineScaleSets/aks-agentpool-22126781-vmss/virtualMachines/k8s-agentpool-36841236-vmss_1
 func (ss *ScaleSet) GetNodeNameByProviderID(providerID string) (types.NodeName, error) {
-
 	vmManagementType, err := ss.getVMManagementTypeByProviderID(providerID, azcache.CacheReadTypeUnsafe)
 	if err != nil {
 		klog.Errorf("Failed to check VM management type: %v", err)
@@ -2094,7 +2108,6 @@ func (ss *ScaleSet) EnsureBackendPoolDeleted(service *v1.Service, backendPoolIDs
 	}
 
 	return updated, nil
-
 }
 
 // GetNodeCIDRMaskByProviderID returns the node CIDR subnet mask by provider ID.
diff --git a/pkg/provider/azure_vmssflex.go b/pkg/provider/azure_vmssflex.go
index dabec9fc9..a74c3a3fa 100644
--- a/pkg/provider/azure_vmssflex.go
+++ b/pkg/provider/azure_vmssflex.go
@@ -41,10 +41,8 @@ import (
 	vmutil "sigs.k8s.io/cloud-provider-azure/pkg/util/vm"
 )
 
-var (
-	// ErrorVmssIDIsEmpty indicates the vmss id is empty.
-	ErrorVmssIDIsEmpty = errors.New("VMSS ID is empty")
-)
+// ErrorVmssIDIsEmpty indicates the vmss id is empty.
+var ErrorVmssIDIsEmpty = errors.New("VMSS ID is empty")
 
 // FlexScaleSet implements VMSet interface for Azure Flexible VMSS.
 type FlexScaleSet struct {
@@ -60,6 +58,23 @@ type FlexScaleSet struct {
 	lockMap *lockMap
 }
 
+// RefreshCaches invalidates and renew all related caches.
+func (fs *FlexScaleSet) RefreshCaches(ctx context.Context) error {
+	logger := klog.Background().WithName("fs.RefreshCaches")
+	var err error
+	fs.vmssFlexCache, err = fs.newVmssFlexCache(ctx)
+	if err != nil {
+		logger.Error(err, "failed to create or refresh vmssFlexCache")
+		return err
+	}
+	fs.vmssFlexVMCache, err = fs.newVmssFlexVMCache(ctx)
+	if err != nil {
+		logger.Error(err, "failed to create or refresh vmssFlexVMCache")
+		return err
+	}
+	return nil
+}
+
 func newFlexScaleSet(ctx context.Context, az *Cloud) (VMSet, error) {
 	fs := &FlexScaleSet{
 		Cloud:                    az,
@@ -68,13 +83,7 @@ func newFlexScaleSet(ctx context.Context, az *Cloud) (VMSet, error) {
 		lockMap:                  newLockMap(),
 	}
 
-	var err error
-	fs.vmssFlexCache, err = fs.newVmssFlexCache(ctx)
-	if err != nil {
-		return nil, err
-	}
-	fs.vmssFlexVMCache, err = fs.newVmssFlexVMCache(ctx)
-	if err != nil {
+	if err := fs.RefreshCaches(ctx); err != nil {
 		return nil, err
 	}
 
@@ -98,7 +107,6 @@ func (fs *FlexScaleSet) getNodeVmssFlexName(nodeName string) (string, error) {
 		return "", err
 	}
 	return vmssFlexName, nil
-
 }
 
 // GetNodeVMSetName returns the availability set or vmss name by the node name.
@@ -196,7 +204,6 @@ func (fs *FlexScaleSet) GetInstanceIDByNodeName(name string) (string, error) {
 		return "", err
 	}
 	return convertedResourceID, nil
-
 }
 
 // GetInstanceTypeByNodeName gets the instance type by node name.
@@ -342,7 +349,6 @@ func (fs *FlexScaleSet) GetIPByNodeName(name string) (string, string, error) {
 	}
 
 	return privateIP, publicIP, nil
-
 }
 
 // GetPrivateIPsByNodeName returns a slice of all private ips assigned to node (ipv6 and ipv4)
@@ -397,7 +403,6 @@ func (fs *FlexScaleSet) getNodeInformationByIPConfigurationID(ipConfigurationID
 	}
 
 	vmssFlexName, err := fs.getNodeVmssFlexName(nodeName)
-
 	if err != nil {
 		klog.Errorf("Unable to get the vmss flex name by node name %s: %v", vmName, err)
 		return "", "", "", err
@@ -548,7 +553,6 @@ func (fs *FlexScaleSet) EnsureHostInPool(service *v1.Service, nodeName types.Nod
 	}
 
 	return nodeResourceGroup, vmssFlexName, name, nil, nil
-
 }
 
 func (fs *FlexScaleSet) ensureVMSSFlexInPool(service *v1.Service, nodes []*v1.Node, backendPoolID string, vmSetNameOfLB string) error {
@@ -913,7 +917,6 @@ func (fs *FlexScaleSet) EnsureBackendPoolDeleted(service *v1.Service, backendPoo
 					continue
 				}
 			}
-
 		}
 	}
 
@@ -939,7 +942,6 @@ func (fs *FlexScaleSet) EnsureBackendPoolDeleted(service *v1.Service, backendPoo
 
 	isOperationSucceeded = true
 	return nicUpdated, nil
-
 }
 
 func (fs *FlexScaleSet) ensureBackendPoolDeletedFromNode(vmssFlexVMNameMap map[string]string, backendPoolIDs []string) (bool, error) {
diff --git a/vendor/k8s.io/utils/integer/integer.go b/vendor/k8s.io/utils/integer/integer.go
index e4e740cad..f64d64955 100644
--- a/vendor/k8s.io/utils/integer/integer.go
+++ b/vendor/k8s.io/utils/integer/integer.go
@@ -16,7 +16,10 @@ limitations under the License.
 
 package integer
 
-// IntMax returns the maximum of the params
+import "math"
+
+// IntMax returns the maximum of the params.
+// Deprecated: for new code, use the max() builtin instead.
 func IntMax(a, b int) int {
 	if b > a {
 		return b
@@ -24,7 +27,8 @@ func IntMax(a, b int) int {
 	return a
 }
 
-// IntMin returns the minimum of the params
+// IntMin returns the minimum of the params.
+// Deprecated: for new code, use the min() builtin instead.
 func IntMin(a, b int) int {
 	if b < a {
 		return b
@@ -32,7 +36,8 @@ func IntMin(a, b int) int {
 	return a
 }
 
-// Int32Max returns the maximum of the params
+// Int32Max returns the maximum of the params.
+// Deprecated: for new code, use the max() builtin instead.
 func Int32Max(a, b int32) int32 {
 	if b > a {
 		return b
@@ -40,7 +45,8 @@ func Int32Max(a, b int32) int32 {
 	return a
 }
 
-// Int32Min returns the minimum of the params
+// Int32Min returns the minimum of the params.
+// Deprecated: for new code, use the min() builtin instead.
 func Int32Min(a, b int32) int32 {
 	if b < a {
 		return b
@@ -48,7 +54,8 @@ func Int32Min(a, b int32) int32 {
 	return a
 }
 
-// Int64Max returns the maximum of the params
+// Int64Max returns the maximum of the params.
+// Deprecated: for new code, use the max() builtin instead.
 func Int64Max(a, b int64) int64 {
 	if b > a {
 		return b
@@ -56,7 +63,8 @@ func Int64Max(a, b int64) int64 {
 	return a
 }
 
-// Int64Min returns the minimum of the params
+// Int64Min returns the minimum of the params.
+// Deprecated: for new code, use the min() builtin instead.
 func Int64Min(a, b int64) int64 {
 	if b < a {
 		return b
@@ -65,9 +73,7 @@ func Int64Min(a, b int64) int64 {
 }
 
 // RoundToInt32 rounds floats into integer numbers.
+// Deprecated: use math.Round() and a cast directly.
 func RoundToInt32(a float64) int32 {
-	if a < 0 {
-		return int32(a - 0.5)
-	}
-	return int32(a + 0.5)
+	return int32(math.Round(a))
 }
diff --git a/vendor/k8s.io/utils/net/multi_listen.go b/vendor/k8s.io/utils/net/multi_listen.go
new file mode 100644
index 000000000..7cb7795be
--- /dev/null
+++ b/vendor/k8s.io/utils/net/multi_listen.go
@@ -0,0 +1,195 @@
+/*
+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 net
+
+import (
+	"context"
+	"fmt"
+	"net"
+	"sync"
+)
+
+// connErrPair pairs conn and error which is returned by accept on sub-listeners.
+type connErrPair struct {
+	conn net.Conn
+	err  error
+}
+
+// multiListener implements net.Listener
+type multiListener struct {
+	listeners []net.Listener
+	wg        sync.WaitGroup
+
+	// connCh passes accepted connections, from child listeners to parent.
+	connCh chan connErrPair
+	// stopCh communicates from parent to child listeners.
+	stopCh chan struct{}
+}
+
+// compile time check to ensure *multiListener implements net.Listener
+var _ net.Listener = &multiListener{}
+
+// MultiListen returns net.Listener which can listen on and accept connections for
+// the given network on multiple addresses. Internally it uses stdlib to create
+// sub-listener and multiplexes connection requests using go-routines.
+// The network must be "tcp", "tcp4" or "tcp6".
+// It follows the semantics of net.Listen that primarily means:
+//  1. If the host is an unspecified/zero IP address with "tcp" network, MultiListen
+//     listens on all available unicast and anycast IP addresses of the local system.
+//  2. Use "tcp4" or "tcp6" to exclusively listen on IPv4 or IPv6 family, respectively.
+//  3. The host can accept names (e.g, localhost) and it will create a listener for at
+//     most one of the host's IP.
+func MultiListen(ctx context.Context, network string, addrs ...string) (net.Listener, error) {
+	var lc net.ListenConfig
+	return multiListen(
+		ctx,
+		network,
+		addrs,
+		func(ctx context.Context, network, address string) (net.Listener, error) {
+			return lc.Listen(ctx, network, address)
+		})
+}
+
+// multiListen implements MultiListen by consuming stdlib functions as dependency allowing
+// mocking for unit-testing.
+func multiListen(
+	ctx context.Context,
+	network string,
+	addrs []string,
+	listenFunc func(ctx context.Context, network, address string) (net.Listener, error),
+) (net.Listener, error) {
+	if !(network == "tcp" || network == "tcp4" || network == "tcp6") {
+		return nil, fmt.Errorf("network %q not supported", network)
+	}
+	if len(addrs) == 0 {
+		return nil, fmt.Errorf("no address provided to listen on")
+	}
+
+	ml := &multiListener{
+		connCh: make(chan connErrPair),
+		stopCh: make(chan struct{}),
+	}
+	for _, addr := range addrs {
+		l, err := listenFunc(ctx, network, addr)
+		if err != nil {
+			// close all the sub-listeners and exit
+			_ = ml.Close()
+			return nil, err
+		}
+		ml.listeners = append(ml.listeners, l)
+	}
+
+	for _, l := range ml.listeners {
+		ml.wg.Add(1)
+		go func(l net.Listener) {
+			defer ml.wg.Done()
+			for {
+				// Accept() is blocking, unless ml.Close() is called, in which
+				// case it will return immediately with an error.
+				conn, err := l.Accept()
+				// This assumes that ANY error from Accept() will terminate the
+				// sub-listener. We could maybe be more precise, but it
+				// doesn't seem necessary.
+				terminate := err != nil
+
+				select {
+				case ml.connCh <- connErrPair{conn: conn, err: err}:
+				case <-ml.stopCh:
+					// In case we accepted a connection AND were stopped, and
+					// this select-case was chosen, just throw away the
+					// connection.  This avoids potentially blocking on connCh
+					// or leaking a connection.
+					if conn != nil {
+						_ = conn.Close()
+					}
+					terminate = true
+				}
+				// Make sure we don't loop on Accept() returning an error and
+				// the select choosing the channel case.
+				if terminate {
+					return
+				}
+			}
+		}(l)
+	}
+	return ml, nil
+}
+
+// Accept implements net.Listener. It waits for and returns a connection from
+// any of the sub-listener.
+func (ml *multiListener) Accept() (net.Conn, error) {
+	// wait for any sub-listener to enqueue an accepted connection
+	connErr, ok := <-ml.connCh
+	if !ok {
+		// The channel will be closed only when Close() is called on the
+		// multiListener. Closing of this channel implies that all
+		// sub-listeners are also closed, which causes a "use of closed
+		// network connection" error on their Accept() calls. We return the
+		// same error for multiListener.Accept() if multiListener.Close()
+		// has already been called.
+		return nil, fmt.Errorf("use of closed network connection")
+	}
+	return connErr.conn, connErr.err
+}
+
+// Close implements net.Listener. It will close all sub-listeners and wait for
+// the go-routines to exit.
+func (ml *multiListener) Close() error {
+	// Make sure this can be called repeatedly without explosions.
+	select {
+	case <-ml.stopCh:
+		return fmt.Errorf("use of closed network connection")
+	default:
+	}
+
+	// Tell all sub-listeners to stop.
+	close(ml.stopCh)
+
+	// Closing the listeners causes Accept() to immediately return an error in
+	// the sub-listener go-routines.
+	for _, l := range ml.listeners {
+		_ = l.Close()
+	}
+
+	// Wait for all the sub-listener go-routines to exit.
+	ml.wg.Wait()
+	close(ml.connCh)
+
+	// Drain any already-queued connections.
+	for connErr := range ml.connCh {
+		if connErr.conn != nil {
+			_ = connErr.conn.Close()
+		}
+	}
+	return nil
+}
+
+// Addr is an implementation of the net.Listener interface.  It always returns
+// the address of the first listener.  Callers should  use conn.LocalAddr() to
+// obtain the actual local address of the sub-listener.
+func (ml *multiListener) Addr() net.Addr {
+	return ml.listeners[0].Addr()
+}
+
+// Addrs is like Addr, but returns the address for all registered listeners.
+func (ml *multiListener) Addrs() []net.Addr {
+	var ret []net.Addr
+	for _, l := range ml.listeners {
+		ret = append(ret, l.Addr())
+	}
+	return ret
+}
diff --git a/vendor/k8s.io/utils/pointer/pointer.go b/vendor/k8s.io/utils/pointer/pointer.go
index b8103223a..b673a6425 100644
--- a/vendor/k8s.io/utils/pointer/pointer.go
+++ b/vendor/k8s.io/utils/pointer/pointer.go
@@ -14,12 +14,15 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
+// Deprecated: Use functions in k8s.io/utils/ptr instead: ptr.To to obtain
+// a pointer, ptr.Deref to dereference a pointer, ptr.Equal to compare
+// dereferenced pointers.
 package pointer
 
 import (
-	"fmt"
-	"reflect"
 	"time"
+
+	"k8s.io/utils/ptr"
 )
 
 // AllPtrFieldsNil tests whether all pointer fields in a struct are nil.  This is useful when,
@@ -28,383 +31,219 @@ import (
 //
 // This function is only valid for structs and pointers to structs.  Any other
 // type will cause a panic.  Passing a typed nil pointer will return true.
-func AllPtrFieldsNil(obj interface{}) bool {
-	v := reflect.ValueOf(obj)
-	if !v.IsValid() {
-		panic(fmt.Sprintf("reflect.ValueOf() produced a non-valid Value for %#v", obj))
-	}
-	if v.Kind() == reflect.Ptr {
-		if v.IsNil() {
-			return true
-		}
-		v = v.Elem()
-	}
-	for i := 0; i < v.NumField(); i++ {
-		if v.Field(i).Kind() == reflect.Ptr && !v.Field(i).IsNil() {
-			return false
-		}
-	}
-	return true
-}
-
-// Int returns a pointer to an int
-func Int(i int) *int {
-	return &i
-}
+//
+// Deprecated: Use ptr.AllPtrFieldsNil instead.
+var AllPtrFieldsNil = ptr.AllPtrFieldsNil
+
+// Int returns a pointer to an int.
+var Int = ptr.To[int]
 
 // IntPtr is a function variable referring to Int.
 //
-// Deprecated: Use Int instead.
+// Deprecated: Use ptr.To instead.
 var IntPtr = Int // for back-compat
 
 // IntDeref dereferences the int ptr and returns it if not nil, or else
 // returns def.
-func IntDeref(ptr *int, def int) int {
-	if ptr != nil {
-		return *ptr
-	}
-	return def
-}
+var IntDeref = ptr.Deref[int]
 
 // IntPtrDerefOr is a function variable referring to IntDeref.
 //
-// Deprecated: Use IntDeref instead.
+// Deprecated: Use ptr.Deref instead.
 var IntPtrDerefOr = IntDeref // for back-compat
 
 // Int32 returns a pointer to an int32.
-func Int32(i int32) *int32 {
-	return &i
-}
+var Int32 = ptr.To[int32]
 
 // Int32Ptr is a function variable referring to Int32.
 //
-// Deprecated: Use Int32 instead.
+// Deprecated: Use ptr.To instead.
 var Int32Ptr = Int32 // for back-compat
 
 // Int32Deref dereferences the int32 ptr and returns it if not nil, or else
 // returns def.
-func Int32Deref(ptr *int32, def int32) int32 {
-	if ptr != nil {
-		return *ptr
-	}
-	return def
-}
+var Int32Deref = ptr.Deref[int32]
 
 // Int32PtrDerefOr is a function variable referring to Int32Deref.
 //
-// Deprecated: Use Int32Deref instead.
+// Deprecated: Use ptr.Deref instead.
 var Int32PtrDerefOr = Int32Deref // for back-compat
 
 // Int32Equal returns true if both arguments are nil or both arguments
 // dereference to the same value.
-func Int32Equal(a, b *int32) bool {
-	if (a == nil) != (b == nil) {
-		return false
-	}
-	if a == nil {
-		return true
-	}
-	return *a == *b
-}
+var Int32Equal = ptr.Equal[int32]
 
 // Uint returns a pointer to an uint
-func Uint(i uint) *uint {
-	return &i
-}
+var Uint = ptr.To[uint]
 
 // UintPtr is a function variable referring to Uint.
 //
-// Deprecated: Use Uint instead.
+// Deprecated: Use ptr.To instead.
 var UintPtr = Uint // for back-compat
 
 // UintDeref dereferences the uint ptr and returns it if not nil, or else
 // returns def.
-func UintDeref(ptr *uint, def uint) uint {
-	if ptr != nil {
-		return *ptr
-	}
-	return def
-}
+var UintDeref = ptr.Deref[uint]
 
 // UintPtrDerefOr is a function variable referring to UintDeref.
 //
-// Deprecated: Use UintDeref instead.
+// Deprecated: Use ptr.Deref instead.
 var UintPtrDerefOr = UintDeref // for back-compat
 
 // Uint32 returns a pointer to an uint32.
-func Uint32(i uint32) *uint32 {
-	return &i
-}
+var Uint32 = ptr.To[uint32]
 
 // Uint32Ptr is a function variable referring to Uint32.
 //
-// Deprecated: Use Uint32 instead.
+// Deprecated: Use ptr.To instead.
 var Uint32Ptr = Uint32 // for back-compat
 
 // Uint32Deref dereferences the uint32 ptr and returns it if not nil, or else
 // returns def.
-func Uint32Deref(ptr *uint32, def uint32) uint32 {
-	if ptr != nil {
-		return *ptr
-	}
-	return def
-}
+var Uint32Deref = ptr.Deref[uint32]
 
 // Uint32PtrDerefOr is a function variable referring to Uint32Deref.
 //
-// Deprecated: Use Uint32Deref instead.
+// Deprecated: Use ptr.Deref instead.
 var Uint32PtrDerefOr = Uint32Deref // for back-compat
 
 // Uint32Equal returns true if both arguments are nil or both arguments
 // dereference to the same value.
-func Uint32Equal(a, b *uint32) bool {
-	if (a == nil) != (b == nil) {
-		return false
-	}
-	if a == nil {
-		return true
-	}
-	return *a == *b
-}
+var Uint32Equal = ptr.Equal[uint32]
 
 // Int64 returns a pointer to an int64.
-func Int64(i int64) *int64 {
-	return &i
-}
+var Int64 = ptr.To[int64]
 
 // Int64Ptr is a function variable referring to Int64.
 //
-// Deprecated: Use Int64 instead.
+// Deprecated: Use ptr.To instead.
 var Int64Ptr = Int64 // for back-compat
 
 // Int64Deref dereferences the int64 ptr and returns it if not nil, or else
 // returns def.
-func Int64Deref(ptr *int64, def int64) int64 {
-	if ptr != nil {
-		return *ptr
-	}
-	return def
-}
+var Int64Deref = ptr.Deref[int64]
 
 // Int64PtrDerefOr is a function variable referring to Int64Deref.
 //
-// Deprecated: Use Int64Deref instead.
+// Deprecated: Use ptr.Deref instead.
 var Int64PtrDerefOr = Int64Deref // for back-compat
 
 // Int64Equal returns true if both arguments are nil or both arguments
 // dereference to the same value.
-func Int64Equal(a, b *int64) bool {
-	if (a == nil) != (b == nil) {
-		return false
-	}
-	if a == nil {
-		return true
-	}
-	return *a == *b
-}
+var Int64Equal = ptr.Equal[int64]
 
 // Uint64 returns a pointer to an uint64.
-func Uint64(i uint64) *uint64 {
-	return &i
-}
+var Uint64 = ptr.To[uint64]
 
 // Uint64Ptr is a function variable referring to Uint64.
 //
-// Deprecated: Use Uint64 instead.
+// Deprecated: Use ptr.To instead.
 var Uint64Ptr = Uint64 // for back-compat
 
 // Uint64Deref dereferences the uint64 ptr and returns it if not nil, or else
 // returns def.
-func Uint64Deref(ptr *uint64, def uint64) uint64 {
-	if ptr != nil {
-		return *ptr
-	}
-	return def
-}
+var Uint64Deref = ptr.Deref[uint64]
 
 // Uint64PtrDerefOr is a function variable referring to Uint64Deref.
 //
-// Deprecated: Use Uint64Deref instead.
+// Deprecated: Use ptr.Deref instead.
 var Uint64PtrDerefOr = Uint64Deref // for back-compat
 
 // Uint64Equal returns true if both arguments are nil or both arguments
 // dereference to the same value.
-func Uint64Equal(a, b *uint64) bool {
-	if (a == nil) != (b == nil) {
-		return false
-	}
-	if a == nil {
-		return true
-	}
-	return *a == *b
-}
+var Uint64Equal = ptr.Equal[uint64]
 
 // Bool returns a pointer to a bool.
-func Bool(b bool) *bool {
-	return &b
-}
+var Bool = ptr.To[bool]
 
 // BoolPtr is a function variable referring to Bool.
 //
-// Deprecated: Use Bool instead.
+// Deprecated: Use ptr.To instead.
 var BoolPtr = Bool // for back-compat
 
 // BoolDeref dereferences the bool ptr and returns it if not nil, or else
 // returns def.
-func BoolDeref(ptr *bool, def bool) bool {
-	if ptr != nil {
-		return *ptr
-	}
-	return def
-}
+var BoolDeref = ptr.Deref[bool]
 
 // BoolPtrDerefOr is a function variable referring to BoolDeref.
 //
-// Deprecated: Use BoolDeref instead.
+// Deprecated: Use ptr.Deref instead.
 var BoolPtrDerefOr = BoolDeref // for back-compat
 
 // BoolEqual returns true if both arguments are nil or both arguments
 // dereference to the same value.
-func BoolEqual(a, b *bool) bool {
-	if (a == nil) != (b == nil) {
-		return false
-	}
-	if a == nil {
-		return true
-	}
-	return *a == *b
-}
+var BoolEqual = ptr.Equal[bool]
 
 // String returns a pointer to a string.
-func String(s string) *string {
-	return &s
-}
+var String = ptr.To[string]
 
 // StringPtr is a function variable referring to String.
 //
-// Deprecated: Use String instead.
+// Deprecated: Use ptr.To instead.
 var StringPtr = String // for back-compat
 
 // StringDeref dereferences the string ptr and returns it if not nil, or else
 // returns def.
-func StringDeref(ptr *string, def string) string {
-	if ptr != nil {
-		return *ptr
-	}
-	return def
-}
+var StringDeref = ptr.Deref[string]
 
 // StringPtrDerefOr is a function variable referring to StringDeref.
 //
-// Deprecated: Use StringDeref instead.
+// Deprecated: Use ptr.Deref instead.
 var StringPtrDerefOr = StringDeref // for back-compat
 
 // StringEqual returns true if both arguments are nil or both arguments
 // dereference to the same value.
-func StringEqual(a, b *string) bool {
-	if (a == nil) != (b == nil) {
-		return false
-	}
-	if a == nil {
-		return true
-	}
-	return *a == *b
-}
+var StringEqual = ptr.Equal[string]
 
 // Float32 returns a pointer to a float32.
-func Float32(i float32) *float32 {
-	return &i
-}
+var Float32 = ptr.To[float32]
 
 // Float32Ptr is a function variable referring to Float32.
 //
-// Deprecated: Use Float32 instead.
+// Deprecated: Use ptr.To instead.
 var Float32Ptr = Float32
 
 // Float32Deref dereferences the float32 ptr and returns it if not nil, or else
 // returns def.
-func Float32Deref(ptr *float32, def float32) float32 {
-	if ptr != nil {
-		return *ptr
-	}
-	return def
-}
+var Float32Deref = ptr.Deref[float32]
 
 // Float32PtrDerefOr is a function variable referring to Float32Deref.
 //
-// Deprecated: Use Float32Deref instead.
+// Deprecated: Use ptr.Deref instead.
 var Float32PtrDerefOr = Float32Deref // for back-compat
 
 // Float32Equal returns true if both arguments are nil or both arguments
 // dereference to the same value.
-func Float32Equal(a, b *float32) bool {
-	if (a == nil) != (b == nil) {
-		return false
-	}
-	if a == nil {
-		return true
-	}
-	return *a == *b
-}
+var Float32Equal = ptr.Equal[float32]
 
 // Float64 returns a pointer to a float64.
-func Float64(i float64) *float64 {
-	return &i
-}
+var Float64 = ptr.To[float64]
 
 // Float64Ptr is a function variable referring to Float64.
 //
-// Deprecated: Use Float64 instead.
+// Deprecated: Use ptr.To instead.
 var Float64Ptr = Float64
 
 // Float64Deref dereferences the float64 ptr and returns it if not nil, or else
 // returns def.
-func Float64Deref(ptr *float64, def float64) float64 {
-	if ptr != nil {
-		return *ptr
-	}
-	return def
-}
+var Float64Deref = ptr.Deref[float64]
 
 // Float64PtrDerefOr is a function variable referring to Float64Deref.
 //
-// Deprecated: Use Float64Deref instead.
+// Deprecated: Use ptr.Deref instead.
 var Float64PtrDerefOr = Float64Deref // for back-compat
 
 // Float64Equal returns true if both arguments are nil or both arguments
 // dereference to the same value.
-func Float64Equal(a, b *float64) bool {
-	if (a == nil) != (b == nil) {
-		return false
-	}
-	if a == nil {
-		return true
-	}
-	return *a == *b
-}
+var Float64Equal = ptr.Equal[float64]
 
 // Duration returns a pointer to a time.Duration.
-func Duration(d time.Duration) *time.Duration {
-	return &d
-}
+var Duration = ptr.To[time.Duration]
 
 // DurationDeref dereferences the time.Duration ptr and returns it if not nil, or else
 // returns def.
-func DurationDeref(ptr *time.Duration, def time.Duration) time.Duration {
-	if ptr != nil {
-		return *ptr
-	}
-	return def
-}
+var DurationDeref = ptr.Deref[time.Duration]
 
 // DurationEqual returns true if both arguments are nil or both arguments
 // dereference to the same value.
-func DurationEqual(a, b *time.Duration) bool {
-	if (a == nil) != (b == nil) {
-		return false
-	}
-	if a == nil {
-		return true
-	}
-	return *a == *b
-}
+var DurationEqual = ptr.Equal[time.Duration]
diff --git a/vendor/k8s.io/utils/ptr/OWNERS b/vendor/k8s.io/utils/ptr/OWNERS
new file mode 100644
index 000000000..0d6392752
--- /dev/null
+++ b/vendor/k8s.io/utils/ptr/OWNERS
@@ -0,0 +1,10 @@
+# See the OWNERS docs at https://go.k8s.io/owners
+
+approvers:
+- apelisse
+- stewart-yu
+- thockin
+reviewers:
+- apelisse
+- stewart-yu
+- thockin
diff --git a/vendor/k8s.io/utils/ptr/README.md b/vendor/k8s.io/utils/ptr/README.md
new file mode 100644
index 000000000..2ca8073dc
--- /dev/null
+++ b/vendor/k8s.io/utils/ptr/README.md
@@ -0,0 +1,3 @@
+# Pointer
+
+This package provides some functions for pointer-based operations.
diff --git a/vendor/k8s.io/utils/ptr/ptr.go b/vendor/k8s.io/utils/ptr/ptr.go
new file mode 100644
index 000000000..659ed3b9e
--- /dev/null
+++ b/vendor/k8s.io/utils/ptr/ptr.go
@@ -0,0 +1,73 @@
+/*
+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 ptr
+
+import (
+	"fmt"
+	"reflect"
+)
+
+// AllPtrFieldsNil tests whether all pointer fields in a struct are nil.  This is useful when,
+// for example, an API struct is handled by plugins which need to distinguish
+// "no plugin accepted this spec" from "this spec is empty".
+//
+// This function is only valid for structs and pointers to structs.  Any other
+// type will cause a panic.  Passing a typed nil pointer will return true.
+func AllPtrFieldsNil(obj interface{}) bool {
+	v := reflect.ValueOf(obj)
+	if !v.IsValid() {
+		panic(fmt.Sprintf("reflect.ValueOf() produced a non-valid Value for %#v", obj))
+	}
+	if v.Kind() == reflect.Ptr {
+		if v.IsNil() {
+			return true
+		}
+		v = v.Elem()
+	}
+	for i := 0; i < v.NumField(); i++ {
+		if v.Field(i).Kind() == reflect.Ptr && !v.Field(i).IsNil() {
+			return false
+		}
+	}
+	return true
+}
+
+// To returns a pointer to the given value.
+func To[T any](v T) *T {
+	return &v
+}
+
+// Deref dereferences ptr and returns the value it points to if no nil, or else
+// returns def.
+func Deref[T any](ptr *T, def T) T {
+	if ptr != nil {
+		return *ptr
+	}
+	return def
+}
+
+// Equal returns true if both arguments are nil or both arguments
+// dereference to the same value.
+func Equal[T comparable](a, b *T) bool {
+	if (a == nil) != (b == nil) {
+		return false
+	}
+	if a == nil {
+		return true
+	}
+	return *a == *b
+}
diff --git a/vendor/k8s.io/utils/trace/trace.go b/vendor/k8s.io/utils/trace/trace.go
index 187eb5d8c..559aebb59 100644
--- a/vendor/k8s.io/utils/trace/trace.go
+++ b/vendor/k8s.io/utils/trace/trace.go
@@ -192,7 +192,7 @@ func (t *Trace) Log() {
 	t.endTime = &endTime
 	t.lock.Unlock()
 	// an explicit logging request should dump all the steps out at the higher level
-	if t.parentTrace == nil { // We don't start logging until Log or LogIfLong is called on the root trace
+	if t.parentTrace == nil && klogV(2) { // We don't start logging until Log or LogIfLong is called on the root trace
 		t.logTrace()
 	}
 }
diff --git a/vendor/modules.txt b/vendor/modules.txt
index ba026a1ae..757844a42 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -1409,7 +1409,7 @@ k8s.io/kubelet/pkg/apis/credentialprovider/install
 k8s.io/kubelet/pkg/apis/credentialprovider/v1
 k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1
 k8s.io/kubelet/pkg/apis/credentialprovider/v1beta1
-# k8s.io/utils v0.0.0-20230406110748-d93618cff8a2
+# k8s.io/utils v0.0.0-20240921022957-49e7df575cb6
 ## explicit; go 1.18
 k8s.io/utils/buffer
 k8s.io/utils/clock
@@ -1422,6 +1422,7 @@ k8s.io/utils/lru
 k8s.io/utils/net
 k8s.io/utils/path
 k8s.io/utils/pointer
+k8s.io/utils/ptr
 k8s.io/utils/strings/slices
 k8s.io/utils/trace
 # sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.1.2


Test patch:
diff --git a/pkg/provider/azure_loadbalancer_test.go b/pkg/provider/azure_loadbalancer_test.go
index 494c1afd0..e9d386b2d 100644
--- a/pkg/provider/azure_loadbalancer_test.go
+++ b/pkg/provider/azure_loadbalancer_test.go
@@ -36,12 +36,16 @@ import (
 	"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"
+	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/sets"
 	"k8s.io/client-go/kubernetes/fake"
+	k8stesting "k8s.io/client-go/testing"
 	"k8s.io/utils/pointer"
+	"k8s.io/utils/ptr"
 
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/loadbalancerclient/mockloadbalancerclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/privatelinkserviceclient/mockprivatelinkserviceclient"
@@ -787,6 +791,118 @@ func TestEnsureLoadBalancerDeleted(t *testing.T) {
 	}
 }
 
+func TestEnsureLoadBalancerLock(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
+
+	az := GetTestCloud(ctrl)
+	kubeClient := fake.NewSimpleClientset()
+	kubeClient.PrependReactor(
+		"get", "leases",
+		func(_ k8stesting.Action) (handled bool, ret runtime.Object, err error) {
+			return true, nil, errors.New("get lease failed")
+		})
+	az.KubeClient = kubeClient
+	az.azureResourceLocker = NewAzureResourceLocker(
+		az, "holder", "aks-managed-resource-locker", "kube-system", 900,
+	)
+
+	svc := getTestService("service", v1.ProtocolTCP, nil, false, 80)
+	_, err := az.EnsureLoadBalancer(context.Background(), testClusterName, &svc, nil)
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "get lease failed")
+	assert.Contains(t, err.Error(), "EnsureLoadBalancer failed due to fail to lock azure resources")
+
+	kubeClient = fake.NewSimpleClientset(&coordinationv1.Lease{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "aks-managed-resource-locker",
+			Namespace: "kube-system",
+		},
+		Spec: coordinationv1.LeaseSpec{
+			LeaseDurationSeconds: ptr.To[int32](900),
+		},
+	})
+	// update lease failed for the second time
+	count := 0
+	kubeClient.PrependReactor(
+		"update", "leases",
+		func(_ k8stesting.Action) (handled bool, ret runtime.Object, err error) {
+			count++
+			if count == 2 {
+				return true, nil, errors.New("update lease failed")
+			}
+			return false, nil, nil
+		})
+	az.KubeClient = kubeClient
+	az.azureResourceLocker = NewAzureResourceLocker(
+		az, "holder", "aks-managed-resource-locker", "kube-system", 900,
+	)
+	mockLBClient := az.LoadBalancerClient.(*mockloadbalancerclient.MockInterface)
+	mockLBClient.EXPECT().List(gomock.Any(), gomock.Any()).
+		Return(nil, retry.NewError(false, errors.New("list lb failed")))
+
+	_, err = az.EnsureLoadBalancer(context.Background(), testClusterName, &svc, nil)
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "update lease failed")
+	assert.Contains(t, err.Error(), "list lb failed")
+}
+
+func TestEnsureLoadBalancerDeletedLock(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
+
+	az := GetTestCloud(ctrl)
+	kubeClient := fake.NewSimpleClientset()
+	kubeClient.PrependReactor(
+		"get", "leases",
+		func(_ k8stesting.Action) (handled bool, ret runtime.Object, err error) {
+			return true, nil, errors.New("get lease failed")
+		})
+	az.KubeClient = kubeClient
+	az.azureResourceLocker = NewAzureResourceLocker(
+		az, "holder", "aks-managed-resource-locker", "kube-system", 900,
+	)
+
+	svc := getTestService("service", v1.ProtocolTCP, nil, false, 80)
+	err := az.EnsureLoadBalancerDeleted(context.Background(), testClusterName, &svc)
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "get lease failed")
+	assert.Contains(t, err.Error(), "EnsureLoadBalancerDeleted failed due to fail to lock azure resources")
+
+	kubeClient = fake.NewSimpleClientset(&coordinationv1.Lease{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "aks-managed-resource-locker",
+			Namespace: "kube-system",
+		},
+		Spec: coordinationv1.LeaseSpec{
+			LeaseDurationSeconds: ptr.To[int32](900),
+		},
+	})
+	// update lease failed for the second time
+	count := 0
+	kubeClient.PrependReactor(
+		"update", "leases",
+		func(_ k8stesting.Action) (handled bool, ret runtime.Object, err error) {
+			count++
+			if count == 2 {
+				return true, nil, errors.New("update lease failed")
+			}
+			return false, nil, nil
+		})
+	az.KubeClient = kubeClient
+	az.azureResourceLocker = NewAzureResourceLocker(
+		az, "holder", "aks-managed-resource-locker", "kube-system", 900,
+	)
+	mockLBClient := az.LoadBalancerClient.(*mockloadbalancerclient.MockInterface)
+	mockLBClient.EXPECT().List(gomock.Any(), gomock.Any()).
+		Return(nil, retry.NewError(false, errors.New("list lb failed")))
+
+	err = az.EnsureLoadBalancerDeleted(context.Background(), testClusterName, &svc)
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "update lease failed")
+	assert.Contains(t, err.Error(), "list lb failed")
+}
+
 func TestServiceOwnsPublicIP(t *testing.T) {
 	tests := []struct {
 		desc                    string
diff --git a/pkg/provider/azure_lock_test.go b/pkg/provider/azure_lock_test.go
new file mode 100644
index 000000000..6de1be610
--- /dev/null
+++ b/pkg/provider/azure_lock_test.go
@@ -0,0 +1,241 @@
+/*
+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 provider
+
+import (
+	"context"
+	"errors"
+	"testing"
+	"time"
+
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
+	"github.com/golang/mock/gomock"
+	"github.com/stretchr/testify/assert"
+
+	v1 "k8s.io/api/coordination/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/client-go/kubernetes"
+	"k8s.io/client-go/kubernetes/fake"
+	client_go_testing "k8s.io/client-go/testing"
+)
+
+func newTestAzureResourceLocker(ctrl *gomock.Controller, kubeClient kubernetes.Interface) *AzureResourceLocker {
+	cloud := GetTestCloud(ctrl)
+	cloud.KubeClient = kubeClient
+	return NewAzureResourceLocker(cloud, "holder", "aks-managed-resource-locker", "kube-system", 900)
+}
+
+func TestLock(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
+
+	for _, tc := range []struct {
+		desc        string
+		getErr      bool
+		createErr   bool
+		updateErr   bool
+		existed     bool
+		expired     bool
+		sameHolder  bool
+		expectedErr string
+	}{
+		{
+			desc:        "should return an error if failed to get the lease",
+			getErr:      true,
+			expectedErr: "failed to get leases",
+		},
+		{
+			desc:        "should return an error if failed to create the lease",
+			createErr:   true,
+			expectedErr: "failed to create lease",
+		},
+		{
+			desc:        "should return an error if failed to get the lease when acquiring it",
+			getErr:      true,
+			existed:     true,
+			expectedErr: "failed to get leases",
+		},
+		{
+			desc:        "should return an error when trying to acquire a lease which has not expired",
+			existed:     true,
+			expectedErr: "Lease has not expired yet",
+		},
+		{
+			desc:       "should not return an error if the lease has not expired but the previous holder is the same",
+			existed:    true,
+			sameHolder: true,
+		},
+		{
+			desc:        "should return an error if failed to update the lease",
+			existed:     true,
+			updateErr:   true,
+			expired:     true,
+			expectedErr: "failed to update lease",
+		},
+	} {
+		t.Run(tc.desc, func(t *testing.T) {
+			testLease := getTestLease()
+			kubeClient := fake.NewSimpleClientset()
+			if !tc.expired {
+				testLease.Spec.HolderIdentity = to.Ptr("prevHolder")
+				testLease.Spec.RenewTime = &metav1.MicroTime{Time: time.Now().Add(-100)}
+				if tc.sameHolder {
+					testLease.Spec.HolderIdentity = to.Ptr("holder")
+				}
+			}
+			if tc.existed {
+				kubeClient = fake.NewSimpleClientset(testLease)
+			}
+			getCount := 0
+			if tc.getErr {
+				kubeClient.PrependReactor(
+					"get", "leases",
+					func(_ client_go_testing.Action) (handled bool, ret runtime.Object, err error) {
+						getCount++
+						if tc.existed {
+							if getCount == 2 {
+								return true, nil, errors.New("failed to get leases")
+							}
+							return true, testLease, nil
+						}
+						return true, nil, errors.New("failed to get leases")
+					},
+				)
+			}
+			if tc.createErr {
+				kubeClient.PrependReactor(
+					"create", "leases",
+					func(_ client_go_testing.Action) (handled bool, ret runtime.Object, err error) {
+						return true, nil, errors.New("failed to create lease")
+					},
+				)
+			}
+			if tc.updateErr {
+				kubeClient.PrependReactor(
+					"update", "leases",
+					func(_ client_go_testing.Action) (handled bool, ret runtime.Object, err error) {
+						return true, nil, errors.New("failed to update lease")
+					},
+				)
+			}
+			if !tc.getErr && !tc.createErr {
+				kubeClient.PrependReactor(
+					"get", "leases",
+					func(_ client_go_testing.Action) (handled bool, ret runtime.Object, err error) {
+						return true, testLease, nil
+					},
+				)
+			}
+
+			locker := newTestAzureResourceLocker(ctrl, kubeClient)
+			err := locker.Lock(context.TODO())
+			if tc.expectedErr != "" {
+				assert.Error(t, err)
+				assert.Contains(t, err.Error(), tc.expectedErr)
+			} else {
+				assert.Nil(t, err)
+			}
+		})
+	}
+}
+
+func TestUnlock(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
+
+	for _, tc := range []struct {
+		desc            string
+		getErr          bool
+		updateErr       bool
+		expired         bool
+		differentHolder bool
+		expectedErr     string
+	}{
+		{
+			desc:        "should return an error if failed to get the lease",
+			getErr:      true,
+			expectedErr: "failed to get lease",
+		},
+		{
+			desc:    "should do nothing if the lease has expired",
+			expired: true,
+		},
+		{
+			desc:            "should do nothing if the lease is holding by another holder",
+			differentHolder: true,
+		},
+		{
+			desc:        "should return an error if failed to update the lease",
+			updateErr:   true,
+			expired:     true,
+			expectedErr: "failed to update lease",
+		},
+	} {
+		t.Run(tc.desc, func(t *testing.T) {
+			testLease := getTestLease()
+			testLease.Spec.HolderIdentity = to.Ptr("holder")
+			testLease.Spec.RenewTime = &metav1.MicroTime{Time: time.Now().Add(-1000)}
+			if !tc.expired {
+				testLease.Spec.RenewTime = &metav1.MicroTime{Time: time.Now().Add(-100)}
+				if tc.differentHolder {
+					testLease.Spec.HolderIdentity = to.Ptr("prevHolder")
+				}
+			}
+
+			kubeClient := fake.NewSimpleClientset(testLease)
+			if tc.getErr {
+				kubeClient.PrependReactor(
+					"get", "leases",
+					func(_ client_go_testing.Action) (handled bool, ret runtime.Object, err error) {
+						return true, nil, errors.New("failed to get lease")
+					},
+				)
+			}
+			if tc.updateErr {
+				kubeClient.PrependReactor(
+					"update", "leases",
+					func(_ client_go_testing.Action) (handled bool, ret runtime.Object, err error) {
+						return true, nil, errors.New("failed to update lease")
+					},
+				)
+			}
+
+			locker := newTestAzureResourceLocker(ctrl, kubeClient)
+			err := locker.Unlock(context.TODO())
+			if tc.expectedErr != "" {
+				assert.Error(t, err)
+				assert.Contains(t, err.Error(), tc.expectedErr)
+			} else {
+				assert.Nil(t, err)
+			}
+		})
+	}
+}
+
+func getTestLease() *v1.Lease {
+	return &v1.Lease{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "aks-managed-resource-locker",
+			Namespace: "kube-system",
+		},
+		Spec: v1.LeaseSpec{
+			HolderIdentity:       to.Ptr(""),
+			LeaseDurationSeconds: to.Ptr[int32](900),
+		},
+	}
+}