Support bind driver for QAT HW cards
[yardstick.git] / yardstick / network_services / vnf_generic / vnf / ipsec_vnf.py
1 # Copyright (c) 2019 Viosoft Corporation
2 #
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
6 #
7 #      http://www.apache.org/licenses/LICENSE-2.0
8 #
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.
14
15 import logging
16 import re
17 import time
18 from collections import Counter
19 from enum import Enum
20
21 from yardstick.benchmark.contexts.base import Context
22 from yardstick.common.process import check_if_process_failed
23 from yardstick.network_services import constants
24 from yardstick.network_services.vnf_generic.vnf.sample_vnf import SampleVNF
25 from yardstick.network_services.vnf_generic.vnf.vpp_helpers import \
26     VppSetupEnvHelper, VppConfigGenerator
27
28 LOG = logging.getLogger(__name__)
29
30
31 class CryptoAlg(Enum):
32     """Encryption algorithms."""
33     AES_CBC_128 = ('aes-cbc-128', 'AES-CBC', 16)
34     AES_CBC_192 = ('aes-cbc-192', 'AES-CBC', 24)
35     AES_CBC_256 = ('aes-cbc-256', 'AES-CBC', 32)
36     AES_GCM_128 = ('aes-gcm-128', 'AES-GCM', 20)
37
38     def __init__(self, alg_name, scapy_name, key_len):
39         self.alg_name = alg_name
40         self.scapy_name = scapy_name
41         self.key_len = key_len
42
43
44 class IntegAlg(Enum):
45     """Integrity algorithms."""
46     SHA1_96 = ('sha1-96', 'HMAC-SHA1-96', 20)
47     SHA_256_128 = ('sha-256-128', 'SHA2-256-128', 32)
48     SHA_384_192 = ('sha-384-192', 'SHA2-384-192', 48)
49     SHA_512_256 = ('sha-512-256', 'SHA2-512-256', 64)
50     AES_GCM_128 = ('aes-gcm-128', 'AES-GCM', 20)
51
52     def __init__(self, alg_name, scapy_name, key_len):
53         self.alg_name = alg_name
54         self.scapy_name = scapy_name
55         self.key_len = key_len
56
57
58 class VipsecApproxSetupEnvHelper(VppSetupEnvHelper):
59     DEFAULT_IPSEC_VNF_CFG = {
60         'crypto_type': 'SW_cryptodev',
61         'rxq': 1,
62         'worker_config': '1C/1T',
63         'worker_threads': 1,
64     }
65
66     def __init__(self, vnfd_helper, ssh_helper, scenario_helper):
67         super(VipsecApproxSetupEnvHelper, self).__init__(
68             vnfd_helper, ssh_helper, scenario_helper)
69
70     def _get_crypto_type(self):
71         vnf_cfg = self.scenario_helper.options.get('vnf_config',
72                                                    self.DEFAULT_IPSEC_VNF_CFG)
73         return vnf_cfg.get('crypto_type', 'SW_cryptodev')
74
75     def _get_crypto_algorithms(self):
76         vpp_cfg = self.scenario_helper.all_options.get('vpp_config', {})
77         return vpp_cfg.get('crypto_algorithms', 'aes-gcm')
78
79     def _get_n_tunnels(self):
80         vpp_cfg = self.scenario_helper.all_options.get('vpp_config', {})
81         return vpp_cfg.get('tunnels', 1)
82
83     def _get_n_connections(self):
84         try:
85             flow_cfg = self.scenario_helper.all_options['flow']
86             return flow_cfg['count']
87         except KeyError:
88             raise KeyError("Missing flow definition in scenario section" +
89                            " of the task definition file")
90
91     def _get_flow_src_start_ip(self):
92         node_name = self.find_encrypted_data_interface()["node_name"]
93         try:
94             flow_cfg = self.scenario_helper.all_options['flow']
95             src_ips = flow_cfg['src_ip']
96             dst_ips = flow_cfg['dst_ip']
97         except KeyError:
98             raise KeyError("Missing flow definition in scenario section" +
99                            " of the task definition file")
100
101         for src, dst in zip(src_ips, dst_ips):
102             flow_src_start_ip, _ = src.split('-')
103             flow_dst_start_ip, _ = dst.split('-')
104
105             if node_name == "vnf__0":
106                 return flow_src_start_ip
107             elif node_name == "vnf__1":
108                 return flow_dst_start_ip
109
110     def _get_flow_dst_start_ip(self):
111         node_name = self.find_encrypted_data_interface()["node_name"]
112         try:
113             flow_cfg = self.scenario_helper.all_options['flow']
114             src_ips = flow_cfg['src_ip']
115             dst_ips = flow_cfg['dst_ip']
116         except KeyError:
117             raise KeyError("Missing flow definition in scenario section" +
118                            " of the task definition file")
119
120         for src, dst in zip(src_ips, dst_ips):
121             flow_src_start_ip, _ = src.split('-')
122             flow_dst_start_ip, _ = dst.split('-')
123
124             if node_name == "vnf__0":
125                 return flow_dst_start_ip
126             elif node_name == "vnf__1":
127                 return flow_src_start_ip
128
129     def build_config(self):
130         vnf_cfg = self.scenario_helper.options.get('vnf_config',
131                                                    self.DEFAULT_IPSEC_VNF_CFG)
132         rxq = vnf_cfg.get('rxq', 1)
133         phy_cores = vnf_cfg.get('worker_threads', 1)
134         # worker_config = vnf_cfg.get('worker_config', '1C/1T').split('/')[1].lower()
135
136         vpp_cfg = self.create_startup_configuration_of_vpp()
137         self.add_worker_threads_and_rxqueues(vpp_cfg, phy_cores, rxq)
138         self.add_pci_devices(vpp_cfg)
139
140         frame_size_cfg = self.scenario_helper.all_options.get('framesize', {})
141         uplink_cfg = frame_size_cfg.get('uplink', {})
142         downlink_cfg = frame_size_cfg.get('downlink', {})
143         framesize = min(self.calculate_frame_size(uplink_cfg),
144                         self.calculate_frame_size(downlink_cfg))
145         if framesize < 1522:
146             vpp_cfg.add_dpdk_no_multi_seg()
147
148         crypto_algorithms = self._get_crypto_algorithms()
149         if crypto_algorithms == 'aes-gcm':
150             self.add_dpdk_cryptodev(vpp_cfg, 'aesni_gcm', phy_cores)
151         elif crypto_algorithms == 'cbc-sha1':
152             self.add_dpdk_cryptodev(vpp_cfg, 'aesni_mb', phy_cores)
153
154         vpp_cfg.add_dpdk_dev_default_rxd(2048)
155         vpp_cfg.add_dpdk_dev_default_txd(2048)
156         self.apply_config(vpp_cfg, True)
157         self.update_vpp_interface_data()
158
159     def setup_vnf_environment(self):
160         resource = super(VipsecApproxSetupEnvHelper,
161                          self).setup_vnf_environment()
162
163         self.start_vpp_service()
164         # for QAT device DH895xCC, the number of VFs is required as 32
165         if self._get_crypto_type() == 'HW_cryptodev':
166             sriov_numvfs = self.get_sriov_numvfs(
167                 self.find_encrypted_data_interface()["vpci"])
168             if sriov_numvfs != 32:
169                 self.crypto_device_init(
170                     self.find_encrypted_data_interface()["vpci"], 32)
171
172         self._update_vnfd_helper(self.sys_cores.get_cpu_layout())
173         self.update_vpp_interface_data()
174         self.iface_update_numa()
175
176         return resource
177
178     @staticmethod
179     def calculate_frame_size(frame_cfg):
180         if not frame_cfg:
181             return 64
182
183         imix_count = {size.upper().replace('B', ''): int(weight)
184                       for size, weight in frame_cfg.items()}
185         imix_sum = sum(imix_count.values())
186         if imix_sum <= 0:
187             return 64
188         packets_total = sum([int(size) * weight
189                              for size, weight in imix_count.items()])
190         return packets_total / imix_sum
191
192     def check_status(self):
193         ipsec_created = False
194         cmd = "vppctl show int"
195         _, stdout, _ = self.ssh_helper.execute(cmd)
196         entries = re.split(r"\n+", stdout)
197         tmp = [re.split(r"\s\s+", entry, 5) for entry in entries]
198
199         for item in tmp:
200             if isinstance(item, list):
201                 if item[0] and item[0] != 'local0':
202                     if "ipsec" in item[0] and not ipsec_created:
203                         ipsec_created = True
204                     if len(item) > 2 and item[2] == 'down':
205                         return False
206         return ipsec_created
207
208     def get_vpp_statistics(self):
209         cmd = "vppctl show int {intf}"
210         result = {}
211         for interface in self.vnfd_helper.interfaces:
212             iface_name = self.get_value_by_interface_key(
213                 interface["virtual-interface"]["ifname"], "vpp_name")
214             command = cmd.format(intf=iface_name)
215             _, stdout, _ = self.ssh_helper.execute(command)
216             result.update(
217                 self.parser_vpp_stats(interface["virtual-interface"]["ifname"],
218                                       iface_name, stdout))
219         self.ssh_helper.execute("vppctl clear interfaces")
220         return result
221
222     @staticmethod
223     def parser_vpp_stats(interface, iface_name, stats):
224         packets_in = 0
225         packets_fwd = 0
226         packets_dropped = 0
227         result = {}
228
229         entries = re.split(r"\n+", stats)
230         tmp = [re.split(r"\s\s+", entry, 5) for entry in entries]
231
232         for item in tmp:
233             if isinstance(item, list):
234                 if item[0] == iface_name and len(item) >= 5:
235                     if item[3] == 'rx packets':
236                         packets_in = int(item[4])
237                     elif item[4] == 'rx packets':
238                         packets_in = int(item[5])
239                 elif len(item) == 3:
240                     if item[1] == 'tx packets':
241                         packets_fwd = int(item[2])
242                     elif item[1] == 'drops' or item[1] == 'rx-miss':
243                         packets_dropped = int(item[2])
244         if packets_dropped == 0 and packets_in > 0 and packets_fwd > 0:
245             packets_dropped = abs(packets_fwd - packets_in)
246
247         result[interface] = {
248             'packets_in': packets_in,
249             'packets_fwd': packets_fwd,
250             'packets_dropped': packets_dropped,
251         }
252
253         return result
254
255     def create_ipsec_tunnels(self):
256         self.initialize_ipsec()
257
258         # TODO generate the same key
259         crypto_algorithms = self._get_crypto_algorithms()
260         if crypto_algorithms == 'aes-gcm':
261             encr_alg = CryptoAlg.AES_GCM_128
262             auth_alg = IntegAlg.AES_GCM_128
263             encr_key = 'LNYZXMBQDKESNLREHJMS'
264             auth_key = 'SWGLDTYZSQKVBZZMPIEV'
265         elif crypto_algorithms == 'cbc-sha1':
266             encr_alg = CryptoAlg.AES_CBC_128
267             auth_alg = IntegAlg.SHA1_96
268             encr_key = 'IFEMSHYLCZIYFUTT'
269             auth_key = 'PEALEIPSCPTRHYJSDXLY'
270
271         self.execute_script("enable_dpdk_traces.vat", json_out=False)
272         self.execute_script("enable_vhost_user_traces.vat", json_out=False)
273         self.execute_script("enable_memif_traces.vat", json_out=False)
274
275         node_name = self.find_encrypted_data_interface()["node_name"]
276         n_tunnels = self._get_n_tunnels()
277         n_connections = self._get_n_connections()
278         flow_dst_start_ip = self._get_flow_dst_start_ip()
279         if node_name == "vnf__0":
280             self.vpp_create_ipsec_tunnels(
281                 self.find_encrypted_data_interface()["local_ip"],
282                 self.find_encrypted_data_interface()["peer_intf"]["local_ip"],
283                 self.find_encrypted_data_interface()["ifname"],
284                 n_tunnels, n_connections, encr_alg, encr_key, auth_alg,
285                 auth_key, flow_dst_start_ip)
286         elif node_name == "vnf__1":
287             self.vpp_create_ipsec_tunnels(
288                 self.find_encrypted_data_interface()["local_ip"],
289                 self.find_encrypted_data_interface()["peer_intf"]["local_ip"],
290                 self.find_encrypted_data_interface()["ifname"],
291                 n_tunnels, n_connections, encr_alg, encr_key, auth_alg,
292                 auth_key, flow_dst_start_ip, 20000, 10000)
293
294     def find_raw_data_interface(self):
295         try:
296             return self.vnfd_helper.find_virtual_interface(vld_id="uplink_0")
297         except KeyError:
298             return self.vnfd_helper.find_virtual_interface(vld_id="downlink_0")
299
300     def find_encrypted_data_interface(self):
301         return self.vnfd_helper.find_virtual_interface(vld_id="ciphertext")
302
303     def create_startup_configuration_of_vpp(self):
304         vpp_config_generator = VppConfigGenerator()
305         vpp_config_generator.add_unix_log()
306         vpp_config_generator.add_unix_cli_listen()
307         vpp_config_generator.add_unix_nodaemon()
308         vpp_config_generator.add_unix_coredump()
309         vpp_config_generator.add_dpdk_socketmem('1024,1024')
310         vpp_config_generator.add_dpdk_no_tx_checksum_offload()
311         vpp_config_generator.add_dpdk_log_level('debug')
312         for interface in self.vnfd_helper.interfaces:
313             vpp_config_generator.add_dpdk_uio_driver(
314                 interface["virtual-interface"]["driver"])
315         vpp_config_generator.add_heapsize('4G')
316         # TODO Enable configuration depend on VPP version
317         vpp_config_generator.add_statseg_size('4G')
318         vpp_config_generator.add_plugin('disable', ['default'])
319         vpp_config_generator.add_plugin('enable', ['dpdk_plugin.so'])
320         vpp_config_generator.add_ip6_hash_buckets('2000000')
321         vpp_config_generator.add_ip6_heap_size('4G')
322         vpp_config_generator.add_ip_heap_size('4G')
323         return vpp_config_generator
324
325     def add_worker_threads_and_rxqueues(self, vpp_cfg, phy_cores,
326                                         rx_queues=None):
327         thr_count_int = phy_cores
328         cpu_count_int = phy_cores
329         num_mbufs_int = 32768
330
331         numa_list = []
332
333         if_list = [self.find_encrypted_data_interface()["ifname"],
334                    self.find_raw_data_interface()["ifname"]]
335         for if_key in if_list:
336             try:
337                 numa_list.append(
338                     self.get_value_by_interface_key(if_key, 'numa_node'))
339             except KeyError:
340                 pass
341         numa_cnt_mc = Counter(numa_list).most_common()
342
343         if numa_cnt_mc and numa_cnt_mc[0][0] is not None and \
344                 numa_cnt_mc[0][0] != -1:
345             numa = numa_cnt_mc[0][0]
346         elif len(numa_cnt_mc) > 1 and numa_cnt_mc[0][0] == -1:
347             numa = numa_cnt_mc[1][0]
348         else:
349             numa = 0
350
351         try:
352             smt_used = self.sys_cores.is_smt_enabled()
353         except KeyError:
354             smt_used = False
355
356         cpu_main = self.sys_cores.cpu_list_per_node_str(numa, skip_cnt=1,
357                                                         cpu_cnt=1)
358         cpu_wt = self.sys_cores.cpu_list_per_node_str(numa, skip_cnt=2,
359                                                       cpu_cnt=cpu_count_int,
360                                                       smt_used=smt_used)
361
362         if smt_used:
363             thr_count_int = 2 * cpu_count_int
364
365         if rx_queues is None:
366             rxq_count_int = int(thr_count_int / 2)
367         else:
368             rxq_count_int = rx_queues
369
370         if rxq_count_int == 0:
371             rxq_count_int = 1
372
373         num_mbufs_int = num_mbufs_int * rxq_count_int
374
375         vpp_cfg.add_cpu_main_core(cpu_main)
376         vpp_cfg.add_cpu_corelist_workers(cpu_wt)
377         vpp_cfg.add_dpdk_dev_default_rxq(rxq_count_int)
378         vpp_cfg.add_dpdk_num_mbufs(num_mbufs_int)
379
380     def add_pci_devices(self, vpp_cfg):
381         pci_devs = [self.find_encrypted_data_interface()["vpci"],
382                     self.find_raw_data_interface()["vpci"]]
383         vpp_cfg.add_dpdk_dev(*pci_devs)
384
385     def add_dpdk_cryptodev(self, vpp_cfg, sw_pmd_type, count):
386         crypto_type = self._get_crypto_type()
387         smt_used = self.sys_cores.is_smt_enabled()
388         cryptodev = self.find_encrypted_data_interface()["vpci"]
389         socket_id = self.get_value_by_interface_key(
390             self.find_encrypted_data_interface()["ifname"], "numa_node")
391
392         if smt_used:
393             thr_count_int = count * 2
394             if crypto_type == 'HW_cryptodev':
395                 vpp_cfg.add_dpdk_cryptodev(thr_count_int, cryptodev)
396             else:
397                 vpp_cfg.add_dpdk_sw_cryptodev(sw_pmd_type, socket_id,
398                                               thr_count_int)
399         else:
400             thr_count_int = count
401             if crypto_type == 'HW_cryptodev':
402                 vpp_cfg.add_dpdk_cryptodev(thr_count_int, cryptodev)
403             else:
404                 vpp_cfg.add_dpdk_sw_cryptodev(sw_pmd_type, socket_id,
405                                               thr_count_int)
406
407     def initialize_ipsec(self):
408         flow_src_start_ip = self._get_flow_src_start_ip()
409
410         self.set_interface_state(
411             self.find_encrypted_data_interface()["ifname"], 'up')
412         self.set_interface_state(self.find_raw_data_interface()["ifname"],
413                                  'up')
414         self.vpp_interfaces_ready_wait()
415         self.vpp_set_interface_mtu(
416             self.find_encrypted_data_interface()["ifname"])
417         self.vpp_set_interface_mtu(self.find_raw_data_interface()["ifname"])
418         self.vpp_interfaces_ready_wait()
419
420         self.set_ip(self.find_encrypted_data_interface()["ifname"],
421                     self.find_encrypted_data_interface()["local_ip"], 24)
422         self.set_ip(self.find_raw_data_interface()["ifname"],
423                     self.find_raw_data_interface()["local_ip"],
424                     24)
425
426         self.add_arp_on_dut(self.find_encrypted_data_interface()["ifname"],
427                             self.find_encrypted_data_interface()["peer_intf"][
428                                 "local_ip"],
429                             self.find_encrypted_data_interface()["peer_intf"][
430                                 "local_mac"])
431         self.add_arp_on_dut(self.find_raw_data_interface()["ifname"],
432                             self.find_raw_data_interface()["peer_intf"][
433                                 "local_ip"],
434                             self.find_raw_data_interface()["peer_intf"][
435                                 "local_mac"])
436
437         self.vpp_route_add(flow_src_start_ip, 8,
438                            self.find_raw_data_interface()["peer_intf"][
439                                "local_ip"],
440                            self.find_raw_data_interface()["ifname"])
441
442
443 class VipsecApproxVnf(SampleVNF):
444     """ This class handles vIPSEC VNF model-driver definitions """
445
446     APP_NAME = 'vIPSEC'
447     APP_WORD = 'vipsec'
448     WAIT_TIME = 20
449
450     def __init__(self, name, vnfd, setup_env_helper_type=None,
451                  resource_helper_type=None):
452         if setup_env_helper_type is None:
453             setup_env_helper_type = VipsecApproxSetupEnvHelper
454         super(VipsecApproxVnf, self).__init__(
455             name, vnfd, setup_env_helper_type,
456             resource_helper_type)
457
458     def _run(self):
459         # we can't share ssh paramiko objects to force new connection
460         self.ssh_helper.drop_connection()
461         # kill before starting
462         self.setup_helper.kill_vnf()
463         self._build_config()
464         self.setup_helper.create_ipsec_tunnels()
465
466     def wait_for_instantiate(self):
467         time.sleep(self.WAIT_TIME)
468         while True:
469             status = self.setup_helper.check_status()
470             if not self._vnf_process.is_alive() and not status:
471                 raise RuntimeError("%s VNF process died." % self.APP_NAME)
472             LOG.info("Waiting for %s VNF to start.. ", self.APP_NAME)
473             time.sleep(self.WAIT_TIME_FOR_SCRIPT)
474             status = self.setup_helper.check_status()
475             if status:
476                 LOG.info("%s VNF is up and running.", self.APP_NAME)
477                 self._vnf_up_post()
478                 return self._vnf_process.exitcode
479
480     def terminate(self):
481         self.setup_helper.kill_vnf()
482         self._tear_down()
483         self.resource_helper.stop_collect()
484         if self._vnf_process is not None:
485             # be proper and join first before we kill
486             LOG.debug("joining before terminate %s", self._vnf_process.name)
487             self._vnf_process.join(constants.PROCESS_JOIN_TIMEOUT)
488             self._vnf_process.terminate()
489
490     def collect_kpi(self):
491         # we can't get KPIs if the VNF is down
492         check_if_process_failed(self._vnf_process, 0.01)
493         physical_node = Context.get_physical_node_from_server(
494             self.scenario_helper.nodes[self.name])
495         result = {"physical_node": physical_node}
496         result["collect_stats"] = self.setup_helper.get_vpp_statistics()
497         LOG.debug("%s collect KPIs %s", self.APP_NAME, result)
498         return result