From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from dpdk.org (dpdk.org [92.243.14.124]) by inbox.dpdk.org (Postfix) with ESMTP id 915FFA046B for ; Mon, 19 Aug 2019 09:26:02 +0200 (CEST) Received: from [92.243.14.124] (localhost [127.0.0.1]) by dpdk.org (Postfix) with ESMTP id 46E112BE1; Mon, 19 Aug 2019 09:26:02 +0200 (CEST) Received: from mga03.intel.com (mga03.intel.com [134.134.136.65]) by dpdk.org (Postfix) with ESMTP id 0947BDE3 for ; Mon, 19 Aug 2019 09:25:59 +0200 (CEST) X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from fmsmga001.fm.intel.com ([10.253.24.23]) by orsmga103.jf.intel.com with ESMTP/TLS/DHE-RSA-AES256-GCM-SHA384; 19 Aug 2019 00:25:58 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.64,403,1559545200"; d="scan'208";a="195477869" Received: from fmsmsx104.amr.corp.intel.com ([10.18.124.202]) by fmsmga001.fm.intel.com with ESMTP; 19 Aug 2019 00:25:58 -0700 Received: from shsmsx105.ccr.corp.intel.com (10.239.4.158) by fmsmsx104.amr.corp.intel.com (10.18.124.202) with Microsoft SMTP Server (TLS) id 14.3.439.0; Mon, 19 Aug 2019 00:25:57 -0700 Received: from shsmsx104.ccr.corp.intel.com ([169.254.5.112]) by SHSMSX105.ccr.corp.intel.com ([169.254.11.15]) with mapi id 14.03.0439.000; Mon, 19 Aug 2019 15:25:56 +0800 From: "Wang, Yinan" To: "Mo, YufengX" , "dts@dpdk.org" Thread-Topic: [dts][PATCH V1]framework/qemu_libvirt: add new features Thread-Index: AQHVVkp3s0kfp7MKS0GzQ8laZ2NuOKcCEgFQ Date: Mon, 19 Aug 2019 07:25:55 +0000 Message-ID: References: <1566190649-151210-1-git-send-email-yufengx.mo@intel.com> <1566190649-151210-2-git-send-email-yufengx.mo@intel.com> In-Reply-To: <1566190649-151210-2-git-send-email-yufengx.mo@intel.com> Accept-Language: en-US Content-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: x-titus-metadata-40: eyJDYXRlZ29yeUxhYmVscyI6IiIsIk1ldGFkYXRhIjp7Im5zIjoiaHR0cDpcL1wvd3d3LnRpdHVzLmNvbVwvbnNcL0ludGVsMyIsImlkIjoiYTNiZjQ0NzAtZjE0NC00ZjE1LTlmMmMtMTRhYmM2Mjc4YmIzIiwicHJvcHMiOlt7Im4iOiJDVFBDbGFzc2lmaWNhdGlvbiIsInZhbHMiOlt7InZhbHVlIjoiQ1RQX05UIn1dfV19LCJTdWJqZWN0TGFiZWxzIjpbXSwiVE1DVmVyc2lvbiI6IjE3LjEwLjE4MDQuNDkiLCJUcnVzdGVkTGFiZWxIYXNoIjoidk50Y0RFT3FnODdDTmNyNHB3cVZiV3QyRXpGd2NMMUU3SHBqMVR0NTdrMVV0a0t0ak4xS0lPWmNFckpzM3VhdiJ9 x-ctpclassification: CTP_NT dlp-product: dlpe-windows dlp-version: 11.2.0.6 dlp-reaction: no-action x-originating-ip: [10.239.127.40] Content-Type: text/plain; charset="iso-2022-jp" Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 Subject: Re: [dts] [PATCH V1]framework/qemu_libvirt: add new features X-BeenThere: dts@dpdk.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: test suite reviews and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dts-bounces@dpdk.org Sender: "dts" Acked-by: Wang, Yinan > -----Original Message----- > From: Mo, YufengX > Sent: 2019=1B$BG/=1B(B8=1B$B7n=1B(B19=1B$BF|=1B(B 12:57 > To: dts@dpdk.org; Wang, Yinan > Cc: Mo, YufengX > Subject: [dts][PATCH V1]framework/qemu_libvirt: add new features >=20 >=20 > *. support bridge setting in xml. > *. support vhost-user setting in xml. support to add driver attribute by = set option > opt_queue and opt_setting. > *. add __add_vm_net_tap method to add tap&bridge net device. > *. add __generate_net_config_script method to set default br0 using scrip= t. > *. add __write_config method to make recursion write config in xml config= file. > *. use dut logger to take the place of print. > *. remove graphics vnc config in set_vm_default method, add add_vm_vnc > method to do graphics vnc setting. This is aimed to keep the same as qemu= _kvm > method. > *. add add_vm_daemon method to keep the same as qemu_kvm method. libvirt > run virtual machine with daemon status by default. >=20 > Signed-off-by: yufengmx > --- > framework/qemu_libvirt.py | 327 > ++++++++++++++++++++++++++++++++++++++-------- > 1 file changed, 273 insertions(+), 54 deletions(-) >=20 > diff --git a/framework/qemu_libvirt.py b/framework/qemu_libvirt.py index > 66fc54b..d2edd31 100644 > --- a/framework/qemu_libvirt.py > +++ b/framework/qemu_libvirt.py > @@ -44,9 +44,25 @@ from config import VIRTCONF from exception import > StartVMFailedException import xml.etree.ElementTree as ET from > xml.etree.ElementTree import ElementTree > +from xml.dom import minidom >=20 >=20 > class LibvirtKvm(VirtBase): > + DEFAULT_BRIDGE =3D 'br0' > + QEMU_IFUP =3D "#!/bin/sh\n\n" + \ > + "set -x\n\n" + \ > + "switch=3D%(switch)s\n\n" + \ > + "if [ -n '$1' ];then\n" + \ > + " tunctl -t $1\n" + \ > + " ip link set $1 up\n" + \ > + " sleep 0.5s\n" + \ > + " brctl addif $switch $1\n" + \ > + " exit 0\n" + \ > + "else\n" + \ > + " echo 'Error: no interface specified'\n" + \ > + " exit 1\n" + \ > + "fi" > + QEMU_IFUP_PATH =3D '/etc/qemu-ifup' >=20 > def __init__(self, dut, name, suite): > # initialize virtualization base module @@ -55,6 +71,7 @@ class > LibvirtKvm(VirtBase): > # initialize qemu emulator, example: qemu-system-x86_64 > self.qemu_emulator =3D self.get_qemu_emulator() >=20 > + self.logger =3D dut.logger > # disk and pci device default index > self.diskindex =3D 'a' > self.controllerindex =3D 0 > @@ -102,7 +119,8 @@ class LibvirtKvm(VirtBase): > """ > arch =3D self.host_session.send_expect('uname -m', '# ') > if arch =3D=3D 'aarch64': > - out =3D self.host_session.send_expect('service libvirtd stat= us', "# ") > + out =3D self.host_session.send_expect( > + 'service libvirtd status', "# ") > if 'active (running)' not in out: > return False > return True > @@ -214,13 +232,12 @@ class LibvirtKvm(VirtBase): > os =3D self.domain.find('os') > if 'loader' in options.keys(): > loader =3D ET.SubElement( > - os, 'loader', {'readonly': 'yes', 'type': 'pflash'}) > + os, 'loader', {'readonly': 'yes', 'type': 'pflash'}) > loader.text =3D options['loader'] > if 'nvram' in options.keys(): > nvram =3D ET.SubElement(os, 'nvram') > nvram.text =3D options['nvram'] >=20 > - > def set_vm_default_aarch64(self): > os =3D ET.SubElement(self.domain, 'os') > type =3D ET.SubElement( > @@ -231,7 +248,7 @@ class LibvirtKvm(VirtBase): > ET.SubElement(features, 'acpi') >=20 > ET.SubElement(self.domain, 'cpu', > - {'mode': 'host-passthrough', 'check': 'none'}) > + {'mode': 'host-passthrough', 'check': 'none'}) >=20 > def set_vm_default_x86_64(self): > os =3D ET.SubElement(self.domain, 'os') @@ -252,15 +269,11 @@ cl= ass > LibvirtKvm(VirtBase): > set_default_func =3D getattr(self, 'set_vm_default_' + arch) > if callable(set_default_func): > set_default_func() > - >=20 > # qemu-kvm for emulator > device =3D ET.SubElement(self.domain, 'devices') > ET.SubElement(device, 'emulator').text =3D self.qemu_emulator >=20 > - # graphic device > - ET.SubElement(device, 'graphics', { > - 'type': 'vnc', 'port': '-1', 'autoport': 'yes'}) > # qemu guest agent > self.add_vm_qga(None) >=20 > @@ -338,15 +351,48 @@ class LibvirtKvm(VirtBase): >=20 > if 'opt_controller' in options: > controller =3D ET.SubElement(devices, 'controller', > - {'type': bus, > - 'index': hex(self.controllerindex)[2:], > - 'model': options['opt_controller']}) > + {'type': bus, > + 'index': > hex(self.controllerindex)[2:], > + 'model': > + options['opt_controller']}) > self.controllerindex +=3D 1 > - ET.SubElement(controller, 'address', > + ET.SubElement( > + controller, 'address', > {'type': 'pci', 'domain': '0x0000', 'bus': hex(self.pcii= ndex), > - 'slot': '0x00', 'function': '0x00'}) > + 'slot': '0x00', 'function': '0x00'}) > self.pciindex +=3D 1 >=20 > + def add_vm_daemon(self, **options): > + pass > + > + def add_vm_vnc(self, **options): > + """ > + Add VM display option > + """ > + disable =3D options.get('disable') > + if disable and disable =3D=3D 'True': > + return > + else: > + displayNum =3D options.get('displayNum') > + port =3D \ > + displayNum if displayNum else \ > + self.virt_pool.alloc_port(self.vm_name, port_type=3D"dis= play") > + ip =3D self.host_dut.get_ip_address() > + # set main block > + graphics =3D { > + 'type': 'vnc', > + 'port': port, > + 'autoport': 'yes', > + 'listen': ip, > + 'keymap': 'en-us', } > + > + devices =3D self.domain.find('devices') > + graphics =3D ET.SubElement(devices, 'graphics', graphics) > + # set sub block > + listen =3D { > + 'type': 'address', > + 'address': ip, } > + ET.SubElement(graphics, 'listen', listen) > + > def add_vm_serial_port(self, **options): > if 'enable' in options.keys(): > if options['enable'].lower() =3D=3D 'yes': > @@ -356,18 +402,26 @@ class LibvirtKvm(VirtBase): > else: > serial_type =3D 'unix' > if serial_type =3D=3D 'pty': > - serial =3D ET.SubElement(devices, 'serial', {'type': > serial_type}) > + serial =3D ET.SubElement( > + devices, 'serial', {'type': serial_type}) > ET.SubElement(serial, 'target', {'port': '0'}) > elif serial_type =3D=3D 'unix': > - serial =3D ET.SubElement(devices, 'serial', {'type': > serial_type}) > + serial =3D ET.SubElement( > + devices, 'serial', {'type': serial_type}) > self.serial_path =3D "/tmp/%s_serial.sock" % self.vm= _name > - ET.SubElement(serial, 'source', {'mode': 'bind', 'pa= th': > self.serial_path}) > + ET.SubElement( > + serial, > + 'source', > + {'mode': 'bind', 'path': self.serial_path}) > ET.SubElement(serial, 'target', {'port': '0'}) > else: > - print utils.RED("Serial type %s is not supported!" % > serial_type) > + msg =3D "Serial type %s is not supported!" % serial_= type > + self.logger.error(msg) > return False > - console =3D ET.SubElement(devices, 'console', {'type': > serial_type}) > - ET.SubElement(console, 'target', {'type': 'serial', 'por= t': '0'}) > + console =3D ET.SubElement( > + devices, 'console', {'type': serial_type}) > + ET.SubElement( > + console, 'target', {'type': 'serial', 'port': '0'}) >=20 > def add_vm_login(self, **options): > """ > @@ -396,67 +450,201 @@ class LibvirtKvm(VirtBase): > bus =3D m.group(1) > slot =3D m.group(2) > func =3D m.group(3) > - dom =3D '0' > + dom =3D '0' > return (bus, slot, func, dom) > m =3D re.match(pci_regex_domain, pci_address) > if m is not None: > bus =3D m.group(2) > slot =3D m.group(3) > func =3D m.group(4) > - dom =3D m.group(1) > + dom =3D m.group(1) > return (bus, slot, func, dom) > return None >=20 > def set_vm_device(self, driver=3D'pci-assign', **opts): > + opts['driver'] =3D driver > self.add_vm_device(**opts) >=20 > - def add_vm_device(self, **options): > + def __generate_net_config_script(self, switch=3DDEFAULT_BRIDGE): > """ > - options: > - pf_idx: device index of pass-through device > - guestpci: assigned pci address in vm > + Generate a script for qemu emulator to build a tap device > + between host and guest. > """ > - devices =3D self.domain.find('devices') > - hostdevice =3D ET.SubElement(devices, 'hostdev', { > - 'mode': 'subsystem', 'type': 'pci', > - 'managed': 'yes'}) > + qemu_ifup =3D self.QEMU_IFUP % {'switch': switch} > + file_name =3D os.path.basename(self.QEMU_IFUP_PATH) > + tmp_file_path =3D '/tmp/%s' % file_name > + self.host_dut.create_file(qemu_ifup, tmp_file_path) > + self.host_session.send_expect( > + 'mv -f ~/%s %s' % (file_name, self.QEMU_IFUP_PATH), '# ') > + self.host_session.send_expect( > + 'chmod +x %s' % self.QEMU_IFUP_PATH, '# ') >=20 > - if 'opt_host' in options.keys(): > - pci_addr =3D options['opt_host'] > - else: > - print utils.RED("Missing opt_host for device option!!!") > - return False > + def __parse_opt_setting(self, opt_settings): > + if '=3D' not in opt_settings: > + msg =3D 'wrong opt_settings setting' > + raise Exception(msg) > + setting =3D [item.split('=3D') for item in opt_settings.split(',= ')] > + return dict(setting) >=20 > + def __get_pci_addr_config(self, pci): > + pci =3D self.__parse_pci(pci) > + if pci is None: > + msg =3D 'Invalid guestpci for host device pass-through !!!' > + self.logger.error(msg) > + return False > + bus, slot, func, dom =3D pci > + config =3D { > + 'type': 'pci', 'domain': '0x%s' % dom, 'bus': '0x%s' % bus, > + 'slot': '0x%s' % slot, 'function': '0x%s' % func} > + return config > + > + def __write_config(self, parent, configs): > + for config in configs: > + node_name =3D config[0] > + opt =3D config[1] > + node =3D ET.SubElement(parent, node_name, opt) > + if len(config) =3D=3D 3: > + self.__write_config(node, config[2]) > + > + def __set_vm_bridge_interface(self, **options): > + mac =3D options.get('opt_mac') > + opt_br =3D options.get('opt_br') > + if not mac or not opt_br: > + msg =3D "Missing some bridge device option !!!" > + self.logger.error(msg) > + return False > + _config =3D [ > + ['mac', {'address': mac}], > + ['source', {'bridge': opt_br, }], > + ['model', {'type': 'virtio', }]] > + config =3D [['interface', {'type': 'bridge'}, _config]] > + # set xml file > + parent =3D self.domain.find('devices') > + self.__write_config(parent, config) > + > + def __add_vm_virtio_user_pci(self, **options): > + mac =3D options.get('opt_mac') > + mode =3D options.get('opt_server') or 'client' > + # unix socket path of character device > + sock_path =3D options.get('opt_path') > + queue =3D options.get('opt_queue') > + settings =3D options.get('opt_settings') > + # pci address in virtual machine > + pci =3D options.get('opt_host') > + if not mac or not sock_path: > + msg =3D "Missing some vhostuser device option !!!" > + self.logger.error(msg) > + return False > + node_name =3D 'interface' > + # basic options > + _config =3D [ > + ['mac', {'address': mac}], > + ['source', {'type': 'unix', > + 'path': sock_path, > + 'mode': mode, }], > + ['model', {'type': 'virtio', }]] > + # append pci address > + if pci: > + _config.append(['address', self.__get_pci_addr_config(pci)]) > + if queue or settings: > + drv_config =3D {'name': 'vhost'} > + if settings: > + _sub_opt =3D self.__parse_opt_setting(settings) > + drv_opt =3D {} > + guest_opt =3D {} > + host_opt =3D {} > + for key, value in _sub_opt.iteritems(): > + if key.startswith('host_'): > + host_opt[key[5:]] =3D value > + continue > + if key.startswith('guest_'): > + guest_opt[key[6:]] =3D value > + continue > + drv_opt[key] =3D value > + drv_config.update(drv_opt) > + sub_drv_config =3D [] > + if host_opt: > + sub_drv_config.append(['host', host_opt]) > + if guest_opt: > + sub_drv_config.append(['guest', guest_opt]) > + # The optional queues attribute controls the number of queue= s to > be > + # used for either Multiqueue virtio-net or vhost-user networ= k > + # interfaces. Each queue will potentially be handled by a di= fferent > + # processor, resulting in much higher throughput. virtio-net= since > + # 1.0.6 (QEMU and KVM only) vhost-user since 1.2.17(QEMU and > KVM > + # only). > + if queue: > + drv_config.update({'queues': queue, }) > + # set driver config > + if sub_drv_config: > + _config.append(['driver', drv_config, sub_drv_config]) > + else: > + _config.append(['driver', drv_config]) > + config =3D [[node_name, {'type': 'vhostuser'}, _config]] > + # set xml file > + parent =3D self.domain.find('devices') > + self.__write_config(parent, config) >=20 > + def __add_vm_pci_assign(self, **options): > + devices =3D self.domain.find('devices') > + # add hostdev config block > + config =3D { > + 'mode': 'subsystem', > + 'type': 'pci', > + 'managed': 'yes'} > + hostdevice =3D ET.SubElement(devices, 'hostdev', config) > + # add hostdev/source config block > + pci_addr =3D options.get('opt_host') > + if not pci_addr: > + msg =3D "Missing opt_host for device option!!!" > + self.logger.error(msg) > + return False > pci =3D self.__parse_pci(pci_addr) > if pci is None: > return False > bus, slot, func, dom =3D pci > - > source =3D ET.SubElement(hostdevice, 'source') > - ET.SubElement(source, 'address', { > - 'domain': '0x%s' % dom, 'bus': '0x%s' % bus, > - 'slot': '0x%s' % slot, > - 'function': '0x%s' % func}) > - if 'guestpci' in options.keys(): > - guest_pci_addr =3D options['guestpci'] > - else: > + config =3D { > + 'domain': '0x%s' % dom, > + 'bus': '0x%s' % bus, > + 'slot': '0x%s' % slot, > + 'function': '0x%s' % func} > + ET.SubElement(source, 'address', config) > + # add hostdev/source/address config block > + guest_pci_addr =3D options.get('guestpci') > + if not guest_pci_addr: > guest_pci_addr =3D '0000:%s:00.0' % hex(self.pciindex)[2:] > self.pciindex +=3D 1 > - pci =3D self.__parse_pci(guest_pci_addr) > - if pci is None: > - print utils.RED('Invalid guestpci for host device pass-throu= gh!!!') > - return False > - bus, slot, func, dom =3D pci > - ET.SubElement(hostdevice, 'address', { > - 'type': 'pci', 'domain': '0x%s' % dom, 'bus': '0x%s' % bus= , > - 'slot': '0x%s' % slot, 'function': '0x%s' % func}) > + config =3D self.__get_pci_addr_config(guest_pci_addr) > + ET.SubElement(hostdevice, 'address', config) > # save host and guest pci address mapping > pci_map =3D {} > pci_map['hostpci'] =3D pci_addr > pci_map['guestpci'] =3D guest_pci_addr > self.pci_maps.append(pci_map) >=20 > + def add_vm_device(self, **options): > + """ > + options: > + pf_idx: device index of pass-through device > + guestpci: assigned pci address in vm > + """ > + driver_table =3D { > + 'vhost-user': > + self.__add_vm_virtio_user_pci, > + 'bridge': > + self.__set_vm_bridge_interface, > + 'pci-assign': > + self.__add_vm_pci_assign, > + } > + driver =3D options.get('driver') > + if not driver or driver not in driver_table.keys(): > + driver =3D 'pci-assign' > + msg =3D 'use {0} configuration as default driver'.format(dri= ver) > + self.logger.warning(msg) > + func =3D driver_table.get(driver) > + func(**options) > + > def add_vm_net(self, **options): > """ > Options: > @@ -465,6 +653,8 @@ class LibvirtKvm(VirtBase): > if 'type' in options.keys(): > if options['type'] =3D=3D 'nic': > self.__add_vm_net_nic(**options) > + elif options['type'] =3D=3D 'tap': > + self.__add_vm_net_tap(**options) >=20 > def __add_vm_net_nic(self, **options): > """ > @@ -506,6 +696,30 @@ class LibvirtKvm(VirtBase): > ET.SubElement(qemu, 'qemu:arg', {'value': 'user,hostfwd=3D' > 'tcp:%s:%d-:22' % (dut_ip, > port)}) >=20 > + def __add_vm_net_tap(self, **options): > + """ > + type: tap > + opt_br: br0 > + note: if choosing tap, need to specify bridge name, > + else it will be br0. > + opt_script: QEMU_IFUP_PATH > + note: if not specified, default is self.QEMU_IFUP_PATH. > + """ > + _config =3D [['target', {'dev': 'tap0'}]] > + # add bridge info > + opt_br =3D options.get('opt_br') > + bridge =3D opt_br if opt_br else self.DEFAULT_BRIDGE > + _config.append(['source', {'bridge': bridge}]) > + self.__generate_net_config_script(str(bridge)) > + # add network configure script path > + opt_script =3D options.get('opt_script') > + script_path =3D opt_script if opt_script else self.QEMU_IFUP_PAT= H > + _config.append(['script', {'path': script_path}]) > + config =3D [['interface', {'type': 'bridge'}, _config]] > + # set xml file > + parent =3D self.domain.find('devices') > + self.__write_config(parent, config) > + > def add_vm_virtio_serial_channel(self, **options): > """ > Options: > @@ -516,7 +730,8 @@ class LibvirtKvm(VirtBase): > channel =3D ET.SubElement(devices, 'channel', {'type': 'unix'}) > for opt in ['path', 'name']: > if opt not in options.keys(): > - print "invalid virtio serial channel setting" > + msg =3D "invalid virtio serial channel setting" > + self.logger.error(msg) > return >=20 > ET.SubElement( > @@ -570,11 +785,15 @@ class LibvirtKvm(VirtBase): >=20 > def _start_vm(self): > xml_file =3D "/tmp/%s.xml" % self.vm_name > - try: > + if os.path.exists(xml_file): > os.remove(xml_file) > - except: > - pass > self.root.write(xml_file) > + with open(xml_file, 'rb') as fp: > + content =3D fp.read() > + doc =3D minidom.parseString(content) > + vm_content =3D doc.toprettyxml(indent=3D' ') > + with open(xml_file, 'wb') as fp: > + fp.write(vm_content) > self.host_session.copy_file_to(xml_file) > time.sleep(2) >=20 > -- > 1.9.3