Describe the bug
The PodCIDR field in NodeSpec is "immutable": once set, it cannot be changed.
However, because the NodeRouteController uses a workqueue (with the Node name as the key), it is possible to observe the same Node name in addNodeRoute, but with a different PodCIDR. This is not something we account for today, which means the controller may be buggy.
More precisely, the following sequence of events would trigger a bug, where the datapath uses a stale PodCIDR:
- Node X with name "foo" PodCIDR 1.1.1.0/24 is created
- Node X is deleted
- Node Y with name "foo" (same name) and PodCIDR 1.1.1.1/24 is created
The Delete handler (step 2) and the Add handler (step 3) will both enqueue the Node name ("foo"). If the time between steps 2 and 3 is small enough, the events will be "merged" in the workqueue, and syncNodeRoute will be called only once. At that point, syncNodeRoute will get Node Y from the lister and addNodeRoute will be called. There will still be an entry in the installedNodes indexer (corresponding to Node Y) and because we do not check that the PodCIDRs match, we will skip updating the datapath:
|
if installed && nrInfo.(*nodeRouteInfo).nodeMAC.String() == peerNodeMAC.String() && |
|
peerNodeIPs.Equal(*nrInfo.(*nodeRouteInfo).nodeIPs) && |
|
nrInfo.(*nodeRouteInfo).wireGuardPublicKey == peerWireGuardPublicKey { |
|
return nil |
|
} |
Note that because we do check that the Node IPs match, Node Y must have the same IPs as Node X in order for the issue to be observed (i.e., same name and same IPs). This "constraint", coupled with timing considerations, mean that this issue is quite unlikely.
To Reproduce
The following unit test can be used (add it to pkg/agent/controller/noderoute/node_route_controller_test.go):
func TestNodeRecreateNewPodCIDR(t *testing.T) {
c := newController(t, &config.NetworkConfig{})
defer c.queue.ShutDown()
stopCh := make(chan struct{})
defer close(stopCh)
c.informerFactory.Start(stopCh)
c.informerFactory.WaitForCacheSync(stopCh)
newNode := node1.DeepCopy()
newNode.Spec.PodCIDR = podCIDR2.String()
newNode.Spec.PodCIDRs = []string{podCIDR2.String()}
c.clientset.CoreV1().Nodes().Create(context.TODO(), node1, metav1.CreateOptions{})
c.ofClient.EXPECT().InstallNodeFlows("node1", gomock.Any(), &dsIPs1, uint32(0), nil).Times(1)
c.routeClient.EXPECT().AddRoutes(podCIDR, "node1", nodeIP1, podCIDRGateway).Times(1)
c.processNextWorkItem()
// Node is deleted, then shortly after is "recreated" (same name) with a different PodCIDR
c.clientset.CoreV1().Nodes().Delete(context.TODO(), node1.Name, metav1.DeleteOptions{})
time.Sleep(100 * time.Millisecond)
c.clientset.CoreV1().Nodes().Create(context.TODO(), newNode, metav1.CreateOptions{})
// At the moment, the test will fail because these expectations are not met.
c.ofClient.EXPECT().InstallNodeFlows("node1", gomock.Any(), &dsIPs1, uint32(0), nil).Times(1)
c.routeClient.EXPECT().AddRoutes(podCIDR2, "node1", nodeIP1, podCIDR2Gateway).Times(1)
// Using a sleep for convenience here.
time.Sleep(1 * time.Second)
c.processNextWorkItem()
}
Versions:
v2.2
Describe the bug
The
PodCIDRfield inNodeSpecis "immutable": once set, it cannot be changed.However, because the
NodeRouteControlleruses a workqueue (with the Node name as the key), it is possible to observe the same Node name inaddNodeRoute, but with a different PodCIDR. This is not something we account for today, which means the controller may be buggy.More precisely, the following sequence of events would trigger a bug, where the datapath uses a stale PodCIDR:
The Delete handler (step 2) and the Add handler (step 3) will both enqueue the Node name ("foo"). If the time between steps 2 and 3 is small enough, the events will be "merged" in the workqueue, and
syncNodeRoutewill be called only once. At that point,syncNodeRoutewill get Node Y from the lister andaddNodeRoutewill be called. There will still be an entry in theinstalledNodesindexer (corresponding to Node Y) and because we do not check that the PodCIDRs match, we will skip updating the datapath:antrea/pkg/agent/controller/noderoute/node_route_controller.go
Lines 524 to 528 in e4aedec
Note that because we do check that the Node IPs match, Node Y must have the same IPs as Node X in order for the issue to be observed (i.e., same name and same IPs). This "constraint", coupled with timing considerations, mean that this issue is quite unlikely.
To Reproduce
The following unit test can be used (add it to
pkg/agent/controller/noderoute/node_route_controller_test.go):Versions:
v2.2