@@ -11,6 +11,7 @@ import (
1111 "net"
1212 "os"
1313 "regexp"
14+ "strconv"
1415 "strings"
1516 "testing"
1617 "time"
@@ -254,6 +255,90 @@ func ValidateSysctlConfig(ctx context.Context, s *Scenario, customSysctls map[st
254255 }
255256}
256257
258+ // ValidateNetworkInterfaceConfig validates network interface configuration settings using ethtool.
259+ // It identifies network interfaces with slot names matching the enP* pattern (same logic as the udev rule),
260+ // then verifies that each interface has the expected configuration settings (e.g., rx buffer size).
261+ // The nicConfig map specifies the ethtool settings to validate (key: setting name, value: expected value).
262+ func ValidateNetworkInterfaceConfig (ctx context.Context , s * Scenario , nicConfig map [string ]string ) {
263+ s .T .Helper ()
264+
265+ // Get list of NICs using udevadm (same logic as udev rule)
266+ getNicsCommand := []string {
267+ "#!/usr/bin/env bash" ,
268+ "set -euo pipefail" ,
269+ "echo '=== NICs to Configure ==='" ,
270+ "enp_ifaces=()" ,
271+ "for dev in /sys/class/net/*; do" ,
272+ " iface=\" $(basename \" $dev\" )\" " ,
273+ " slot=\" $(udevadm info -q property -p \" $dev\" 2>/dev/null | awk -F= '$1==\" ID_NET_NAME_SLOT\" {print $2; exit}')\" " ,
274+ " [[ \" $slot\" == enP* ]] && enp_ifaces+=(\" $iface\" )" ,
275+ "done" ,
276+ "IFS=,; echo \" ${enp_ifaces[*]}\" " ,
277+ }
278+ nicsResult := execScriptOnVMForScenarioValidateExitCode (ctx , s , strings .Join (getNicsCommand , "\n " ), 0 , "could not get nics to configure" )
279+ s .T .Logf ("NICs to configure:\n %s" , nicsResult .stdout )
280+
281+ // Parse NIC output - it may be multi-line with header
282+ lines := strings .Split (strings .TrimSpace (nicsResult .stdout ), "\n " )
283+ nicsOutput := ""
284+ for _ , line := range lines {
285+ line = strings .TrimSpace (line )
286+ // Skip header lines
287+ if strings .Contains (line , "===" ) || line == "" {
288+ continue
289+ }
290+ nicsOutput = line
291+ break
292+ }
293+
294+ nics := strings .Split (nicsOutput , "," )
295+
296+ s .T .Logf ("Parsed NICs list: %v (count: %d)" , nics , len (nics ))
297+
298+ if len (nics ) == 0 || (len (nics ) == 1 && strings .TrimSpace (nics [0 ]) == "" ) {
299+ s .T .Fatalf ("no nics found to validate network interface config" )
300+ return
301+ }
302+
303+ for _ , nic := range nics {
304+ // Skip empty entries
305+ nic = strings .TrimSpace (nic )
306+ if nic == "" {
307+ continue
308+ }
309+
310+ s .T .Logf ("Validating network interface config for NIC: %s" , nic )
311+
312+ for setting , expectedValue := range nicConfig {
313+ // Get full ethtool output for debugging
314+ debugCommand := []string {
315+ "set -ex" ,
316+ fmt .Sprintf ("echo '=== Full ethtool output for %s ==='" , nic ),
317+ fmt .Sprintf ("sudo ethtool -g %s" , nic ),
318+ }
319+ debugResult := execScriptOnVMForScenario (ctx , s , strings .Join (debugCommand , "\n " ))
320+ s .T .Logf ("Full ethtool output for %s:\n %s" , nic , debugResult .stdout )
321+
322+ command := []string {
323+ "set -ex" ,
324+ fmt .Sprintf ("sudo ethtool -g %s | grep -A 5 'Current hardware settings' | grep -i %s: | awk '{print $2}'" , nic , setting ),
325+ }
326+ execResult := execScriptOnVMForScenarioValidateExitCode (ctx , s , strings .Join (command , "\n " ), 0 , "could not get ethtool config" )
327+ actualValue := strings .TrimSpace (execResult .stdout )
328+ s .T .Logf ("Ethtool setting %s for NIC %s: expected=%s, actual=%s" , setting , nic , expectedValue , actualValue )
329+ require .Equal (s .T , expectedValue , actualValue , "expected %s to be %s on nic %s, but got %s.\n Full ethtool output:\n %s" , setting , expectedValue , nic , actualValue , debugResult .stdout )
330+ }
331+ }
332+ }
333+
334+ // ValidateAzureNetworkFiles checks that udev rules files exist.
335+ func ValidateAzureNetworkFiles (ctx context.Context , s * Scenario ) {
336+ s .T .Helper ()
337+
338+ ValidateFileExists (ctx , s , "/opt/azure-network/configure-azure-network.sh" )
339+ ValidateFileExists (ctx , s , "/etc/udev/rules.d/99-azure-network.rules" )
340+ }
341+
257342func ValidateNvidiaSMINotInstalled (ctx context.Context , s * Scenario ) {
258343 s .T .Helper ()
259344 command := []string {
@@ -1623,3 +1708,35 @@ func ValidateNodeHasLabel(ctx context.Context, s *Scenario, labelKey, expectedVa
16231708 require .True (s .T , exists , "expected node %q to have label %q, but it was not found" , s .Runtime .VM .KubeName , labelKey )
16241709 require .Equal (s .T , expectedValue , actualValue , "expected node %q label %q to have value %q, but got %q" , s .Runtime .VM .KubeName , labelKey , expectedValue , actualValue )
16251710}
1711+
1712+ // ValidateRxBufferDefault validates rx buffer config using default values based on VM's CPU count
1713+ func ValidateRxBufferDefault (ctx context.Context , s * Scenario ) {
1714+ s .T .Helper ()
1715+
1716+ // Query the VM's actual CPU count using nproc
1717+ cpuCountCmd := "nproc"
1718+ result := execScriptOnVMForScenarioValidateExitCode (ctx , s , cpuCountCmd , 0 , "could not get CPU count from VM" )
1719+ vmCPUCount := strings .TrimSpace (result .stdout )
1720+
1721+ // Parse CPU count
1722+ cpuCount , err := strconv .Atoi (vmCPUCount )
1723+ require .NoError (s .T , err , "failed to parse CPU count: %s" , vmCPUCount )
1724+
1725+ // Determine expected rx based on VM's CPU count (matching configure-azure-network.sh logic)
1726+ expectedRx := "1024"
1727+ if cpuCount >= 4 {
1728+ expectedRx = "2048"
1729+ }
1730+
1731+ s .T .Logf ("VM has %d CPUs, expecting rx buffer size: %s" , cpuCount , expectedRx )
1732+
1733+ customNicConfig := map [string ]string {
1734+ "rx" : expectedRx ,
1735+ }
1736+
1737+ // Validate files exist
1738+ ValidateAzureNetworkFiles (ctx , s )
1739+
1740+ // Validate network interface settings match expected default
1741+ ValidateNetworkInterfaceConfig (ctx , s , customNicConfig )
1742+ }
0 commit comments