diff --git a/staging/src/k8s.io/cloud-provider/cloud.go b/staging/src/k8s.io/cloud-provider/cloud.go index d4c11dc6649..d250d5accaf 100644 --- a/staging/src/k8s.io/cloud-provider/cloud.go +++ b/staging/src/k8s.io/cloud-provider/cloud.go @@ -327,4 +327,9 @@ type InstanceMetadata struct { // * topology.kubernetes.io/region= // * failure-domain.beta.kubernetes.io/region= (DEPRECATED) Region string + + // AdditionalLabels is a map of additional labels provided by the cloud provider. + // When provided, they will be applied to the node and enable cloud providers + // to labels nodes with information that may be valuable to that provider. + AdditionalLabels map[string]string } diff --git a/staging/src/k8s.io/cloud-provider/controllers/node/node_controller.go b/staging/src/k8s.io/cloud-provider/controllers/node/node_controller.go index c7141a8fe4e..a7251ff05f7 100644 --- a/staging/src/k8s.io/cloud-provider/controllers/node/node_controller.go +++ b/staging/src/k8s.io/cloud-provider/controllers/node/node_controller.go @@ -20,6 +20,7 @@ import ( "context" "errors" "fmt" + "regexp" "time" v1 "k8s.io/api/core/v1" @@ -572,6 +573,30 @@ func (cnc *CloudNodeController) getNodeModifiersFromCloudProvider( }) } + if len(instanceMeta.AdditionalLabels) > 0 { + klog.V(2).Infof("Adding additional node label(s) from cloud provider: %v", instanceMeta.AdditionalLabels) + nodeModifiers = append(nodeModifiers, func(n *v1.Node) { + if n.Labels == nil { + n.Labels = map[string]string{} + } + + k8sNamespaceRegex := regexp.MustCompile("(kubernetes|k8s).io/") + for k, v := range instanceMeta.AdditionalLabels { + // Cloud provider should not be using kubernetes namespaces in labels + if isK8sNamespace := k8sNamespaceRegex.MatchString(k); isK8sNamespace { + klog.Warningf("Discarding node label %s with kubernetes namespace", k) + continue + } else if originalVal, ok := n.Labels[k]; ok { + if originalVal != v { + klog.Warningf("Discarding node label %s that is already present", k) + } + continue + } + n.Labels[k] = v + } + }) + } + return nodeModifiers, nil } diff --git a/staging/src/k8s.io/cloud-provider/controllers/node/node_controller_test.go b/staging/src/k8s.io/cloud-provider/controllers/node/node_controller_test.go index 75d8e021295..7f5e95e0a9d 100644 --- a/staging/src/k8s.io/cloud-provider/controllers/node/node_controller_test.go +++ b/staging/src/k8s.io/cloud-provider/controllers/node/node_controller_test.go @@ -1302,6 +1302,249 @@ func Test_syncNode(t *testing.T) { }, }, }, + { + name: "[instanceV2] provided additional labels", + fakeCloud: &fakecloud.Cloud{ + EnableInstancesV2: true, + Addresses: []v1.NodeAddress{ + { + Type: v1.NodeInternalIP, + Address: "10.0.0.1", + }, + { + Type: v1.NodeExternalIP, + Address: "132.143.154.163", + }, + }, + ExistsByProviderID: true, + Err: nil, + Zone: cloudprovider.Zone{ + FailureDomain: "us-west-1a", + Region: "us-west", + }, + AdditionalLabels: map[string]string{ + "topology.k8s.cp/zone-id": "az1", + "my.custom.label/foo": "bar", + }, + }, + existingNode: &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Annotations: map[string]string{ + cloudproviderapi.AnnotationAlphaProvidedIPAddr: "10.0.0.1", + }, + }, + Spec: v1.NodeSpec{ + Taints: []v1.Taint{ + { + Key: "ImproveCoverageTaint", + Value: "true", + Effect: v1.TaintEffectNoSchedule, + }, + { + Key: cloudproviderapi.TaintExternalCloudProvider, + Value: "true", + Effect: v1.TaintEffectNoSchedule, + }, + }, + ProviderID: "node0.cp.12345", + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionUnknown, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + Addresses: []v1.NodeAddress{ + { + Type: v1.NodeHostName, + Address: "node0.cloud.internal", + }, + }, + }, + }, + updatedNode: &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Annotations: map[string]string{ + cloudproviderapi.AnnotationAlphaProvidedIPAddr: "10.0.0.1", + }, + Labels: map[string]string{ + "failure-domain.beta.kubernetes.io/region": "us-west", + "failure-domain.beta.kubernetes.io/zone": "us-west-1a", + "topology.kubernetes.io/region": "us-west", + "topology.kubernetes.io/zone": "us-west-1a", + "topology.k8s.cp/zone-id": "az1", + "my.custom.label/foo": "bar", + }, + }, + Spec: v1.NodeSpec{ + Taints: []v1.Taint{ + { + Key: "ImproveCoverageTaint", + Value: "true", + Effect: v1.TaintEffectNoSchedule, + }, + }, + ProviderID: "node0.cp.12345", + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionUnknown, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + Addresses: []v1.NodeAddress{ + { + Type: v1.NodeInternalIP, + Address: "10.0.0.1", + }, + { + Type: v1.NodeExternalIP, + Address: "132.143.154.163", + }, + { + Type: v1.NodeHostName, + Address: "node0.cloud.internal", + }, + }, + }, + }, + }, + { + name: "[instanceV2] provided additional labels with labels to discard", + fakeCloud: &fakecloud.Cloud{ + EnableInstancesV2: true, + Addresses: []v1.NodeAddress{ + { + Type: v1.NodeInternalIP, + Address: "10.0.0.1", + }, + { + Type: v1.NodeExternalIP, + Address: "132.143.154.163", + }, + }, + ExistsByProviderID: true, + Err: nil, + Zone: cloudprovider.Zone{ + FailureDomain: "us-west-1a", + Region: "us-west", + }, + AdditionalLabels: map[string]string{ + // Kubernetes reserves k8s.io and kubernetes.io namespaces + // and should be discarded + "topology.kubernetes.io/region": "us-other-west", + "topology.k8s.io/region": "us-other-west", + // Should discard labels that already exist + "my.custom.label/foo": "bar", + "my.custom.label/bar": "foo", + }, + }, + existingNode: &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Annotations: map[string]string{ + cloudproviderapi.AnnotationAlphaProvidedIPAddr: "10.0.0.1", + }, + Labels: map[string]string{ + "my.custom.label/foo": "fizz", + "my.custom.label/bar": "foo", + }, + }, + Spec: v1.NodeSpec{ + Taints: []v1.Taint{ + { + Key: "ImproveCoverageTaint", + Value: "true", + Effect: v1.TaintEffectNoSchedule, + }, + { + Key: cloudproviderapi.TaintExternalCloudProvider, + Value: "true", + Effect: v1.TaintEffectNoSchedule, + }, + }, + ProviderID: "node0.cp.12345", + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionUnknown, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + Addresses: []v1.NodeAddress{ + { + Type: v1.NodeHostName, + Address: "node0.cloud.internal", + }, + }, + }, + }, + updatedNode: &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Annotations: map[string]string{ + cloudproviderapi.AnnotationAlphaProvidedIPAddr: "10.0.0.1", + }, + Labels: map[string]string{ + "failure-domain.beta.kubernetes.io/region": "us-west", + "failure-domain.beta.kubernetes.io/zone": "us-west-1a", + "topology.kubernetes.io/region": "us-west", + "topology.kubernetes.io/zone": "us-west-1a", + "my.custom.label/foo": "fizz", + "my.custom.label/bar": "foo", + }, + }, + Spec: v1.NodeSpec{ + Taints: []v1.Taint{ + { + Key: "ImproveCoverageTaint", + Value: "true", + Effect: v1.TaintEffectNoSchedule, + }, + }, + ProviderID: "node0.cp.12345", + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionUnknown, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + Addresses: []v1.NodeAddress{ + { + Type: v1.NodeInternalIP, + Address: "10.0.0.1", + }, + { + Type: v1.NodeExternalIP, + Address: "132.143.154.163", + }, + { + Type: v1.NodeHostName, + Address: "node0.cloud.internal", + }, + }, + }, + }, + }, { name: "[instanceV2] provider ID already set", fakeCloud: &fakecloud.Cloud{ diff --git a/staging/src/k8s.io/cloud-provider/fake/fake.go b/staging/src/k8s.io/cloud-provider/fake/fake.go index 14a2c2f9b09..38e1540af08 100644 --- a/staging/src/k8s.io/cloud-provider/fake/fake.go +++ b/staging/src/k8s.io/cloud-provider/fake/fake.go @@ -97,7 +97,8 @@ type Cloud struct { ProviderID map[types.NodeName]string addCallLock sync.Mutex cloudprovider.Zone - VolumeLabelMap map[string]map[string]string + VolumeLabelMap map[string]map[string]string + AdditionalLabels map[string]string OverrideInstanceMetadata func(ctx context.Context, node *v1.Node) (*cloudprovider.InstanceMetadata, error) @@ -373,11 +374,12 @@ func (f *Cloud) InstanceMetadata(ctx context.Context, node *v1.Node) (*cloudprov } return &cloudprovider.InstanceMetadata{ - ProviderID: providerID, - InstanceType: f.InstanceTypes[types.NodeName(node.Spec.ProviderID)], - NodeAddresses: f.Addresses, - Zone: f.Zone.FailureDomain, - Region: f.Zone.Region, + ProviderID: providerID, + InstanceType: f.InstanceTypes[types.NodeName(node.Spec.ProviderID)], + NodeAddresses: f.Addresses, + Zone: f.Zone.FailureDomain, + Region: f.Zone.Region, + AdditionalLabels: f.AdditionalLabels, }, f.MetadataErr }