Skip to content

Commit 1954678

Browse files
oilbeaterclaude
andauthored
fix(daemon): clean VLAN subinterfaces and label when excluding node from provider network (#6521)
* fix(daemon): clean VLAN subinterfaces and label when excluding node from provider network cleanProviderNetwork was missing two cleanup operations compared to handleDeleteProviderNetwork: it did not call cleanupAutoCreatedVlanInterfaces to remove auto-created VLAN subinterfaces, and did not clear the ProviderNetworkVlanIntTemplate node label. This caused VLAN subinterface leaks and stale labels when a node was permanently excluded from a ProviderNetwork. Signed-off-by: Mengxin Liu <liumengxinfly@gmail.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Mengxin Liu <liumengxinfly@gmail.com> * test(daemon): strengthen cleanProviderNetwork tests with JSON parsing Address review feedback: parse patch JSON and verify label values are explicitly null (removal) instead of just checking substring presence. Also ensure tests fail if no PatchAction is recorded. Signed-off-by: Mengxin Liu <liumengxinfly@gmail.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Mengxin Liu <liumengxinfly@gmail.com> --------- Signed-off-by: Mengxin Liu <liumengxinfly@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 932fccc commit 1954678

File tree

2 files changed

+121
-1
lines changed

2 files changed

+121
-1
lines changed

pkg/daemon/controller.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -535,13 +535,18 @@ func (c *Controller) cleanProviderNetwork(pn *kubeovnv1.ProviderNetwork, node *v
535535
fmt.Sprintf(util.ProviderNetworkInterfaceTemplate, pn.Name): nil,
536536
fmt.Sprintf(util.ProviderNetworkMtuTemplate, pn.Name): nil,
537537
fmt.Sprintf(util.ProviderNetworkExcludeTemplate, pn.Name): "true",
538+
fmt.Sprintf(util.ProviderNetworkVlanIntTemplate, pn.Name): nil,
538539
}
539540
if err := util.PatchLabels(c.config.KubeClient.CoreV1().Nodes(), node.Name, patch); err != nil {
540541
klog.Errorf("failed to patch labels of node %s: %v", node.Name, err)
541542
return err
542543
}
543544

544-
return c.ovsCleanProviderNetwork(pn.Name, providerNetworkNic(pn, node.Name))
545+
if err := c.ovsCleanProviderNetwork(pn.Name, providerNetworkNic(pn, node.Name)); err != nil {
546+
return err
547+
}
548+
549+
return c.cleanupAutoCreatedVlanInterfaces(pn.Name, "", nil)
545550
}
546551

