Repository: kubernetes/kubernetes (language: go)

Issue:
PersistentVolumeLabel admission plugin provides wrong Azure region
### What happened?

On Kubernetes 1.29 with PersistentVolumeLabel admission plugin enabled and no in-tree cloud provider configured in the kube-apiserver, the admission plugins adds label `topology.kubernetes.io/region: ""` to in-tree Azure Disk PVs.

The label is completely wrong - the Azure cloud provider is not initialized and [this](https://github.com/kubernetes/kubernetes/blob/d9d45306df052aa5824fd8b2dab4e61f5ecc481c/staging/src/k8s.io/legacy-cloud-providers/azure/azure_managedDiskController.go#L368) just provides empty string. This makes any pod that uses these PVs unschedulable.

As result, all in-tree PVs dynamically provisioned using CSI migration are not usable, because they have a wrong region.

### What did you expect to happen?

When there is no cloud config provided, PersistentVolumeLabel does not add any labels to PVs. PVs created by the CSI driver + CSI migration already have correct `nodeAffinity`, the labels are useless.

### How can we reproduce it (as minimally and precisely as possible)?

1. Enable PersistentVolumeLabel admission in your cluster and disable any cloud providers. It does not need access to Azure, you can run it anywhere.

    Example for `local-up-cluster.sh`:

    ```
    ENABLE_ADMISSION_PLUGINS="NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,Priority,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota,NodeRestriction,PersistentVolumeLabel"
    hack/local-up-cluster.sh
    ```

2. Create in-tree Azure Disk PV. Again, we're testing just the admission, we're not going to use the PV in a pod, so no access to Azure is needed. UUIDs were sanitized ;-)

    ```
    $ kubectl create -f <<EOF
    
    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: myvol
    spec:
      accessModes:
      - ReadWriteOnce
      capacity:
        storage: 5Gi
      azureDisk:
          cachingMode: ReadWrite
          diskName: pvc-ade30d58-5a51-4a62-a83a-142267b74f9e
          diskURI: /subscriptions/4347f140-6c74-4199-b4fc-1938eb6c16c0/resourceGroups/jsafrane-h-jsafrane-h-kxvpr/providers/Microsoft.Compute/disks/pvc-ade30d58-5a51-4a62-a83a-142267b74f9e
          fsType: ext4
          kind: Managed
          readOnly: false
    
    EOF
    ```

Actual result: wrong region label on the PV:

```
$ kubectl get pv --show-labels
LABELS
topology.kubernetes.io/region=
```

Expected result: no labels on the PV.

### Anything else we need to know?

_No response_

### Kubernetes version

<details>

```console
$ kubectl version
Server Version: v1.29.3
```

</details>


### Cloud provider

<details>
None
</details>


### OS version

_No response_

### Install tools

<details>

</details>


### Container runtime (CRI) and version (if applicable)

<details>

</details>


### Related plugins (CNI, CSI, ...) and versions (if applicable)

<details>

</details>



Discussion / hints:
/sig storage
/sig cloud-provider
/sig storage

@andyzhangx it would be great if Azure cloud provider did not provide any labels when it's actually not configured (i.e. the external one is used). Is there an easy way to detect that in the admission plugin in the API server?

`GetCloudProvider` [here](https://github.com/kubernetes/kubernetes/blob/d9d45306df052aa5824fd8b2dab4e61f5ecc481c/plugin/pkg/admission/storage/persistentvolume/label/admission.go#L285-L285) _could_ return some error + the admission _would_ ignore it (i.e. add no labels to PVs, but admit it; now the admission is denied on error)

Or `GetAzureDiskLabels` [deep inside the cloud provider](https://github.com/kubernetes/kubernetes/blob/d9d45306df052aa5824fd8b2dab4e61f5ecc481c/staging/src/k8s.io/legacy-cloud-providers/azure/azure_managedDiskController.go#L368-L368) would see it's not initialized and return no labels, say
```
if c.Location == "" {
    // Cloud provider is not initialized.
    return nil, nil
}
```

Note that it affects 1.29 and older that still have the cloud provider. I am fixing 1.30 differently in https://github.com/kubernetes/kubernetes/pull/124505
/priority important-soon

The PersistentVolumeLabel admission plugin + CSI migration on azure are basically unusable when used together. I guess it affects a small nr. of clusters, but there is no workaround.
@jsafrane the pv lable issue only exists on in-tree disk pv, right? since in-tree disk volume is already deprecated long time ago, I think we could live with that issue.

Fix patch:
diff --git a/staging/src/k8s.io/legacy-cloud-providers/azure/azure_managedDiskController.go b/staging/src/k8s.io/legacy-cloud-providers/azure/azure_managedDiskController.go
index b31f732033567..76ea737e69464 100644
--- a/staging/src/k8s.io/legacy-cloud-providers/azure/azure_managedDiskController.go
+++ b/staging/src/k8s.io/legacy-cloud-providers/azure/azure_managedDiskController.go
@@ -364,6 +364,11 @@ func (c *Cloud) GetAzureDiskLabels(diskURI string) (map[string]string, error) {
 		return nil, err
 	}
 
+	if c.Location == "" {
+		// The cloud provider is not initialized and cannot get the topology labels.
+		return nil, nil
+	}
+
 	labels := map[string]string{
 		v1.LabelTopologyRegion: c.Location,
 	}


Test patch:
diff --git a/staging/src/k8s.io/legacy-cloud-providers/azure/azure_managedDiskController_test.go b/staging/src/k8s.io/legacy-cloud-providers/azure/azure_managedDiskController_test.go
index 9d0aa3d95a655..58534d23b157c 100644
--- a/staging/src/k8s.io/legacy-cloud-providers/azure/azure_managedDiskController_test.go
+++ b/staging/src/k8s.io/legacy-cloud-providers/azure/azure_managedDiskController_test.go
@@ -28,6 +28,7 @@ import (
 	"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-12-01/compute"
 	"github.com/golang/mock/gomock"
 	"github.com/stretchr/testify/assert"
+	cloudprovider "k8s.io/cloud-provider"
 
 	v1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/resource"
@@ -551,3 +552,31 @@ func TestGetLabelsForVolume(t *testing.T) {
 		assert.Equal(t, test.expectedErrMsg, err, "TestCase[%d]: %s, expected: %v, return: %v", i, test.desc, test.expectedErrMsg, err)
 	}
 }
+
+func TestGetAzureDiskLabelsUninitialized(t *testing.T) {
+	// Create an uninitialized Azure cloud provider
+	testCloud, err := NewCloud(nil)
+	if err != nil {
+		t.Fatalf("Failed to create uninitialized cloud: %v", err)
+	}
+
+	pv := &v1.PersistentVolume{
+		Spec: v1.PersistentVolumeSpec{
+			PersistentVolumeSource: v1.PersistentVolumeSource{
+				AzureDisk: &v1.AzureDiskVolumeSource{
+					DiskName:    cloudvolume.ProvisionedVolumeName,
+					DataDiskURI: "/subscriptions/4347f140-6c74-4199-b4fc-1938eb6c16c0/resourceGroups/test/providers/Microsoft.Compute/disks/pvc-ade30d58-5a51-4a62-a83a-142267b74f9e",
+				},
+			},
+		},
+	}
+
+	labeller := testCloud.(cloudprovider.PVLabeler)
+	labels, err := labeller.GetLabelsForVolume(context.Background(), pv)
+	if err != nil {
+		t.Errorf("Unexpected GetLabelsForVolume error: %v", err)
+	}
+	if labels != nil {
+		t.Errorf("The cloud provider should return nil labels for uninitialized cloud, got: %v", labels)
+	}
+}