Skip to content

Commit 7842725

Browse files
committed
Add case to support hotplug/hot-unplug nic device in a loop
Automate VIRT-99272 hotplug/hot-unplug nic device in a loop Signed-off-by: Lei Yang <leiyang@redhat.com>
1 parent 2ea8ce0 commit 7842725

File tree

2 files changed

+307
-0
lines changed

2 files changed

+307
-0
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
- virtual_network.qemu_test.hotplug_nic_with_repetition:
2+
type = hotplug_nic_with_repetition
3+
only virtio_net
4+
create_vm_libvirt = "yes"
5+
kill_vm_libvirt = "yes"
6+
hotplug_cycles = 100
7+
login_timeout = 360
8+
dhcp_cmd = "dhcpcd -n %s"
9+
make_change = "no"
10+
RHEL.7, RHEL.8, RHEL.9:
11+
dhcp_cmd = "dhclient -r && dhclient %s"
12+
RHEL.7:
13+
make_change = "yes"
14+
ovmf:
15+
kill_vm_libvirt_options = --nvram
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
from virttest import virsh
2+
from virttest import utils_misc
3+
from virttest import utils_net
4+
from virttest import utils_test
5+
from virttest import utils_libvirtd
6+
7+
from avocado.utils import process
8+
from virttest.libvirt_xml import vm_xml
9+
from virttest.utils_test import libvirt
10+
11+
virsh_opt = {"debug": True, "ignore_status": False}
12+
13+
14+
def domif_setlink(vm_name, device, operation, options):
15+
"""
16+
Set the domain link state
17+
18+
:param vm_name : domain name
19+
:param device : domain virtual interface
20+
:param operation : domain virtual interface state
21+
:param options : some options like --config
22+
"""
23+
return virsh.domif_setlink(vm_name, device, operation, options, **virsh_opt)
24+
25+
26+
def check_connectivity(test, ip, mac, session, is_linux_guest):
27+
"""
28+
Check connectivity by pinging host from guest for both Linux and Windows
29+
30+
:param test: Test instance for logging
31+
:param ip: Target IP address to ping
32+
:param mac: MAC address of the hotplug NIC
33+
:param session: Guest session
34+
:param is_linux_guest: Boolean indicating if guest is Linux
35+
"""
36+
status, output = utils_test.ping(ip, 5, timeout=30)
37+
if status != 0:
38+
if not is_linux_guest:
39+
raise Exception(f"Ping failed with status {status}: {output}")
40+
ifname = utils_net.get_linux_ifname(session, mac)
41+
add_route_cmd = "route add %s dev %s" % (ip, ifname)
42+
del_route_cmd = "route del %s dev %s" % (ip, ifname)
43+
test.log.warning("Failed to ping %s from host." % ip)
44+
test.log.info("Add route and try again")
45+
session.cmd_output_safe(add_route_cmd)
46+
status, output = utils_test.ping(ip, 5, timeout=30)
47+
test.log.info("Del the route.")
48+
session.cmd_output_safe(del_route_cmd)
49+
if status != 0:
50+
raise Exception(f"Ping failed with status {status}: {output}")
51+
52+
test.log.info("Ping succeeded")
53+
54+
55+
def renew_ip_address(session, mac, is_linux_guest, params):
56+
if not is_linux_guest:
57+
utils_net.restart_windows_guest_network_by_key(session, "macaddress", mac)
58+
return None
59+
60+
ifname = utils_net.get_linux_ifname(session, mac)
61+
if params.get("make_change") == "yes":
62+
# keep this configuration for guests running RHEL7 and earlier
63+
# as it is a more stable approach.
64+
p_cfg = "/etc/sysconfig/network-scripts/ifcfg-%s" % ifname
65+
cfg_con = "DEVICE=%s\\nBOOTPROTO=dhcp\\nONBOOT=yes" % ifname
66+
make_conf = "test -f %s || echo '%s' > %s" % (p_cfg, cfg_con, p_cfg)
67+
else:
68+
make_conf = (
69+
"nmcli connection add type ethernet con-name %s ifname"
70+
" %s autoconnect yes" % (ifname, ifname)
71+
)
72+
arp_clean = "arp -n|awk '/^[1-9]/{print \"arp -d \" $1}'|sh"
73+
session.cmd_output_safe(make_conf)
74+
session.cmd_output_safe("ip link set dev %s up" % ifname)
75+
dhcp_cmd = params.get("dhcp_cmd")
76+
session.cmd_output_safe(dhcp_cmd % ifname, timeout=240)
77+
session.cmd_output_safe(arp_clean)
78+
return None
79+
80+
81+
def get_hotplug_nic_ip(test, vm, hotplug_mac, session, is_linux_guest, params):
82+
def __get_address():
83+
try:
84+
ip_addr = vm.address_cache.get(hotplug_mac)
85+
if ip_addr:
86+
return ip_addr
87+
88+
if is_linux_guest:
89+
ifname = utils_net.get_linux_ifname(session, hotplug_mac)
90+
ip_cmd = f"ip addr show {ifname} | grep 'inet ' | awk '{{print $2}}' | cut -d'/' -f1"
91+
try:
92+
result = session.cmd_output_safe(ip_cmd)
93+
if result.strip():
94+
return result.strip()
95+
except Exception as e:
96+
test.log.debug(f"Failed to get IP from guest interface: {e}")
97+
return None
98+
except Exception as e:
99+
test.log.debug(f"Error getting IP, attempting to renew: {e}")
100+
try:
101+
renew_ip_address(session, hotplug_mac, is_linux_guest, params)
102+
except Exception as renew_error:
103+
test.log.debug(f"Failed to renew IP address: {renew_error}")
104+
return None
105+
106+
nic_ip = utils_misc.wait_for(__get_address, timeout=360)
107+
if nic_ip:
108+
return nic_ip
109+
110+
cached_ip = vm.address_cache.get(hotplug_mac)
111+
arps = process.system_output("arp -aen").decode()
112+
test.log.debug("Can't get IP address:")
113+
test.log.debug("\tCached IP: %s", cached_ip)
114+
test.log.debug("\tARP table: %s", arps)
115+
return None
116+
117+
118+
def attach_hotplug_interface(test, vm, original_iface):
119+
120+
new_mac = utils_net.generate_mac_address_simple()
121+
122+
iface_type = original_iface.type_name
123+
source = ""
124+
model = ""
125+
126+
if hasattr(original_iface, 'source') and original_iface.source:
127+
if iface_type == 'network':
128+
source = original_iface.source.get('network', 'default')
129+
elif iface_type == 'bridge':
130+
source = original_iface.source.get('bridge', 'virbr0')
131+
132+
if hasattr(original_iface, 'model') and original_iface.model:
133+
model = str(original_iface.model)
134+
135+
attach_options = f"--type {iface_type} --mac {new_mac}"
136+
if source:
137+
attach_options += f" --source {source}"
138+
if model:
139+
attach_options += f" --model {model}"
140+
attach_options += " --live"
141+
142+
test.log.info(f"Hotplug interface: {attach_options}")
143+
144+
vm.attach_interface(
145+
option=attach_options,
146+
ignore_status=False,
147+
debug=True
148+
)
149+
150+
test.log.info(f"Hotplug succeed: {new_mac}")
151+
return new_mac, iface_type
152+
153+
154+
def detach_hotplug_interface(test, vm, mac_address, expected_type=None,
155+
wait_for_event=True, event_type="device-removed"):
156+
# Add event check in this cases. however, it's better suited to be added to
157+
# inside framework so it can used by other device in the future.
158+
test.log.info(f"Hot unplug interface: {mac_address}")
159+
160+
iface_type = expected_type or "network"
161+
162+
detach_options = f"--type {iface_type} --mac {mac_address} --live"
163+
164+
vm.detach_interface(
165+
option=detach_options,
166+
ignore_status=False,
167+
debug=True,
168+
wait_for_event=wait_for_event,
169+
event_type=event_type
170+
)
171+
172+
test.log.info(f"Hot unplug succeed: {mac_address}")
173+
return True
174+
175+
176+
def run(test, params, env):
177+
"""
178+
Test hotplug of NIC devices
179+
180+
Test Steps:
181+
1. Boot guest with a device
182+
2. setlink down
183+
3. hotplug a device
184+
4. check connection
185+
5. stop&resume
186+
6. check connection again
187+
7. setlink up
188+
8. unplug this device
189+
"""
190+
login_timeout = params.get_numeric("login_timeout", 360)
191+
hotplug_cycles = params.get_numeric("hotplug_cycles", 100)
192+
vm_name = params.get("main_vm")
193+
vm = env.get_vm(vm_name)
194+
vm.verify_alive()
195+
guest_is_linux = "linux" == params.get("os_type", "")
196+
host_ip_addr = utils_net.get_host_ip_address(params)
197+
ips = {'host_ip': host_ip_addr}
198+
test.log.info("STEP 1: VM started to got the org vm interface info")
199+
vmxml = vm_xml.VMXML.new_from_dumpxml(vm_name)
200+
original_iface = libvirt.get_vm_device(vmxml, "interface", index=-1)[0]
201+
original_mac = original_iface.mac_address
202+
test.log.info(f"original interface: MAC={original_mac}, Type={original_iface.type_name}")
203+
204+
# This is a workaround to let virt-install create vm can be management by domif-setlink
205+
# and cleanup exists serial session, since restart libvirtd will render the original invalid
206+
test.log.info("STEP 2: Restart libvirtd server")
207+
libvirtd = utils_libvirtd.Libvirtd()
208+
libvirtd.restart()
209+
if vm.serial_console:
210+
vm.cleanup_serial_console()
211+
212+
vm.create_serial_console()
213+
test.log.info("Create a new serial console")
214+
215+
# Starting hotplug/unplug loop tests, 100 times
216+
for iteration in range(hotplug_cycles):
217+
test.log.info(f"\n=== The {iteration + 1}/{hotplug_cycles} round ===")
218+
219+
# 2. setlink down
220+
domif_setlink(vm_name, original_mac, "down", "")
221+
test.log.info("setlink down succeed")
222+
223+
# 3. hotplug a device
224+
hotplug_mac, hotplug_type = attach_hotplug_interface(test, vm, original_iface)
225+
226+
# 4. check connection
227+
session = vm.wait_for_serial_login(timeout=login_timeout)
228+
test.log.info("First time to obtain hotplug nic ip address...")
229+
hotnic_ip = get_hotplug_nic_ip(
230+
test, vm, hotplug_mac, session, guest_is_linux, params
231+
)
232+
233+
if not hotnic_ip:
234+
test.log.info("First time failed, try to renew ip address...")
235+
try:
236+
renew_ip_address(session, hotplug_mac, guest_is_linux, params)
237+
hotnic_ip = get_hotplug_nic_ip(
238+
test, vm, hotplug_mac, session, guest_is_linux, params
239+
)
240+
except Exception as e:
241+
test.log.warning(f"Still failed after renew ip address: {e}")
242+
243+
if not hotnic_ip:
244+
test.log.info("Rebooting vm after renew ip address failed...")
245+
session = vm.reboot(session=session, serial=True)
246+
vm.verify_alive()
247+
248+
hotnic_ip = get_hotplug_nic_ip(
249+
test, vm, hotplug_mac, session, guest_is_linux, params
250+
)
251+
252+
if not hotnic_ip:
253+
session.close()
254+
test.fail(f"The {iteration + 1} round: Hotplug nic still can't obtain ip after reboot vm")
255+
test.log.info(f"Obtain the hotplug nic ip: {hotnic_ip}")
256+
257+
# Check connectivity
258+
host_ip = ips['host_ip']
259+
try:
260+
check_connectivity(test, host_ip, hotplug_mac, session, guest_is_linux)
261+
except Exception as e:
262+
session.close()
263+
test.fail(f"The {iteration + 1} round: ping failed: {e}")
264+
265+
session.close()
266+
267+
# 5. stop&resume
268+
virsh.suspend(vm_name, **virsh_opt)
269+
virsh.resume(vm_name, **virsh_opt)
270+
271+
# 6. check connection again
272+
test.log.info("check connection again")
273+
session = vm.wait_for_serial_login(timeout=login_timeout)
274+
275+
# Check connectivity again
276+
try:
277+
check_connectivity(test, host_ip, hotplug_mac, session, guest_is_linux)
278+
except Exception as e:
279+
session.close()
280+
test.fail(f"The {iteration + 1} round: ping failed after resume: {e}")
281+
282+
session.close()
283+
284+
# 7. setlink up
285+
domif_setlink(vm_name, original_mac, "up", "")
286+
test.log.info("setlink up succeed")
287+
288+
# 8. unplug this device
289+
test.log.info("unplug device")
290+
detach_hotplug_interface(test, vm, hotplug_mac, hotplug_type)
291+
292+
test.log.info(f"The {iteration + 1} round finished")

0 commit comments

Comments
 (0)