547552
func (c *Controller) handleDeleteProviderNetwork(pn *kubeovnv1.ProviderNetwork) error {

pkg/daemon/controller_test.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
package daemon
22

33
import (
4+
"encoding/json"
5+
"fmt"
46
"testing"
57

68
"github.com/stretchr/testify/require"
79
v1 "k8s.io/api/core/v1"
810
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
"k8s.io/client-go/kubernetes/fake"
912
listerv1 "k8s.io/client-go/listers/core/v1"
13+
k8stesting "k8s.io/client-go/testing"
1014
"k8s.io/client-go/tools/cache"
1115
kubevirtv1 "kubevirt.io/api/core/v1"
16+
17+
kubeovnv1 "github.com/kubeovn/kube-ovn/pkg/apis/kubeovn/v1"
18+
"github.com/kubeovn/kube-ovn/pkg/util"
1219
)
1320

1421
func newLauncherPod(name, namespace, vmiName string, useNewLabel bool) *v1.Pod {
@@ -125,3 +132,111 @@ func TestIsVMLauncherPodAlive(t *testing.T) {
125132
})
126133
}
127134
}
135+
136+
// parsePatchLabels extracts the metadata.labels map from a merge-patch JSON.
137+
// Each key maps to a *string (nil means the label is being removed).
138+
func parsePatchLabels(t *testing.T, patchBytes []byte) map[string]*string {
139+
t.Helper()
140+
var raw struct {
141+
Metadata struct {
142+
Labels map[string]*string `json:"labels"`
143+
} `json:"metadata"`
144+
}
145+
require.NoError(t, json.Unmarshal(patchBytes, &raw))
146+
return raw.Metadata.Labels
147+
}
148+
149+
func TestCleanProviderNetworkPatchIncludesVlanIntLabel(t *testing.T) {
150+
t.Parallel()
151+
152+
pnName := "test-pn"
153+
nodeName := "test-node"
154+
155+
node := &v1.Node{
156+
ObjectMeta: metav1.ObjectMeta{
157+
Name: nodeName,
158+
Labels: map[string]string{
159+
fmt.Sprintf(util.ProviderNetworkReadyTemplate, pnName): "true",
160+
fmt.Sprintf(util.ProviderNetworkVlanIntTemplate, pnName): "true",
161+
},
162+
},
163+
}
164+
fakeClient := fake.NewSimpleClientset(node)
165+
166+
c := &Controller{
167+
config: &Configuration{KubeClient: fakeClient},
168+
}
169+
pn := &kubeovnv1.ProviderNetwork{
170+
ObjectMeta: metav1.ObjectMeta{Name: pnName},
171+
Spec: kubeovnv1.ProviderNetworkSpec{DefaultInterface: "eth0"},
172+
}
173+
174+
// PatchLabels is called before ovsCleanProviderNetwork, so the patch is
175+
// captured even when ovsCleanProviderNetwork fails in a test environment.
176+
_ = c.cleanProviderNetwork(pn, node)
177+
178+
vlanIntKey := fmt.Sprintf(util.ProviderNetworkVlanIntTemplate, pnName)
179+
var patchFound bool
180+
for _, action := range fakeClient.Actions() {
181+
pa, ok := action.(k8stesting.PatchAction)
182+
if !ok {
183+
continue
184+
}
185+
labels := parsePatchLabels(t, pa.GetPatch())
186+
val, exists := labels[vlanIntKey]
187+
if exists {
188+
require.Nil(t, val, "VlanIntTemplate label should be set to null (removal)")
189+
patchFound = true
190+
break
191+
}
192+
}
193+
require.True(t, patchFound, "patch should include %s label cleanup", vlanIntKey)
194+
}
195+
196+
func TestCleanProviderNetworkLabelConsistency(t *testing.T) {
197+
t.Parallel()
198+
199+
pnName := "test-pn"
200+
201+
// All labels that handleDeleteProviderNetwork cleans (except ExcludeTemplate
202+
// which is set to "true" in cleanProviderNetwork instead of nil).
203+
expectedCleanedLabels := []string{
204+
fmt.Sprintf(util.ProviderNetworkReadyTemplate, pnName),
205+
fmt.Sprintf(util.ProviderNetworkInterfaceTemplate, pnName),
206+
fmt.Sprintf(util.ProviderNetworkMtuTemplate, pnName),
207+
fmt.Sprintf(util.ProviderNetworkVlanIntTemplate, pnName),
208+
}
209+
210+
labels := make(map[string]string)
211+
for _, key := range expectedCleanedLabels {
212+
labels[key] = "true"
213+
}
214+
node := &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: labels}}
215+
fakeClient := fake.NewSimpleClientset(node)
216+
217+
c := &Controller{config: &Configuration{KubeClient: fakeClient}}
218+
pn := &kubeovnv1.ProviderNetwork{
219+
ObjectMeta: metav1.ObjectMeta{Name: pnName},
220+
Spec: kubeovnv1.ProviderNetworkSpec{DefaultInterface: "eth0"},
221+
}
222+
223+
_ = c.cleanProviderNetwork(pn, node)
224+
225+
var patchFound bool
226+
for _, action := range fakeClient.Actions() {
227+
pa, ok := action.(k8stesting.PatchAction)
228+
if !ok {
229+
continue
230+
}
231+
patchFound = true
232+
patchLabels := parsePatchLabels(t, pa.GetPatch())
233+
for _, key := range expectedCleanedLabels {
234+
val, exists := patchLabels[key]
235+
require.True(t, exists,
236+
"cleanProviderNetwork should clean label %s (consistent with handleDeleteProviderNetwork)", key)
237+
require.Nil(t, val,
238+
"label %s should be set to null (removal)", key)
239+
}
240+
}
241+
require.True(t, patchFound, "expected at least one PatchAction to be recorded")
242+
}

0 commit comments

Comments
 (0)