1+ import ast
12import logging
23import platform
34import shutil
910from virttest import utils_misc
1011from virttest import virsh
1112from virttest import virt_vm
13+ from virttest import libvirt_version
1214
1315from virttest .libvirt_xml import vm_xml , xcepts
1416from virttest .utils_libvirt import libvirt_disk
17+ from virttest .utils_test import libvirt
18+
19+ from provider .virtual_disk import disk_base
1520
1621LOG = logging .getLogger ('avocado.' + __name__ )
1722
@@ -20,15 +25,20 @@ def run(test, params, env):
2025 """
2126 Test nvme virtual.
2227
28+ For attach test case:
2329 1.Prepare a vm with nvme type disk
2430 2.Attach the virtual disk to the vm
2531 3.Start vm
2632 4.Check the disk in vm
2733 5.Detach nvme device
28- """
29- vm_name = params .get ("main_vm" )
30- vm = env .get_vm (vm_name )
3134
35+ For blockcopy test case:
36+ 1.Start a VM with a regular disk
37+ 2.Prepare nvme destination disk XML
38+ 3.Execute blockcopy with --xml option (--finish or --pivot)
39+ 4.Check VM XML after operation
40+ 5.Verify disk source in VM
41+ """
3242 def get_new_disks (old_partitions ):
3343 """
3444 Get new virtual disks in VM after disk plug.
@@ -81,40 +91,92 @@ def create_customized_disk(params, pci_address):
8191 disk_device = params .get ("device_type" )
8292 device_target = params .get ("target_dev" )
8393 device_bus = params .get ("target_bus" )
84- device_format = params .get ("target_format " )
94+ device_format = params .get ("disk_image_format " )
8595
8696 customized_disk = libvirt_disk .create_primitive_disk_xml (
8797 type_name , disk_device ,
8898 device_target , device_bus ,
8999 device_format , None , None )
90100
91- source_dict = eval (params .get ("source_attrs" ))
101+ source_dict = ast . literal_eval (params .get ("source_attrs" , "{} " ))
92102 disk_src_dict = {"attrs" : source_dict }
93103 disk_source = customized_disk .new_disk_source (** disk_src_dict )
94104 disk_source .address = pci_address
95105 customized_disk .source = disk_source
96106 LOG .debug ("create customized xml: %s" , customized_disk )
97107 return customized_disk
98108
99- hotplug = "yes" == params .get ("virt_device_hotplug" )
100- pkgs_host = params .get ("pkgs_host" , "" )
109+ def create_block_nvme_disk (params , disk_type , disk_obj , disk_dict ):
110+ """
111+ Create a block NVME disk by partitioning an NVME device and creating disk XML
101112
102- # Get disk partitions info before hot/cold plug virtual disk
103- if vm .is_dead ():
104- vm .start ()
105- session = vm .wait_for_login ()
106- old_partitions = utils_disk .get_parts_list (session )
107- session .close ()
108- vm .destroy (gracefully = False )
113+ :param params: Dictionary containing test parameters
114+ :param disk_type: the disk type
115+ :param disk_obj: DiskBase object used for preparing disk XML configuration
116+ :param disk_dict: Dictionary containing disk configuration parameters
117+ :return: Tuple containing:
118+ disk_xml: Disk XML object for the created NVME partition
119+ nvme_device: String path of the base NVME device (e.g., "/dev/nvme0n1")
120+ nvme_partition_path: String path of the created partition (e.g., "/dev/nvme0n1p1")
121+ luks_sec_uuid: UUID of the LUKS secret (None if LUKS not used)
122+ """
123+ luks_secret_passwd = params .get ("luks_secret_passwd" , "redhat" )
124+ with_luks = "yes" == params .get ("with_luks" , "no" )
125+ luks_sec_uuid = None
126+ try :
127+ LOG .info ("SETUP_TEST: Getting available NVME device in host" )
128+ lsblk_result = process .run ("lsblk -d -n -o NAME | grep nvme" ,
129+ timeout = 10 , ignore_status = True , shell = True )
130+ if lsblk_result .exit_status != 0 :
131+ test .cancel ("No NVME device found in host" )
109132
110- # Backup vm xml
111- vmxml_backup = vm_xml .VMXML .new_from_inactive_dumpxml (vm_name )
112- vmxml = vmxml_backup .copy ()
133+ nvme_devices = lsblk_result .stdout_text .strip ().split ('\n ' )
134+ for device in nvme_devices :
135+ device_path = "/dev/" + device .strip ()
136+ mount_check = process .run (f"lsblk { device_path } | grep -q SWAP\\ |/" ,
137+ ignore_status = True , shell = True )
138+ if mount_check .exit_status != 0 :
139+ nvme_device = device_path
140+ break
141+ else :
142+ test .cancel ("No available unmounted NVME device found" )
113143
114- try :
115- # install essential package in host
116- if not shutil .which ('lspci' ):
117- test .error ("package {} not installed, please install them before testing" .format (pkgs_host ))
144+ LOG .info ("SETUP_TEST: Creating GPT partition table on NVME device" )
145+ result = process .run ("parted -s %s mklabel gpt" % nvme_device ,
146+ timeout = 30 , ignore_status = False , verbose = True , shell = True )
147+ LOG .debug ("GPT partition table creation result: %s" , result )
148+
149+ LOG .info ("SETUP_TEST: Creating 1G primary partition on NVME device" )
150+ result = process .run ("parted -s %s mkpart primary 0%% 1G" % nvme_device ,
151+ timeout = 30 , ignore_status = False , verbose = True , shell = True )
152+ LOG .debug ("Partition creation result: %s" , result )
153+ time .sleep (2 )
154+
155+ # Use the created partition as disk image
156+ nvme_partition_path = nvme_device + "p1"
157+ if with_luks :
158+ luks_sec_uuid = libvirt .create_secret (params )
159+ ret = virsh .secret_set_value (luks_sec_uuid , luks_secret_passwd ,
160+ encode = True , debug = True )
161+ libvirt .check_exit_status (ret )
162+
163+ disk_xml , _ = disk_obj .prepare_disk_obj (disk_type , disk_dict , nvme_partition_path )
164+ LOG .debug ("Created disk XML: %s" , disk_xml )
165+ return disk_xml , nvme_device , nvme_partition_path , luks_sec_uuid
166+
167+ except Exception as err :
168+ test .fail ("Failed to create block NVME disk: %s" % str (err ))
169+
170+ def run_attach_detach_test ():
171+ """
172+ Run original attach/detach test with nvme device
173+ """
174+ if vm .is_dead ():
175+ vm .start ()
176+ session = vm .wait_for_login ()
177+ old_partitions = utils_disk .get_parts_list (session )
178+ session .close ()
179+ vm .destroy (gracefully = False )
118180
119181 pci_addr_in_dict = get_usable_nvme_pci_address ()
120182
@@ -131,13 +193,7 @@ def create_customized_disk(params, pci_address):
131193 if hotplug :
132194 virsh .attach_device (vm_name , device_obj .xml , flagstr = "--live" ,
133195 ignore_status = False , debug = True )
134- except virt_vm .VMStartError as details :
135- test .fail ("VM failed to start."
136- "Error: %s" % str (details ))
137- except xcepts .LibvirtXMLError as xml_error :
138- test .fail ("VM failed to define"
139- "Error: %s" % str (xml_error ))
140- else :
196+
141197 utils_misc .wait_for (lambda : get_new_disks (old_partitions ), 20 )
142198 new_disks = get_new_disks (old_partitions )
143199 if len (new_disks ) != 1 :
@@ -147,8 +203,117 @@ def create_customized_disk(params, pci_address):
147203 time .sleep (10 )
148204 if not libvirt_disk .check_virtual_disk_io (vm , new_disk ):
149205 test .fail ("Cannot operate the newly added disk in vm." )
206+
150207 virsh .detach_device (vm_name , device_obj .xml , flagstr = "--live" ,
151208 debug = True , ignore_status = False )
209+
210+ def run_blockcopy_test ():
211+ """
212+ Run blockcopy test with nvme destination
213+ """
214+ libvirt_version .is_libvirt_feature_supported (params )
215+ target_dev = params .get ("target_dev" , "vdb" )
216+ disk_type = params .get ("disk_type" , "file" )
217+ nvme_disk_type = params .get ("nvme_disk_type" , "block" )
218+ disk_dict = ast .literal_eval (params .get ("disk_dict" , "{}" ))
219+ nvme_disk_dict = ast .literal_eval (params .get ("nvme_disk_dict" , "{}" ))
220+ common_options = params .get ("blockcopy_common_options" )
221+ specific_option = params .get ("blockcopy_option" , "" )
222+ with_luks = "yes" == params .get ("with_luks" , "no" )
223+ luks_extra_parameter = params .get ("luks_extra_parameter" , "" )
224+ disk_obj = disk_base .DiskBase (test , vm , params )
225+
226+ LOG .info ("TEST_STEP1: Start VM." )
227+ nvme_disk , nvme_device , nvme_partition_path , luks_sec_uuid = create_block_nvme_disk (params ,
228+ nvme_disk_type ,
229+ disk_obj ,
230+ nvme_disk_dict )
231+ disk_info_dict = utils_misc .get_image_info (nvme_partition_path )
232+ disk_size = disk_info_dict .get ("vsize" )
233+ disk_size_bytes = str (disk_size ) + "b"
234+ original_disk_source = disk_obj .add_vm_disk (disk_type , disk_dict , size = disk_size_bytes )
235+ LOG .debug ("Original disk source: %s" , original_disk_source )
236+ if vm .is_dead ():
237+ vm .start ()
238+ LOG .debug ("The current guest xml is: %s" % (virsh .dumpxml (vm_name ).stdout_text ))
239+ vm .wait_for_login ().close ()
240+
241+ LOG .info ("TEST_STEP2: Execute blockcopy command with option '%s'." , specific_option )
242+ if with_luks :
243+ cmd = ("qemu-img create -f qcow2 %s %s %s" % (luks_extra_parameter ,
244+ nvme_partition_path ,
245+ disk_size_bytes ))
246+ process .run (cmd , shell = True , ignore_status = False )
247+ options = "--xml %s %s %s" % (nvme_disk .xml , specific_option , common_options )
248+ virsh .blockcopy (vm_name , target_dev , options , debug = True , ignore_status = False )
249+ LOG .debug ("Blockcopy completed successfully." )
250+
251+ LOG .info ("TEST_STEP3: Verify blockcopy result in VM XML." )
252+ verify_blockcopy_result (params , original_disk_source , nvme_partition_path )
253+
254+ if vm .is_alive ():
255+ vm .destroy (gracefully = False )
256+ process .run ("sgdisk --zap-all %s" % nvme_device , shell = True , ignore_status = False )
257+ disk_obj .cleanup_disk_preparation (disk_type )
258+ if with_luks :
259+ virsh .secret_undefine (luks_sec_uuid , debug = True , ignore_status = False )
260+
261+ def verify_blockcopy_result (params , original_source , nvme_dest_disk ):
262+ """
263+ Verify blockcopy result by checking VM XML
264+
265+ :param params: Dictionary containing test parameters
266+ :param original_source: Original disk source path before blockcopy operation
267+ :param nvme_dest_disk: NVME destination disk XML object used in blockcopy
268+ """
269+ specific_option = params .get ("blockcopy_option" , "" )
270+ target_dev = params .get ("target_dev" , "vdb" )
271+ disk_type = params .get ("disk_type" , "file" )
272+ nvme_disk_type = params .get ("nvme_disk_type" , "block" )
273+ current_vmxml = vm_xml .VMXML .new_from_dumpxml (vm_name )
274+ LOG .debug ("The current guest xml after blockcopy is: %s" % current_vmxml )
275+ disk_sources = current_vmxml .get_disk_source (vm_name )
276+ disk_obj = disk_base .DiskBase (test , vm , params )
277+ if len (disk_sources ) < 2 :
278+ test .fail ("VM doesn't have enough disks after blockcopy operation." )
279+
280+ if "--finish" in specific_option :
281+ current_disk_source = disk_obj .get_source_list (current_vmxml , disk_type , target_dev )[0 ]
282+ if current_disk_source != original_source :
283+ test .fail ("Expected VM to keep old disk source '%s', but found '%s'" %
284+ (original_source , current_disk_source ))
285+ LOG .info ("PASS: VM disk kept the old disk source as expected with --finish" )
286+
287+ elif "--pivot" in specific_option :
288+ current_disk_source = disk_obj .get_source_list (current_vmxml , nvme_disk_type , target_dev )[0 ]
289+ if current_disk_source != nvme_dest_disk :
290+ test .fail ("Expected VM to pivot to nvme destination, but disk source remains unchanged: '%s'" %
291+ current_disk_source )
292+ LOG .info ("PASS: VM disk pivoted to the new nvme destination as expected with --pivot" )
293+
294+ vm_name = params .get ("main_vm" )
295+ vm = env .get_vm (vm_name )
296+ with_blockcopy = "yes" == params .get ("with_blockcopy" , "no" )
297+ hotplug = "yes" == params .get ("virt_device_hotplug" )
298+ pkgs_host = params .get ("pkgs_host" , "" )
299+
300+ vmxml_backup = vm_xml .VMXML .new_from_inactive_dumpxml (vm_name )
301+ vmxml = vmxml_backup .copy ()
302+
303+ try :
304+ # install essential package in host
305+ if not shutil .which ('lspci' ):
306+ test .error ("package {} not installed, please install them before testing" .format (pkgs_host ))
307+
308+ if with_blockcopy :
309+ run_blockcopy_test ()
310+ else :
311+ run_attach_detach_test ()
312+
313+ except virt_vm .VMStartError as details :
314+ test .fail ("VM failed to start. Error: %s" % str (details ))
315+ except xcepts .LibvirtXMLError as xml_error :
316+ test .fail ("VM failed to define. Error: %s" % str (xml_error ))
152317 finally :
153318 if vm .is_alive ():
154319 vm .destroy (gracefully = False )
0 commit comments