def _pre_start_vm(self): """ The steps to be executed before vm started """ # Steps: # 1. Clean any existing checkponits # 2. Add test disk to the vm utils_backup.clean_checkpoints(self.main_vm.name) self.add_test_disk_to_vm(self.main_vm)
def run(test, params, env): """ Test the pull-mode backup function Steps: 1. create a vm with extra disk vdb 2. create some data on vdb 3. start a pull mode full backup on vdb 4. monitor block-threshold event on scratch file/dev 5. create some data on vdb's same position as step 2 to trigger event 6. check the block-threshold event captured """ # Basic case config hotplug_disk = "yes" == params.get("hotplug_disk", "no") original_disk_size = params.get("original_disk_size", "100M") original_disk_type = params.get("original_disk_type", "local") original_disk_target = params.get("original_disk_target", "vdb") event_type = params.get("event_type") usage_threshold = params.get("usage_threshold", "100") tmp_dir = data_dir.get_tmp_dir() local_hostname = params.get("loal_hostname", "localhost") # Backup config scratch_type = params.get("scratch_type", "file") reuse_scratch_file = "yes" == params.get("reuse_scratch_file") scratch_blkdev_size = params.get("scratch_blkdev_size", original_disk_size) # NBD service config nbd_protocol = params.get("nbd_protocol", "unix") nbd_socket = params.get("nbd_socket", "/tmp/pull_backup.socket") nbd_tcp_port = params.get("nbd_tcp_port", "10809") nbd_hostname = local_hostname # LUKS config scratch_luks_encrypted = "yes" == params.get("scratch_luks_encrypted") luks_passphrase = params.get("luks_passphrase", "password") # Open a new virsh session for event monitor virsh_session = aexpect.ShellSession(virsh.VIRSH_EXEC, auto_close=True) # Cancel the test if libvirt support related functions if not libvirt_version.version_compare(7, 0, 0): test.cancel("Current libvirt version doesn't support " "event monitor for incremental backup.") def get_backup_disk_index(vm_name, disk_name): """ Get the index of the backup disk to be monitored by the virsh event :param vm_name: vm name :param disk_name: virtual disk name, such as 'vdb' :return: the index of the virtual disk in backup xml """ backup_xml = virsh.backup_dumpxml(vm_name).stdout.strip() logging.debug("%s's current backup xml is: %s" % (vm_name, backup_xml)) backup_xml_dom = xml_utils.XMLTreeFile(backup_xml) index_xpath = "/disks/disk" for disk_element in backup_xml_dom.findall(index_xpath): if disk_element.get("name") == disk_name: return disk_element.get("index") def is_event_captured(virsh_session, re_pattern): """ Check if event captured :param virsh_session: the virsh session of the event monitor :param re_pattern: the re pattern used to represent the event :return: True means event captured, False means not """ ret_output = virsh_session.get_stripped_output() if (not re.search(re_pattern, ret_output, re.IGNORECASE)): return False logging.debug("event monitor output: %s", ret_output) return True try: vm_name = params.get("main_vm") vm = env.get_vm(vm_name) # Make sure thedisk_element.getre is no checkpoint metadata before test utils_backup.clean_checkpoints(vm_name) # Backup vm xml vmxml = vm_xml.VMXML.new_from_inactive_dumpxml(vm_name) vmxml_backup = vmxml.copy() utils_backup.enable_inc_backup_for_vm(vm) # Prepare libvirt secret if scratch_luks_encrypted: utils_secret.clean_up_secrets() luks_secret_uuid = libvirt.create_secret(params) virsh.secret_set_value(luks_secret_uuid, luks_passphrase, encode=True, debug=True) # Prepare the disk to be backuped. disk_params = {} disk_path = "" image_name = "{}_image.qcow2".format(original_disk_target) disk_path = os.path.join(tmp_dir, image_name) libvirt.create_local_disk("file", disk_path, original_disk_size, "qcow2") disk_params = { "device_type": "disk", "type_name": "file", "driver_type": "qcow2", "target_dev": original_disk_target, "source_file": disk_path } disk_params["target_dev"] = original_disk_target disk_xml = libvirt.create_disk_xml(disk_params) virsh.attach_device(vm.name, disk_xml, flagstr="--config", debug=True) vm.start() session = vm.wait_for_login() new_disks_in_vm = list(utils_disk.get_linux_disks(session).keys()) session.close() if len(new_disks_in_vm) != 1: test.fail("Test disk not prepared in vm") # Use the newly added disk as the test disk test_disk_in_vm = "/dev/" + new_disks_in_vm[0] vmxml = vm_xml.VMXML.new_from_dumpxml(vm_name) vm_disks = list(vmxml.get_disk_all().keys()) checkpoint_list = [] backup_file_list = [] # Prepare backup xml backup_params = {"backup_mode": "pull"} # Set libvirt default nbd export name and bitmap name nbd_export_name = original_disk_target nbd_bitmap_name = "backup-" + original_disk_target backup_server_dict = {} if nbd_protocol == "unix": backup_server_dict["transport"] = "unix" backup_server_dict["socket"] = nbd_socket else: backup_server_dict["name"] = nbd_hostname backup_server_dict["port"] = nbd_tcp_port backup_params["backup_server"] = backup_server_dict backup_disk_xmls = [] for vm_disk in vm_disks: backup_disk_params = {"disk_name": vm_disk} if vm_disk != original_disk_target: backup_disk_params["enable_backup"] = "no" else: backup_disk_params["enable_backup"] = "yes" backup_disk_params["disk_type"] = scratch_type # Prepare nbd scratch file/dev params scratch_params = {"attrs": {}} scratch_path = None if scratch_type == "file": scratch_file_name = "scratch_file" scratch_path = os.path.join(tmp_dir, scratch_file_name) if reuse_scratch_file: libvirt.create_local_disk("file", scratch_path, original_disk_size, "qcow2") scratch_params["attrs"]["file"] = scratch_path elif scratch_type == "block": scratch_path = libvirt.setup_or_cleanup_iscsi( is_setup=True, image_size=scratch_blkdev_size) scratch_params["attrs"]["dev"] = scratch_path else: test.fail("We do not support backup scratch type: '%s'" % scratch_type) if scratch_luks_encrypted: encryption_dict = { "encryption": "luks", "secret": { "type": "passphrase", "uuid": luks_secret_uuid } } scratch_params["encryption"] = encryption_dict logging.debug("scratch params: %s", scratch_params) backup_disk_params["backup_scratch"] = scratch_params backup_disk_xml = utils_backup.create_backup_disk_xml( backup_disk_params) backup_disk_xmls.append(backup_disk_xml) logging.debug("disk list %s", backup_disk_xmls) backup_xml = utils_backup.create_backup_xml(backup_params, backup_disk_xmls) logging.debug("Backup Xml: %s", backup_xml) # Prepare checkpoint xml checkpoint_name = "checkpoint" checkpoint_list.append(checkpoint_name) cp_params = {"checkpoint_name": checkpoint_name} cp_params["checkpoint_desc"] = params.get("checkpoint_desc", "desc of cp") disk_param_list = [] for vm_disk in vm_disks: cp_disk_param = {"name": vm_disk} if vm_disk != original_disk_target: cp_disk_param["checkpoint"] = "no" else: cp_disk_param["checkpoint"] = "bitmap" cp_disk_bitmap = params.get("cp_disk_bitmap") if cp_disk_bitmap: cp_disk_param["bitmap"] = cp_disk_bitmap disk_param_list.append(cp_disk_param) checkpoint_xml = utils_backup.create_checkpoint_xml( cp_params, disk_param_list) logging.debug("Checkpoint Xml: %s", checkpoint_xml) # Generate some random data in vm's test disk def dd_data_to_testdisk(): """ Generate some data to vm's test disk """ dd_count = "1" dd_seek = "10" dd_bs = "1M" session = vm.wait_for_login() utils_disk.dd_data_to_vm_disk(session, test_disk_in_vm, dd_bs, dd_seek, dd_count) session.close() dd_data_to_testdisk() # Start backup backup_options = backup_xml.xml + " " + checkpoint_xml.xml if reuse_scratch_file: backup_options += " --reuse-external" backup_result = virsh.backup_begin(vm_name, backup_options, debug=True) # Start to monitor block-threshold of backup disk's scratch file/dev backup_disk_index = get_backup_disk_index(vm_name, original_disk_target) if not backup_disk_index: test.fail("Backup xml has no index for disks.") backup_disk_obj = original_disk_target + "[%s]" % backup_disk_index virsh.domblkthreshold( vm_name, original_disk_target + "[%s]" % backup_disk_index, usage_threshold) event_cmd = "event %s %s --loop" % (vm_name, event_type) virsh_session.sendline(event_cmd) # Generate some random data to same position of vm's test disk dd_data_to_testdisk() # Check if the block-threshold event captured by monitor if event_type == "block-threshold": event_pattern = (".*block-threshold.*%s.*%s\[%s\].* %s .*" % (vm_name, original_disk_target, backup_disk_index, usage_threshold)) if not utils_misc.wait_for( lambda: is_event_captured(virsh_session, event_pattern), 10): test.fail("Event not captured by event monitor") # Abort backup job virsh.domjobabort(vm_name, debug=True) finally: # Remove checkpoints if "checkpoint_list" in locals() and checkpoint_list: for checkpoint_name in checkpoint_list: virsh.checkpoint_delete(vm_name, checkpoint_name) if vm.is_alive(): vm.destroy(gracefully=False) # Restoring vm vmxml_backup.sync() # Remove libvirt secret if "luks_secret_uuid" in locals(): virsh.secret_undefine(luks_secret_uuid, ignore_status=True) # Remove iscsi devices if scratch_type == "block": libvirt.setup_or_cleanup_iscsi(False) # Remove scratch file if "scratch_path" in locals(): if scratch_type == "file" and os.path.exists(scratch_path): os.remove(scratch_path)
def run(test, params, env): """ Test the pull-mode backup function Steps: 1. craete a vm with extra disk vdb 2. create some data on vdb 3. start a pull mode full backup on vdb 4. create some data on vdb 5. start a pull mode incremental backup 6. repeat step 5 to 7 7. check the full/incremental backup file data """ # Basic case config hotplug_disk = "yes" == params.get("hotplug_disk", "no") original_disk_size = params.get("original_disk_size", "100M") original_disk_type = params.get("original_disk_type", "local") original_disk_target = params.get("original_disk_target", "vdb") local_hostname = params.get("loal_hostname", "localhost") local_ip = params.get("local_ip", "127.0.0.1") local_user_name = params.get("local_user_name", "root") local_user_password = params.get("local_user_password", "redhat") tmp_dir = data_dir.get_tmp_dir() # Backup config scratch_type = params.get("scratch_type", "file") reuse_scratch_file = "yes" == params.get("reuse_scratch_file") prepare_scratch_file = "yes" == params.get("prepare_scratch_file") scratch_blkdev_path = params.get("scratch_blkdev_path") scratch_blkdev_size = params.get("scratch_blkdev_size", original_disk_size) prepare_scratch_blkdev = "yes" == params.get("prepare_scratch_blkdev") backup_rounds = int(params.get("backup_rounds", 3)) backup_error = "yes" == params.get("backup_error") expect_backup_canceled = "yes" == params.get("expect_backup_canceled") # NBD service config nbd_protocol = params.get("nbd_protocol", "unix") nbd_socket = params.get("nbd_socket", "/tmp/pull_backup.socket") nbd_tcp_port = params.get("nbd_tcp_port", "10809") nbd_hostname = local_hostname set_exportname = "yes" == params.get("set_exportname") set_exportbitmap = "yes" == params.get("set_exportbitmap") # TLS service config tls_enabled = "yes" == params.get("tls_enabled") tls_x509_verify = "yes" == params.get("tls_x509_verify") custom_pki_path = "yes" == params.get("custom_pki_path") tls_client_ip = tls_server_ip = local_ip tls_client_cn = tls_server_cn = local_hostname tls_client_user = tls_server_user = local_user_name tls_client_pwd = tls_server_pwd = local_user_password tls_provide_client_cert = "yes" == params.get("tls_provide_client_cert") tls_error = "yes" == params.get("tls_error") # LUKS config scratch_luks_encrypted = "yes" == params.get("scratch_luks_encrypted") luks_passphrase = params.get("luks_passphrase", "password") # Cancel the test if libvirt support related functions if not libvirt_version.version_compare(6, 0, 0): test.cancel("Current libvirt version doesn't support " "incremental backup.") if tls_enabled and not libvirt_version.version_compare(6, 6, 0): test.cancel("Current libvirt version doesn't support pull mode " "backup with tls nbd.") try: vm_name = params.get("main_vm") vm = env.get_vm(vm_name) # Make sure there is no checkpoint metadata before test utils_backup.clean_checkpoints(vm_name) # Backup vm xml vmxml = vm_xml.VMXML.new_from_inactive_dumpxml(vm_name) vmxml_backup = vmxml.copy() utils_backup.enable_inc_backup_for_vm(vm) # Prepare tls env if tls_enabled: # Prepare pki tls_config = { "qemu_tls": "yes", "auto_recover": "yes", "client_ip": tls_client_ip, "server_ip": tls_server_ip, "client_cn": tls_client_cn, "server_cn": tls_server_cn, "client_user": tls_client_user, "server_user": tls_server_user, "client_pwd": tls_client_pwd, "server_pwd": tls_server_pwd, } if custom_pki_path: pki_path = os.path.join(tmp_dir, "inc_bkup_pki") else: pki_path = "/etc/pki/libvirt-backup/" if tls_x509_verify: tls_config["client_ip"] = tls_client_ip tls_config["custom_pki_path"] = pki_path tls_obj = TLSConnection(tls_config) tls_obj.conn_setup(True, tls_provide_client_cert) logging.debug("TLS certs in: %s" % pki_path) # Set qemu.conf qemu_config = LibvirtQemuConfig() if tls_x509_verify: qemu_config.backup_tls_x509_verify = True else: qemu_config.backup_tls_x509_verify = False if custom_pki_path: qemu_config.backup_tls_x509_cert_dir = pki_path utils_libvirtd.Libvirtd().restart() # Prepare libvirt secret if scratch_luks_encrypted: utils_secret.clean_up_secrets() luks_secret_uuid = libvirt.create_secret(params) virsh.secret_set_value(luks_secret_uuid, luks_passphrase, encode=True, debug=True) # Prepare the disk to be backuped. disk_params = {} disk_path = "" if original_disk_type == "local": image_name = "{}_image.qcow2".format(original_disk_target) disk_path = os.path.join(tmp_dir, image_name) libvirt.create_local_disk("file", disk_path, original_disk_size, "qcow2") disk_params = { "device_type": "disk", "type_name": "file", "driver_type": "qcow2", "target_dev": original_disk_target, "source_file": disk_path } if original_disk_target: disk_params["target_dev"] = original_disk_target elif original_disk_type == "iscsi": iscsi_host = '127.0.0.1' iscsi_target, lun_num = libvirt.setup_or_cleanup_iscsi( is_setup=True, is_login=False, image_size=original_disk_size, portal_ip=iscsi_host) disk_path = ("iscsi://[%s]/%s/%s" % (iscsi_host, iscsi_target, lun_num)) process.run("qemu-img create -f qcow2 %s %s" % (disk_path, original_disk_size), shell=True, verbose=True) disk_params = { 'device_type': 'disk', 'type_name': 'network', "driver_type": "qcow2", 'target_dev': original_disk_target } disk_params_src = { 'source_protocol': 'iscsi', 'source_name': iscsi_target + "/%s" % lun_num, 'source_host_name': iscsi_host, 'source_host_port': '3260' } disk_params.update(disk_params_src) elif original_disk_type == "gluster": gluster_vol_name = "gluster_vol" gluster_pool_name = "gluster_pool" gluster_img_name = "gluster.qcow2" gluster_host_ip = gluster.setup_or_cleanup_gluster( is_setup=True, vol_name=gluster_vol_name, pool_name=gluster_pool_name, **params) disk_path = 'gluster://%s/%s/%s' % ( gluster_host_ip, gluster_vol_name, gluster_img_name) process.run("qemu-img create -f qcow2 %s %s" % (disk_path, original_disk_size), shell=True, verbose=True) disk_params = { 'device_type': 'disk', 'type_name': 'network', "driver_type": "qcow2", 'target_dev': original_disk_target } disk_params_src = { 'source_protocol': 'gluster', 'source_name': gluster_vol_name + "/%s" % gluster_img_name, 'source_host_name': gluster_host_ip, 'source_host_port': '24007' } disk_params.update(disk_params_src) else: test.error("The disk type '%s' not supported in this script.", original_disk_type) if hotplug_disk: vm.start() session = vm.wait_for_login().close() disk_xml = libvirt.create_disk_xml(disk_params) virsh.attach_device(vm_name, disk_xml, debug=True) else: disk_xml = libvirt.create_disk_xml(disk_params) virsh.attach_device(vm.name, disk_xml, flagstr="--config", debug=True) vm.start() session = vm.wait_for_login() new_disks_in_vm = list(utils_disk.get_linux_disks(session).keys()) session.close() if len(new_disks_in_vm) != 1: test.fail("Test disk not prepared in vm") # Use the newly added disk as the test disk test_disk_in_vm = "/dev/" + new_disks_in_vm[0] vmxml = vm_xml.VMXML.new_from_dumpxml(vm_name) vm_disks = list(vmxml.get_disk_all().keys()) checkpoint_list = [] is_incremental = False backup_file_list = [] for backup_index in range(backup_rounds): # Prepare backup xml backup_params = {"backup_mode": "pull"} if backup_index > 0: is_incremental = True backup_params["backup_incremental"] = "checkpoint_" + str( backup_index - 1) # Set libvirt default nbd export name and bitmap name nbd_export_name = original_disk_target nbd_bitmap_name = "backup-" + original_disk_target backup_server_dict = {} if nbd_protocol == "unix": backup_server_dict["transport"] = "unix" backup_server_dict["socket"] = nbd_socket else: backup_server_dict["name"] = nbd_hostname backup_server_dict["port"] = nbd_tcp_port if tls_enabled: backup_server_dict["tls"] = "yes" backup_params["backup_server"] = backup_server_dict backup_disk_xmls = [] for vm_disk in vm_disks: backup_disk_params = {"disk_name": vm_disk} if vm_disk != original_disk_target: backup_disk_params["enable_backup"] = "no" else: backup_disk_params["enable_backup"] = "yes" backup_disk_params["disk_type"] = scratch_type # Custom nbd export name and bitmap name if required if set_exportname: nbd_export_name = original_disk_target + "_custom_exp" backup_disk_params["exportname"] = nbd_export_name if set_exportbitmap: nbd_bitmap_name = original_disk_target + "_custom_bitmap" backup_disk_params["exportbitmap"] = nbd_bitmap_name # Prepare nbd scratch file/dev params scratch_params = {"attrs": {}} scratch_path = None if scratch_type == "file": scratch_file_name = "scratch_file_%s" % backup_index scratch_path = os.path.join(tmp_dir, scratch_file_name) if prepare_scratch_file: libvirt.create_local_disk("file", scratch_path, original_disk_size, "qcow2") scratch_params["attrs"]["file"] = scratch_path elif scratch_type == "block": if prepare_scratch_blkdev: scratch_path = libvirt.setup_or_cleanup_iscsi( is_setup=True, image_size=scratch_blkdev_size) scratch_params["attrs"]["dev"] = scratch_path else: test.fail( "We do not support backup scratch type: '%s'" % scratch_type) if scratch_luks_encrypted: encryption_dict = { "encryption": "luks", "secret": { "type": "passphrase", "uuid": luks_secret_uuid } } scratch_params["encryption"] = encryption_dict logging.debug("scratch params: %s", scratch_params) backup_disk_params["backup_scratch"] = scratch_params backup_disk_xml = utils_backup.create_backup_disk_xml( backup_disk_params) backup_disk_xmls.append(backup_disk_xml) logging.debug("disk list %s", backup_disk_xmls) backup_xml = utils_backup.create_backup_xml( backup_params, backup_disk_xmls) logging.debug("ROUND_%s Backup Xml: %s", backup_index, backup_xml) # Prepare checkpoint xml checkpoint_name = "checkpoint_%s" % backup_index checkpoint_list.append(checkpoint_name) cp_params = {"checkpoint_name": checkpoint_name} cp_params["checkpoint_desc"] = params.get( "checkpoint_desc", "desc of cp_%s" % backup_index) disk_param_list = [] for vm_disk in vm_disks: cp_disk_param = {"name": vm_disk} if vm_disk != original_disk_target: cp_disk_param["checkpoint"] = "no" else: cp_disk_param["checkpoint"] = "bitmap" cp_disk_bitmap = params.get("cp_disk_bitmap") if cp_disk_bitmap: cp_disk_param["bitmap"] = cp_disk_bitmap + str( backup_index) disk_param_list.append(cp_disk_param) checkpoint_xml = utils_backup.create_checkpoint_xml( cp_params, disk_param_list) logging.debug("ROUND_%s Checkpoint Xml: %s", backup_index, checkpoint_xml) # Create some data in vdb dd_count = "1" dd_seek = str(backup_index * 10 + 10) dd_bs = "1M" session = vm.wait_for_login() utils_disk.dd_data_to_vm_disk(session, test_disk_in_vm, dd_bs, dd_seek, dd_count) session.close() # Start backup backup_options = backup_xml.xml + " " + checkpoint_xml.xml if reuse_scratch_file: backup_options += " --reuse-external" backup_result = virsh.backup_begin(vm_name, backup_options, ignore_status=True, debug=True) if backup_result.exit_status: raise utils_backup.BackupBeginError( backup_result.stderr.strip()) # If required, do some error operations during backup job error_operation = params.get("error_operation") if error_operation: if "destroy_vm" in error_operation: vm.destroy(gracefully=False) if "kill_qemu" in error_operation: utils_misc.safe_kill(vm.get_pid(), signal.SIGKILL) if utils_misc.wait_for( lambda: utils_backup.is_backup_canceled(vm_name), timeout=5): raise utils_backup.BackupCanceledError() elif expect_backup_canceled: test.fail("Backup job should be canceled but not.") backup_file_path = os.path.join( tmp_dir, "backup_file_%s.qcow2" % str(backup_index)) backup_file_list.append(backup_file_path) nbd_params = { "nbd_protocol": nbd_protocol, "nbd_export": nbd_export_name } if nbd_protocol == "unix": nbd_params["nbd_socket"] = nbd_socket elif nbd_protocol == "tcp": nbd_params["nbd_hostname"] = nbd_hostname nbd_params["nbd_tcp_port"] = nbd_tcp_port if tls_enabled: nbd_params["tls_dir"] = pki_path nbd_params["tls_server_ip"] = tls_server_ip if not is_incremental: # Do full backup try: utils_backup.pull_full_backup_to_file( nbd_params, backup_file_path) except Exception as details: if tls_enabled and tls_error: raise utils_backup.BackupTLSError(details) else: test.fail("Fail to get full backup data: %s" % details) logging.debug("Full backup to: %s", backup_file_path) else: # Do incremental backup utils_backup.pull_incremental_backup_to_file( nbd_params, backup_file_path, nbd_bitmap_name, original_disk_size) # Check if scratch file encrypted if scratch_luks_encrypted and scratch_path: cmd = "qemu-img info -U %s" % scratch_path result = process.run(cmd, shell=True, verbose=True).stdout_text.strip() if (not re.search("format.*luks", result, re.IGNORECASE) or not re.search("encrypted.*yes", result, re.IGNORECASE)): test.fail("scratch file/dev is not encrypted by LUKS") virsh.domjobabort(vm_name, debug=True) for checkpoint_name in checkpoint_list: virsh.checkpoint_delete(vm_name, checkpoint_name, debug=True) if vm.is_alive(): vm.destroy(gracefully=False) # Compare the backup data and original data original_data_file = os.path.join(tmp_dir, "original_data.qcow2") cmd = "qemu-img convert -f qcow2 %s -O qcow2 %s" % (disk_path, original_data_file) process.run(cmd, shell=True, verbose=True) for backup_file in backup_file_list: if not utils_backup.cmp_backup_data(original_data_file, backup_file): test.fail("Backup and original data are not identical for" "'%s' and '%s'" % (disk_path, backup_file)) else: logging.debug("'%s' contains correct backup data", backup_file) except utils_backup.BackupBeginError as detail: if backup_error: logging.debug("Backup failed as expected.") else: test.fail("Backup failed to start: %s" % detail) except utils_backup.BackupTLSError as detail: if tls_error: logging.debug("Failed to get backup data as expected.") else: test.fail("Failed to get tls backup data: %s" % detail) except utils_backup.BackupCanceledError as detail: if expect_backup_canceled: logging.debug("Backup canceled as expected.") if not vm.is_alive(): logging.debug("Check if vm can be started again when backup " "canceled.") vm.start() vm.wait_for_login().close() else: test.fail("Backup job canceled: %s" % detail) finally: # Remove checkpoints clean_checkpoint_metadata = not vm.is_alive() if "error_operation" in locals() and error_operation is not None: if "kill_qemu" in error_operation: clean_checkpoint_metadata = True utils_backup.clean_checkpoints( vm_name, clean_metadata=clean_checkpoint_metadata) if vm.is_alive(): vm.destroy(gracefully=False) # Restoring vm vmxml_backup.sync() # Remove iscsi devices if original_disk_type == "iscsi" or scratch_type == "block": libvirt.setup_or_cleanup_iscsi(False) # Remove gluster devices if original_disk_type == "gluster": gluster.setup_or_cleanup_gluster(is_setup=False, vol_name=gluster_vol_name, pool_name=gluster_pool_name, **params) # Recover qemu.conf if "qemu_config" in locals(): qemu_config.restore() # Remove tls object if "tls_obj" in locals(): del tls_obj # Remove libvirt secret if "luks_secret_uuid" in locals(): virsh.secret_undefine(luks_secret_uuid, ignore_status=True)
def run(test, params, env): """ Test multiple disks' backup function Steps: 1. Attach disk_1 to vm 2. Do full backup of this disk 3. Attach disk_2 to vm 4. Do incremental backup for disk_1 and full backup for disk_2 5. Repeat step 3~4 if having more test disks """ def get_disks_need_backup(disk_dict): """ Get the disks which need to be backuped :param disk_list: All test disks :return: Disks need to be backuped """ disks = [] for disk in list(disk_dict.keys()): if disk_dict[disk]['is_attached']: disks.append(disk) return disks # Cancel the test if libvirt version is too low if not libvirt_version.version_compare(6, 6, 0): test.cancel("Current libvirt version doesn't support " "mixed full/incremental backup.") # Basic test setting test_disk_size = params.get("test_disk_size", "100M") total_test_disk = int(params.get("total_test_disk", 3)) tmp_dir = data_dir.get_tmp_dir() backup_error = "yes" == params.get("backup_error") # Backup setting scratch_type = params.get("scratch_type", "file") nbd_protocol = params.get("nbd_protocol", "tcp") nbd_tcp_port = params.get("nbd_tcp_port", "10809") set_export_name = "yes" == params.get("set_export_name") set_export_bitmap = "yes" == params.get("set_export_bitmap") try: vm_name = params.get("main_vm") vm = env.get_vm(vm_name) # Make sure there is no checkpoint metadata before test utils_backup.clean_checkpoints(vm_name) # Backup vm xml vmxml = vm_xml.VMXML.new_from_inactive_dumpxml(vm_name) vmxml_backup = vmxml.copy() utils_backup.enable_inc_backup_for_vm(vm) # Prepare a dict to save the disks' info to be attached and backuped. # This will generate a dict as: # {'vdb': {'path': '', 'is_attached': False, 'checkpoints': []}, # 'vdc': {'path': '', 'is_attached': False, 'checkpoints': []}} test_disk_prefix = 'vd' test_disk_sequence = 'b' test_disk_dict = {} for i in range(total_test_disk): test_disk_name = test_disk_prefix + chr(i + ord(test_disk_sequence)) test_disk_dict[test_disk_name] = { 'path': '', 'is_attached': False, 'checkpoints': [] } if not vm.is_alive(): vm.start() vm.wait_for_login().close() # Start to prepare backup and checkpoint xml checkpoint_round = 0 checkpoint_list = [] backup_params = {'backup_mode': 'pull'} backup_server_dict = {} if nbd_protocol == 'tcp': backup_server_dict['name'] = 'localhost' backup_server_dict['port'] = nbd_tcp_port else: test.cancel("We only test nbd export via tcp/ip fow now.") backup_params["backup_server"] = backup_server_dict test_disk_list = list(test_disk_dict.keys()) def prepare_disk_img(test_disk): """ Prepare a qcow2 image for test disk :param test_disk: The vm's disk, such as 'vdb' :return: The path to the image """ image_name = "{}_image.qcow2".format(test_disk) image_path = os.path.join(tmp_dir, image_name) libvirt.create_local_disk("file", image_path, test_disk_size, "qcow2") return image_path def prepare_disk_xml(test_disk, image_path): """ Prepare disk xml to be hot plugged :param test_disk: The vm's disk, such as 'vdb' :param image_path: The path to the iamge :return: The xml of the test disk """ test_disk_params = { "device_type": "disk", "type_name": "file", "driver_type": "qcow2", "target_dev": test_disk, "source_file": image_path, "target_bus": "scsi" } test_disk_xml = libvirt.create_disk_xml(test_disk_params) return test_disk_xml def prepare_backup_xml(backup_disks, all_vm_disks): """ Prepare backup xml :param backup_disks: List of the disks to be backuped :param all_vm_disks: List of all the vm disks :return: Backup xml """ backup_disk_xmls = [] for vm_disk in all_vm_disks: backup_disk_params = {"disk_name": vm_disk} if vm_disk not in backup_disks: backup_disk_params["enable_backup"] = "no" else: backup_disk_params["enable_backup"] = "yes" backup_disk_params["disk_type"] = scratch_type # Custom nbd export name and bitmap name if required if set_export_name: nbd_export_name = vm_disk + "_custom_exp" backup_disk_params["exportname"] = nbd_export_name if set_export_bitmap: nbd_bitmap_name = vm_disk + "_custom_bitmap" backup_disk_params["exportbitmap"] = nbd_bitmap_name # Prepare nbd scratch file params scratch_params = {"attrs": {}} if scratch_type == "file": scratch_file_name = "scratch_file_%s" % vm_disk scratch_file_path = os.path.join( tmp_dir, scratch_file_name) scratch_params["attrs"]["file"] = scratch_file_path logging.debug("scratch_params: %s", scratch_params) else: test.cancel("We only use local file scratch for now.") backup_disk_params["backup_scratch"] = scratch_params # Prepare 'backupmode' and 'incremental' attributes if test_disk_dict[vm_disk]['checkpoints']: backup_disk_params['backupmode'] = 'incremental' backup_disk_params['incremental'] = test_disk_dict[ vm_disk]['checkpoints'][-1] else: backup_disk_params['backupmode'] = 'full' backup_disk_xml = utils_backup.create_backup_disk_xml( backup_disk_params) backup_disk_xmls.append(backup_disk_xml) logging.debug("disk list %s", backup_disk_xmls) backup_xml = utils_backup.create_backup_xml( backup_params, backup_disk_xmls) return backup_xml def prepare_checkpoint_xml(backup_disks, all_vm_disks): """ Preapre checkpoint xml :param backup_disks: List of disks to be backuped :param all_vm_disks: List of vm disks :return: checkpoint name and checkpoint xml """ checkpoint_name = "checkpoint_%s" % str(checkpoint_round) cp_params = {"checkpoint_name": checkpoint_name} cp_params["checkpoint_desc"] = params.get( "checkpoint_desc", "desc of cp_%s" % str(checkpoint_round)) disk_param_list = [] for vm_disk in all_vm_disks: cp_disk_param = {"name": vm_disk} if vm_disk not in backup_disks: cp_disk_param["checkpoint"] = "no" else: test_disk_dict[vm_disk]['checkpoints'].append( checkpoint_name) cp_disk_param["checkpoint"] = "bitmap" cp_disk_bitmap = params.get("cp_disk_bitmap") if cp_disk_bitmap: cp_disk_param["bitmap"] = cp_disk_bitmap + str( checkpoint_round) disk_param_list.append(cp_disk_param) checkpoint_xml = utils_backup.create_checkpoint_xml( cp_params, disk_param_list) return checkpoint_name, checkpoint_xml for test_disk in test_disk_list: if checkpoint_list: enable_incremental_backup = True backup_params["backup_incremental"] = checkpoint_list[-1] # Prepare disk image image_path = prepare_disk_img(test_disk) # Prepare disk xml to be hotplugged test_disk_xml = prepare_disk_xml(test_disk, image_path) # Hotplug disk virsh.attach_device(vm_name, test_disk_xml, debug=True, ignore_status=False) test_disk_dict[test_disk]['path'] = image_path test_disk_dict[test_disk]['is_attached'] = True # Now we use attached disk as backup disks backup_disks = get_disks_need_backup(test_disk_dict) vmxml = vm_xml.VMXML.new_from_dumpxml(vm_name) all_vm_disks = list(vmxml.get_disk_all().keys()) # Prepare backup xml backup_xml = prepare_backup_xml(backup_disks, all_vm_disks) logging.debug("ROUND_%s Backup xml: %s", checkpoint_round, backup_xml) # Prepare checkpoint xml checkpoint_name, checkpoint_xml = prepare_checkpoint_xml( backup_disks, all_vm_disks) logging.debug("ROUND_%s Checkpoint Xml: %s", checkpoint_round, checkpoint_xml) # Start backup job backup_options = backup_xml.xml + " " + checkpoint_xml.xml virsh.backup_begin(vm_name, backup_options, debug=True, ignore_status=False) # Abort backup job virsh.domjobabort(vm_name, debug=True, ignore_status=False) checkpoint_list.append(checkpoint_name) checkpoint_round += 1 # Destroy vm to make sure correct info can be read from images if vm.is_alive(): vm.destroy(gracefully=False) # Check if images' bitmap info is same as the checkponits we created bitmaps_eq_checkponits = True for test_disk in list(test_disk_dict.keys()): bitmaps = utils_backup.get_img_bitmaps( test_disk_dict[test_disk]['path']) if sorted(bitmaps) != sorted( test_disk_dict[test_disk]['checkpoints']): bitmaps_eq_checkponits = False logging.error( "%s(%s): checkpoints %s created by libvirt," "but bitmaps %s can be found by qemu-img info." % (test_disk, test_disk_dict[test_disk]['path'], test_disk_dict[test_disk]['checkpoints'], bitmaps)) if not bitmaps_eq_checkponits: test.fail("The checkponits created by libvirt are not same as " "the bitmaps created in qemu image, detailed info can " "be found with 'logging.error'.") except utils_backup.BackupBeginError as details: if backup_error: logging.debug("Backup failed as expected.") else: test.fail(details) finally: # Remove checkpoints. Since the OS image not touched during the test, # we only need to remove the checkpoints' metadata here. if "checkpoint_list" in locals() and checkpoint_list: for checkpoint_name in checkpoint_list: virsh.checkpoint_delete(vm_name, checkpoint_name, '--metadata') if vm.is_alive(): vm.destroy(gracefully=False) # Restoring vm vmxml_backup.sync()
def run(test, params, env): """ Test push-mode incremental backup Steps: 1. create a vm with extra disk vdb 2. create some data on vdb in vm 3. start a push mode full backup on vdb 4. create some data on vdb in vm 5. start a push mode incremental backup 6. repeat step 4 and 5 as required 7. check the full/incremental backup file data """ def backup_job_done(vm_name, vm_disk): """ Check if a backup job for a vm's specific disk is finished. :param vm_name: vm's name :param vm_disk: the disk to be checked, such as 'vdb' :return: 'True' means job finished """ result = virsh.blockjob(vm_name, vm_disk, debug=True) if "no current block job" in result.stdout_text.strip().lower(): return True # Cancel the test if libvirt version is too low if not libvirt_version.version_compare(6, 0, 0): test.cancel("Current libvirt version doesn't support " "incremental backup.") hotplug_disk = "yes" == params.get("hotplug_disk", "no") original_disk_size = params.get("original_disk_size", "100M") original_disk_type = params.get("original_disk_type", "local") original_disk_target = params.get("original_disk_target", "vdb") target_driver = params.get("target_driver", "qcow2") target_type = params.get("target_type", "file") target_blkdev_path = params.get("target_blkdev_path") target_blkdev_size = params.get("target_blkdev_size", original_disk_size) reuse_target_file = "yes" == params.get("reuse_target_file") prepare_target_file = "yes" == params.get("prepare_target_file") prepare_target_blkdev = "yes" == params.get("prepare_target_blkdev") backup_rounds = int(params.get("backup_rounds", 3)) backup_error = "yes" == params.get("backup_error") tmp_dir = data_dir.get_data_dir() virsh_dargs = {'debug': True, 'ignore_status': True} try: vm_name = params.get("main_vm") vm = env.get_vm(vm_name) # Make sure there is no checkpoint metadata before test utils_backup.clean_checkpoints(vm_name) # Backup vm xml vmxml = vm_xml.VMXML.new_from_inactive_dumpxml(vm_name) vmxml_backup = vmxml.copy() utils_backup.enable_inc_backup_for_vm(vm) # Prepare the disk to be backuped. disk_params = {} disk_path = "" if original_disk_type == "local": image_name = "{}_image.qcow2".format(original_disk_target) disk_path = os.path.join(tmp_dir, image_name) libvirt.create_local_disk("file", disk_path, original_disk_size, "qcow2") disk_params = { "device_type": "disk", "type_name": "file", "driver_type": "qcow2", "target_dev": original_disk_target, "source_file": disk_path } if original_disk_target: disk_params["target_dev"] = original_disk_target elif original_disk_type == "ceph": ceph_mon_host = params.get("ceph_mon_host", "EXAMPLE_MON_HOST_AUTHX") ceph_host_port = params.get("ceph_host_port", "EXAMPLE_PORT") ceph_pool_name = params.get("ceph_pool_name", "EXAMPLE_POOL") ceph_file_name = params.get("ceph_file_name", "EXAMPLE_FILE") ceph_disk_name = ceph_pool_name + "/" + ceph_file_name ceph_client_name = params.get("ceph_client_name", "EXAMPLE_CLIENT_NAME") ceph_client_key = params.get("ceph_client_key", "EXAMPLE_CLIENT_KEY") ceph_auth_user = params.get("ceph_auth_user", "EXAMPLE_AUTH_USER") ceph_auth_key = params.get("ceph_auth_key", "EXAMPLE_AUTH_KEY") auth_sec_usage_type = "ceph" enable_auth = "yes" == params.get("enable_auth", "yes") key_file = os.path.join(tmp_dir, "ceph.key") key_opt = "" # Prepare a blank params to confirm if delete the configure at the end of the test ceph_cfg = "" if not utils_package.package_install(["ceph-common"]): test.error("Failed to install ceph-common") # Create config file if it doesn't exist ceph_cfg = ceph.create_config_file(ceph_mon_host) if enable_auth: # If enable auth, prepare a local file to save key if ceph_client_name and ceph_client_key: with open(key_file, 'w') as f: f.write("[%s]\n\tkey = %s\n" % (ceph_client_name, ceph_client_key)) key_opt = "--keyring %s" % key_file auth_sec_dict = { "sec_usage": auth_sec_usage_type, "sec_name": "ceph_auth_secret" } auth_sec_uuid = libvirt.create_secret(auth_sec_dict) virsh.secret_set_value(auth_sec_uuid, ceph_auth_key, debug=True) disk_params_auth = { "auth_user": ceph_auth_user, "secret_type": auth_sec_usage_type, "secret_uuid": auth_sec_uuid, "auth_in_source": True } else: test.error("No ceph client name/key provided.") disk_path = "rbd:%s:mon_host=%s:keyring=%s" % ( ceph_disk_name, ceph_mon_host, key_file) ceph.rbd_image_rm(ceph_mon_host, ceph_pool_name, ceph_file_name, ceph_cfg, key_file) process.run("qemu-img create -f qcow2 %s %s" % (disk_path, original_disk_size), shell=True, verbose=True) disk_params = { 'device_type': 'disk', 'type_name': 'network', "driver_type": "qcow2", 'target_dev': original_disk_target } disk_params_src = { 'source_protocol': 'rbd', 'source_name': ceph_disk_name, 'source_host_name': ceph_mon_host, 'source_host_port': ceph_host_port } disk_params.update(disk_params_src) disk_params.update(disk_params_auth) else: test.error("The disk type '%s' not supported in this script." % original_disk_type) if hotplug_disk: vm.start() session = vm.wait_for_login().close() disk_xml = libvirt.create_disk_xml(disk_params) virsh.attach_device(vm_name, disk_xml, debug=True) else: disk_xml = libvirt.create_disk_xml(disk_params) virsh.attach_device(vm.name, disk_xml, flagstr="--config", debug=True) vm.start() session = vm.wait_for_login() new_disks_in_vm = list(utils_disk.get_linux_disks(session).keys()) session.close() if len(new_disks_in_vm) != 1: test.fail("Test disk not prepared in vm") # Use the newly added disk as test disk test_disk_in_vm = "/dev/" + new_disks_in_vm[0] vmxml = vm_xml.VMXML.new_from_dumpxml(vm_name) vm_disks = list(vmxml.get_disk_all().keys()) checkpoint_list = [] is_incremental = False backup_path_list = [] for backup_index in range(backup_rounds): # Prepare backup xml backup_params = {"backup_mode": "push"} if backup_index > 0: is_incremental = True backup_params["backup_incremental"] = "checkpoint_" + str( backup_index - 1) backup_disk_xmls = [] for vm_disk in vm_disks: backup_disk_params = {"disk_name": vm_disk} if vm_disk != original_disk_target: backup_disk_params["enable_backup"] = "no" else: backup_disk_params["enable_backup"] = "yes" backup_disk_params["disk_type"] = target_type target_params = {"attrs": {}} if target_type == "file": target_file_name = "target_file_%s" % backup_index target_file_path = os.path.join( tmp_dir, target_file_name) if prepare_target_file: libvirt.create_local_disk("file", target_file_path, original_disk_size, target_driver) target_params["attrs"]["file"] = target_file_path backup_path_list.append(target_file_path) elif target_type == "block": if prepare_target_blkdev: target_blkdev_path = libvirt.setup_or_cleanup_iscsi( is_setup=True, image_size=target_blkdev_size) target_params["attrs"]["dev"] = target_blkdev_path backup_path_list.append(target_blkdev_path) else: test.fail( "We do not support backup target type: '%s'" % target_type) logging.debug("target params: %s", target_params) backup_disk_params["backup_target"] = target_params driver_params = {"type": target_driver} backup_disk_params["backup_driver"] = driver_params backup_disk_xml = utils_backup.create_backup_disk_xml( backup_disk_params) backup_disk_xmls.append(backup_disk_xml) logging.debug("disk list %s", backup_disk_xmls) backup_xml = utils_backup.create_backup_xml( backup_params, backup_disk_xmls) logging.debug("ROUND_%s Backup Xml: %s", backup_index, backup_xml) # Prepare checkpoint xml checkpoint_name = "checkpoint_%s" % backup_index checkpoint_list.append(checkpoint_name) cp_params = {"checkpoint_name": checkpoint_name} cp_params["checkpoint_desc"] = params.get( "checkpoint_desc", "desc of cp_%s" % backup_index) disk_param_list = [] for vm_disk in vm_disks: cp_disk_param = {"name": vm_disk} if vm_disk != original_disk_target: cp_disk_param["checkpoint"] = "no" else: cp_disk_param["checkpoint"] = "bitmap" cp_disk_bitmap = params.get("cp_disk_bitmap") if cp_disk_bitmap: cp_disk_param["bitmap"] = cp_disk_bitmap + str( backup_index) disk_param_list.append(cp_disk_param) checkpoint_xml = utils_backup.create_checkpoint_xml( cp_params, disk_param_list) logging.debug("ROUND_%s Checkpoint Xml: %s", backup_index, checkpoint_xml) # Start backup backup_options = backup_xml.xml + " " + checkpoint_xml.xml # Create some data in vdb dd_count = "1" dd_seek = str(backup_index * 10 + 10) dd_bs = "1M" session = vm.wait_for_login() utils_disk.dd_data_to_vm_disk(session, test_disk_in_vm, dd_bs, dd_seek, dd_count) session.close() if reuse_target_file: backup_options += " --reuse-external" backup_result = virsh.backup_begin(vm_name, backup_options, debug=True) if backup_result.exit_status: raise utils_backup.BackupBeginError( backup_result.stderr.strip()) # Wait for the backup job actually finished if not utils_misc.wait_for( lambda: backup_job_done(vm_name, original_disk_target), 60): test.fail("Backup job not finished in 60s") for checkpoint_name in checkpoint_list: virsh.checkpoint_delete(vm_name, checkpoint_name, debug=True) if vm.is_alive(): vm.destroy(gracefully=False) # Compare the backup data and original data original_data_file = os.path.join(tmp_dir, "original_data.qcow2") cmd = "qemu-img convert -f qcow2 %s -O qcow2 %s" % (disk_path, original_data_file) process.run(cmd, shell=True, verbose=True) for backup_path in backup_path_list: if target_driver == "qcow2": # Clear backup image's backing file before comparison qemu_cmd = ("qemu-img rebase -u -f qcow2 -b '' -F qcow2 %s" % backup_path) process.run(qemu_cmd, shell=True, verbose=True) if not utils_backup.cmp_backup_data( original_data_file, backup_path, backup_file_driver=target_driver): test.fail("Backup and original data are not identical for" "'%s' and '%s'" % (disk_path, backup_path)) else: logging.debug("'%s' contains correct backup data", backup_path) except utils_backup.BackupBeginError as details: if backup_error: logging.debug("Backup failed as expected.") else: test.fail(details) finally: # Remove checkpoints if "checkpoint_list" in locals() and checkpoint_list: for checkpoint_name in checkpoint_list: virsh.checkpoint_delete(vm_name, checkpoint_name) if vm.is_alive(): vm.destroy(gracefully=False) # Restoring vm vmxml_backup.sync() # Remove local backup file if "target_file_path" in locals(): if os.path.exists(target_file_path): os.remove(target_file_path) # Remove iscsi devices libvirt.setup_or_cleanup_iscsi(False) # Remove ceph related data if original_disk_type == "ceph": ceph.rbd_image_rm(ceph_mon_host, ceph_pool_name, ceph_file_name, ceph_cfg, key_file) if "auth_sec_uuid" in locals() and auth_sec_uuid: virsh.secret_undefine(auth_sec_uuid) if "ceph_cfg" in locals() and os.path.exists(ceph_cfg): os.remove(ceph_cfg) if os.path.exists(key_file): os.remove(key_file)
def run(test, params, env): """ Integration test of backup and backing_chain. Steps: 1. craete a vm with extra disk vdb 2. create some data on vdb 3. start a pull mode full backup on vdb 4. create some data on vdb 5. start a pull mode incremental backup 6. repeat step 5 to 7 7. before the last round of backup job, do a blockcommit/pull/copy 8. check the full/incremental backup file data """ def run_blk_cmd(): """ Run blockcommit/blockpull/blockcopy command. """ def run_blockpull(): """ Run blockpull command. """ if from_to == "mid_to_top": cmd_option = ("--base {0}[{1}] --wait").format( original_disk_target, middle_layer1_index) elif from_to == "base_to_top": cmd_option = ("--base {0}[{1}] --wait").format( original_disk_target, base_layer_index) virsh.blockpull(vm_name, original_disk_target, cmd_option, debug=True, ignore_status=False) def run_blockcommit(): """ Run blockcommit command. """ if from_to == "top_to_base": # Do blockcommit from top layer to base layer cmd_option = ( "--top {0}[{1}] --base {0}[{2}] --active --pivot " "--wait".format(original_disk_target, top_layer_index, base_layer_index)) elif from_to == "mid_to_mid": # Do blockcommit from middle layer to another middle layer if len(indice) < 4: test.fail( "At lease 4 layers required for the test 'mid_to_mid'") cmd_option = ("--top {0}[{1}] --base {0}[{2}] " "--wait".format(original_disk_target, middle_layer1_index, middle_layer2_index)) elif from_to == "top_to_mid": # Do blockcommit from top layer to middle layer cmd_option = ( "--top {0}[{1}] --base {0}[{2}] --active --pivot " "--wait".format(original_disk_target, top_layer_index, middle_layer1_index)) elif from_to == "mid_to_base": # Do blockcommit from middle layer to base layer cmd_option = ("--top {0}[{1}] --base {0}[{2}] " "--wait".format(original_disk_target, middle_layer1_index, base_layer_index)) virsh.blockcommit(vm_name, original_disk_target, cmd_option, debug=True, ignore_stauts=False) def run_blockcopy(): """ Run blockcopy command. """ copy_dest = os.path.join(tmp_dir, "copy_dest.qcow2") cmd_option = "--wait --verbose --transient-job --pivot" if blockcopy_method == "shallow_copy": cmd_option += " --shallow" if blockcopy_reuse == "reuse_external": cmd_option += " --reuse-external" if blockcopy_method == "shallow_copy": create_img_cmd = "qemu-img create -f qcow2 -F qcow2 -b %s %s" create_img_cmd %= (backend_img, copy_dest) else: create_img_cmd = "qemu-img create -f qcow2 %s %s" create_img_cmd %= (copy_dest, original_disk_size) process.run(create_img_cmd, shell=True, ignore_status=False) virsh.blockcopy(vm_name, original_disk_target, copy_dest, cmd_option, debug=True, ignore_status=False) # Get disk backing store indice info in vm disk xml cur_vm_xml = vm_xml.VMXML.new_from_dumpxml(vm_name) cur_disk_xmls = cur_vm_xml.get_devices(device_type="disk") cur_test_disk_xml = '' for disk_xml in cur_disk_xmls: if disk_xml.target['dev'] == original_disk_target: cur_test_disk_xml = disk_xml logging.debug("Current disk xml for %s is:\n %s", original_disk_target, cur_test_disk_xml) break indice = re.findall(r".*index=['|\"](\d+)['|\"].*", str(cur_test_disk_xml)) logging.debug("backing store indice for %s is: %s", original_disk_target, indice) if len(indice) < 3: test.fail("At least 3 layers required for the test.") top_layer_index = indice[0] middle_layer1_index = indice[1] middle_layer2_index = indice[-2] base_layer_index = indice[-1] logging.debug( "Following backing store will be used: %s", "top:%s; middle_1: %s, middle_2:%s, base: %s" % (top_layer_index, middle_layer1_index, middle_layer2_index, base_layer_index)) # Start the block command if blockcommand == "blockpull": run_blockpull() if blockcommand == "blockcommit": run_blockcommit() if blockcommand == "blockcopy": run_blockcopy() def create_shutoff_snapshot(original_img, snapshot_img): """ Create shutoff snapshot, which means the disk snapshot is not controlled by libvirt, but created directly by qemu command. :param original_img: The image we will take shutoff snapshot for. :param snapshot_img: The newly created shutoff snapshot image. """ cmd = "qemu-img info --output=json -f qcow2 {}".format(original_img) img_info = process.run(cmd, shell=True, ignore_status=False).stdout_text json_data = json.loads(img_info) cmd = "qemu-img create -f qcow2 -F qcow2 -b {0} {1}".format( original_img, snapshot_img) process.run(cmd, shell=True, ignore_status=False) try: bitmaps = json_data['format-specific']['data']['bitmaps'] for bitmap in bitmaps: bitmap_flags = bitmap['flags'] bitmap_name = bitmap['name'] if 'auto' in bitmap_flags and 'in-use' not in bitmap_flags: cmd = "qemu-img bitmap -f qcow2 {0} --add {1}".format( snapshot_img, bitmap_name) process.run(cmd, shell=True, ignore_status=False) except Exception as bitmap_error: logging.debug("Cannot add bitmap to new image, skip it: %s", bitmap_error) # Cancel the test if libvirt version is too low if not libvirt_version.version_compare(6, 0, 0): test.cancel("Current libvirt version doesn't support " "incremental backup.") # vm's origianl disk config original_disk_size = params.get("original_disk_size", "100M") original_disk_type = params.get("original_disk_type", "local") original_disk_target = params.get("original_disk_target", "vdb") # pull mode backup config scratch_type = params.get("scratch_type", "file") nbd_protocol = params.get("nbd_protocol", "tcp") nbd_tcp_port = params.get("nbd_tcp_port", "10809") # test config backup_rounds = int(params.get("backup_rounds", 4)) shutoff_snapshot = "yes" == params.get("shutoff_snapshot") blockcommand = params.get("blockcommand") from_to = params.get("from_to") blockcopy_method = params.get("blockcopy_method") blockcopy_reuse = params.get("blockcopy_reuse") backup_error = "yes" == params.get("backup_error") tmp_dir = data_dir.get_tmp_dir() try: vm_name = params.get("main_vm") vm = env.get_vm(vm_name) # Make sure there is no checkpoint metadata before test utils_backup.clean_checkpoints(vm_name) # Backup vm xml vmxml = vm_xml.VMXML.new_from_inactive_dumpxml(vm_name) vmxml_backup = vmxml.copy() disks_not_tested = list(vmxml.get_disk_all().keys()) logging.debug("Not tested disks are: %s", disks_not_tested) utils_backup.enable_inc_backup_for_vm(vm) # Destroy vm before test if vm.is_alive(): vm.destroy(gracefully=False) # Prepare the disk to be backuped. disk_params = {} disk_path = "" if original_disk_type == "local": image_name = "%s_image.qcow2" % original_disk_target disk_path = os.path.join(tmp_dir, image_name) libvirt.create_local_disk("file", disk_path, original_disk_size, "qcow2") disk_params = { "device_type": "disk", "type_name": "file", "driver_type": "qcow2", "target_dev": original_disk_target, "source_file": disk_path } if original_disk_target: disk_params["target_dev"] = original_disk_target else: logging.cancel("The disk type '%s' not supported in this script.", original_disk_type) disk_xml = libvirt.create_disk_xml(disk_params) virsh.attach_device(vm.name, disk_xml, flagstr="--config", debug=True) vm.start() session = vm.wait_for_login() new_disks_in_vm = list(utils_disk.get_linux_disks(session).keys()) session.close() if len(new_disks_in_vm) != 1: test.fail("Test disk not prepared in vm") # Use the newly added disk as the test disk test_disk_in_vm = "/dev/" + new_disks_in_vm[0] vmxml = vm_xml.VMXML.new_from_dumpxml(vm_name) vm_disks = list(vmxml.get_disk_all().keys()) checkpoint_list = [] is_incremental = False backup_file_list = [] snapshot_list = [] cur_disk_xml = disk_xml cur_disk_path = disk_path cur_disk_params = disk_params backend_img = "" for backup_index in range(backup_rounds): # Do external snapshot if shutoff_snapshot: virsh.detach_disk(vm.name, original_disk_target, extra="--persistent", ignore_status=False, debug=True) if vm.is_alive(): vm.destroy(gracefully=False) shutoff_snapshot_name = "shutoff_snap_%s" % str(backup_index) shutoff_snapshot_path = os.path.join(tmp_dir, shutoff_snapshot_name) create_shutoff_snapshot(cur_disk_path, shutoff_snapshot_path) cur_disk_params["source_file"] = shutoff_snapshot_path cur_disk_xml = libvirt.create_disk_xml(cur_disk_params) virsh.attach_device(vm.name, cur_disk_xml, flagstr="--config", ignore_status=False, debug=True) vm.start() vm.wait_for_login().close() cur_disk_path = shutoff_snapshot_path else: snapshot_name = "snap_%s" % str(backup_index) snapshot_option = "" snapshot_file_name = os.path.join(tmp_dir, snapshot_name) for disk_name in disks_not_tested: snapshot_option += "--diskspec %s,snapshot=no " % disk_name snapshot_option += "--diskspec %s,file=%s" % ( original_disk_target, snapshot_file_name) virsh.snapshot_create_as(vm_name, "%s --disk-only %s" % (snapshot_name, snapshot_option), debug=True) snapshot_list.append(snapshot_name) # Prepare backup xml backup_params = {"backup_mode": "pull"} if backup_index > 0: is_incremental = True backup_params["backup_incremental"] = "checkpoint_" + str( backup_index - 1) # Set libvirt default nbd export name and bitmap name nbd_export_name = original_disk_target nbd_bitmap_name = "backup-" + original_disk_target backup_server_dict = {"name": "localhost", "port": nbd_tcp_port} backup_params["backup_server"] = backup_server_dict backup_disk_xmls = [] for vm_disk in vm_disks: backup_disk_params = {"disk_name": vm_disk} if vm_disk != original_disk_target: backup_disk_params["enable_backup"] = "no" else: backup_disk_params["enable_backup"] = "yes" backup_disk_params["disk_type"] = scratch_type # Prepare nbd scratch file/dev params scratch_params = {"attrs": {}} scratch_file_name = "scratch_file_%s" % backup_index scratch_file_path = os.path.join(tmp_dir, scratch_file_name) scratch_params["attrs"]["file"] = scratch_file_path logging.debug("scratch_params: %s", scratch_params) backup_disk_params["backup_scratch"] = scratch_params backup_disk_xml = utils_backup.create_backup_disk_xml( backup_disk_params) backup_disk_xmls.append(backup_disk_xml) logging.debug("disk list %s", backup_disk_xmls) backup_xml = utils_backup.create_backup_xml( backup_params, backup_disk_xmls) logging.debug("ROUND_%s Backup Xml: %s", backup_index, backup_xml) # Prepare checkpoint xml checkpoint_name = "checkpoint_%s" % backup_index checkpoint_list.append(checkpoint_name) cp_params = {"checkpoint_name": checkpoint_name} cp_params["checkpoint_desc"] = params.get( "checkpoint_desc", "desc of cp_%s" % backup_index) disk_param_list = [] for vm_disk in vm_disks: cp_disk_param = {"name": vm_disk} if vm_disk != original_disk_target: cp_disk_param["checkpoint"] = "no" else: cp_disk_param["checkpoint"] = "bitmap" cp_disk_bitmap = params.get("cp_disk_bitmap") if cp_disk_bitmap: cp_disk_param["bitmap"] = cp_disk_bitmap + str( backup_index) disk_param_list.append(cp_disk_param) checkpoint_xml = utils_backup.create_checkpoint_xml( cp_params, disk_param_list) logging.debug("ROUND_%s Checkpoint Xml: %s", backup_index, checkpoint_xml) # Start backup backup_options = backup_xml.xml + " " + checkpoint_xml.xml # Create some data in vdb dd_count = "1" dd_seek = str(backup_index * 10 + 10) dd_bs = "1M" session = vm.wait_for_login() utils_disk.dd_data_to_vm_disk(session, test_disk_in_vm, dd_bs, dd_seek, dd_count) session.close() backup_result = virsh.backup_begin(vm_name, backup_options, debug=True) if backup_result.exit_status: raise utils_backup.BackupBeginError( backup_result.stderr.strip()) backup_file_path = os.path.join( tmp_dir, "backup_file_%s.qcow2" % str(backup_index)) backup_file_list.append(backup_file_path) nbd_params = { "nbd_protocol": nbd_protocol, "nbd_hostname": "localhost", "nbd_export": nbd_export_name, "nbd_tcp_port": nbd_tcp_port } if not is_incremental: # Do full backup utils_backup.pull_full_backup_to_file(nbd_params, backup_file_path) logging.debug("Full backup to: %s", backup_file_path) else: # Do incremental backup utils_backup.pull_incremental_backup_to_file( nbd_params, backup_file_path, nbd_bitmap_name, original_disk_size) virsh.domjobabort(vm_name, debug=True) # Start to run the blockcommit/blockpull cmd before the last round # of backup job, this is to test if the block command will keep the # dirty bitmap data. if backup_index == backup_rounds - 2: run_blk_cmd() cur_disk_path = vm.get_blk_devices( )[original_disk_target]['source'] if backup_index == backup_rounds - 3: backend_img = vm.get_blk_devices( )[original_disk_target]['source'] # Get current active image for the test disk vm_disks = vm.get_blk_devices() current_active_image = vm_disks[original_disk_target]['source'] logging.debug("The current active image for '%s' is '%s'", original_disk_target, current_active_image) for checkpoint_name in checkpoint_list: virsh.checkpoint_delete(vm_name, checkpoint_name, debug=True, ignore_status=False) if vm.is_alive(): vm.destroy(gracefully=False) # Compare the backup data and original data original_data_file = os.path.join(tmp_dir, "original_data.qcow2") cmd = "qemu-img convert -f qcow2 %s -O qcow2 %s" % ( current_active_image, original_data_file) process.run(cmd, shell=True, verbose=True) for backup_file in backup_file_list: if not utils_backup.cmp_backup_data(original_data_file, backup_file): test.fail("Backup and original data are not identical for" "'%s' and '%s'" % (current_active_image, backup_file)) else: logging.debug("'%s' contains correct backup data", backup_file) except utils_backup.BackupBeginError as details: if backup_error: logging.debug("Backup failed as expected.") else: test.fail(details) finally: # Remove checkpoints' metadata again to make sure vm has no checkpoints if "checkpoint_list" in locals(): for checkpoint_name in checkpoint_list: virsh.checkpoint_delete(vm_name, checkpoint_name, options="--metadata") # Remove snapshots if "snapshot_list" in locals(): for snapshot_name in snapshot_list: virsh.snapshot_delete(vm_name, "%s --metadata" % snapshot_name, debug=True) if vm.is_alive(): vm.destroy(gracefully=False) # Restoring vm vmxml_backup.sync() for file_name in os.listdir(tmp_dir): file_path = os.path.join(tmp_dir, file_name) if 'env' not in file_path: if os.path.isfile(file_path): os.remove(file_path) elif os.path.isdir(file_path): shutil.rmtree(file_path)
def run(test, params, env): """ Test pure checkpoint commands """ def prepare_checkpoints(disk="vdb", num=1, cp_prefix="test_checkpoint_"): """ Create checkpoints for specific disk :param disk: The disk to create checkpoint. :param num: How many checkpoints to be created :param cp_prefix: The prefix to name the checkpoint. """ option_pattern = ("{0} --diskspec vda,checkpoint=no " "--diskspec {1},checkpoint=bitmap,bitmap={0}") for i in range(num): # create checkpoints checkpoint_name = cp_prefix + str(i) options = option_pattern.format(checkpoint_name, disk) virsh.checkpoint_create_as(vm_name, options, **virsh_dargs) current_checkpoints.append(checkpoint_name) # Cancel the test if libvirt version is too low if not libvirt_version.version_compare(6, 0, 0): test.cancel("Current libvirt version doesn't support " "incremental backup.") checkpoint_cmd = params.get("checkpoint_cmd") cmd_flag = params.get("flag") required_checkpoints = int(params.get("required_checkpoints", 0)) test_disk_size = params.get("test_disk_size", "100M") test_disk_target = params.get("test_disk_target", "vdb") status_error = "yes" == params.get("status_error") tmp_dir = data_dir.get_tmp_dir() current_checkpoints = [] virsh_dargs = {'debug': True, 'ignore_status': False} try: vm_name = params.get("main_vm") vm = env.get_vm(vm_name) # Make sure there is no checkpoint metadata before test utils_backup.clean_checkpoints(vm_name) # Backup vm xml vmxml = vm_xml.VMXML.new_from_inactive_dumpxml(vm_name) vmxml_backup = vmxml.copy() utils_backup.enable_inc_backup_for_vm(vm) if vm.is_alive(): vm.destroy(gracefully=False) # Prepare the disk to be used. disk_params = {} disk_path = "" image_name = "{}_image.qcow2".format(test_disk_target) disk_path = os.path.join(tmp_dir, image_name) libvirt.create_local_disk("file", disk_path, test_disk_size, "qcow2") disk_params = {"device_type": "disk", "type_name": "file", "driver_type": "qcow2", "target_dev": test_disk_target, "source_file": disk_path} disk_xml = libvirt.create_disk_xml(disk_params) virsh.attach_device(vm.name, disk_xml, flagstr="--config", **virsh_dargs) vm.start() session = vm.wait_for_login() new_disks_in_vm = list(utils_disk.get_linux_disks(session).keys()) session.close() if required_checkpoints > 0: prepare_checkpoints(test_disk_target, required_checkpoints) if checkpoint_cmd == "checkpoint-create": if not current_checkpoints: test.fail("No existing checkpoints prepared.") if "--redefine" in cmd_flag: no_domain = "yes" == params.get("no_domain") extra_flag = params.get("extra_flag") image_with_bitmap = "yes" == params.get("image_with_bitmap") cp_dumpxml_options = "" if no_domain: cp_dumpxml_options = "--no-domain" if libvirt_version.version_compare(6, 6, 0): # libvirt-6.6.0-9.el8 starts to allow redefine VM # backup checkpoint without the domain XML (bz1901830) status_error = False checkpoint_redef = current_checkpoints[0] cp_xml = checkpoint_xml.CheckpointXML.new_from_checkpoint_dumpxml( vm_name, checkpoint_redef, cp_dumpxml_options) logging.debug("Checkpoint XML to be redefined is: %s", cp_xml) xml_file = cp_xml.xml virsh.checkpoint_delete(vm_name, checkpoint_redef, "--metadata", **virsh_dargs) cmd_options = xml_file + " " + cmd_flag if extra_flag: cmd_options += " " + extra_flag if "--redefine-validate" in extra_flag: if not libvirt_version.version_compare(6, 6, 0): test.cancel("--redefine-validate not supported in " "current libvirt versoin.") if not image_with_bitmap: status_error = True # replace vdb's image with a new qcow2 file to make sure # the image has no block dirty bitmap anymore vm.destroy(gracefully=False) libvirt.create_local_disk("file", disk_path, test_disk_size, "qcow2") vm.start() vm.wait_for_login().close() result = virsh.checkpoint_create(vm_name, cmd_options, debug=True) libvirt.check_exit_status(result, status_error) elif checkpoint_cmd == "checkpoint-create-as": if "--print-xml" in cmd_flag: checkpoint_name = "test_checkpoint_0" options = ("{0} --diskspec vda,checkpoint=no --diskspec {1}," "checkpoint=bitmap,bitmap={0} " "--print-xml".format(checkpoint_name, test_disk_target)) virsh.checkpoint_create_as(vm_name, options, **virsh_dargs) # The checkpiont should not be created, so we have following check cp_list_result = virsh.checkpoint_list(vm_name, checkpoint_name, debug=True) libvirt.check_exit_status(cp_list_result, True) elif checkpoint_cmd == "checkpoint-info": if len(current_checkpoints) != 3: test.fail("We should prepare 3 checkpoints.") parent_checkpoint = current_checkpoints[0] test_checkpoint = current_checkpoints[1] stdout = virsh.checkpoint_info(vm_name, test_checkpoint, **virsh_dargs).stdout_text.strip() if ( not re.search("domain.*%s" % vm_name, stdout, re.IGNORECASE) or not re.search("parent.*%s" % parent_checkpoint, stdout, re.IGNORECASE) or not re.search("children.*1", stdout, re.IGNORECASE) or not re.search("descendants.*1", stdout, re.IGNORECASE) ): test.fail("checkpoint-info return inaccurate informaion: %s" % stdout) elif checkpoint_cmd == "checkpoint-list": logic_error = False if not cmd_flag: stdout = virsh.checkpoint_list(vm_name, **virsh_dargs).stdout_text.strip() for checkpoint in current_checkpoints: if checkpoint not in stdout: logic_error = True elif cmd_flag == "--parent": stdout = virsh.checkpoint_list(vm_name, cmd_flag, **virsh_dargs).stdout_text.strip() for checkpoint in current_checkpoints: if checkpoint == current_checkpoints[-1]: if stdout.count(checkpoint) != 1: logic_error = True else: if stdout.count(checkpoint) != 2: logic_error = True elif cmd_flag == "--roots": stdout = virsh.checkpoint_list(vm_name, cmd_flag, **virsh_dargs).stdout_text.strip() for checkpoint in current_checkpoints: if checkpoint == current_checkpoints[0]: if stdout.count(checkpoint) != 1: logic_eror = True else: if stdout.count(checkpoint) != 0: logic_error = True elif cmd_flag == "--tree": stdout = virsh.checkpoint_list(vm_name, cmd_flag, **virsh_dargs).stdout_text.strip() lines = stdout.splitlines() prev_indent_num = -1 for line in lines: for checkpoint in current_checkpoints: if checkpoint in line: cur_indent_num = line.rstrip().count(" ") if cur_indent_num <= prev_indent_num: logic_error = True break prev_indent_num = cur_indent_num elif cmd_flag == "--name": stdout = virsh.checkpoint_list(vm_name, cmd_flag, **virsh_dargs).stdout_text.strip() checkpoint_names = stdout.splitlines() if not operator.eq(checkpoint_names, current_checkpoints): logic_error = True elif cmd_flag == "--topological": stdout = virsh.checkpoint_list(vm_name, cmd_flag, **virsh_dargs).stdout_text.strip() for checkpoint in current_checkpoints: if checkpoint not in stdout: logical_error = True elif cmd_flag == "--from": cmd_options = cmd_flag + " " + current_checkpoints[0] stdout = virsh.checkpoint_list(vm_name, cmd_options, **virsh_dargs).stdout_text.strip() if (current_checkpoints[0] in stdout or current_checkpoints[2] in stdout or current_checkpoints[1] not in stdout): logic_error = True elif cmd_flag == "--descendants": cmd_options = cmd_flag + " " + current_checkpoints[0] stdout = virsh.checkpoint_list(vm_name, cmd_options, **virsh_dargs).stdout_text.strip() if (current_checkpoints[0] in stdout or current_checkpoints[1] not in stdout or current_checkpoints[2] not in stdout): logic_error = True elif cmd_flag == "--no-leaves": stdout = virsh.checkpoint_list(vm_name, cmd_flag, **virsh_dargs).stdout_text.strip() if (current_checkpoints[0] not in stdout or current_checkpoints[1] not in stdout or current_checkpoints[2] in stdout): logic_error = True elif cmd_flag == "--leaves": stdout = virsh.checkpoint_list(vm_name, cmd_flag, **virsh_dargs).stdout_text.strip() if (current_checkpoints[0] in stdout or current_checkpoints[1] in stdout or current_checkpoints[2] not in stdout): logic_error = True if logic_error: test.fail("checkpoint-list with '%s' gives wrong output" % cmd_flag) elif checkpoint_cmd == "checkpoint-dumpxml": if "--size" in cmd_flag: if not libvirt_version.version_compare(6, 6, 0): test.cancel("Current libvirt version doesn't support " "'--size' for 'checkpoint-dumpxml'.") test_disk = new_disks_in_vm[-1] test_disk_path = "/dev/" + test_disk test_checkpoint = current_checkpoints[-1] dd_count = 1 dd_bs = "1M" dd_seek = "10" dd_size = dd_count * 1024 * 1024 session = vm.wait_for_login() utils_disk.dd_data_to_vm_disk(session, test_disk_path, bs=dd_bs, seek=dd_seek, count=str(dd_count)) session.close() stdout = virsh.checkpoint_dumpxml(vm_name, test_checkpoint + " --size", **virsh_dargs).stdout_text.strip() re_pattern = ".*%s.*%s.*size.*" % (test_disk, test_checkpoint) size_info_line = re.search(re_pattern, stdout) if not size_info_line: test.fail("There is no size info for disk:%s checkpoint:%s" % (test_disk, test_checkpoint)) if str(dd_size) not in size_info_line.group(0): test.fail("Size info incorrect in checkpoint xml, " "'dd_size' is %s, size info in xml is:%s" % (dd_size, size_info_line.group(0))) elif "--security-info" in cmd_flag: if vm.is_alive(): vm.destroy(gracefully=False) password = "******" vm_xml.VMXML.set_graphics_attr(vm_name, {'passwd': password}) vm.start() vm.wait_for_login().close() prepare_checkpoints() test_checkpoint = current_checkpoints[0] stdout = virsh.checkpoint_dumpxml(vm_name, test_checkpoint, **virsh_dargs).stdout_text.strip() if password in stdout: logging.debug("checkpoint xml is: %s", stdout) test.fail("Security info displayed in unsecurity dumpxml.") stdout = virsh.checkpoint_dumpxml(vm_name, test_checkpoint + " --security-info", **virsh_dargs).stdout_text.strip() if password not in stdout: logging.debug("checkpoint xml is: %s", stdout) test.fail("Security info not displayed in security dumpxml.") elif checkpoint_cmd == "virsh_list": stdout = virsh.dom_list(cmd_flag, **virsh_dargs).stdout_text.strip() if ((vm_name in stdout and cmd_flag == "--without-checkpoint") or (vm_name not in stdout and cmd_flag == "--with-checkpoint")): test.fail("virsh list with '%s' contains wrong data" % cmd_flag) # Make sure vm is running and check checkpoints can be normally deleted if not vm.is_alive(): vm.start() vm.wait_for_login().close() utils_backup.clean_checkpoints(vm_name, clean_metadata=False, ignore_status=False) finally: # Remove checkpoints utils_backup.clean_checkpoints(vm_name, clean_metadata=not vm.is_alive()) if vm.is_alive(): vm.destroy(gracefully=False) # Restoring vm vmxml_backup.sync() # Remove test image if "disk_path" in locals(): if os.path.exists(disk_path): os.remove(disk_path)