Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions pkg/state/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,13 +220,58 @@ func isUnmanaged(ifaceData map[string]interface{}) bool {
return ifaceData["state"] == "ignore"
}

const (
// interfaceCountThreshold is the number of interfaces above which
// verbose fields are stripped from VLAN interfaces to reduce the
// NodeNetworkState object size and avoid exceeding the etcd request
// size limit (1.5 MB).
interfaceCountThreshold = 500

// vlanInterfaceType is the nmstate type string for VLAN interfaces.
vlanInterfaceType = "vlan"
)

// vlanEssentialFields is the set of fields to preserve on VLAN interfaces
// when the interface count exceeds the threshold. All other fields are
// stripped to reduce serialized size.
var vlanEssentialFields = map[string]struct{}{
"name": {},
"type": {},
"state": {},
"ipv4": {},
"ipv6": {},
"vlan": {},
}

// stripVerboseVlanFields removes non-essential fields from VLAN interfaces
// when the total interface count exceeds the threshold. This prevents the
// NodeNetworkState from exceeding the etcd request size limit on nodes
// with large numbers of VLANs.
func stripVerboseVlanFields(interfaces []interfaceState) []interfaceState {
if len(interfaces) <= interfaceCountThreshold {
return interfaces
}
for i := range interfaces {
if interfaces[i].Type != vlanInterfaceType {
continue
}
for key := range interfaces[i].Data {
if _, keep := vlanEssentialFields[key]; !keep {
delete(interfaces[i].Data, key)
}
}
}
return interfaces
}

func filterOut(currentState shared.State) (shared.State, error) {
var state rootState
if err := yaml.Unmarshal(currentState.Raw, &state); err != nil {
return currentState, err
}

state.Interfaces = filterOutInterfaces(state.Interfaces)
state.Interfaces = stripVerboseVlanFields(state.Interfaces)
if state.Routes != nil {
state.Routes.Running = filterOutRoutes(state.Routes.Running, state.Interfaces)
state.Routes.Config = filterOutRoutes(state.Routes.Config, state.Interfaces)
Expand Down
99 changes: 99 additions & 0 deletions pkg/state/filter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@ limitations under the License.
package state

import (
"fmt"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

nmstate "github.com/nmstate/kubernetes-nmstate/api/shared"
"sigs.k8s.io/yaml"
)

var _ = Describe("FilterOut", func() {
Expand Down Expand Up @@ -389,6 +392,102 @@ routes:
Expect(returnedState).To(MatchYAML(filteredState))
})
})

Context("when the number of interfaces exceeds the threshold", func() {
It("should strip verbose fields from VLAN interfaces but keep essential ones", func() {
// Build a state with more than interfaceCountThreshold interfaces
// by generating VLAN interfaces
yamlStr := "interfaces:\n"
yamlStr += "- name: eth0\n state: up\n type: ethernet\n mtu: 1500\n mac-address: 00:11:22:33:44:55\n"
for i := 0; i < interfaceCountThreshold+10; i++ {
yamlStr += fmt.Sprintf(`- name: eth0.%d
type: vlan
state: up
mtu: 1400
mac-address: 02:00:00:%02x:%02x:00
ipv4:
enabled: true
address:
- ip: 192.168.%d.1
prefix-length: 24
vlan:
id: %d
base-iface: eth0
lldp:
enabled: false
ethtool:
feature:
tx-checksum-ip-generic: true
`, i, i/256, i%256, i%256, i)
}
yamlStr += "routes:\n config: []\n running: []\n"

state := nmstate.NewState(yamlStr)
result, err := filterOut(state)
Expect(err).ToNot(HaveOccurred())

// Parse the result to verify
var parsed rootState
err = yaml.Unmarshal(result.Raw, &parsed)
Expect(err).ToNot(HaveOccurred())

// Ethernet interface should be untouched
eth0 := parsed.Interfaces[0]
Expect(eth0.Name).To(Equal("eth0"))
Expect(eth0.Data).To(HaveKey("mtu"))
Expect(eth0.Data).To(HaveKey("mac-address"))

// VLAN interfaces should have verbose fields stripped
vlan0 := parsed.Interfaces[1]
Expect(vlan0.Name).To(Equal("eth0.0"))
Expect(vlan0.Type).To(Equal("vlan"))
Expect(vlan0.Data).To(HaveKey("state"))
Expect(vlan0.Data).To(HaveKey("vlan"))
Expect(vlan0.Data).To(HaveKey("ipv4"))
// Verbose fields should be gone
Expect(vlan0.Data).NotTo(HaveKey("mtu"))
Expect(vlan0.Data).NotTo(HaveKey("mac-address"))
Expect(vlan0.Data).NotTo(HaveKey("lldp"))
Expect(vlan0.Data).NotTo(HaveKey("ethtool"))
})

It("should not strip fields when interface count is below threshold", func() {
state := nmstate.NewState(`
interfaces:
- name: eth0
state: up
type: ethernet
mtu: 1500
- name: eth0.100
type: vlan
state: up
mtu: 1400
mac-address: 02:00:00:00:64:00
ipv4:
enabled: true
vlan:
id: 100
base-iface: eth0
lldp:
enabled: false
routes:
config: []
running: []
`)
result, err := filterOut(state)
Expect(err).ToNot(HaveOccurred())

var parsed rootState
err = yaml.Unmarshal(result.Raw, &parsed)
Expect(err).ToNot(HaveOccurred())

// VLAN should retain all fields when below threshold
vlan := parsed.Interfaces[1]
Expect(vlan.Data).To(HaveKey("mtu"))
Expect(vlan.Data).To(HaveKey("mac-address"))
Expect(vlan.Data).To(HaveKey("lldp"))
})
})
})

var _ = Describe("CountRoutes", func() {
Expand Down