Change:
} } <del> // test syncNode with instanceV2, same test case with TestGCECondition. func TestGCEConditionV2(t *testing.T) { existingNode := &v1.Node{ ObjectMeta: metav1.ObjectMeta{ Name:              "node0", CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), }, 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), }, }, }, Spec: v1.NodeSpec{ Taints: []v1.Taint{ { Key:    cloudproviderapi.TaintExternalCloudProvider, Value:  "true", Effect: v1.TaintEffectNoSchedule, }, }, }, } fakeCloud := &fakecloud.Cloud{ EnableInstancesV2: true, InstanceTypes: map[types.NodeName]string{ types.NodeName("node0"): "t1.micro", }, ProviderID: map[types.NodeName]string{ types.NodeName("node0"): "fake://12334", }, Addresses: []v1.NodeAddress{ { Type:    v1.NodeHostName, Address: "node0.cloud.internal", }, { Type:    v1.NodeInternalIP, Address: "10.0.0.1", }, { Type:    v1.NodeExternalIP, Address: "132.143.154.163", }, }, Provider: "gce", Err:      nil, } clientset := fake.NewSimpleClientset(existingNode) factory := informers.NewSharedInformerFactory(clientset, 0) eventBroadcaster := record.NewBroadcaster() cloudNodeController := &CloudNodeController{ kubeClient:                clientset, nodeInformer:              factory.Core().V1().Nodes(), nodesLister:               factory.Core().V1().Nodes().Lister(), cloud:                     fakeCloud, recorder:                  eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "cloud-node-controller"}), nodeStatusUpdateFrequency: 1 * time.Second, } stopCh := make(chan struct{}) defer close(stopCh) factory.Start(stopCh) factory.WaitForCacheSync(stopCh) w := eventBroadcaster.StartLogging(klog.Infof) defer w.Stop() cloudNodeController.syncNode(context.TODO(), existingNode.Name) updatedNode, err := clientset.CoreV1().Nodes().Get(context.TODO(), existingNode.Name, metav1.GetOptions{}) if err != nil { t.Fatalf("error getting updated nodes: %v", err) } conditionAdded := false for _, cond := range updatedNode.Status.Conditions { if cond.Status == "True" && cond.Type == "NetworkUnavailable" && cond.Reason == "NoRouteCreated" { conditionAdded = true } } assert.True(t, conditionAdded, "Network Route Condition for GCE not added by external cloud initializer") } // This test checks that a node with the external cloud provider taint is cloudprovider initialized and // the GCE route condition is added if cloudprovider is GCE func TestGCECondition(t *testing.T) { existingNode := &v1.Node{ ObjectMeta: metav1.ObjectMeta{ Name:              "node0", CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), }, 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), }, }, }, Spec: v1.NodeSpec{ Taints: []v1.Taint{ { Key:    cloudproviderapi.TaintExternalCloudProvider, Value:  "true", Effect: v1.TaintEffectNoSchedule, }, }, }, } fakeCloud := &fakecloud.Cloud{ EnableInstancesV2: false, InstanceTypes: map[types.NodeName]string{ types.NodeName("node0"): "t1.micro", }, Addresses: []v1.NodeAddress{ { Type:    v1.NodeHostName, Address: "node0.cloud.internal", }, { Type:    v1.NodeInternalIP, Address: "10.0.0.1", }, { Type:    v1.NodeExternalIP, Address: "132.143.154.163", }, }, Provider: "gce", Err:      nil, } clientset := fake.NewSimpleClientset(existingNode) factory := informers.NewSharedInformerFactory(clientset, 0) eventBroadcaster := record.NewBroadcaster() cloudNodeController := &CloudNodeController{ kubeClient:                clientset, nodeInformer:              factory.Core().V1().Nodes(), nodesLister:               factory.Core().V1().Nodes().Lister(), cloud:                     fakeCloud, recorder:                  eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "cloud-node-controller"}), nodeStatusUpdateFrequency: 1 * time.Second, } stopCh := make(chan struct{}) defer close(stopCh) factory.Start(stopCh) factory.WaitForCacheSync(stopCh) w := eventBroadcaster.StartLogging(klog.Infof) defer w.Stop() cloudNodeController.syncNode(context.TODO(), existingNode.Name) updatedNode, err := clientset.CoreV1().Nodes().Get(context.TODO(), existingNode.Name, metav1.GetOptions{}) if err != nil { t.Fatalf("error getting updated nodes: %v", err) } conditionAdded := false for _, cond := range updatedNode.Status.Conditions { if cond.Status == "True" && cond.Type == "NetworkUnavailable" && cond.Reason == "NoRouteCreated" { conditionAdded = true } } assert.True(t, conditionAdded, "Network Route Condition for GCE not added by external cloud initializer") } </del> func Test_reconcileNodeLabels(t *testing.T) { testcases := []struct { name           string

Discussion:
This pertains more for clusters running in GCP but not necessarily in a GKE environment. Clusters that using a CNI supporting overlay such as Calico, do not need explicit routes to be created on the infrastructure level, meaning that the route controller should not be necessary. The following snippet sets the condition for new GCE nodes when a new node is picked up by the node controller. This condition is supposed to be removed later by the route controller in However, as stated above there are cases where the route controller is not needed and should not be running. When Calico is deployed in such a way that the calico-node pods are ignoring any and all taints including the cloudprovider's taint (which we consider reasonable for a system pod such as calico), the above check creates the following race condition: a Calico pod may start and set the node network conditions ( to false) before the node controller reconciles the node. After that the node controller picks up the node and sets the condition to an otherwise healthy node. Leaving aside the race condition itself, I have to ask whether the check is meaningful in the first place. AFAIK, the kubelet in Kubelet only sets the condition for provider, but clusters should already be using kubelet with cloudprovider= For the node controller to not set the condition unnecessarily. An easy way to do it with a custom CCM image is to introduce some delay in the node sync loop, enough for pods such as calico-node to start on that node. No response <details</details<detailsGCP </details<details</details<details</details<details</details<detailsCalico </details
This should be fixed by /cc
/sig cloud-provider /sig network /assign
/triage accepted