Repository: kubernetes-sigs/cloud-provider-azure
Pull request #3105 (2023-01-11 08:54:24)

[release-1.25] Use TimedCache.Get() for read-only 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 bug

#### What this PR does / why we need it:

Cherry pick of #2949 against release-1.25: Use TimedCache.Get() for read-only resources

#### 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
fix: Use TimedCache.Get() for read-only 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

```



Patch:
diff --git a/go.mod b/go.mod
index 9e0f25e69..4a8a99c81 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@ module sigs.k8s.io/cloud-provider-azure
 go 1.18
 
 require (
-	github.com/Azure/azure-sdk-for-go v67.3.0+incompatible
+	github.com/Azure/azure-sdk-for-go v67.2.0+incompatible
 	github.com/Azure/go-autorest/autorest v0.11.28
 	github.com/Azure/go-autorest/autorest/adal v0.9.21
 	github.com/Azure/go-autorest/autorest/mocks v0.4.2
@@ -13,7 +13,7 @@ require (
 	github.com/fsnotify/fsnotify v1.6.0
 	github.com/go-logr/logr v1.2.3
 	github.com/golang/mock v1.6.0
-	github.com/onsi/ginkgo/v2 v2.7.0
+	github.com/onsi/ginkgo/v2 v2.6.1
 	github.com/onsi/gomega v1.24.2
 	github.com/spf13/cobra v1.6.1
 	github.com/spf13/pflag v1.0.5
diff --git a/go.sum b/go.sum
index 8a5e3d8b4..f8c3c4cb1 100644
--- a/go.sum
+++ b/go.sum
@@ -32,8 +32,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
-github.com/Azure/azure-sdk-for-go v67.3.0+incompatible h1:QEvenaO+Y9ShPeCWsSAtolzVUcb0T0tPeek5TDsovuM=
-github.com/Azure/azure-sdk-for-go v67.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
+github.com/Azure/azure-sdk-for-go v67.2.0+incompatible h1:Uu/Ww6ernvPTrpq31kITVTIm/I5jlJ1wjtEH/bmSB2k=
+github.com/Azure/azure-sdk-for-go v67.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
 github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
 github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
 github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
@@ -279,8 +279,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
-github.com/onsi/ginkgo/v2 v2.7.0 h1:/XxtEV3I3Eif/HobnVx9YmJgk8ENdRsuUmM+fLCFNow=
-github.com/onsi/ginkgo/v2 v2.7.0/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo=
+github.com/onsi/ginkgo/v2 v2.6.1 h1:1xQPCjcqYw/J5LchOcp4/2q/jzJFjiAOc25chhnDw+Q=
+github.com/onsi/ginkgo/v2 v2.6.1/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo=
 github.com/onsi/gomega v1.24.2 h1:J/tulyYK6JwBldPViHJReihxxZ+22FHs0piGjQAvoUE=
 github.com/onsi/gomega v1.24.2/go.mod h1:gs3J10IS7Z7r7eXRoNJIrNqU4ToQukCJhFtKrWgHWnk=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
diff --git a/pkg/cache/azure_cache.go b/pkg/cache/azure_cache.go
index d88041de6..1ac799588 100644
--- a/pkg/cache/azure_cache.go
+++ b/pkg/cache/azure_cache.go
@@ -122,8 +122,13 @@ func (t *TimedCache) getInternal(key string) (*AzureCacheEntry, error) {
 	return newEntry, nil
 }
 
-// Get returns the requested item by key with deep copy.
+// Get returns the requested item by key.
 func (t *TimedCache) Get(key string, crt AzureCacheReadType) (interface{}, error) {
+	return t.get(key, crt)
+}
+
+// Get returns the requested item by key with deep copy.
+func (t *TimedCache) GetWithDeepCopy(key string, crt AzureCacheReadType) (interface{}, error) {
 	data, err := t.get(key, crt)
 	copied := deepcopy.Copy(data)
 	return copied, err
diff --git a/pkg/provider/azure_privatelinkservice.go b/pkg/provider/azure_privatelinkservice.go
index c7ed28c4e..43c356a6f 100644
--- a/pkg/provider/azure_privatelinkservice.go
+++ b/pkg/provider/azure_privatelinkservice.go
@@ -29,7 +29,6 @@ import (
 
 	azcache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
 	"sigs.k8s.io/cloud-provider-azure/pkg/consts"
-	"sigs.k8s.io/cloud-provider-azure/pkg/metrics"
 	"sigs.k8s.io/cloud-provider-azure/pkg/retry"
 )
 
@@ -46,17 +45,6 @@ func (az *Cloud) reconcilePrivateLinkService(
 	fipConfigID := fipConfig.ID
 	klog.V(2).Infof("reconcilePrivateLinkService for service(%s) - LB fipConfigID(%s) - wantPLS(%t) - createPLS(%t)", serviceName, to.String(fipConfig.Name), wantPLS, createPLS)
 
-	request := "ensure_privatelinkservice"
-	if !wantPLS {
-		request = "ensure_privatelinkservice_deleted"
-	}
-	mc := metrics.NewMetricContext("services", request, az.ResourceGroup, az.getNetworkResourceSubscriptionID(), serviceName)
-
-	isOperationSucceeded := false
-	defer func() {
-		mc.ObserveOperationWithResult(isOperationSucceeded)
-	}()
-
 	if createPLS {
 		// Firstly, make sure it's internal service
 		if !requiresInternalLoadBalancer(service) && !consts.IsK8sServiceDisableLoadBalancerFloatingIP(service) {
@@ -154,7 +142,6 @@ func (az *Cloud) reconcilePrivateLinkService(
 		}
 	}
 
-	isOperationSucceeded = true
 	klog.V(2).Infof("reconcilePrivateLinkService for service(%s) finished", serviceName)
 	return nil
 }
diff --git a/pkg/provider/azure_vmss.go b/pkg/provider/azure_vmss.go
index 548b93407..5e7432425 100644
--- a/pkg/provider/azure_vmss.go
+++ b/pkg/provider/azure_vmss.go
@@ -172,7 +172,7 @@ func (ss *ScaleSet) getVmssVMByNodeIdentity(node *nodeIdentity, crt azcache.Azur
 
 		virtualMachines := cached.(*sync.Map)
 		if entry, ok := virtualMachines.Load(nodeName); ok {
-			result := entry.(*VMSSVirtualMachinesEntry)
+			result := entry.(*VMSSVirtualMachineEntry)
 			if result.VirtualMachine == nil {
 				klog.Warningf("VM is nil on Node %q, VM is in deleting state", nodeName)
 				return nil, true, nil
@@ -309,7 +309,7 @@ func (ss *ScaleSet) getVmssVMByInstanceID(resourceGroup, scaleSetName, instanceI
 
 		virtualMachines := cached.(*sync.Map)
 		virtualMachines.Range(func(key, value interface{}) bool {
-			vmEntry := value.(*VMSSVirtualMachinesEntry)
+			vmEntry := value.(*VMSSVirtualMachineEntry)
 			if strings.EqualFold(vmEntry.ResourceGroup, resourceGroup) &&
 				strings.EqualFold(vmEntry.VMSSName, scaleSetName) &&
 				strings.EqualFold(vmEntry.InstanceID, instanceID) {
diff --git a/pkg/provider/azure_vmss_cache.go b/pkg/provider/azure_vmss_cache.go
index d12a857cf..a1a622b0c 100644
--- a/pkg/provider/azure_vmss_cache.go
+++ b/pkg/provider/azure_vmss_cache.go
@@ -33,7 +33,7 @@ import (
 	"sigs.k8s.io/cloud-provider-azure/pkg/consts"
 )
 
-type VMSSVirtualMachinesEntry struct {
+type VMSSVirtualMachineEntry struct {
 	ResourceGroup  string
 	VMSSName       string
 	InstanceID     string
@@ -157,7 +157,7 @@ func (ss *ScaleSet) newVMSSVirtualMachinesCache(resourceGroupName, vmssName, cac
 	getter := func(key string) (interface{}, error) {
 		localCache := &sync.Map{} // [nodeName]*vmssVirtualMachinesEntry
 
-		oldCache := make(map[string]VMSSVirtualMachinesEntry)
+		oldCache := make(map[string]VMSSVirtualMachineEntry)
 
 		if vmssCache, ok := ss.vmssVMCache.Load(cacheKey); ok {
 			// get old cache before refreshing the cache
@@ -171,7 +171,7 @@ func (ss *ScaleSet) newVMSSVirtualMachinesCache(resourceGroupName, vmssName, cac
 				if cached != nil {
 					virtualMachines := cached.(*sync.Map)
 					virtualMachines.Range(func(key, value interface{}) bool {
-						oldCache[key.(string)] = *value.(*VMSSVirtualMachinesEntry)
+						oldCache[key.(string)] = *value.(*VMSSVirtualMachineEntry)
 						return true
 					})
 				}
@@ -196,7 +196,7 @@ func (ss *ScaleSet) newVMSSVirtualMachinesCache(resourceGroupName, vmssName, cac
 				continue
 			}
 
-			vmssVMCacheEntry := &VMSSVirtualMachinesEntry{
+			vmssVMCacheEntry := &VMSSVirtualMachineEntry{
 				ResourceGroup:  resourceGroupName,
 				VMSSName:       vmssName,
 				InstanceID:     to.String(vm.InstanceID),
@@ -231,7 +231,7 @@ func (ss *ScaleSet) newVMSSVirtualMachinesCache(resourceGroupName, vmssName, cac
 			}
 
 			klog.V(5).Infof("adding old entries to new cache for %s", name)
-			localCache.Store(name, &VMSSVirtualMachinesEntry{
+			localCache.Store(name, &VMSSVirtualMachineEntry{
 				ResourceGroup:  vmEntry.ResourceGroup,
 				VMSSName:       vmEntry.VMSSName,
 				InstanceID:     vmEntry.InstanceID,
@@ -280,7 +280,6 @@ func (ss *ScaleSet) DeleteCacheForNode(nodeName string) error {
 		return err
 	}
 
-	// Delete in VMSS VM cache
 	if err := ss.gcVMSSVMCache(); err != nil {
 		klog.Errorf("DeleteCacheForNode(%s) failed to gc stale vmss caches: %v", nodeName, err)
 	}
diff --git a/pkg/provider/azure_wrap.go b/pkg/provider/azure_wrap.go
index 71a86852d..05e965ea8 100644
--- a/pkg/provider/azure_wrap.go
+++ b/pkg/provider/azure_wrap.go
@@ -86,7 +86,7 @@ func (az *Cloud) getRouteTable(crt azcache.AzureCacheReadType) (routeTable netwo
 		return routeTable, false, fmt.Errorf("Route table name is not configured")
 	}
 
-	cachedRt, err := az.rtCache.Get(az.RouteTableName, crt)
+	cachedRt, err := az.rtCache.GetWithDeepCopy(az.RouteTableName, crt)
 	if err != nil {
 		return routeTable, false, err
 	}
@@ -109,7 +109,7 @@ func (az *Cloud) getPIPCacheKey(pipResourceGroup string, pipName string) string
 func (az *Cloud) getPublicIPAddress(pipResourceGroup string, pipName string, crt azcache.AzureCacheReadType) (network.PublicIPAddress, bool, error) {
 	pip := network.PublicIPAddress{}
 	cacheKey := az.getPIPCacheKey(pipResourceGroup, pipName)
-	cachedPIP, err := az.pipCache.Get(cacheKey, crt)
+	cachedPIP, err := az.pipCache.GetWithDeepCopy(cacheKey, crt)
 	if err != nil {
 		return pip, false, err
 	}
@@ -146,7 +146,7 @@ func (az *Cloud) getSubnet(virtualNetworkName string, subnetName string) (networ
 }
 
 func (az *Cloud) getAzureLoadBalancer(name string, crt azcache.AzureCacheReadType) (lb *network.LoadBalancer, exists bool, err error) {
-	cachedLB, err := az.lbCache.Get(name, crt)
+	cachedLB, err := az.lbCache.GetWithDeepCopy(name, crt)
 	if err != nil {
 		return lb, false, err
 	}
@@ -164,7 +164,7 @@ func (az *Cloud) getSecurityGroup(crt azcache.AzureCacheReadType) (network.Secur
 		return nsg, fmt.Errorf("securityGroupName is not configured")
 	}
 
-	securityGroup, err := az.nsgCache.Get(az.SecurityGroupName, crt)
+	securityGroup, err := az.nsgCache.GetWithDeepCopy(az.SecurityGroupName, crt)
 	if err != nil {
 		return nsg, err
 	}
@@ -177,7 +177,7 @@ func (az *Cloud) getSecurityGroup(crt azcache.AzureCacheReadType) (network.Secur
 }
 
 func (az *Cloud) getPrivateLinkService(frontendIPConfigID *string, crt azcache.AzureCacheReadType) (pls network.PrivateLinkService, err error) {
-	cachedPLS, err := az.plsCache.Get(*frontendIPConfigID, crt)
+	cachedPLS, err := az.plsCache.GetWithDeepCopy(*frontendIPConfigID, crt)
 	if err != nil {
 		return pls, err
 	}
diff --git a/vendor/github.com/Azure/azure-sdk-for-go/version/version.go b/vendor/github.com/Azure/azure-sdk-for-go/version/version.go
index f25ee4914..2dde09441 100644
--- a/vendor/github.com/Azure/azure-sdk-for-go/version/version.go
+++ b/vendor/github.com/Azure/azure-sdk-for-go/version/version.go
@@ -4,4 +4,4 @@ package version
 // Licensed under the MIT License. See License.txt in the project root for license information.
 
 // Number contains the semantic version of this SDK.
-const Number = "v67.3.0"
+const Number = "v67.2.0"
diff --git a/vendor/github.com/onsi/ginkgo/v2/CHANGELOG.md b/vendor/github.com/onsi/ginkgo/v2/CHANGELOG.md
index 43c44435f..d777b793b 100644
--- a/vendor/github.com/onsi/ginkgo/v2/CHANGELOG.md
+++ b/vendor/github.com/onsi/ginkgo/v2/CHANGELOG.md
@@ -1,16 +1,3 @@
-## 2.7.0
-
-### Features
-- Introduce ContinueOnFailure for Ordered containers [e0123ca] - Ordered containers that are also decorated with ContinueOnFailure will not stop running specs after the first spec fails.
-- Support for bootstrap commands to use custom data for templates (#1110) [7a2b242]
-- Support for labels and pending decorator in ginkgo outline output (#1113) [e6e3b98]
-- Color aliases for custom color support (#1101) [49fab7a]
-
-### Fixes
-- correctly ensure deterministic spec order, even if specs are generated by iterating over a map [89dda20]
-- Fix a bug where timedout specs were not correctly treated as failures when determining whether or not to run AfterAlls in an Ordered container.
-- Ensure go test coverprofile outputs to the expected location (#1105) [b0bd77b]
-
 ## 2.6.1
 
 ### Features
diff --git a/vendor/github.com/onsi/ginkgo/v2/decorator_dsl.go b/vendor/github.com/onsi/ginkgo/v2/decorator_dsl.go
index c65af4ce1..e43d9cbbb 100644
--- a/vendor/github.com/onsi/ginkgo/v2/decorator_dsl.go
+++ b/vendor/github.com/onsi/ginkgo/v2/decorator_dsl.go
@@ -46,7 +46,7 @@ const Pending = internal.Pending
 
 /*
 Serial is a decorator that allows you to mark a spec or container as serial.  These specs will never run in parallel with other specs.
-Specs in ordered containers cannot be marked as serial - mark the ordered container instead.
+Tests in ordered containers cannot be marked as serial - mark the ordered container instead.
 
 You can learn more here: https://onsi.github.io/ginkgo/#serial-specs
 You can learn more about decorators here: https://onsi.github.io/ginkgo/#decorator-reference
@@ -54,7 +54,7 @@ You can learn more about decorators here: https://onsi.github.io/ginkgo/#decorat
 const Serial = internal.Serial
 
 /*
-Ordered is a decorator that allows you to mark a container as ordered.  Specs in the container will always run in the order they appear.
+Ordered is a decorator that allows you to mark a container as ordered.  Tests in the container will always run in the order they appear.
 They will never be randomized and they will never run in parallel with one another, though they may run in parallel with other specs.
 
 You can learn more here: https://onsi.github.io/ginkgo/#ordered-containers
@@ -62,16 +62,6 @@ You can learn more about decorators here: https://onsi.github.io/ginkgo/#decorat
 */
 const Ordered = internal.Ordered
 
-/*
-ContinueOnFailure is a decorator that allows you to mark an Ordered container to continue running specs even if failures occur.  Ordinarily an ordered container will stop running specs after the first failure occurs.  Note that if a BeforeAll or a BeforeEach/JustBeforeEach annotated with OncePerOrdered fails then no specs will run as the precondition for the Ordered container will consider to be failed.
-
-ContinueOnFailure only applies to the outermost Ordered container.  Attempting to place ContinueOnFailure in a nested container will result in an error.
-
-You can learn more here: https://onsi.github.io/ginkgo/#ordered-containers
-You can learn more about decorators here: https://onsi.github.io/ginkgo/#decorator-reference
-*/
-const ContinueOnFailure = internal.ContinueOnFailure
-
 /*
 OncePerOrdered is a decorator that allows you to mark outer BeforeEach, AfterEach, JustBeforeEach, and JustAfterEach setup nodes to run once
 per ordered context.  Normally these setup nodes run around each individual spec, with OncePerOrdered they will run once around the set of specs in an ordered container.
diff --git a/vendor/github.com/onsi/ginkgo/v2/formatter/formatter.go b/vendor/github.com/onsi/ginkgo/v2/formatter/formatter.go
index ece35d17b..da224eab6 100644
--- a/vendor/github.com/onsi/ginkgo/v2/formatter/formatter.go
+++ b/vendor/github.com/onsi/ginkgo/v2/formatter/formatter.go
@@ -4,7 +4,6 @@ import (
 	"fmt"
 	"os"
 	"regexp"
-	"strconv"
 	"strings"
 )
 
@@ -51,35 +50,13 @@ func NewWithNoColorBool(noColor bool) Formatter {
 }
 
 func New(colorMode ColorMode) Formatter {
-	colorAliases := map[string]int{
-		"black":   0,
-		"red":     1,
-		"green":   2,
-		"yellow":  3,
-		"blue":    4,
-		"magenta": 5,
-		"cyan":    6,
-		"white":   7,
-	}
-	for colorAlias, n := range colorAliases {
-		colorAliases[fmt.Sprintf("bright-%s", colorAlias)] = n + 8
-	}
-
 	getColor := func(color, defaultEscapeCode string) string {
 		color = strings.ToUpper(strings.ReplaceAll(color, "-", "_"))
 		envVar := fmt.Sprintf("GINKGO_CLI_COLOR_%s", color)
-		envVarColor := os.Getenv(envVar)
-		if envVarColor == "" {
-			return defaultEscapeCode
-		}
-		if colorCode, ok := colorAliases[envVarColor]; ok {
-			return fmt.Sprintf("\x1b[38;5;%dm", colorCode)
-		}
-		colorCode, err := strconv.Atoi(envVarColor)
-		if err != nil || colorCode < 0 || colorCode > 255 {
-			return defaultEscapeCode
+		if escapeCode := os.Getenv(envVar); escapeCode != "" {
+			return escapeCode
 		}
-		return fmt.Sprintf("\x1b[38;5;%dm", colorCode)
+		return defaultEscapeCode
 	}
 
 	f := Formatter{
diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/group.go b/vendor/github.com/onsi/ginkgo/v2/internal/group.go
index ae1b7b011..5c782d3ff 100644
--- a/vendor/github.com/onsi/ginkgo/v2/internal/group.go
+++ b/vendor/github.com/onsi/ginkgo/v2/internal/group.go
@@ -94,19 +94,15 @@ type group struct {
 	runOncePairs   map[uint]runOncePairs
 	runOnceTracker map[runOncePair]types.SpecState
 
-	succeeded              bool
-	failedInARunOnceBefore bool
-	continueOnFailure      bool
+	succeeded bool
 }
 
 func newGroup(suite *Suite) *group {
 	return &group{
-		suite:                  suite,
-		runOncePairs:           map[uint]runOncePairs{},
-		runOnceTracker:         map[runOncePair]types.SpecState{},
-		succeeded:              true,
-		failedInARunOnceBefore: false,
-		continueOnFailure:      false,
+		suite:          suite,
+		runOncePairs:   map[uint]runOncePairs{},
+		runOnceTracker: map[runOncePair]types.SpecState{},
+		succeeded:      true,
 	}
 }
 
@@ -141,14 +137,10 @@ func (g *group) evaluateSkipStatus(spec Spec) (types.SpecState, types.Failure) {
 	if !g.suite.deadline.IsZero() && g.suite.deadline.Before(time.Now()) {
 		return types.SpecStateSkipped, types.Failure{}
 	}
-	if !g.succeeded && !g.continueOnFailure {
+	if !g.succeeded {
 		return types.SpecStateSkipped, g.suite.failureForLeafNodeWithMessage(spec.FirstNodeWithType(types.NodeTypeIt),
 			"Spec skipped because an earlier spec in an ordered container failed")
 	}
-	if g.failedInARunOnceBefore && g.continueOnFailure {
-		return types.SpecStateSkipped, g.suite.failureForLeafNodeWithMessage(spec.FirstNodeWithType(types.NodeTypeIt),
-			"Spec skipped because a BeforeAll node failed")
-	}
 	beforeOncePairs := g.runOncePairs[spec.SubjectID()].withType(types.NodeTypeBeforeAll | types.NodeTypeBeforeEach | types.NodeTypeJustBeforeEach)
 	for _, pair := range beforeOncePairs {
 		if g.runOnceTracker[pair].Is(types.SpecStateSkipped) {
@@ -176,8 +168,7 @@ func (g *group) isLastSpecWithPair(specID uint, pair runOncePair) bool {
 	return lastSpecID == specID
 }
 
-func (g *group) attemptSpec(isFinalAttempt bool, spec Spec) bool {
-	failedInARunOnceBefore := false
+func (g *group) attemptSpec(isFinalAttempt bool, spec Spec) {
 	pairs := g.runOncePairs[spec.SubjectID()]
 
 	nodes := spec.Nodes.WithType(types.NodeTypeBeforeAll)
@@ -203,7 +194,6 @@ func (g *group) attemptSpec(isFinalAttempt bool, spec Spec) bool {
 		}
 		if g.suite.currentSpecReport.State != types.SpecStatePassed {
 			terminatingNode, terminatingPair = node, oncePair
-			failedInARunOnceBefore = !terminatingPair.isZero()
 			break
 		}
 	}
@@ -226,7 +216,7 @@ func (g *group) attemptSpec(isFinalAttempt bool, spec Spec) bool {
 				//this node has already been run on this attempt, don't rerun it
 				return false
 			}
-			var pair runOncePair
+			pair := runOncePair{}
 			switch node.NodeType {
 			case types.NodeTypeCleanupAfterEach, types.NodeTypeCleanupAfterAll:
 				// check if we were generated in an AfterNode that has already run
@@ -256,13 +246,9 @@ func (g *group) attemptSpec(isFinalAttempt bool, spec Spec) bool {
 				if !terminatingPair.isZero() && terminatingNode.NestingLevel == node.NestingLevel {
 					return true //...or, a run-once node at our nesting level was skipped which means this is our last chance to run
 				}
-			case types.SpecStateFailed, types.SpecStatePanicked, types.SpecStateTimedout: // the spec has failed...
+			case types.SpecStateFailed, types.SpecStatePanicked: // the spec has failed...
 				if isFinalAttempt {
-					if g.continueOnFailure {
-						return isLastSpecWithPair || failedInARunOnceBefore //...we're configured to continue on failures - so we should only run if we're the last spec for this pair or if we failed in a runOnceBefore (which means we _are_ the last spec to run)
-					} else {
-						return true //...this was the last attempt and continueOnFailure is false therefore we are the last spec to run and so the AfterNode should run
-					}
+					return true //...if this was the last attempt then we're the last spec to run and so the AfterNode should run
 				}
 				if !terminatingPair.isZero() { // ...and it failed in a run-once.  which will be running again
 					if node.NodeType.Is(types.NodeTypeCleanupAfterEach | types.NodeTypeCleanupAfterAll) {
@@ -295,12 +281,10 @@ func (g *group) attemptSpec(isFinalAttempt bool, spec Spec) bool {
 		includeDeferCleanups = true
 	}
 
-	return failedInARunOnceBefore
 }
 
 func (g *group) run(specs Specs) {
 	g.specs = specs
-	g.continueOnFailure = specs[0].Nodes.FirstNodeMarkedOrdered().MarkedContinueOnFailure
 	for _, spec := range g.specs {
 		g.runOncePairs[spec.SubjectID()] = runOncePairsForSpec(spec)
 	}
@@ -317,8 +301,8 @@ func (g *group) run(specs Specs) {
 		skip := g.suite.config.DryRun || g.suite.currentSpecReport.State.Is(types.SpecStateFailureStates|types.SpecStateSkipped|types.SpecStatePending)
 
 		g.suite.currentSpecReport.StartTime = time.Now()
-		failedInARunOnceBefore := false
 		if !skip {
+
 			var maxAttempts = 1
 
 			if g.suite.currentSpecReport.MaxMustPassRepeatedly > 0 {
@@ -343,7 +327,7 @@ func (g *group) run(specs Specs) {
 					}
 				}
 
-				failedInARunOnceBefore = g.attemptSpec(attempt == maxAttempts-1, spec)
+				g.attemptSpec(attempt == maxAttempts-1, spec)
 
 				g.suite.currentSpecReport.EndTime = time.Now()
 				g.suite.currentSpecReport.RunTime = g.suite.currentSpecReport.EndTime.Sub(g.suite.currentSpecReport.StartTime)
@@ -371,7 +355,6 @@ func (g *group) run(specs Specs) {
 		g.suite.processCurrentSpecReport()
 		if g.suite.currentSpecReport.State.Is(types.SpecStateFailureStates) {
 			g.succeeded = false
-			g.failedInARunOnceBefore = g.failedInARunOnceBefore || failedInARunOnceBefore
 		}
 		g.suite.selectiveLock.Lock()
 		g.suite.currentSpecReport = types.SpecReport{}
diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/node.go b/vendor/github.com/onsi/ginkgo/v2/internal/node.go
index 9bbd258c0..69928eed6 100644
--- a/vendor/github.com/onsi/ginkgo/v2/internal/node.go
+++ b/vendor/github.com/onsi/ginkgo/v2/internal/node.go
@@ -47,20 +47,19 @@ type Node struct {
 	ReportEachBody  func(types.SpecReport)
 	ReportSuiteBody func(types.Report)
 
-	MarkedFocus             bool
-	MarkedPending           bool
-	MarkedSerial            bool
-	MarkedOrdered           bool
-	MarkedContinueOnFailure bool
-	MarkedOncePerOrdered    bool
-	FlakeAttempts           int
-	MustPassRepeatedly      int
-	Labels                  Labels
-	PollProgressAfter       time.Duration
-	PollProgressInterval    time.Duration
-	NodeTimeout             time.Duration
-	SpecTimeout             time.Duration
-	GracePeriod             time.Duration
+	MarkedFocus          bool
+	MarkedPending        bool
+	MarkedSerial         bool
+	MarkedOrdered        bool
+	MarkedOncePerOrdered bool
+	FlakeAttempts        int
+	MustPassRepeatedly   int
+	Labels               Labels
+	PollProgressAfter    time.Duration
+	PollProgressInterval time.Duration
+	NodeTimeout          time.Duration
+	SpecTimeout          time.Duration
+	GracePeriod          time.Duration
 
 	NodeIDWhereCleanupWasGenerated uint
 }
@@ -70,7 +69,6 @@ type focusType bool
 type pendingType bool
 type serialType bool
 type orderedType bool
-type continueOnFailureType bool
 type honorsOrderedType bool
 type suppressProgressReporting bool
 
@@ -78,7 +76,6 @@ const Focus = focusType(true)
 const Pending = pendingType(true)
 const Serial = serialType(true)
 const Ordered = orderedType(true)
-const ContinueOnFailure = continueOnFailureType(true)
 const OncePerOrdered = honorsOrderedType(true)
 const SuppressProgressReporting = suppressProgressReporting(true)
 
@@ -136,8 +133,6 @@ func isDecoration(arg interface{}) bool {
 		return true
 	case t == reflect.TypeOf(Ordered):
 		return true
-	case t == reflect.TypeOf(ContinueOnFailure):
-		return true
 	case t == reflect.TypeOf(OncePerOrdered):
 		return true
 	case t == reflect.TypeOf(SuppressProgressReporting):
@@ -246,11 +241,6 @@ func NewNode(deprecationTracker *types.DeprecationTracker, nodeType types.NodeTy
 			if !nodeType.Is(types.NodeTypeContainer) {
 				appendError(types.GinkgoErrors.InvalidDecoratorForNodeType(node.CodeLocation, nodeType, "Ordered"))
 			}
-		case t == reflect.TypeOf(ContinueOnFailure):
-			node.MarkedContinueOnFailure = bool(arg.(continueOnFailureType))
-			if !nodeType.Is(types.NodeTypeContainer) {
-				appendError(types.GinkgoErrors.InvalidDecoratorForNodeType(node.CodeLocation, nodeType, "ContinueOnFailure"))
-			}
 		case t == reflect.TypeOf(OncePerOrdered):
 			node.MarkedOncePerOrdered = bool(arg.(honorsOrderedType))
 			if !nodeType.Is(types.NodeTypeBeforeEach | types.NodeTypeJustBeforeEach | types.NodeTypeAfterEach | types.NodeTypeJustAfterEach) {
@@ -396,10 +386,6 @@ func NewNode(deprecationTracker *types.DeprecationTracker, nodeType types.NodeTy
 		appendError(types.GinkgoErrors.InvalidDeclarationOfFocusedAndPending(node.CodeLocation, nodeType))
 	}
 
-	if node.MarkedContinueOnFailure && !node.MarkedOrdered {
-		appendError(types.GinkgoErrors.InvalidContinueOnFailureDecoration(node.CodeLocation))
-	}
-
 	hasContext := node.HasContext || node.SynchronizedAfterSuiteProc1BodyHasContext || node.SynchronizedAfterSuiteAllProcsBodyHasContext || node.SynchronizedBeforeSuiteProc1BodyHasContext || node.SynchronizedBeforeSuiteAllProcsBodyHasContext
 
 	if !hasContext && (node.NodeTimeout > 0 || node.SpecTimeout > 0 || node.GracePeriod > 0) && len(errors) == 0 {
diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/ordering.go b/vendor/github.com/onsi/ginkgo/v2/internal/ordering.go
index 7ed43c7fd..161be820c 100644
--- a/vendor/github.com/onsi/ginkgo/v2/internal/ordering.go
+++ b/vendor/github.com/onsi/ginkgo/v2/internal/ordering.go
@@ -7,58 +7,6 @@ import (
 	"github.com/onsi/ginkgo/v2/types"
 )
 
-type SortableSpecs struct {
-	Specs   Specs
-	Indexes []int
-}
-
-func NewSortableSpecs(specs Specs) *SortableSpecs {
-	indexes := make([]int, len(specs))
-	for i := range specs {
-		indexes[i] = i
-	}
-	return &SortableSpecs{
-		Specs:   specs,
-		Indexes: indexes,
-	}
-}
-func (s *SortableSpecs) Len() int      { return len(s.Indexes) }
-func (s *SortableSpecs) Swap(i, j int) { s.Indexes[i], s.Indexes[j] = s.Indexes[j], s.Indexes[i] }
-func (s *SortableSpecs) Less(i, j int) bool {
-	a, b := s.Specs[s.Indexes[i]], s.Specs[s.Indexes[j]]
-
-	firstOrderedA := a.Nodes.FirstNodeMarkedOrdered()
-	firstOrderedB := b.Nodes.FirstNodeMarkedOrdered()
-	if firstOrderedA.ID == firstOrderedB.ID && !firstOrderedA.IsZero() {
-		// strictly preserve order in ordered containers.  ID will track this as IDs are generated monotonically
-		return a.FirstNodeWithType(types.NodeTypeIt).ID < b.FirstNodeWithType(types.NodeTypeIt).ID
-	}
-
-	aCLs := a.Nodes.WithType(types.NodeTypesForContainerAndIt).CodeLocations()
-	bCLs := b.Nodes.WithType(types.NodeTypesForContainerAndIt).CodeLocations()
-	for i := 0; i < len(aCLs) && i < len(bCLs); i++ {
-		aCL, bCL := aCLs[i], bCLs[i]
-		if aCL.FileName < bCL.FileName {
-			return true
-		} else if aCL.FileName > bCL.FileName {
-			return false
-		}
-		if aCL.LineNumber < bCL.LineNumber {
-			return true
-		} else if aCL.LineNumber > bCL.LineNumber {
-			return false
-		}
-	}
-	// either everything is equal or we have different lengths of CLs
-	if len(aCLs) < len(bCLs) {
-		return true
-	} else if len(aCLs) > len(bCLs) {
-		return false
-	}
-	// ok, now we are sure everything was equal. so we use the spec text to break ties
-	return a.Text() < b.Text()
-}
-
 type GroupedSpecIndices []SpecIndices
 type SpecIndices []int
 
@@ -80,17 +28,12 @@ func OrderSpecs(specs Specs, suiteConfig types.SuiteConfig) (GroupedSpecIndices,
 	// Seed a new random source based on thee configured random seed.
 	r := rand.New(rand.NewSource(suiteConfig.RandomSeed))
 
-	// first, we sort the entire suite to ensure a deterministic order.  the sort is performed by filename, then line number, and then spec text.  this ensures every parallel process has the exact same spec order and is only necessary to cover the edge case where the user iterates over a map to generate specs.
-	sortableSpecs := NewSortableSpecs(specs)
-	sort.Sort(sortableSpecs)
-
-	// then we break things into execution groups
+	// first break things into execution groups
 	// a group represents a single unit of execution and is a collection of SpecIndices
 	// usually a group is just a single spec, however ordered containers must be preserved as a single group
 	executionGroupIDs := []uint{}
 	executionGroups := map[uint]SpecIndices{}
-	for _, idx := range sortableSpecs.Indexes {
-		spec := specs[idx]
+	for idx, spec := range specs {
 		groupNode := spec.Nodes.FirstNodeMarkedOrdered()
 		if groupNode.IsZero() {
 			groupNode = spec.Nodes.FirstNodeWithType(types.NodeTypeIt)
@@ -105,6 +48,7 @@ func OrderSpecs(specs Specs, suiteConfig types.SuiteConfig) (GroupedSpecIndices,
 	// we shuffle outermost containers.  so we need to form shufflable groupings of GroupIDs
 	shufflableGroupingIDs := []uint{}
 	shufflableGroupingIDToGroupIDs := map[uint][]uint{}
+	shufflableGroupingsIDToSortKeys := map[uint]string{}
 
 	// for each execution group we're going to have to pick a node to represent how the
 	// execution group is grouped for shuffling:
@@ -113,7 +57,7 @@ func OrderSpecs(specs Specs, suiteConfig types.SuiteConfig) (GroupedSpecIndices,
 		nodeTypesToShuffle = types.NodeTypeIt
 	}
 
-	//so, for each execution group:
+	//so, fo reach execution group:
 	for _, groupID := range executionGroupIDs {
 		// pick out a representative spec
 		representativeSpec := specs[executionGroups[groupID][0]]
@@ -128,9 +72,22 @@ func OrderSpecs(specs Specs, suiteConfig types.SuiteConfig) (GroupedSpecIndices,
 		if len(shufflableGroupingIDToGroupIDs[shufflableGroupingNode.ID]) == 1 {
 			// record the shuffleable group ID
 			shufflableGroupingIDs = append(shufflableGroupingIDs, shufflableGroupingNode.ID)
+			// and record the sort key to use
+			shufflableGroupingsIDToSortKeys[shufflableGroupingNode.ID] = shufflableGroupingNode.CodeLocation.String()
 		}
 	}
 
+	// now we sort the shufflable groups by the sort key.  We use the shufflable group nodes code location and break ties using its node id
+	sort.SliceStable(shufflableGroupingIDs, func(i, j int) bool {
+		keyA := shufflableGroupingsIDToSortKeys[shufflableGroupingIDs[i]]
+		keyB := shufflableGroupingsIDToSortKeys[shufflableGroupingIDs[j]]
+		if keyA == keyB {
+			return shufflableGroupingIDs[i] < shufflableGroupingIDs[j]
+		} else {
+			return keyA < keyB
+		}
+	})
+
 	// now we permute the sorted shufflable grouping IDs and build the ordered Groups
 	orderedGroups := GroupedSpecIndices{}
 	permutation := r.Perm(len(shufflableGroupingIDs))
diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/suite.go b/vendor/github.com/onsi/ginkgo/v2/internal/suite.go
index 5ddf10072..f470641a6 100644
--- a/vendor/github.com/onsi/ginkgo/v2/internal/suite.go
+++ b/vendor/github.com/onsi/ginkgo/v2/internal/suite.go
@@ -151,13 +151,6 @@ func (suite *Suite) PushNode(node Node) error {
 		}
 	}
 
-	if node.MarkedContinueOnFailure {
-		firstOrderedNode := suite.tree.AncestorNodeChain().FirstNodeMarkedOrdered()
-		if !firstOrderedNode.IsZero() {
-			return types.GinkgoErrors.InvalidContinueOnFailureDecoration(node.CodeLocation)
-		}
-	}
-
 	if node.NodeType == types.NodeTypeContainer {
 		// During PhaseBuildTopLevel we only track the top level containers without entering them
 		// We only enter the top level container nodes during PhaseBuildTree
diff --git a/vendor/github.com/onsi/ginkgo/v2/types/errors.go b/vendor/github.com/onsi/ginkgo/v2/types/errors.go
index 1e0dbfd9d..a0f59fb4b 100644
--- a/vendor/github.com/onsi/ginkgo/v2/types/errors.go
+++ b/vendor/github.com/onsi/ginkgo/v2/types/errors.go
@@ -298,15 +298,6 @@ func (g ginkgoErrors) SetupNodeNotInOrderedContainer(cl CodeLocation, nodeType N
 	}
 }
 
-func (g ginkgoErrors) InvalidContinueOnFailureDecoration(cl CodeLocation) error {
-	return GinkgoError{
-		Heading:      "ContinueOnFailure not decorating an outermost Ordered Container",
-		Message:      "ContinueOnFailure can only decorate an Ordered container, and this Ordered container must be the outermost Ordered container.",
-		CodeLocation: cl,
-		DocLink:      "ordered-containers",
-	}
-}
-
 /* DeferCleanup errors */
 func (g ginkgoErrors) DeferCleanupInvalidFunction(cl CodeLocation) error {
 	return GinkgoError{
diff --git a/vendor/github.com/onsi/ginkgo/v2/types/types.go b/vendor/github.com/onsi/ginkgo/v2/types/types.go
index d048a8ada..3e979ba8c 100644
--- a/vendor/github.com/onsi/ginkgo/v2/types/types.go
+++ b/vendor/github.com/onsi/ginkgo/v2/types/types.go
@@ -604,9 +604,6 @@ var ssEnumSupport = NewEnumSupport(map[uint]string{
 func (ss SpecState) String() string {
 	return ssEnumSupport.String(uint(ss))
 }
-func (ss SpecState) GomegaString() string {
-	return ssEnumSupport.String(uint(ss))
-}
 func (ss *SpecState) UnmarshalJSON(b []byte) error {
 	out, err := ssEnumSupport.UnmarshJSON(b)
 	*ss = SpecState(out)
diff --git a/vendor/github.com/onsi/ginkgo/v2/types/version.go b/vendor/github.com/onsi/ginkgo/v2/types/version.go
index ef61d5e7a..03d5ff98a 100644
--- a/vendor/github.com/onsi/ginkgo/v2/types/version.go
+++ b/vendor/github.com/onsi/ginkgo/v2/types/version.go
@@ -1,3 +1,3 @@
 package types
 
-const VERSION = "2.7.0"
+const VERSION = "2.6.1"
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 571b2bdd5..90631b861 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -1,4 +1,4 @@
-# github.com/Azure/azure-sdk-for-go v67.3.0+incompatible
+# github.com/Azure/azure-sdk-for-go v67.2.0+incompatible
 ## explicit
 github.com/Azure/azure-sdk-for-go/services/authorization/mgmt/2015-07-01/authorization
 github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2022-03-01/compute
@@ -183,7 +183,7 @@ github.com/modern-go/reflect2
 # github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822
 ## explicit
 github.com/munnerz/goautoneg
-# github.com/onsi/ginkgo/v2 v2.7.0
+# github.com/onsi/ginkgo/v2 v2.6.1
 ## explicit; go 1.18
 github.com/onsi/ginkgo/v2
 github.com/onsi/ginkgo/v2/config


Test patch:
diff --git a/pkg/cache/azure_cache_test.go b/pkg/cache/azure_cache_test.go
index 50b110d59..82e30d0e6 100644
--- a/pkg/cache/azure_cache_test.go
+++ b/pkg/cache/azure_cache_test.go
@@ -98,7 +98,7 @@ func TestCacheGet(t *testing.T) {
 	for _, c := range cases {
 		dataSource, cache := newFakeCache(t)
 		dataSource.set(c.data)
-		val, err := cache.Get(c.key, CacheReadTypeDefault)
+		val, err := cache.GetWithDeepCopy(c.key, CacheReadTypeDefault)
 		assert.NoError(t, err, c.name)
 		assert.Equal(t, c.expected, val, c.name)
 	}
@@ -112,7 +112,7 @@ func TestCacheGetError(t *testing.T) {
 	cache, err := NewTimedcache(fakeCacheTTL, getter)
 	assert.NoError(t, err)
 
-	val, err := cache.Get("key", CacheReadTypeDefault)
+	val, err := cache.GetWithDeepCopy("key", CacheReadTypeDefault)
 	assert.Error(t, err)
 	assert.Equal(t, getError, err)
 	assert.Nil(t, val)
@@ -140,7 +140,7 @@ func TestCacheGetWithDeepCopy(t *testing.T) {
 			dataSource, cache := newFakeCache(t)
 			dataSource.set(c.data)
 			cache.Set(c.key, valFake)
-			val, err := cache.Get(c.key, CacheReadTypeDefault)
+			val, err := cache.GetWithDeepCopy(c.key, CacheReadTypeDefault)
 			assert.NoError(t, err)
 			assert.Equal(t, c.expected, val.(*fakeDataObj).Data)
 
@@ -160,13 +160,13 @@ func TestCacheDelete(t *testing.T) {
 	dataSource, cache := newFakeCache(t)
 	dataSource.set(data)
 
-	v, err := cache.Get(testKey, CacheReadTypeDefault)
+	v, err := cache.GetWithDeepCopy(testKey, CacheReadTypeDefault)
 	assert.NoError(t, err)
 	assert.Equal(t, val, v, "cache should get correct data")
 
 	dataSource.set(nil)
 	_ = cache.Delete(testKey)
-	v, err = cache.Get(testKey, CacheReadTypeDefault)
+	v, err = cache.GetWithDeepCopy(testKey, CacheReadTypeDefault)
 	assert.NoError(t, err)
 	assert.Equal(t, 1, dataSource.called)
 	assert.Equal(t, nil, v, "cache should get nil after data is removed")
@@ -180,13 +180,13 @@ func TestCacheExpired(t *testing.T) {
 	dataSource, cache := newFakeCache(t)
 	dataSource.set(data)
 
-	v, err := cache.Get(testKey, CacheReadTypeDefault)
+	v, err := cache.GetWithDeepCopy(testKey, CacheReadTypeDefault)
 	assert.NoError(t, err)
 	assert.Equal(t, 1, dataSource.called)
 	assert.Equal(t, val, v, "cache should get correct data")
 
 	time.Sleep(fakeCacheTTL)
-	v, err = cache.Get(testKey, CacheReadTypeDefault)
+	v, err = cache.GetWithDeepCopy(testKey, CacheReadTypeDefault)
 	assert.NoError(t, err)
 	assert.Equal(t, 2, dataSource.called)
 	assert.Equal(t, val, v, "cache should get correct data even after expired")
@@ -200,13 +200,13 @@ func TestCacheAllowUnsafeRead(t *testing.T) {
 	dataSource, cache := newFakeCache(t)
 	dataSource.set(data)
 
-	v, err := cache.Get(testKey, CacheReadTypeDefault)
+	v, err := cache.GetWithDeepCopy(testKey, CacheReadTypeDefault)
 	assert.NoError(t, err)
 	assert.Equal(t, 1, dataSource.called)
 	assert.Equal(t, val, v, "cache should get correct data")
 
 	time.Sleep(fakeCacheTTL)
-	v, err = cache.Get(testKey, CacheReadTypeUnsafe)
+	v, err = cache.GetWithDeepCopy(testKey, CacheReadTypeUnsafe)
 	assert.NoError(t, err)
 	assert.Equal(t, 1, dataSource.called)
 	assert.Equal(t, val, v, "cache should return expired as allow unsafe read is allowed")
@@ -226,10 +226,10 @@ func TestCacheNoConcurrentGet(t *testing.T) {
 		wg.Add(1)
 		go func() {
 			defer wg.Done()
-			_, _ = cache.Get(testKey, CacheReadTypeDefault)
+			_, _ = cache.GetWithDeepCopy(testKey, CacheReadTypeDefault)
 		}()
 	}
-	v, err := cache.Get(testKey, CacheReadTypeDefault)
+	v, err := cache.GetWithDeepCopy(testKey, CacheReadTypeDefault)
 	wg.Wait()
 	assert.NoError(t, err)
 	assert.Equal(t, 1, dataSource.called)
@@ -244,12 +244,12 @@ func TestCacheForceRefresh(t *testing.T) {
 	dataSource, cache := newFakeCache(t)
 	dataSource.set(data)
 
-	v, err := cache.Get(testKey, CacheReadTypeDefault)
+	v, err := cache.GetWithDeepCopy(testKey, CacheReadTypeDefault)
 	assert.NoError(t, err)
 	assert.Equal(t, 1, dataSource.called)
 	assert.Equal(t, val, v, "cache should get correct data")
 
-	v, err = cache.Get(testKey, CacheReadTypeForceRefresh)
+	v, err = cache.GetWithDeepCopy(testKey, CacheReadTypeForceRefresh)
 	assert.NoError(t, err)
 	assert.Equal(t, 2, dataSource.called)
 	assert.Equal(t, val, v, "should refetch unexpired data as forced refresh")
diff --git a/pkg/provider/azure_backoff_test.go b/pkg/provider/azure_backoff_test.go
index a661b05b0..76db3e1b7 100644
--- a/pkg/provider/azure_backoff_test.go
+++ b/pkg/provider/azure_backoff_test.go
@@ -234,7 +234,7 @@ func TestCreateOrUpdateSecurityGroupCanceled(t *testing.T) {
 	assert.EqualError(t, fmt.Errorf("Retriable: false, RetryAfter: 0s, HTTPStatusCode: 0, RawError: %w", fmt.Errorf("canceledandsupersededduetoanotheroperation")), err.Error())
 
 	// security group should be removed from cache if the operation is canceled
-	shouldBeEmpty, err := az.nsgCache.Get("sg", cache.CacheReadTypeDefault)
+	shouldBeEmpty, err := az.nsgCache.GetWithDeepCopy("sg", cache.CacheReadTypeDefault)
 	assert.NoError(t, err)
 	assert.Empty(t, shouldBeEmpty)
 }
@@ -287,12 +287,12 @@ func TestCreateOrUpdateLB(t *testing.T) {
 		assert.EqualError(t, test.expectedErr, err.Error())
 
 		// loadbalancer should be removed from cache if the etag is mismatch or the operation is canceled
-		shouldBeEmpty, err := az.lbCache.Get("lb", cache.CacheReadTypeDefault)
+		shouldBeEmpty, err := az.lbCache.GetWithDeepCopy("lb", cache.CacheReadTypeDefault)
 		assert.NoError(t, err)
 		assert.Empty(t, shouldBeEmpty)
 
 		// public ip cache should be populated since there's GetPIP
-		shouldNotBeEmpty, err := az.pipCache.Get(az.getPIPCacheKey(az.ResourceGroup, "pip"), cache.CacheReadTypeDefault)
+		shouldNotBeEmpty, err := az.pipCache.GetWithDeepCopy(az.getPIPCacheKey(az.ResourceGroup, "pip"), cache.CacheReadTypeDefault)
 		assert.NoError(t, err)
 		assert.NotEmpty(t, shouldNotBeEmpty)
 	}
@@ -417,7 +417,7 @@ func TestCreateOrUpdatePIP(t *testing.T) {
 		err := az.CreateOrUpdatePIP(&v1.Service{}, az.ResourceGroup, network.PublicIPAddress{Name: to.StringPtr("nic")})
 		assert.EqualError(t, test.expectedErr, err.Error())
 
-		cachedPIP, err := az.pipCache.Get(az.getPIPCacheKey(az.ResourceGroup, "nic"), cache.CacheReadTypeDefault)
+		cachedPIP, err := az.pipCache.GetWithDeepCopy(az.getPIPCacheKey(az.ResourceGroup, "nic"), cache.CacheReadTypeDefault)
 		assert.NoError(t, err)
 		if test.cacheExpectedEmpty {
 			assert.Empty(t, cachedPIP)
@@ -496,7 +496,7 @@ func TestCreateOrUpdateRouteTable(t *testing.T) {
 		assert.EqualError(t, test.expectedErr, err.Error())
 
 		// route table should be removed from cache if the etag is mismatch or the operation is canceled
-		shouldBeEmpty, err := az.rtCache.Get("rt", cache.CacheReadTypeDefault)
+		shouldBeEmpty, err := az.rtCache.GetWithDeepCopy("rt", cache.CacheReadTypeDefault)
 		assert.NoError(t, err)
 		assert.Empty(t, shouldBeEmpty)
 	}
@@ -542,7 +542,7 @@ func TestCreateOrUpdateRoute(t *testing.T) {
 			assert.EqualError(t, test.expectedErr, err.Error())
 		}
 
-		shouldBeEmpty, err := az.rtCache.Get("rt", cache.CacheReadTypeDefault)
+		shouldBeEmpty, err := az.rtCache.GetWithDeepCopy("rt", cache.CacheReadTypeDefault)
 		assert.NoError(t, err)
 		assert.Empty(t, shouldBeEmpty)
 	}
diff --git a/pkg/provider/azure_vmss_test.go b/pkg/provider/azure_vmss_test.go
index f499ebea0..357597a30 100644
--- a/pkg/provider/azure_vmss_test.go
+++ b/pkg/provider/azure_vmss_test.go
@@ -958,7 +958,7 @@ func TestGetVmssVMByNodeIdentity(t *testing.T) {
 			assert.Nil(t, err)
 			virtualMachines := cached.(*sync.Map)
 			for _, vm := range test.goneVMList {
-				entry := VMSSVirtualMachinesEntry{
+				entry := VMSSVirtualMachineEntry{
 					ResourceGroup: ss.ResourceGroup,
 					VMSSName:      testVMSSName,
 				}
diff --git a/tests/e2e/network/node.go b/tests/e2e/network/node.go
index 88b7064e8..01d7473da 100644
--- a/tests/e2e/network/node.go
+++ b/tests/e2e/network/node.go
@@ -320,11 +320,11 @@ var _ = Describe("Azure nodes", func() {
 
 		utils.Logf("scaling VMSS")
 		count := *vmss.Sku.Capacity
-		err = utils.ScaleVMSS(tc, *vmss.Name, tc.GetResourceGroup(), int64(vmssScaleUpCelling))
+		err = utils.ScaleVMSS(tc, *vmss.Name, int64(vmssScaleUpCelling))
 
 		defer func() {
 			utils.Logf("restoring VMSS")
-			err = utils.ScaleVMSS(tc, *vmss.Name, tc.GetResourceGroup(), count)
+			err = utils.ScaleVMSS(tc, *vmss.Name, count)
 			Expect(err).NotTo(HaveOccurred())
 		}()
 
diff --git a/tests/e2e/node/vmss.go b/tests/e2e/node/vmss.go
index 2ba894842..1b7cd4f59 100644
--- a/tests/e2e/node/vmss.go
+++ b/tests/e2e/node/vmss.go
@@ -17,9 +17,6 @@ limitations under the License.
 package node
 
 import (
-	"os"
-	"strings"
-
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
 
@@ -29,7 +26,7 @@ import (
 	"sigs.k8s.io/cloud-provider-azure/tests/e2e/utils"
 )
 
-var _ = Describe("Lifecycle of VMSS", Label(utils.TestSuiteLabelVMSS), func() {
+var _ = Describe("Lifecycle of VMSS", Label(utils.TestSuiteLabelVMSS, utils.TestSuiteLabelVMSSScale), func() {
 	var (
 		ns     *v1.Namespace
 		k8sCli kubernetes.Interface
@@ -72,21 +69,13 @@ var _ = Describe("Lifecycle of VMSS", Label(utils.TestSuiteLabelVMSS), func() {
 		Expect(err).NotTo(HaveOccurred())
 
 		By("deallocate VMSS instance")
-		if strings.EqualFold(os.Getenv(utils.CAPZTestCCM), "true") {
-			err = utils.ScaleMachinePool(*vmss.Name, numInstance-1)
-		} else {
-			err = utils.ScaleVMSS(azCli, *vmss.Name, azCli.GetResourceGroup(), numInstance-1)
-		}
+		err = utils.Scale(azCli, *vmss.Name, numInstance-1)
 		Expect(err).NotTo(HaveOccurred())
 		expectedCap[*vmss.Name] = numInstance - 1
 
 		defer func() {
 			By("reset VMSS instance")
-			if strings.EqualFold(os.Getenv(utils.CAPZTestCCM), "true") {
-				err = utils.ScaleMachinePool(*vmss.Name, numInstance)
-			} else {
-				err = utils.ScaleVMSS(azCli, *vmss.Name, azCli.GetResourceGroup(), numInstance)
-			}
+			err = utils.Scale(azCli, *vmss.Name, numInstance)
 			Expect(err).NotTo(HaveOccurred())
 			expectedCap[*vmss.Name] = numInstance
 
@@ -116,21 +105,13 @@ var _ = Describe("Lifecycle of VMSS", Label(utils.TestSuiteLabelVMSS), func() {
 		Expect(err).NotTo(HaveOccurred())
 
 		By("allocate VMSS instance")
-		if strings.EqualFold(os.Getenv(utils.CAPZTestCCM), "true") {
-			err = utils.ScaleMachinePool(*vmss.Name, numInstance+1)
-		} else {
-			err = utils.ScaleVMSS(azCli, *vmss.Name, azCli.GetResourceGroup(), numInstance+1)
-		}
+		err = utils.Scale(azCli, *vmss.Name, numInstance+1)
 		Expect(err).NotTo(HaveOccurred())
 		expectedCap[*vmss.Name] = numInstance + 1
 
 		defer func() {
 			By("reset VMSS instance")
-			if strings.EqualFold(os.Getenv(utils.CAPZTestCCM), "true") {
-				err = utils.ScaleMachinePool(*vmss.Name, numInstance)
-			} else {
-				err = utils.ScaleVMSS(azCli, *vmss.Name, azCli.GetResourceGroup(), numInstance)
-			}
+			err = utils.Scale(azCli, *vmss.Name, numInstance)
 			Expect(err).NotTo(HaveOccurred())
 			expectedCap[*vmss.Name] = numInstance
 
diff --git a/tests/e2e/utils/consts.go b/tests/e2e/utils/consts.go
index ab9b60985..f85a125fe 100644
--- a/tests/e2e/utils/consts.go
+++ b/tests/e2e/utils/consts.go
@@ -24,6 +24,7 @@ const (
 	TestSuiteLabelMultiNodePools     = "Multi-Nodepool"
 	TestSuiteLabelSingleNodePool     = "Single-Nodepool"
 	TestSuiteLabelVMSS               = "VMSS"
+	TestSuiteLabelVMSSScale          = "VMSS-Scale"
 	TestSuiteLabelSpotVM             = "Spot-VM"
 	TestSuiteLabelKubenet            = "Kubenet"
 	TestSuiteLabelMultiGroup         = "Multi-Group"
diff --git a/tests/e2e/utils/vmss_utils.go b/tests/e2e/utils/vmss_utils.go
index d8649c9b5..525b3b724 100644
--- a/tests/e2e/utils/vmss_utils.go
+++ b/tests/e2e/utils/vmss_utils.go
@@ -19,6 +19,7 @@ package utils
 import (
 	"context"
 	"fmt"
+	"os"
 	"regexp"
 	"strings"
 
@@ -53,9 +54,17 @@ func FindTestVMSS(tc *AzureTestClient, rgName string) (*azcompute.VirtualMachine
 	return &vmssList[0], nil
 }
 
+func Scale(tc *AzureTestClient, vmssName string, instanceCount int64) error {
+	if strings.EqualFold(os.Getenv(CAPZTestCCM), "true") {
+		return ScaleMachinePool(vmssName, instanceCount)
+	}
+	return ScaleVMSS(tc, vmssName, instanceCount)
+}
+
 // ScaleVMSS scales the given VMSS
-func ScaleVMSS(tc *AzureTestClient, vmssName, rgName string, instanceCount int64) (err error) {
+func ScaleVMSS(tc *AzureTestClient, vmssName string, instanceCount int64) (err error) {
 	Logf("ScaleVMSS: start")
+	rgName := tc.GetResourceGroup()
 
 	vmssClient := tc.createVMSSClient()
 
@@ -178,10 +187,11 @@ func ValidateClusterNodesMatchVMSSInstances(tc *AzureTestClient, expectedCap map
 				Logf("Failed to validate VMSS instances: %s", err)
 			}
 
-			Logf("Matching cluster nodes[%s] with VMSS instances[%s]",
+			Logf("Matching cluster nodes[%s] with VMSS instances[%s]\nExpected capacity: %v, actual capacity: %v",
 				strings.Join(nodeSet.List(), ","),
-				strings.Join(instanceSet.List(), ","))
-			Logf("Expected capacity: %v, actual one: %v", expectedCap, actualCap)
+				strings.Join(instanceSet.List(), ","),
+				expectedCap,
+				actualCap)
 		}()
 
 		nodes, err = GetAgentNodes(k8sCli)
@@ -196,6 +206,10 @@ func ValidateClusterNodesMatchVMSSInstances(tc *AzureTestClient, expectedCap map
 		vmssList, _ := ListVMSSes(tc)
 		capMatch := true
 		for _, vmss := range vmssList {
+			cap, ok := expectedCap[*vmss.Name]
+			if !ok {
+				continue
+			}
 			vms, err := ListVMSSVMs(tc, *vmss.Name)
 			if err != nil {
 				return false, err
@@ -210,15 +224,23 @@ func ValidateClusterNodesMatchVMSSInstances(tc *AzureTestClient, expectedCap map
 				vmssInstanceSet.Insert(strings.ToLower(nodeName))
 				instanceSet.Insert(strings.ToLower(nodeName))
 			}
-			cap, ok := expectedCap[*vmss.Name]
-			if ok {
-				actualCap[*vmss.Name] = *vmss.Sku.Capacity
-				// For autoscaling cluster, simply comparing the capacity may not work since if the number of current nodes is lower than the "minCount", a new node may be created after scaling down.
-				// In this situation, we compare the expected capacity with the length of intersection between original nodes and current nodes.
-				if cap != *vmss.Sku.Capacity && cap != int64(originalNodeSet.Intersection(vmssInstanceSet).Len()) {
+
+			actualCap[*vmss.Name] = *vmss.Sku.Capacity
+			if cap != *vmss.Sku.Capacity {
+				if !strings.Contains(os.Getenv(AKSClusterType), "autoscaling") {
 					capMatch = false
+					break
+				}
+				if cap != int64(originalNodeSet.Intersection(vmssInstanceSet).Len()) {
+					// For autoscaling cluster, simply comparing the capacity may not work since if the number of current nodes is lower than the "minCount", a new node may be created after scaling down.
+					// In this situation, we compare the expected capacity with the length of intersection between original nodes and current nodes.
 					Logf("VMSS %q sku capacity is expected to be %d, but actually %d", *vmss.Name, cap, *vmss.Sku.Capacity)
+					capMatch = false
+					break
 				}
+			} else if int64(len(nodeSet)) != cap {
+				capMatch = false
+				break
 			}
 		}