1 # Copyright (c) 2019 Viosoft Corporation
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
23 from collections import OrderedDict
25 from yardstick.common import constants
26 from yardstick.common import exceptions
27 from yardstick.network_services.helpers.cpu import CpuSysCores
28 from yardstick.network_services.vnf_generic.vnf.sample_vnf import \
31 LOG = logging.getLogger(__name__)
34 class VppConfigGenerator(object):
35 VPP_LOG_FILE = '/tmp/vpe.log'
41 def add_config_item(self, config, value, path):
43 config[path[0]] = value
45 if path[0] not in config:
47 elif isinstance(config[path[0]], str):
48 config[path[0]] = {} if config[path[0]] == '' \
49 else {config[path[0]]: ''}
50 self.add_config_item(config[path[0]], value, path[1:])
52 def add_unix_log(self, value=None):
53 path = ['unix', 'log']
55 value = self.VPP_LOG_FILE
56 self.add_config_item(self._nodeconfig, value, path)
58 def add_unix_cli_listen(self, value='/run/vpp/cli.sock'):
59 path = ['unix', 'cli-listen']
60 self.add_config_item(self._nodeconfig, value, path)
62 def add_unix_nodaemon(self):
63 path = ['unix', 'nodaemon']
64 self.add_config_item(self._nodeconfig, '', path)
66 def add_unix_coredump(self):
67 path = ['unix', 'full-coredump']
68 self.add_config_item(self._nodeconfig, '', path)
70 def add_dpdk_dev(self, *devices):
71 for device in devices:
72 if VppConfigGenerator.pci_dev_check(device):
73 path = ['dpdk', 'dev {0}'.format(device)]
74 self.add_config_item(self._nodeconfig, '', path)
76 def add_dpdk_cryptodev(self, count, cryptodev):
77 for i in range(count):
78 cryptodev_config = 'dev {0}'.format(
79 re.sub(r'\d.\d$', '1.' + str(i), cryptodev))
80 path = ['dpdk', cryptodev_config]
81 self.add_config_item(self._nodeconfig, '', path)
82 self.add_dpdk_uio_driver('igb_uio')
84 def add_dpdk_sw_cryptodev(self, sw_pmd_type, socket_id, count):
85 for _ in range(count):
86 cryptodev_config = 'vdev cryptodev_{0}_pmd,socket_id={1}'. \
87 format(sw_pmd_type, str(socket_id))
88 path = ['dpdk', cryptodev_config]
89 self.add_config_item(self._nodeconfig, '', path)
91 def add_dpdk_dev_default_rxq(self, value):
92 path = ['dpdk', 'dev default', 'num-rx-queues']
93 self.add_config_item(self._nodeconfig, value, path)
95 def add_dpdk_dev_default_rxd(self, value):
96 path = ['dpdk', 'dev default', 'num-rx-desc']
97 self.add_config_item(self._nodeconfig, value, path)
99 def add_dpdk_dev_default_txd(self, value):
100 path = ['dpdk', 'dev default', 'num-tx-desc']
101 self.add_config_item(self._nodeconfig, value, path)
103 def add_dpdk_log_level(self, value):
104 path = ['dpdk', 'log-level']
105 self.add_config_item(self._nodeconfig, value, path)
107 def add_dpdk_socketmem(self, value):
108 path = ['dpdk', 'socket-mem']
109 self.add_config_item(self._nodeconfig, value, path)
111 def add_dpdk_num_mbufs(self, value):
112 path = ['dpdk', 'num-mbufs']
113 self.add_config_item(self._nodeconfig, value, path)
115 def add_dpdk_uio_driver(self, value=None):
116 path = ['dpdk', 'uio-driver']
117 self.add_config_item(self._nodeconfig, value, path)
119 def add_cpu_main_core(self, value):
120 path = ['cpu', 'main-core']
121 self.add_config_item(self._nodeconfig, value, path)
123 def add_cpu_corelist_workers(self, value):
124 path = ['cpu', 'corelist-workers']
125 self.add_config_item(self._nodeconfig, value, path)
127 def add_heapsize(self, value):
129 self.add_config_item(self._nodeconfig, value, path)
131 def add_ip6_hash_buckets(self, value):
132 path = ['ip6', 'hash-buckets']
133 self.add_config_item(self._nodeconfig, value, path)
135 def add_ip6_heap_size(self, value):
136 path = ['ip6', 'heap-size']
137 self.add_config_item(self._nodeconfig, value, path)
139 def add_ip_heap_size(self, value):
140 path = ['ip', 'heap-size']
141 self.add_config_item(self._nodeconfig, value, path)
143 def add_statseg_size(self, value):
144 path = ['statseg', 'size']
145 self.add_config_item(self._nodeconfig, value, path)
147 def add_plugin(self, state, *plugins):
148 for plugin in plugins:
149 path = ['plugins', 'plugin {0}'.format(plugin), state]
150 self.add_config_item(self._nodeconfig, ' ', path)
152 def add_dpdk_no_multi_seg(self):
153 path = ['dpdk', 'no-multi-seg']
154 self.add_config_item(self._nodeconfig, '', path)
156 def add_dpdk_no_tx_checksum_offload(self):
157 path = ['dpdk', 'no-tx-checksum-offload']
158 self.add_config_item(self._nodeconfig, '', path)
160 def dump_config(self, obj=None, level=-1):
162 obj = self._nodeconfig
163 obj = OrderedDict(sorted(obj.items()))
167 self._vpp_config += '{}{{\n'.format(level * indent)
168 if isinstance(obj, dict):
169 for key, val in obj.items():
170 if hasattr(val, '__iter__') and not isinstance(val, str):
171 self._vpp_config += '{}{}\n'.format((level + 1) * indent,
173 self.dump_config(val, level + 1)
175 self._vpp_config += '{}{} {}\n'.format(
176 (level + 1) * indent,
179 self._vpp_config += '{}}}\n'.format(level * indent)
181 return self._vpp_config
184 def pci_dev_check(pci_dev):
185 pattern = re.compile("^[0-9A-Fa-f]{4}:[0-9A-Fa-f]{2}:"
186 "[0-9A-Fa-f]{2}\\.[0-9A-Fa-f]$")
187 if not pattern.match(pci_dev):
188 raise ValueError('PCI address {addr} is not in valid format '
189 'xxxx:xx:xx.x'.format(addr=pci_dev))
193 class VppSetupEnvHelper(DpdkVnfSetupEnvHelper):
195 CFG_CONFIG = "/etc/vpp/startup.conf"
197 PIPELINE_COMMAND = ""
198 QAT_DRIVER = "qat_dh895xcc"
200 VAT_BIN_NAME = 'vpp_api_test'
202 def __init__(self, vnfd_helper, ssh_helper, scenario_helper):
203 super(VppSetupEnvHelper, self).__init__(vnfd_helper, ssh_helper,
205 self.sys_cores = CpuSysCores(self.ssh_helper)
209 self.ssh_helper.execute(
210 'service {name} stop'.format(name=self.APP_NAME))
213 'Failed to stop service {name}'.format(name=self.APP_NAME))
218 def start_vpp_service(self):
220 self.ssh_helper.execute(
221 'service {name} restart'.format(name=self.APP_NAME))
224 'Failed to start service {name}'.format(name=self.APP_NAME))
226 def _update_vnfd_helper(self, additional_data, iface_key=None):
227 for k, v in additional_data.items():
228 if iface_key is None:
229 if isinstance(v, dict) and k in self.vnfd_helper:
230 self.vnfd_helper[k].update(v)
232 self.vnfd_helper[k] = v
235 dict) and k in self.vnfd_helper.find_virtual_interface(
237 self.vnfd_helper.find_virtual_interface(ifname=iface_key)[
240 self.vnfd_helper.find_virtual_interface(ifname=iface_key)[
243 def get_value_by_interface_key(self, interface, key):
245 return self.vnfd_helper.find_virtual_interface(
246 ifname=interface).get(key)
247 except (KeyError, ValueError):
250 def crypto_device_init(self, pci_addr, numvfs):
251 # QAT device must be re-bound to kernel driver before initialization.
252 self.dpdk_bind_helper.load_dpdk_driver(self.QAT_DRIVER)
254 # Stop VPP to prevent deadlock.
257 current_driver = self.get_pci_dev_driver(pci_addr.replace(':', r'\:'))
258 if current_driver is not None:
259 self.pci_driver_unbind(pci_addr)
261 # Bind to kernel driver.
262 self.dpdk_bind_helper.bind(pci_addr, self.QAT_DRIVER.replace('qat_', ''))
264 # Initialize QAT VFs.
266 self.set_sriov_numvfs(pci_addr, numvfs)
268 def get_sriov_numvfs(self, pf_pci_addr):
269 command = 'cat /sys/bus/pci/devices/{pci}/sriov_numvfs'. \
270 format(pci=pf_pci_addr.replace(':', r'\:'))
271 _, stdout, _ = self.ssh_helper.execute(command)
275 LOG.debug('Reading sriov_numvfs info failed')
278 def set_sriov_numvfs(self, pf_pci_addr, numvfs=0):
279 command = "sh -c 'echo {num} | tee /sys/bus/pci/devices/{pci}/sriov_numvfs'". \
280 format(num=numvfs, pci=pf_pci_addr.replace(':', r'\:'))
281 self.ssh_helper.execute(command)
283 def pci_driver_unbind(self, pci_addr):
284 command = "sh -c 'echo {pci} | tee /sys/bus/pci/devices/{pcie}/driver/unbind'". \
285 format(pci=pci_addr, pcie=pci_addr.replace(':', r'\:'))
286 self.ssh_helper.execute(command)
288 def get_pci_dev_driver(self, pci_addr):
289 cmd = 'lspci -vmmks {0}'.format(pci_addr)
290 ret_code, stdout, _ = self.ssh_helper.execute(cmd)
292 raise RuntimeError("'{0}' failed".format(cmd))
293 for line in stdout.splitlines():
299 name, value = line.split("\t", 1)
301 if name == "Driver:":
303 if name == 'Driver:':
307 def vpp_create_ipsec_tunnels(self, if1_ip_addr, if2_ip_addr, if_name,
308 n_tunnels, n_connections, crypto_alg,
309 crypto_key, integ_alg, integ_key, addrs_ip,
310 spi_1=10000, spi_2=20000):
312 if n_connections <= n_tunnels:
315 count = int(n_connections / n_tunnels)
316 addr_ip_i = int(ipaddress.ip_address(str(addrs_ip)))
317 dst_start_ip = addr_ip_i
319 tmp_fd, tmp_path = tempfile.mkstemp()
321 vpp_ifname = self.get_value_by_interface_key(if_name, 'vpp_name')
322 ckey = binascii.hexlify(crypto_key.encode())
323 ikey = binascii.hexlify(integ_key.encode())
326 if crypto_alg.alg_name != 'aes-gcm-128':
327 integ = 'integ_alg {integ_alg} ' \
328 'local_integ_key {local_integ_key} ' \
329 'remote_integ_key {remote_integ_key} ' \
330 .format(integ_alg=integ_alg.alg_name,
331 local_integ_key=ikey,
332 remote_integ_key=ikey)
333 create_tunnels_cmds = 'ipsec_tunnel_if_add_del ' \
334 'local_spi {local_spi} ' \
335 'remote_spi {remote_spi} ' \
336 'crypto_alg {crypto_alg} ' \
337 'local_crypto_key {local_crypto_key} ' \
338 'remote_crypto_key {remote_crypto_key} ' \
340 'local_ip {local_ip} ' \
341 'remote_ip {remote_ip}\n'
342 start_tunnels_cmds = 'ip_add_del_route {raddr}/{mask} via {addr} ipsec{i}\n' \
343 'exec set interface unnumbered ipsec{i} use {uifc}\n' \
344 'sw_interface_set_flags ipsec{i} admin-up\n'
346 with os.fdopen(tmp_fd, 'w') as tmp_file:
347 for i in range(0, n_tunnels):
348 create_tunnel = create_tunnels_cmds.format(local_spi=spi_1 + i,
349 remote_spi=spi_2 + i,
350 crypto_alg=crypto_alg.alg_name,
351 local_crypto_key=ckey,
352 remote_crypto_key=ckey,
354 local_ip=if1_ip_addr,
355 remote_ip=if2_ip_addr)
356 tmp_file.write(create_tunnel)
357 self.execute_script(tmp_path, json_out=False, copy_on_execute=True)
360 tmp_fd, tmp_path = tempfile.mkstemp()
362 with os.fdopen(tmp_fd, 'w') as tmp_file:
363 for i in range(0, n_tunnels):
365 dst_start_ip = addr_ip_i + i * count
366 dst_end_ip = ipaddress.ip_address(dst_start_ip + count - 1)
367 ips = [ipaddress.ip_address(ip) for ip in
368 [str(ipaddress.ip_address(dst_start_ip)),
370 lowest_ip, highest_ip = min(ips), max(ips)
371 mask_length = self.get_prefix_length(int(lowest_ip),
373 lowest_ip.max_prefixlen)
374 # TODO check duplicate route for some IPs
376 dst_start_ip = addr_ip_i + i
377 start_tunnel = start_tunnels_cmds.format(
378 raddr=str(ipaddress.ip_address(dst_start_ip)),
383 tmp_file.write(start_tunnel)
384 # TODO add route for remain IPs
386 self.execute_script(tmp_path, json_out=False, copy_on_execute=True)
389 def apply_config(self, vpp_cfg, restart_vpp=True):
390 vpp_config = vpp_cfg.dump_config()
392 self.ssh_helper.execute('echo "{config}" | sudo tee {filename}'.
393 format(config=vpp_config,
394 filename=self.CFG_CONFIG))
396 raise RuntimeError('Writing config file failed')
398 self.start_vpp_service()
400 def vpp_route_add(self, network, prefix_len, gateway=None, interface=None,
401 use_sw_index=True, resolve_attempts=10,
402 count=1, vrf=None, lookup_vrf=None, multipath=False,
403 weight=None, local=False):
406 int_cmd = ('sw_if_index {}'.format(
407 self.get_value_by_interface_key(interface,
414 rap = 'resolve-attempts {}'.format(resolve_attempts) \
415 if resolve_attempts else ''
417 via = 'via {}'.format(gateway) if gateway else ''
419 cnt = 'count {}'.format(count) \
422 vrf = 'vrf {}'.format(vrf) if vrf else ''
424 lookup_vrf = 'lookup-in-vrf {}'.format(
425 lookup_vrf) if lookup_vrf else ''
427 multipath = 'multipath' if multipath else ''
429 weight = 'weight {}'.format(weight) if weight else ''
431 local = 'local' if local else ''
433 with VatTerminal(self.ssh_helper, json_param=False) as vat:
434 vat.vat_terminal_exec_cmd_from_template('add_route.vat',
436 prefix_length=prefix_len,
440 resolve_attempts=rap,
442 lookup_vrf=lookup_vrf,
447 def add_arp_on_dut(self, iface_key, ip_address, mac_address):
448 with VatTerminal(self.ssh_helper) as vat:
449 return vat.vat_terminal_exec_cmd_from_template(
450 'add_ip_neighbor.vat',
451 sw_if_index=self.get_value_by_interface_key(iface_key,
453 ip_address=ip_address, mac_address=mac_address)
455 def set_ip(self, interface, address, prefix_length):
456 with VatTerminal(self.ssh_helper) as vat:
457 return vat.vat_terminal_exec_cmd_from_template(
458 'add_ip_address.vat',
459 sw_if_index=self.get_value_by_interface_key(interface,
461 address=address, prefix_length=prefix_length)
463 def set_interface_state(self, interface, state):
464 sw_if_index = self.get_value_by_interface_key(interface,
468 state = 'admin-up link-up'
469 elif state == 'down':
470 state = 'admin-down link-down'
472 raise ValueError('Unexpected interface state: {}'.format(state))
473 with VatTerminal(self.ssh_helper) as vat:
474 return vat.vat_terminal_exec_cmd_from_template(
475 'set_if_state.vat', sw_if_index=sw_if_index, state=state)
477 def vpp_set_interface_mtu(self, interface, mtu=9200):
478 sw_if_index = self.get_value_by_interface_key(interface,
481 with VatTerminal(self.ssh_helper, json_param=False) as vat:
482 vat.vat_terminal_exec_cmd_from_template(
483 "hw_interface_set_mtu.vat", sw_if_index=sw_if_index,
486 def vpp_interfaces_ready_wait(self, timeout=30):
491 out = self.vpp_get_interface_data()
492 if time.time() - start > timeout:
493 for interface in out:
494 if interface.get('admin_up_down') == 1:
495 if interface.get('link_up_down') != 1:
496 LOG.debug('%s link-down',
497 interface.get('interface_name'))
498 raise RuntimeError('timeout, not up {0}'.format(not_ready))
500 for interface in out:
501 if interface.get('admin_up_down') == 1:
502 if interface.get('link_up_down') != 1:
503 not_ready.append(interface.get('interface_name'))
507 LOG.debug('Interfaces still in link-down state: %s, '
508 'waiting...', not_ready)
511 def vpp_get_interface_data(self, interface=None):
512 with VatTerminal(self.ssh_helper) as vat:
513 response = vat.vat_terminal_exec_cmd_from_template(
514 "interface_dump.vat")
516 if interface is not None:
517 if isinstance(interface, str):
518 param = "interface_name"
519 elif isinstance(interface, int):
520 param = "sw_if_index"
524 if data_if[param] == interface:
529 def update_vpp_interface_data(self):
531 interface_dump_json = self.execute_script_json_out(
532 "dump_interfaces.vat")
533 interface_list = json.loads(interface_dump_json)
534 for interface in self.vnfd_helper.interfaces:
535 if_mac = interface['virtual-interface']['local_mac']
536 interface_dict = VppSetupEnvHelper.get_vpp_interface_by_mac(
537 interface_list, if_mac)
538 if not interface_dict:
539 LOG.debug('Interface %s not found by MAC %s', interface,
542 data[interface['virtual-interface']['ifname']] = {
543 'vpp_name': interface_dict["interface_name"],
544 'vpp_sw_index': interface_dict["sw_if_index"]
546 for iface_key, updated_vnfd in data.items():
547 self._update_vnfd_helper(updated_vnfd, iface_key)
549 def iface_update_numa(self):
551 for interface in self.vnfd_helper.interfaces:
552 cmd = "cat /sys/bus/pci/devices/{}/numa_node".format(
553 interface["virtual-interface"]["vpci"])
554 ret, out, _ = self.ssh_helper.execute(cmd)
559 if self.vnfd_helper["cpuinfo"][-1][3] + 1 == 1:
561 interface['virtual-interface']['ifname']] = {
568 interface['virtual-interface']['ifname']] = {
569 'numa_node': numa_node
573 'Reading numa location failed for: %s',
574 interface["virtual-interface"]["vpci"])
575 for iface_key, updated_vnfd in iface_numa.items():
576 self._update_vnfd_helper(updated_vnfd, iface_key)
578 def execute_script(self, vat_name, json_out=True, copy_on_execute=False):
580 self.ssh_helper.put_file(vat_name, vat_name)
581 remote_file_path = vat_name
583 vat_path = self.ssh_helper.join_bin_path("vpp", "templates")
584 remote_file_path = '{0}/{1}'.format(vat_path, vat_name)
586 cmd = "{vat_bin} {json} in {vat_path} script".format(
587 vat_bin=self.VAT_BIN_NAME,
588 json="json" if json_out is True else "",
589 vat_path=remote_file_path)
592 return self.ssh_helper.execute(cmd=cmd)
594 raise RuntimeError("VAT script execution failed: {0}".format(cmd))
596 def execute_script_json_out(self, vat_name):
597 vat_path = self.ssh_helper.join_bin_path("vpp", "templates")
598 remote_file_path = '{0}/{1}'.format(vat_path, vat_name)
600 _, stdout, _ = self.execute_script(vat_name, json_out=True)
601 return self.cleanup_vat_json_output(stdout, vat_file=remote_file_path)
604 def cleanup_vat_json_output(json_output, vat_file=None):
606 clutter = ['vat#', 'dump_interface_table error: Misc',
607 'dump_interface_table:6019: JSON output supported only ' \
608 'for VPE API calls and dump_stats_table']
610 clutter.append("{0}(2):".format(vat_file))
611 for garbage in clutter:
612 retval = retval.replace(garbage, '')
613 return retval.strip()
616 def _convert_mac_to_number_list(mac_address):
618 for num in mac_address.split(":"):
619 list_mac.append(int(num, 16))
623 def get_vpp_interface_by_mac(interfaces_list, mac_address):
625 list_mac_address = VppSetupEnvHelper._convert_mac_to_number_list(
627 LOG.debug("MAC address %s converted to list %s.", mac_address,
629 for interface in interfaces_list:
630 # TODO: create vat json integrity checking and move there
631 if "l2_address" not in interface:
633 "key l2_address not found in interface dict."
634 "Probably input list is not parsed from correct VAT "
636 if "l2_address_length" not in interface:
638 "key l2_address_length not found in interface "
639 "dict. Probably input list is not parsed from correct "
641 mac_from_json = interface["l2_address"][:6]
642 if mac_from_json == list_mac_address:
643 if interface["l2_address_length"] != 6:
644 raise ValueError("l2_address_length value is not 6.")
645 interface_dict = interface
647 return interface_dict
650 def get_prefix_length(number1, number2, bits):
651 for i in range(bits):
652 if number1 >> i == number2 >> i:
657 class VatTerminal(object):
659 __VAT_PROMPT = ("vat# ",)
660 __LINUX_PROMPT = (":~# ", ":~$ ", "~]$ ", "~]# ")
663 def __init__(self, ssh_helper, json_param=True):
664 json_text = ' json' if json_param else ''
665 self.json = json_param
666 self.ssh_helper = ssh_helper
670 self._tty = self.ssh_helper.interactive_terminal_open()
672 raise RuntimeError("Cannot open interactive terminal")
674 for _ in range(EXEC_RETRY):
676 self.ssh_helper.interactive_terminal_exec_command(
678 'sudo -S {0}{1}'.format(VppSetupEnvHelper.VAT_BIN_NAME,
681 except exceptions.SSHTimeout:
686 self._exec_failure = False
687 self.vat_stdout = None
692 def __exit__(self, exc_type, exc_val, exc_tb):
693 self.vat_terminal_close()
695 def vat_terminal_exec_cmd(self, cmd):
697 out = self.ssh_helper.interactive_terminal_exec_command(self._tty,
700 self.vat_stdout = out
701 except exceptions.SSHTimeout:
702 self._exec_failure = True
704 "VPP is not running on node. VAT command {0} execution failed".
707 obj_start = out.find('{')
708 obj_end = out.rfind('}')
709 array_start = out.find('[')
710 array_end = out.rfind(']')
712 if obj_start == -1 and array_start == -1:
714 "VAT command {0}: no JSON data.".format(cmd))
716 if obj_start < array_start or array_start == -1:
723 json_out = json.loads(out)
728 def vat_terminal_close(self):
729 if not self._exec_failure:
731 self.ssh_helper.interactive_terminal_exec_command(self._tty,
734 except exceptions.SSHTimeout:
735 raise RuntimeError("Failed to close VAT console")
737 self.ssh_helper.interactive_terminal_close(self._tty)
739 raise RuntimeError("Cannot close interactive terminal")
741 def vat_terminal_exec_cmd_from_template(self, vat_template_file, **args):
742 file_path = os.path.join(constants.YARDSTICK_ROOT_PATH,
743 'yardstick/resources/templates/',
745 with open(file_path, 'r') as template_file:
746 cmd_template = template_file.readlines()
748 for line_tmpl in cmd_template:
749 vat_cmd = line_tmpl.format(**args)
750 ret.append(self.vat_terminal_exec_cmd(vat_cmd.replace('\n', '')))