+ :param node: Node to run QEMU on.
+ :param qemu_id: QEMU identifier.
+ :param smp: Number of virtual SMP units (cores).
+ :param mem: Amount of memory.
+ :param vnf: Network function workload.
+ :param img: QEMU disk image or kernel image path.
+ :param bin_path: QEMU binary path.
+ :type node: dict
+ :type qemu_id: int
+ :type smp: int
+ :type mem: int
+ :type vnf: str
+ :type img: str
+ :type bin_path: str
+ """
+ self._vhost_id = 0
+ self._node = node
+ self._vm_info = {
+ 'host': node['host'],
+ 'type': NodeType.VM,
+ 'port': 10021 + qemu_id,
+ 'serial': 4555 + qemu_id,
+ 'username': 'cisco',
+ 'password': 'cisco',
+ 'interfaces': {},
+ }
+ if node['port'] != 22:
+ self._vm_info['host_port'] = node['port']
+ self._vm_info['host_username'] = node['username']
+ self._vm_info['host_password'] = node['password']
+ # Input Options.
+ self._opt = dict()
+ self._opt['qemu_id'] = qemu_id
+ self._opt['bin_path'] = bin_path
+ self._opt['mem'] = int(mem)
+ self._opt['smp'] = int(smp)
+ self._opt['img'] = img
+ self._opt['vnf'] = vnf
+ # Temporary files.
+ self._temp = dict()
+ self._temp['pidfile'] = '/var/run/qemu_{id}.pid'.format(id=qemu_id)
+ if '/var/lib/vm/' in img:
+ self._opt['vm_type'] = 'nestedvm'
+ self._temp['qmp'] = '/var/run/qmp_{id}.sock'.format(id=qemu_id)
+ self._temp['qga'] = '/var/run/qga_{id}.sock'.format(id=qemu_id)
+ elif '/opt/boot/vmlinuz' in img:
+ self._opt['vm_type'] = 'kernelvm'
+ self._temp['log'] = '/tmp/serial_{id}.log'.format(id=qemu_id)
+ self._temp['ini'] = '/etc/vm_init_{id}.conf'.format(id=qemu_id)
+ else:
+ raise RuntimeError('QEMU: Unknown VM image option!')
+ # Computed parameters for QEMU command line.
+ self._params = QemuOptions()
+ self.add_params()
+
+ def add_params(self):
+ """Set QEMU command line parameters."""
+ self.add_default_params()
+ if self._opt.get('vm_type', '') == 'nestedvm':
+ self.add_nestedvm_params()
+ elif self._opt.get('vm_type', '') == 'kernelvm':
+ self.add_kernelvm_params()
+ else:
+ raise RuntimeError('QEMU: Unsupported VM type!')
+
+ def add_default_params(self):
+ """Set default QEMU command line parameters."""
+ self._params.add('daemonize', '')
+ self._params.add('nodefaults', '')
+ self._params.add('name', 'vnf{qemu},debug-threads=on'.
+ format(qemu=self._opt.get('qemu_id')))
+ self._params.add('no-user-config', '')
+ self._params.add('monitor', 'none')
+ self._params.add('display', 'none')
+ self._params.add('vga', 'none')
+ self._params.add('enable-kvm', '')
+ self._params.add('pidfile', '{pidfile}'.
+ format(pidfile=self._temp.get('pidfile')))
+ self._params.add('cpu', 'host')
+ self._params.add('machine', 'pc,accel=kvm,usb=off,mem-merge=off')
+ self._params.add('smp', '{smp},sockets=1,cores={smp},threads=1'.
+ format(smp=self._opt.get('smp')))
+ self._params.add('object',
+ 'memory-backend-file,id=mem,size={mem}M,'
+ 'mem-path=/dev/hugepages,share=on'.
+ format(mem=self._opt.get('mem')))
+ self._params.add('m', '{mem}M'.
+ format(mem=self._opt.get('mem')))
+ self._params.add('numa', 'node,memdev=mem')
+ self._params.add('balloon', 'none')
+
+ def add_nestedvm_params(self):
+ """Set NestedVM QEMU parameters."""
+ self._params.add('net', 'nic,macaddr=52:54:00:00:{qemu:02x}:ff'.
+ format(qemu=self._opt.get('qemu_id')))
+ self._params.add('net', 'user,hostfwd=tcp::{info[port]}-:22'.
+ format(info=self._vm_info))
+ # TODO: Remove try except after fully migrated to Bionic or
+ # qemu_set_node is removed.
+ try:
+ locking = ',file.locking=off'\
+ if self.qemu_version(version='2.10') else ''
+ except AttributeError:
+ locking = ''
+ self._params.add('drive',
+ 'file={img},format=raw,cache=none,if=virtio{locking}'.
+ format(img=self._opt.get('img'), locking=locking))
+ self._params.add('qmp', 'unix:{qmp},server,nowait'.
+ format(qmp=self._temp.get('qmp')))
+ self._params.add('chardev', 'socket,host=127.0.0.1,port={info[serial]},'
+ 'id=gnc0,server,nowait'.format(info=self._vm_info))
+ self._params.add('device', 'isa-serial,chardev=gnc0')
+ self._params.add('chardev',
+ 'socket,path={qga},server,nowait,id=qga0'.
+ format(qga=self._temp.get('qga')))
+ self._params.add('device', 'isa-serial,chardev=qga0')
+
+ def add_kernelvm_params(self):
+ """Set KernelVM QEMU parameters."""
+ self._params.add('chardev', 'file,id=char0,path={log}'.
+ format(log=self._temp.get('log')))
+ self._params.add('device', 'isa-serial,chardev=char0')
+ self._params.add('fsdev', 'local,id=root9p,path=/,security_model=none')
+ self._params.add('device',
+ 'virtio-9p-pci,fsdev=root9p,mount_tag=/dev/root')
+ self._params.add('kernel', '$(readlink -m {img}* | tail -1)'.
+ format(img=self._opt.get('img')))
+ self._params.add('append',
+ '"ro rootfstype=9p rootflags=trans=virtio '
+ 'console=ttyS0 tsc=reliable hugepages=256 '
+ 'init={init}"'.format(init=self._temp.get('ini')))
+
+ def create_kernelvm_config_vpp(self, **kwargs):
+ """Create QEMU VPP config files.
+
+ :param kwargs: Key-value pairs to replace content of VPP configuration
+ file.
+ :type kwargs: dict
+ """
+ startup = ('/etc/vpp/vm_startup_{id}.conf'.
+ format(id=self._opt.get('qemu_id')))
+ running = ('/etc/vpp/vm_running_{id}.exec'.
+ format(id=self._opt.get('qemu_id')))
+
+ self._temp['startup'] = startup
+ self._temp['running'] = running
+ self._opt['vnf_bin'] = ('/usr/bin/vpp -c {startup}'.
+ format(startup=startup))
+
+ # Create VPP startup configuration.
+ vpp_config = VppConfigGenerator()
+ vpp_config.set_node(self._node)
+ vpp_config.add_unix_nodaemon()
+ vpp_config.add_unix_cli_listen()
+ vpp_config.add_unix_exec(running)
+ vpp_config.add_cpu_main_core('0')
+ vpp_config.add_cpu_corelist_workers('1-{smp}'.
+ format(smp=self._opt.get('smp')-1))
+ vpp_config.add_dpdk_dev('0000:00:06.0', '0000:00:07.0')
+ vpp_config.add_dpdk_log_level('debug')
+ vpp_config.add_dpdk_no_tx_checksum_offload()
+ vpp_config.add_dpdk_no_multi_seg()
+ vpp_config.add_plugin('disable', 'default')
+ vpp_config.add_plugin('enable', 'dpdk_plugin.so')
+ vpp_config.apply_config(startup, restart_vpp=False)
+
+ # Create VPP running configuration.
+ template = '{res}/{tpl}.exec'.format(res=Constants.RESOURCES_TPL_VM,
+ tpl=self._opt.get('vnf'))
+ exec_cmd_no_error(self._node, 'rm -f {running}'.format(running=running),
+ sudo=True)
+
+ with open(template, 'r') as src_file:
+ src = Template(src_file.read())
+ exec_cmd_no_error(self._node, "echo '{out}' | sudo tee {running}".
+ format(out=src.safe_substitute(**kwargs),
+ running=running))
+
+ def create_kernelvm_init(self, **kwargs):
+ """Create QEMU init script.
+
+ :param kwargs: Key-value pairs to replace content of init startup file.
+ :type kwargs: dict
+ """
+ template = '{res}/init.sh'.format(res=Constants.RESOURCES_TPL_VM)
+ init = self._temp.get('ini')
+ exec_cmd_no_error(self._node, 'rm -f {init}'.format(init=init),
+ sudo=True)
+
+ with open(template, 'r') as src_file:
+ src = Template(src_file.read())
+ exec_cmd_no_error(self._node, "echo '{out}' | sudo tee {init}".
+ format(out=src.safe_substitute(**kwargs),
+ init=init))
+ exec_cmd_no_error(self._node, "chmod +x {init}".
+ format(init=init), sudo=True)
+
+ def configure_kernelvm_vnf(self, **kwargs):
+ """Create KernelVM VNF configurations.
+
+ :param kwargs: Key-value pairs for templating configs.
+ :type kwargs: dict
+ """
+ if 'vpp' in self._opt.get('vnf'):
+ self.create_kernelvm_config_vpp(**kwargs)
+ else:
+ raise RuntimeError('QEMU: Unsupported VNF!')
+ self.create_kernelvm_init(vnf_bin=self._opt.get('vnf_bin'))