def define(self): """Define node :rtype : None """ name = underscored( deepgetattr(self, 'group.environment.name'), self.name, ) local_disk_devices = [] for disk in self.disk_devices: local_disk_devices.append(dict( disk_type=disk.type, disk_device=disk.device, disk_volume_format=disk.volume.format, disk_volume_path=disk.volume.get_path(), disk_bus=disk.bus, disk_target_dev=disk.target_dev, disk_serial=uuid.uuid4().hex, )) local_interfaces = [] for interface in self.interfaces: if interface.type != 'network': raise NotImplementedError( message='Interface types different from network are not ' 'implemented yet') l2_dev = interface.l2_network_device filter_name = underscored( deepgetattr(self, 'group.environment.name'), l2_dev.name, interface.mac_address ) target_dev = self.driver.get_available_device_name('virnet') local_interfaces.append(dict( interface_type=interface.type, interface_mac_address=interface.mac_address, interface_network_name=l2_dev.network_name, interface_target_dev=target_dev, interface_model=interface.model, interface_filter=filter_name, )) emulator = self.driver.get_capabilities().find( 'guest/arch[@name="{0:>s}"]/' 'domain[@type="{1:>s}"]/emulator'.format( self.architecture, self.hypervisor)).text node_xml = LibvirtXMLBuilder.build_node_xml( name=name, hypervisor=self.hypervisor, use_host_cpu=self.driver.use_host_cpu, vcpu=self.vcpu, memory=self.memory, use_hugepages=self.driver.use_hugepages, hpet=self.driver.hpet, os_type=self.os_type, architecture=self.architecture, boot=self.boot, reboot_timeout=self.driver.reboot_timeout, bootmenu_timeout=self.bootmenu_timeout, emulator=emulator, has_vnc=self.has_vnc, vnc_password=self.driver.vnc_password, local_disk_devices=local_disk_devices, interfaces=local_interfaces, acpi=self.driver.enable_acpi, numa=self.numa, ) logger.debug(node_xml) self.uuid = self.driver.conn.defineXML(node_xml).UUIDString() super(LibvirtNode, self).define()
class DevopsDriver(object): def __init__(self, connection_string="qemu:///system", storage_pool_name="default", stp=True, hpet=True): libvirt.virInitialize() self.conn = libvirt.open(connection_string) self.xml_builder = LibvirtXMLBuilder(self) self.stp = stp self.hpet = hpet self.capabilities = None self.allocated_networks = None self.storage_pool_name = storage_pool_name self.reboot_timeout = None if settings.VNC_PASSWORD: self.vnc_password = settings.VNC_PASSWORD if settings.REBOOT_TIMEOUT: self.reboot_timeout = settings.REBOOT_TIMEOUT def __del__(self): self.conn.close() def _get_name(self, *kwargs): return self.xml_builder._get_name(*kwargs) @retry() def get_capabilities(self): """Get host capabilities :rtype : ET """ if self.capabilities is None: self.capabilities = self.conn.getCapabilities() return ET.fromstring(self.capabilities) @retry() def network_bridge_name(self, network): """Get bridge name from UUID :type network: Network :rtype : String """ return self.conn.networkLookupByUUIDString(network.uuid).bridgeName() @retry() def network_name(self, network): """Get network name from UUID :type network: Network :rtype : String """ return self.conn.networkLookupByUUIDString(network.uuid).name() @retry() def network_active(self, network): """Check if network is active :type network: Network :rtype : Boolean """ return self.conn.networkLookupByUUIDString(network.uuid).isActive() @retry() def node_active(self, node): """Check if node is active :type node: Node :rtype : Boolean """ return self.conn.lookupByUUIDString(node.uuid).isActive() @retry() def network_exists(self, network): """Check if network exists :type network: Network :rtype : Boolean """ try: self.conn.networkLookupByUUIDString(network.uuid) return True except libvirt.libvirtError as e: if e.get_error_code() == libvirt.VIR_ERR_NO_NETWORK: return False else: raise @retry() def node_exists(self, node): """Check if node exists :type node: Node :rtype : Boolean """ try: self.conn.lookupByUUIDString(node.uuid) return True except libvirt.libvirtError as e: if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN: return False else: raise @retry() def node_snapshot_exists(self, node, name): """Check if snapshot exists :type node: Node :type name: String :rtype : Boolean """ ret = self.conn.lookupByUUIDString(node.uuid) return name in ret.snapshotListNames() @retry() def volume_exists(self, volume): """Check if volume exists :type volume: Volume :rtype : Boolean """ try: self.conn.storageVolLookupByKey(volume.uuid) return True except libvirt.libvirtError as e: if e.get_error_code() == libvirt.VIR_ERR_NO_STORAGE_VOL: return False else: raise @retry() def network_define(self, network): """Define network :type network: Network :rtype : None """ ret = self.conn.networkDefineXML( self.xml_builder.build_network_xml(network)) ret.setAutostart(True) network.uuid = ret.UUIDString() @retry() def network_destroy(self, network): """Destroy network :type network: Network :rtype : None """ self.conn.networkLookupByUUIDString(network.uuid).destroy() @retry() def network_undefine(self, network): """Undefine network :type network: Network :rtype : None """ self.conn.networkLookupByUUIDString(network.uuid).undefine() @retry() def network_create(self, network): """Create network :type network: Network :rtype : None """ self.conn.networkLookupByUUIDString(network.uuid).create() @retry() def node_define(self, node): """Define node :type node: Node :rtype : None """ emulator = self.get_capabilities( ).find( 'guest/arch[@name="{0:>s}"]/' 'domain[@type="{1:>s}"]/emulator'.format( node.architecture, node.hypervisor)).text node_xml = self.xml_builder.build_node_xml(node, emulator) logger.info(node_xml) node.uuid = self.conn.defineXML(node_xml).UUIDString() @retry() def node_destroy(self, node): """Destroy node :type node: Node :rtype : None """ self.conn.lookupByUUIDString(node.uuid).destroy() @retry() def node_undefine(self, node, undefine_snapshots=False): """Undefine domain. If undefine_snapshot is set, discard all snapshots. :type node: Node :type undefine_snapshots: Boolean :rtype : None """ domain = self.conn.lookupByUUIDString(node.uuid) if undefine_snapshots: domain.undefineFlags( libvirt.VIR_DOMAIN_UNDEFINE_SNAPSHOTS_METADATA) else: domain.undefine() @retry() def node_undefine_by_name(self, node_name): """Undefine domain discarding all snapshots :type node_name: String :rtype : None """ domain = self.conn.lookupByName(node_name) domain.undefineFlags(libvirt.VIR_DOMAIN_UNDEFINE_SNAPSHOTS_METADATA) @retry() def node_get_vnc_port(self, node): """Get VNC port :type node: Node :rtype : String """ xml_desc = ET.fromstring( self.conn.lookupByUUIDString(node.uuid).XMLDesc(0)) vnc_element = xml_desc.find('devices/graphics[@type="vnc"][@port]') if vnc_element is not None: return vnc_element.get('port') @retry() def node_get_interface_target_dev(self, node, mac): """Get target device :type node: Node :type mac: String :rtype : String """ xml_desc = ET.fromstring( self.conn.lookupByUUIDString(node.uuid).XMLDesc(0)) target = xml_desc.find('.//mac[@address="%s"]/../target' % mac) if target is not None: return target.get('dev') @retry() def node_create(self, node): """Create node :type node: Node :rtype : None """ self.conn.lookupByUUIDString(node.uuid).create() @retry() def node_list(self): # virConnect.listDefinedDomains() only returns stopped domains # https://bugzilla.redhat.com/show_bug.cgi?id=839259 return [item.name() for item in self.conn.listAllDomains()] @retry() def node_reset(self, node): """Reset node :type node: Node :rtype : None """ self.conn.lookupByUUIDString(node.uuid).reset() @retry() def node_reboot(self, node): """Reboot node :type node: Node :rtype : None """ self.conn.lookupByUUIDString(node.uuid).reboot() @retry() def node_suspend(self, node): """Suspend node :type node: Node :rtype : None """ self.conn.lookupByUUIDString(node.uuid).suspend() @retry() def node_resume(self, node): """Resume node :type node: Node :rtype : None """ domain = self.conn.lookupByUUIDString(node.uuid) if domain.info()[0] == libvirt.VIR_DOMAIN_PAUSED: domain.resume() @retry() def node_shutdown(self, node): """Shutdown node :type node: Node :rtype : None """ self.conn.lookupByUUIDString(node.uuid).shutdown() @retry() def node_get_snapshots(self, node): """Get list of snapshots :rtype : List :type node: Node """ return self.conn.lookupByUUIDString(node.uuid).snapshotListNames(0) @retry() def node_create_snapshot(self, node, name=None, description=None): """Create snapshot :type description: String :type name: String :type node: Node :rtype : None """ xml = self.xml_builder.build_snapshot_xml(name, description) logger.info(xml) domain = self.conn.lookupByUUIDString(node.uuid) logger.info(domain.state(0)) domain.snapshotCreateXML(xml, 0) logger.info(domain.state(0)) def _get_snapshot(self, domain, name): """Get snapshot :type domain: Node :type name: String :rtype : libvirt.virDomainSnapshot """ if name is None: return domain.snapshotCurrent(0) else: return domain.snapshotLookupByName(name, 0) @retry() def node_revert_snapshot(self, node, name=None): """Revert snapshot for node :type node: Node :type name: String :rtype : None """ domain = self.conn.lookupByUUIDString(node.uuid) snapshot = self._get_snapshot(domain, name) domain.revertToSnapshot(snapshot, 0) @retry() def node_delete_all_snapshots(self, node): """Delete all snapshots for node :type node: Node """ domain = self.conn.lookupByUUIDString(node.uuid) for name in domain.snapshotListNames( libvirt.VIR_DOMAIN_SNAPSHOT_LIST_ROOTS): snapshot = self._get_snapshot(domain, name) snapshot.delete(libvirt.VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN) @retry() def node_delete_snapshot(self, node, name=None): """Delete snapshot :type node: Node :type name: String """ domain = self.conn.lookupByUUIDString(node.uuid) snapshot = self._get_snapshot(domain, name) snapshot.delete(0) @retry() def node_send_keys(self, node, keys): """Send keys to node :type node: Node :type keys: String :rtype : None """ key_codes = scancodes.from_string(str(keys)) for key_code in key_codes: if isinstance(key_code[0], str): if key_code[0] == 'wait': sleep(1) continue self.conn.lookupByUUIDString(node.uuid).sendKey(0, 0, list(key_code), len(key_code), 0) @retry() def node_set_vcpu(self, node, vcpu): """Set VCPU :type volume: Volume :rtype : None """ domain = self.conn.lookupByUUIDString(node.uuid) domain.setVcpusFlags(vcpu, 4) domain.setVcpusFlags(vcpu, 2) @retry() def node_set_memory(self, node, memory): """Set VCPU :type volume: Volume :rtype : None """ domain = self.conn.lookupByUUIDString(node.uuid) domain.setMaxMemory(memory) domain.setMemoryFlags(memory, 2) @retry() def volume_define(self, volume, pool=None): """Define volume :type volume: Volume :type pool: String :rtype : None """ if pool is None: pool = self.storage_pool_name libvirt_volume = self.conn.storagePoolLookupByName(pool).createXML( self.xml_builder.build_volume_xml(volume), 0) volume.uuid = libvirt_volume.key() @retry() def volume_allocation(self, volume): """Get volume allocation :type volume: Volume :rtype : Long """ return self.conn.storageVolLookupByKey(volume.uuid).info()[2] @retry() def volume_path(self, volume): return self.conn.storageVolLookupByKey(volume.uuid).path() def chunk_render(self, stream, size, fd): return fd.read(size) @retry(count=2) def volume_upload(self, volume, path): size = _get_file_size(path) with open(path, 'rb') as fd: stream = self.conn.newStream(0) self.conn.storageVolLookupByKey(volume.uuid).upload( stream=stream, offset=0, length=size, flags=0) stream.sendAll(self.chunk_render, fd) stream.finish() @retry() def volume_delete(self, volume): """Delete volume :type volume: Volume :rtype : None """ self.conn.storageVolLookupByKey(volume.uuid).delete(0) @retry() def volume_capacity(self, volume): """Get volume capacity :type volume: Volume :rtype : Long """ return self.conn.storageVolLookupByKey(volume.uuid).info()[1] @retry() def volume_format(self, volume): """Get volume format :type volume: Volume :rtype : String """ xml_desc = ET.fromstring( self.conn.storageVolLookupByKey(volume.uuid).XMLDesc(0)) return xml_desc.find('target/format[@type]').get('type') @retry() def get_allocated_networks(self): """Get list of allocated networks :rtype : List """ if self.allocated_networks is None: allocated_networks = [] for network_name in self.conn.listDefinedNetworks(): et = ET.fromstring( self.conn.networkLookupByName(network_name).XMLDesc(0)) ip = et.find('ip[@address]') if ip is not None: address = ip.get('address') prefix_or_netmask = ip.get('prefix') or ip.get('netmask') allocated_networks.append(ipaddr.IPNetwork( "{0:>s}/{1:>s}".format(address, prefix_or_netmask))) self.allocated_networks = allocated_networks return self.allocated_networks
class DevopsDriver(object): def __init__(self, connection_string="qemu:///system", storage_pool_name="default", stp=True, hpet=True, use_host_cpu=True): """libvirt driver :param use_host_cpu: When creating nodes, should libvirt's CPU "host-model" mode be used to set CPU settings. If set to False, default mode ("custom") will be used. (default: True) """ libvirt.virInitialize() self.conn = libvirt.open(connection_string) self.xml_builder = LibvirtXMLBuilder(self) self.stp = stp self.hpet = hpet self.capabilities = None self.allocated_networks = None self.storage_pool_name = storage_pool_name self.reboot_timeout = None self.use_host_cpu = use_host_cpu self.use_hugepages = settings.USE_HUGEPAGES if settings.VNC_PASSWORD: self.vnc_password = settings.VNC_PASSWORD if settings.REBOOT_TIMEOUT: self.reboot_timeout = settings.REBOOT_TIMEOUT def __del__(self): self.conn.close() def _get_name(self, *kwargs): return self.xml_builder._get_name(*kwargs) @retry() def get_capabilities(self): """Get host capabilities :rtype : ET """ if self.capabilities is None: self.capabilities = self.conn.getCapabilities() return ET.fromstring(self.capabilities) @retry() def network_bridge_name(self, network): """Get bridge name from UUID :type network: Network :rtype : String """ return self.conn.networkLookupByUUIDString(network.uuid).bridgeName() @retry() def network_name(self, network): """Get network name from UUID :type network: Network :rtype : String """ return self.conn.networkLookupByUUIDString(network.uuid).name() @retry() def network_active(self, network): """Check if network is active :type network: Network :rtype : Boolean """ return self.conn.networkLookupByUUIDString(network.uuid).isActive() @retry() def node_active(self, node): """Check if node is active :type node: Node :rtype : Boolean """ return self.conn.lookupByUUIDString(node.uuid).isActive() @retry() def network_exists(self, network): """Check if network exists :type network: Network :rtype : Boolean """ try: self.conn.networkLookupByUUIDString(network.uuid) return True except libvirt.libvirtError as e: if e.get_error_code() == libvirt.VIR_ERR_NO_NETWORK: return False else: raise @retry() def node_exists(self, node): """Check if node exists :type node: Node :rtype : Boolean """ try: self.conn.lookupByUUIDString(node.uuid) return True except libvirt.libvirtError as e: if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN: return False else: raise @retry() def node_snapshot_exists(self, node, name): """Check if snapshot exists :type node: Node :type name: String :rtype : Boolean """ ret = self.conn.lookupByUUIDString(node.uuid) return name in ret.snapshotListNames() @retry() def volume_exists(self, volume): """Check if volume exists :type volume: Volume :rtype : Boolean """ try: self.conn.storageVolLookupByKey(volume.uuid) return True except libvirt.libvirtError as e: if e.get_error_code() == libvirt.VIR_ERR_NO_STORAGE_VOL: return False else: raise @retry() def network_define(self, network): """Define network :type network: Network :rtype : None """ ret = self.conn.networkDefineXML( self.xml_builder.build_network_xml(network)) ret.setAutostart(True) network.uuid = ret.UUIDString() @retry() def network_destroy(self, network): """Destroy network :type network: Network :rtype : None """ self.conn.networkLookupByUUIDString(network.uuid).destroy() @retry() def network_undefine(self, network): """Undefine network :type network: Network :rtype : None """ self.conn.networkLookupByUUIDString(network.uuid).undefine() @retry() def network_create(self, network): """Create network :type network: Network :rtype : None """ self.conn.networkLookupByUUIDString(network.uuid).create() @retry() def node_define(self, node): """Define node :type node: Node :rtype : None """ emulator = self.get_capabilities( ).find( 'guest/arch[@name="{0:>s}"]/' 'domain[@type="{1:>s}"]/emulator'.format( node.architecture, node.hypervisor)).text node_xml = self.xml_builder.build_node_xml(node, emulator) logger.info(node_xml) node.uuid = self.conn.defineXML(node_xml).UUIDString() @retry() def node_destroy(self, node): """Destroy node :type node: Node :rtype : None """ self.conn.lookupByUUIDString(node.uuid).destroy() @retry() def node_undefine(self, node, undefine_snapshots=False): """Undefine domain. If undefine_snapshot is set, discard all snapshots. :type node: Node :type undefine_snapshots: Boolean :rtype : None """ domain = self.conn.lookupByUUIDString(node.uuid) if undefine_snapshots: domain.undefineFlags( libvirt.VIR_DOMAIN_UNDEFINE_SNAPSHOTS_METADATA) else: domain.undefine() @retry() def node_undefine_by_name(self, node_name): """Undefine domain discarding all snapshots :type node_name: String :rtype : None """ domain = self.conn.lookupByName(node_name) domain.undefineFlags(libvirt.VIR_DOMAIN_UNDEFINE_SNAPSHOTS_METADATA) @retry() def node_get_vnc_port(self, node): """Get VNC port :type node: Node :rtype : String """ xml_desc = ET.fromstring( self.conn.lookupByUUIDString(node.uuid).XMLDesc(0)) vnc_element = xml_desc.find('devices/graphics[@type="vnc"][@port]') if vnc_element is not None: return vnc_element.get('port') @retry() def node_get_interface_target_dev(self, node, mac): """Get target device :type node: Node :type mac: String :rtype : String """ xml_desc = ET.fromstring( self.conn.lookupByUUIDString(node.uuid).XMLDesc(0)) target = xml_desc.find('.//mac[@address="%s"]/../target' % mac) if target is not None: return target.get('dev') @retry() def node_create(self, node): """Create node :type node: Node :rtype : None """ self.conn.lookupByUUIDString(node.uuid).create() @retry() def node_list(self): # virConnect.listDefinedDomains() only returns stopped domains # https://bugzilla.redhat.com/show_bug.cgi?id=839259 return [item.name() for item in self.conn.listAllDomains()] @retry() def node_reset(self, node): """Reset node :type node: Node :rtype : None """ self.conn.lookupByUUIDString(node.uuid).reset() @retry() def node_reboot(self, node): """Reboot node :type node: Node :rtype : None """ self.conn.lookupByUUIDString(node.uuid).reboot() @retry() def node_suspend(self, node): """Suspend node :type node: Node :rtype : None """ self.conn.lookupByUUIDString(node.uuid).suspend() @retry() def node_resume(self, node): """Resume node :type node: Node :rtype : None """ domain = self.conn.lookupByUUIDString(node.uuid) if domain.info()[0] == libvirt.VIR_DOMAIN_PAUSED: domain.resume() @retry() def node_shutdown(self, node): """Shutdown node :type node: Node :rtype : None """ self.conn.lookupByUUIDString(node.uuid).shutdown() @retry() def node_get_snapshots(self, node): """Get list of snapshots :rtype : List :type node: Node """ snapshots = self.conn.lookupByUUIDString(node.uuid).listAllSnapshots(0) return [Snapshot(snap) for snap in snapshots] @retry() def node_create_snapshot(self, node, name=None, description=None): """Create snapshot :type description: String :type name: String :type node: Node :rtype : None """ xml = self.xml_builder.build_snapshot_xml(name, description) logger.info(xml) domain = self.conn.lookupByUUIDString(node.uuid) logger.info(domain.state(0)) domain.snapshotCreateXML(xml, 0) logger.info(domain.state(0)) def _get_snapshot(self, domain, name): """Get snapshot :type domain: Node :type name: String :rtype : libvirt.virDomainSnapshot """ if name is None: return domain.snapshotCurrent(0) else: return domain.snapshotLookupByName(name, 0) @retry() def node_revert_snapshot(self, node, name=None): """Revert snapshot for node :type node: Node :type name: String :rtype : None """ domain = self.conn.lookupByUUIDString(node.uuid) snapshot = self._get_snapshot(domain, name) domain.revertToSnapshot(snapshot, 0) @retry() def node_delete_all_snapshots(self, node): """Delete all snapshots for node :type node: Node """ domain = self.conn.lookupByUUIDString(node.uuid) for name in domain.snapshotListNames( libvirt.VIR_DOMAIN_SNAPSHOT_LIST_ROOTS): snapshot = self._get_snapshot(domain, name) snapshot.delete(libvirt.VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN) @retry() def node_delete_snapshot(self, node, name=None): """Delete snapshot :type node: Node :type name: String """ domain = self.conn.lookupByUUIDString(node.uuid) snapshot = self._get_snapshot(domain, name) snapshot.delete(0) @retry() def node_send_keys(self, node, keys): """Send keys to node :type node: Node :type keys: String :rtype : None """ key_codes = scancodes.from_string(str(keys)) for key_code in key_codes: if isinstance(key_code[0], str): if key_code[0] == 'wait': sleep(1) continue self.conn.lookupByUUIDString(node.uuid).sendKey(0, 0, list(key_code), len(key_code), 0) @retry() def node_set_vcpu(self, node, vcpu): """Set VCPU :type volume: Volume :rtype : None """ domain = self.conn.lookupByUUIDString(node.uuid) domain.setVcpusFlags(vcpu, 4) domain.setVcpusFlags(vcpu, 2) @retry() def node_set_memory(self, node, memory): """Set VCPU :type volume: Volume :rtype : None """ domain = self.conn.lookupByUUIDString(node.uuid) domain.setMaxMemory(memory) domain.setMemoryFlags(memory, 2) @retry() def volume_define(self, volume, pool=None): """Define volume :type volume: Volume :type pool: String :rtype : None """ if pool is None: pool = self.storage_pool_name libvirt_volume = self.conn.storagePoolLookupByName(pool).createXML( self.xml_builder.build_volume_xml(volume), 0) volume.uuid = libvirt_volume.key() @retry() def volume_allocation(self, volume): """Get volume allocation :type volume: Volume :rtype : Long """ return self.conn.storageVolLookupByKey(volume.uuid).info()[2] @retry() def volume_path(self, volume): return self.conn.storageVolLookupByKey(volume.uuid).path() def chunk_render(self, stream, size, fd): return fd.read(size) @retry(count=2) def volume_upload(self, volume, path): size = _get_file_size(path) with open(path, 'rb') as fd: stream = self.conn.newStream(0) self.conn.storageVolLookupByKey(volume.uuid).upload( stream=stream, offset=0, length=size, flags=0) stream.sendAll(self.chunk_render, fd) stream.finish() @retry() def volume_delete(self, volume): """Delete volume :type volume: Volume :rtype : None """ self.conn.storageVolLookupByKey(volume.uuid).delete(0) @retry() def volume_capacity(self, volume): """Get volume capacity :type volume: Volume :rtype : Long """ return self.conn.storageVolLookupByKey(volume.uuid).info()[1] @retry() def volume_format(self, volume): """Get volume format :type volume: Volume :rtype : String """ xml_desc = ET.fromstring( self.conn.storageVolLookupByKey(volume.uuid).XMLDesc(0)) return xml_desc.find('target/format[@type]').get('type') @retry() def get_allocated_networks(self): """Get list of allocated networks :rtype : List """ if self.allocated_networks is None: allocated_networks = [] for network_name in self.conn.listDefinedNetworks(): et = ET.fromstring( self.conn.networkLookupByName(network_name).XMLDesc(0)) ip = et.find('ip[@address]') if ip is not None: address = ip.get('address') prefix_or_netmask = ip.get('prefix') or ip.get('netmask') allocated_networks.append(ipaddr.IPNetwork( "{0:>s}/{1:>s}".format(address, prefix_or_netmask))) self.allocated_networks = allocated_networks return self.allocated_networks
def define(self): """Define node :rtype : None """ name = underscored( deepgetattr(self, 'group.environment.name'), self.name, ) local_disk_devices = [] for disk in self.disk_devices: local_disk_devices.append( dict( disk_type=disk.type, disk_device=disk.device, disk_volume_format=disk.volume.format, disk_volume_path=disk.volume.get_path(), disk_bus=disk.bus, disk_target_dev=disk.target_dev, disk_serial=uuid.uuid4().hex, )) local_interfaces = [] for interface in self.interfaces: if interface.type != 'network': raise NotImplementedError( message='Interface types different from network are not ' 'implemented yet') l2_dev = interface.l2_network_device filter_name = underscored( deepgetattr(self, 'group.environment.name'), l2_dev.name, interface.mac_address) target_dev = self.driver.get_available_device_name('virnet') local_interfaces.append( dict( interface_type=interface.type, interface_mac_address=interface.mac_address, interface_network_name=l2_dev.network_name, interface_target_dev=target_dev, interface_model=interface.model, interface_filter=filter_name, )) emulator = self.driver.get_capabilities().find( 'guest/arch[@name="{0:>s}"]/' 'domain[@type="{1:>s}"]/emulator'.format(self.architecture, self.hypervisor)).text node_xml = LibvirtXMLBuilder.build_node_xml( name=name, hypervisor=self.hypervisor, use_host_cpu=self.driver.use_host_cpu, vcpu=self.vcpu, memory=self.memory, use_hugepages=self.driver.use_hugepages, hpet=self.driver.hpet, os_type=self.os_type, architecture=self.architecture, boot=self.boot, reboot_timeout=self.driver.reboot_timeout, bootmenu_timeout=self.bootmenu_timeout, emulator=emulator, has_vnc=self.has_vnc, vnc_password=self.driver.vnc_password, local_disk_devices=local_disk_devices, interfaces=local_interfaces, acpi=self.driver.enable_acpi, numa=self.numa, ) logger.debug(node_xml) self.uuid = self.driver.conn.defineXML(node_xml).UUIDString() super(LibvirtNode, self).define()
class DevopsDriver(object): def __init__(self, connection_string="qemu:///system", storage_pool_name="default", stp=True, hpet=True, use_host_cpu=True): """libvirt driver :param use_host_cpu: When creating nodes, should libvirt's CPU "host-model" mode be used to set CPU settings. If set to False, default mode ("custom") will be used. (default: True) """ libvirt.virInitialize() self.conn = libvirt.open(connection_string) self.xml_builder = LibvirtXMLBuilder(self) self.stp = stp self.hpet = hpet self.capabilities = None self.allocated_networks = None self.storage_pool_name = storage_pool_name self.reboot_timeout = None self.use_host_cpu = use_host_cpu self.use_hugepages = settings.USE_HUGEPAGES if settings.VNC_PASSWORD: self.vnc_password = settings.VNC_PASSWORD if settings.REBOOT_TIMEOUT: self.reboot_timeout = settings.REBOOT_TIMEOUT def __del__(self): self.conn.close() def _get_name(self, *kwargs): return self.xml_builder._get_name(*kwargs) def get_libvirt_version(self): return self.conn.getLibVersion() @retry() def get_capabilities(self): """Get host capabilities :rtype : ET """ if self.capabilities is None: self.capabilities = self.conn.getCapabilities() return ET.fromstring(self.capabilities) @retry() def network_bridge_name(self, network): """Get bridge name from UUID :type network: Network :rtype : String """ return self.conn.networkLookupByUUIDString(network.uuid).bridgeName() @retry() def network_name(self, network): """Get network name from UUID :type network: Network :rtype : String """ return self.conn.networkLookupByUUIDString(network.uuid).name() @retry() def network_active(self, network): """Check if network is active :type network: Network :rtype : Boolean """ return self.conn.networkLookupByUUIDString(network.uuid).isActive() @retry() def node_active(self, node): """Check if node is active :type node: Node :rtype : Boolean """ return self.conn.lookupByUUIDString(node.uuid).isActive() @retry() def network_exists(self, network): """Check if network exists :type network: Network :rtype : Boolean """ try: self.conn.networkLookupByUUIDString(network.uuid) return True except libvirt.libvirtError as e: if e.get_error_code() == libvirt.VIR_ERR_NO_NETWORK: return False else: raise @retry() def node_exists(self, node): """Check if node exists :type node: Node :rtype : Boolean """ try: self.conn.lookupByUUIDString(node.uuid) return True except libvirt.libvirtError as e: if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN: return False else: raise @retry() def node_snapshot_exists(self, node, name): """Check if snapshot exists :type node: Node :type name: String :rtype : Boolean """ ret = self.conn.lookupByUUIDString(node.uuid) return name in ret.snapshotListNames() @retry() def volume_exists(self, volume): """Check if volume exists :type volume: Volume :rtype : Boolean """ try: self.conn.storageVolLookupByKey(volume.uuid) return True except libvirt.libvirtError as e: if e.get_error_code() == libvirt.VIR_ERR_NO_STORAGE_VOL: return False else: raise @retry() def network_define(self, network): """Define network :type network: Network :rtype : None """ ret = self.conn.networkDefineXML( self.xml_builder.build_network_xml(network)) ret.setAutostart(True) network.uuid = ret.UUIDString() @retry() def network_destroy(self, network): """Destroy network :type network: Network :rtype : None """ self.conn.networkLookupByUUIDString(network.uuid).destroy() @retry() def network_undefine(self, network): """Undefine network :type network: Network :rtype : None """ self.conn.networkLookupByUUIDString(network.uuid).undefine() @retry() def network_create(self, network): """Create network :type network: Network :rtype : None """ self.conn.networkLookupByUUIDString(network.uuid).create() @retry() def node_define(self, node): """Define node :type node: Node :rtype : None """ emulator = self.get_capabilities( ).find( 'guest/arch[@name="{0:>s}"]/' 'domain[@type="{1:>s}"]/emulator'.format( node.architecture, node.hypervisor)).text node_xml = self.xml_builder.build_node_xml(node, emulator) logger.info(node_xml) node.uuid = self.conn.defineXML(node_xml).UUIDString() @retry() def node_destroy(self, node): """Destroy node :type node: Node :rtype : None """ self.conn.lookupByUUIDString(node.uuid).destroy() @retry() def node_undefine(self, node, undefine_snapshots=False): """Undefine domain. If undefine_snapshot is set, discard all snapshots. :type node: Node :type undefine_snapshots: Boolean :rtype : None """ domain = self.conn.lookupByUUIDString(node.uuid) if undefine_snapshots: # Delete external snapshots snap_list = domain.listAllSnapshots(0) for snapshot in snap_list: self._delete_snapshot_files(snapshot) domain.undefineFlags( libvirt.VIR_DOMAIN_UNDEFINE_SNAPSHOTS_METADATA) else: domain.undefine() @retry() def node_undefine_by_name(self, node_name): """Undefine domain discarding all snapshots :type node_name: String :rtype : None """ domain = self.conn.lookupByName(node_name) domain.undefineFlags(libvirt.VIR_DOMAIN_UNDEFINE_SNAPSHOTS_METADATA) @retry() def node_get_vnc_port(self, node): """Get VNC port :type node: Node :rtype : String """ xml_desc = ET.fromstring( self.conn.lookupByUUIDString(node.uuid).XMLDesc(0)) vnc_element = xml_desc.find('devices/graphics[@type="vnc"][@port]') if vnc_element is not None: return vnc_element.get('port') @retry() def node_get_interface_target_dev(self, node, mac): """Get target device :type node: Node :type mac: String :rtype : String """ xml_desc = ET.fromstring( self.conn.lookupByUUIDString(node.uuid).XMLDesc(0)) target = xml_desc.find('.//mac[@address="%s"]/../target' % mac) if target is not None: return target.get('dev') @retry() def node_create(self, node): """Create node :type node: Node :rtype : None """ self.conn.lookupByUUIDString(node.uuid).create() @retry() def node_list(self): # virConnect.listDefinedDomains() only returns stopped domains # https://bugzilla.redhat.com/show_bug.cgi?id=839259 return [item.name() for item in self.conn.listAllDomains()] @retry() def node_reset(self, node): """Reset node :type node: Node :rtype : None """ self.conn.lookupByUUIDString(node.uuid).reset() @retry() def node_reboot(self, node): """Reboot node :type node: Node :rtype : None """ self.conn.lookupByUUIDString(node.uuid).reboot() @retry() def node_suspend(self, node): """Suspend node :type node: Node :rtype : None """ self.conn.lookupByUUIDString(node.uuid).suspend() @retry() def node_resume(self, node): """Resume node :type node: Node :rtype : None """ domain = self.conn.lookupByUUIDString(node.uuid) if domain.info()[0] == libvirt.VIR_DOMAIN_PAUSED: domain.resume() @retry() def node_shutdown(self, node): """Shutdown node :type node: Node :rtype : None """ self.conn.lookupByUUIDString(node.uuid).shutdown() @retry() def node_get_snapshots(self, node): """Get list of snapshots :rtype : List :type node: Node """ snapshots = self.conn.lookupByUUIDString( node.uuid).listAllSnapshots(0) return [Snapshot(snap) for snap in snapshots] def node_get_snapshot(self, node, name): """Get snapshot with name :rtype : Snapshot :type node: Node :type name: Snapshot name """ snap = self.conn.lookupByUUIDString( node.uuid).snapshotLookupByName(name) return Snapshot(snap) def node_set_snapshot_current(self, node, name): domain = self.conn.lookupByUUIDString(node.uuid) snapshot = self._get_snapshot(domain, name) snapshot_xml = Snapshot(snapshot)._xml domain.snapshotCreateXML( snapshot_xml, libvirt.VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE | libvirt.VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT) @retry() def node_create_snapshot(self, node, name=None, description=None, disk_only=False, external=False): """Create snapshot :type description: String :type name: String :type node: Node :rtype : None """ if self.node_snapshot_exists(node, name): logger.error("Snapshot with name {0} already exists".format(name)) return domain = self.conn.lookupByUUIDString(node.uuid) # If domain has snapshots we must check their type snap_list = self.node_get_snapshots(node) if len(snap_list) > 0: snap_type = snap_list[0].get_type if external and snap_type == 'internal': logger.error( "Cannot create external snapshot when internal exists") return if not external and snap_type == 'external': logger.error( "Cannot create internal snapshot when external exists") return logger.info(domain.state(0)) xml = self.xml_builder.build_snapshot_xml( name, description, node, disk_only, external, settings.SNAPSHOTS_EXTERNAL_DIR) logger.info(xml) if external: # Check whether we have directory for snapshots, if not # create it if not os.path.exists(settings.SNAPSHOTS_EXTERNAL_DIR): os.makedirs(settings.SNAPSHOTS_EXTERNAL_DIR) if domain.isActive() and not disk_only: domain.snapshotCreateXML( xml, libvirt.VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT) else: domain.snapshotCreateXML( xml, libvirt.VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY | libvirt.VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT) self.node_set_snapshot_current(node, name) else: domain.snapshotCreateXML(xml) logger.info(domain.state(0)) def _delete_snapshot_files(self, snapshot): """Delete snapshot external files""" snap_type = Snapshot(snapshot).get_type if snap_type == 'external': for snap_file in self._get_snapshot_files(snapshot): if os.path.isfile(snap_file): try: os.remove(snap_file) logger.info( "Delete external snapshot file {0}".format( snap_file)) except Exception: logger.info( "Cannot delete external snapshot file {0}" " must be deleted from cron script".format( snap_file)) def _get_snapshot(self, domain, name): """Get snapshot :type domain: Node :type name: String :rtype : libvirt.virDomainSnapshot """ if name is None: return domain.snapshotCurrent(0) else: return domain.snapshotLookupByName(name, 0) def _get_snapshot_files(self, snapshot): """Return snapshot files""" xml_tree = ET.fromstring(snapshot.getXMLDesc()) snap_files = [] snap_memory = xml_tree.findall('./memory')[0] if snap_memory.get('file') is not None: snap_files.append(snap_memory.get('file')) return snap_files @retry() def node_revert_snapshot_recreate_disks(self, node, name): """Recreate snapshot disks.""" domain = self.conn.lookupByUUIDString(node.uuid) snapshot = Snapshot(self._get_snapshot(domain, name)) if snapshot.children_num == 0: for s_disk, s_disk_data in snapshot.disks.items(): logger.info("Recreate {0}".format(s_disk_data)) # Save actual volume XML, delete volume and create # new from saved XML volume = self.conn.storageVolLookupByKey(s_disk_data) volume_xml = volume.XMLDesc() volume_pool = volume.storagePoolLookupByVolume() volume.delete() volume_pool.createXML(volume_xml) @retry() def node_revert_snapshot(self, node, name=None): """Revert snapshot for node :type node: Node :type name: String :rtype : None """ domain = self.conn.lookupByUUIDString(node.uuid) snapshot = Snapshot(self._get_snapshot(domain, name)) if snapshot.get_type == 'external': logger.info("Revert {0} ({1}) from external snapshot {2}".format( node.name, snapshot.state, name)) if self.node_active(node): self.node_destroy(node) # When snapshot dont have children we need to update disks in XML # used for reverting, standard revert function will restore links # to original disks, but we need to use disks with snapshot point, # we dont want to change original data # # For snapshot with children we need to create new snapshot chain # and we need to start from original disks, this disks will get new # snapshot point in node class xml_domain = snapshot._xml_tree.find('domain') if snapshot.children_num == 0: domain_disks = xml_domain.findall('./devices/disk') for s_disk, s_disk_data in snapshot.disks.items(): for d_disk in domain_disks: d_disk_dev = d_disk.find('target').get('dev') d_disk_device = d_disk.get('device') if d_disk_dev == s_disk and d_disk_device == 'disk': d_disk.find('source').set('file', s_disk_data) if snapshot.state == 'shutoff': # Redefine domain for snapshot without memory save self.conn.defineXML(ET.tostring(xml_domain)) else: self.conn.restoreFlags( snapshot.memory_file, dxml=ET.tostring(xml_domain), flags=libvirt.VIR_DOMAIN_SAVE_PAUSED) # set snapshot as current self.node_set_snapshot_current(node, name) else: logger.info("Revert {0} ({1}) to internal snapshot {2}".format( node.name, snapshot.state, name)) domain.revertToSnapshot(snapshot._snapshot, 0) @retry() def node_delete_all_snapshots(self, node): """Delete all snapshots for node :type node: Node """ domain = self.conn.lookupByUUIDString(node.uuid) # Delete all external snapshots end return snap_list = domain.listAllSnapshots(0) if len(snap_list) > 0: snap_type = Snapshot(snap_list[0]).get_type if snap_type == 'external': for snapshot in snap_list: snapshot.delete(2) return for name in domain.snapshotListNames( libvirt.VIR_DOMAIN_SNAPSHOT_LIST_ROOTS): snapshot = self._get_snapshot(domain, name) snapshot.delete(libvirt.VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN) @retry() def node_delete_snapshot(self, node, name=None): """Delete snapshot :type node: Node :type name: String """ domain = self.conn.lookupByUUIDString(node.uuid) snapshot = self._get_snapshot(domain, name) snap_type = Snapshot(snapshot).get_type if snap_type == 'external': if snapshot.numChildren() > 0: logger.error("Cannot delete external snapshots with children") return if domain.isActive(): domain.destroy() # Update domain to snapshot state xml_tree = ET.fromstring(snapshot.getXMLDesc()) xml_domain = xml_tree.find('domain') self.conn.defineXML(ET.tostring(xml_domain)) self._delete_snapshot_files(snapshot) snapshot.delete(2) else: snapshot.delete(0) @retry() def node_send_keys(self, node, keys): """Send keys to node :type node: Node :type keys: String :rtype : None """ key_codes = scancodes.from_string(str(keys)) for key_code in key_codes: if isinstance(key_code[0], str): if key_code[0] == 'wait': sleep(1) continue self.conn.lookupByUUIDString(node.uuid).sendKey(0, 0, list(key_code), len(key_code), 0) @retry() def node_set_vcpu(self, node, vcpu): """Set VCPU :type volume: Volume :rtype : None """ domain = self.conn.lookupByUUIDString(node.uuid) domain.setVcpusFlags(vcpu, 4) domain.setVcpusFlags(vcpu, 2) @retry() def node_set_memory(self, node, memory): """Set VCPU :type volume: Volume :rtype : None """ domain = self.conn.lookupByUUIDString(node.uuid) domain.setMaxMemory(memory) domain.setMemoryFlags(memory, 2) @retry() def volume_define(self, volume, pool=None): """Define volume :type volume: Volume :type pool: String :rtype : None """ if pool is None: pool = self.storage_pool_name libvirt_volume = self.conn.storagePoolLookupByName(pool).createXML( self.xml_builder.build_volume_xml(volume), 0) volume.uuid = libvirt_volume.key() @retry() def volume_list(self, pool=None): if pool is None: pool = self.storage_pool_name return self.conn.storagePoolLookupByName(pool).listAllVolumes() @retry() def volume_allocation(self, volume): """Get volume allocation :type volume: Volume :rtype : Long """ return self.conn.storageVolLookupByKey(volume.uuid).info()[2] @retry() def volume_path(self, volume): return self.conn.storageVolLookupByKey(volume.uuid).path() def chunk_render(self, stream, size, fd): return fd.read(size) @retry(count=2) def volume_upload(self, volume, path): size = _get_file_size(path) with open(path, 'rb') as fd: stream = self.conn.newStream(0) self.conn.storageVolLookupByKey(volume.uuid).upload( stream=stream, offset=0, length=size, flags=0) stream.sendAll(self.chunk_render, fd) stream.finish() @retry() def volume_delete(self, volume): """Delete volume :type volume: Volume :rtype : None """ self.conn.storageVolLookupByKey(volume.uuid).delete(0) @retry() def volume_capacity(self, volume): """Get volume capacity :type volume: Volume :rtype : Long """ return self.conn.storageVolLookupByKey(volume.uuid).info()[1] @retry() def volume_format(self, volume): """Get volume format :type volume: Volume :rtype : String """ xml_desc = ET.fromstring( self.conn.storageVolLookupByKey(volume.uuid).XMLDesc(0)) return xml_desc.find('target/format[@type]').get('type') @retry() def get_allocated_networks(self): """Get list of allocated networks :rtype : List """ if self.allocated_networks is None: allocated_networks = [] for network_name in self.conn.listDefinedNetworks(): et = ET.fromstring( self.conn.networkLookupByName(network_name).XMLDesc(0)) ip = et.find('ip[@address]') if ip is not None: address = ip.get('address') prefix_or_netmask = ip.get('prefix') or ip.get('netmask') allocated_networks.append(ipaddr.IPNetwork( "{0:>s}/{1:>s}".format(address, prefix_or_netmask))) self.allocated_networks = allocated_networks return self.allocated_networks