Merge "cachestat: use raw strings to escape \d"
[yardstick.git] / yardstick / network_services / vnf_generic / vnf / vpe_vnf.py
1 # Copyright (c) 2016-2017 Intel 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 """ vPE (Power Edge router) VNF model definitions based on IETS Spec """
15
16 from __future__ import absolute_import
17 from __future__ import print_function
18 import tempfile
19 import time
20 import os
21 import logging
22 import re
23 from multiprocessing import Queue
24 import multiprocessing
25 import ipaddress
26 import six
27
28 from yardstick import ssh
29 from yardstick.network_services.vnf_generic.vnf.base import GenericVNF
30 from yardstick.network_services.utils import provision_tool
31 from yardstick.network_services.vnf_generic.vnf.base import QueueFileWrapper
32 from yardstick.network_services.nfvi.resource import ResourceProfile
33
34 LOG = logging.getLogger(__name__)
35 VPE_PIPELINE_COMMAND = '{tool_path} -p 0x3 -f {cfg_file} -s {script}'
36 CORES = ['0', '1', '2']
37 WAIT_TIME = 20
38
39
40 class VpeApproxVnf(GenericVNF):
41     """ This class handles vPE VNF model-driver definitions """
42
43     def __init__(self, vnfd):
44         super(VpeApproxVnf, self).__init__(vnfd)
45         self.socket = None
46         self.q_in = Queue()
47         self.q_out = Queue()
48         self.vnf_cfg = None
49         self._vnf_process = None
50         self.connection = None
51         self.resource = None
52
53     def _resource_collect_start(self):
54         self.resource.initiate_systemagent(self.bin_path)
55         self.resource.start()
56
57     def _resource_collect_stop(self):
58         self.resource.stop()
59
60     def _collect_resource_kpi(self):
61         result = {}
62
63         status = self.resource.check_if_sa_running("collectd")[0]
64         if status:
65             result = self.resource.amqp_collect_nfvi_kpi()
66
67         result = {"core": result}
68
69         return result
70
71     @classmethod
72     def __setup_hugepages(cls, connection):
73         hugepages = \
74             connection.execute(
75                 "awk '/Hugepagesize/ { print $2$3 }' < /proc/meminfo")[1]
76         hugepages = hugepages.rstrip()
77
78         memory_path = \
79             '/sys/kernel/mm/hugepages/hugepages-%s/nr_hugepages' % hugepages
80         connection.execute("awk -F: '{ print $1 }' < %s" % memory_path)
81
82         pages = 16384 if hugepages.rstrip() == "2048kB" else 16
83         connection.execute("echo %s > %s" % (pages, memory_path))
84
85     def setup_vnf_environment(self, connection):
86         ''' setup dpdk environment needed for vnf to run '''
87
88         self.__setup_hugepages(connection)
89         connection.execute("modprobe uio && modprobe igb_uio")
90
91         exit_status = connection.execute("lsmod | grep -i igb_uio")[0]
92         if exit_status == 0:
93             return
94
95         dpdk = os.path.join(self.bin_path, "dpdk-16.07")
96         dpdk_setup = \
97             provision_tool(self.connection,
98                            os.path.join(self.bin_path, "nsb_setup.sh"))
99         status = connection.execute("ls {} >/dev/null 2>&1".format(dpdk))[0]
100         if status:
101             connection.execute("bash %s dpdk >/dev/null 2>&1" % dpdk_setup)
102
103     def _get_cpu_sibling_list(self):
104         cpu_topo = []
105         for core in CORES:
106             sys_cmd = \
107                 "/sys/devices/system/cpu/cpu%s/topology/thread_siblings_list" \
108                 % core
109             cpuid = \
110                 self.connection.execute("awk -F: '{ print $1 }' < %s" %
111                                         sys_cmd)[1]
112             cpu_topo += \
113                 [(idx) if idx.isdigit() else idx for idx in cpuid.split(',')]
114
115         return [cpu.strip() for cpu in cpu_topo]
116
117     def scale(self, flavor=""):
118         ''' scale vnfbased on flavor input '''
119         super(VpeApproxVnf, self).scale(flavor)
120
121     def instantiate(self, scenario_cfg, context_cfg):
122         vnf_cfg = scenario_cfg['vnf_options']['vpe']['cfg']
123
124         mgmt_interface = self.vnfd["mgmt-interface"]
125         self.connection = ssh.SSH.from_node(mgmt_interface)
126
127         self.tc_file_name = '{0}.yaml'.format(scenario_cfg['tc'])
128
129         self.setup_vnf_environment(self.connection)
130
131         cores = self._get_cpu_sibling_list()
132         self.resource = ResourceProfile(self.vnfd, cores)
133
134         self.connection.execute("pkill vPE_vnf")
135         dpdk_nic_bind = \
136             provision_tool(self.connection,
137                            os.path.join(self.bin_path, "dpdk_nic_bind.py"))
138
139         interfaces = self.vnfd["vdu"][0]['external-interface']
140         self.socket = \
141             next((0 for v in interfaces
142                   if v['virtual-interface']["vpci"][5] == "0"), 1)
143
144         bound_pci = [v['virtual-interface']["vpci"] for v in interfaces]
145         for vpci in bound_pci:
146             self.connection.execute(
147                 "%s --force -b igb_uio %s" % (dpdk_nic_bind, vpci))
148         queue_wrapper = \
149             QueueFileWrapper(self.q_in, self.q_out, "pipeline>")
150         self._vnf_process = multiprocessing.Process(target=self._run_vpe,
151                                                     args=(queue_wrapper,
152                                                           vnf_cfg,))
153         self._vnf_process.start()
154         buf = []
155         time.sleep(WAIT_TIME)  # Give some time for config to load
156         while True:
157             message = ''
158             while self.q_out.qsize() > 0:
159                 buf.append(self.q_out.get())
160                 message = ''.join(buf)
161                 if "pipeline>" in message:
162                     LOG.info("VPE VNF is up and running.")
163                     queue_wrapper.clear()
164                     self._resource_collect_start()
165                     return self._vnf_process.exitcode
166                 if "PANIC" in message:
167                     raise RuntimeError("Error starting vPE VNF.")
168
169             LOG.info("Waiting for VNF to start.. ")
170             time.sleep(3)
171             if not self._vnf_process.is_alive():
172                 raise RuntimeError("vPE VNF process died.")
173
174     def _get_ports_gateway(self, name):
175         if 'routing_table' in self.vnfd['vdu'][0]:
176             routing_table = self.vnfd['vdu'][0]['routing_table']
177
178             for route in routing_table:
179                 if name == route['if']:
180                     return route['gateway']
181
182     def terminate(self):
183         self.execute_command("quit")
184         if self._vnf_process:
185             self._vnf_process.terminate()
186
187     def _run_vpe(self, filewrapper, vnf_cfg):
188         mgmt_interface = self.vnfd["mgmt-interface"]
189
190         self.connection = ssh.SSH.from_node(mgmt_interface)
191         self.connection.wait()
192
193         interfaces = self.vnfd["vdu"][0]['external-interface']
194         port0_ip = ipaddress.ip_interface(six.text_type(
195             "%s/%s" % (interfaces[0]["virtual-interface"]["local_ip"],
196                        interfaces[0]["virtual-interface"]["netmask"])))
197         port1_ip = ipaddress.ip_interface(six.text_type(
198             "%s/%s" % (interfaces[1]["virtual-interface"]["local_ip"],
199                        interfaces[1]["virtual-interface"]["netmask"])))
200         dst_port0_ip = ipaddress.ip_interface(
201             u"%s/%s" % (interfaces[0]["virtual-interface"]["dst_ip"],
202                         interfaces[0]["virtual-interface"]["netmask"]))
203         dst_port1_ip = ipaddress.ip_interface(
204             u"%s/%s" % (interfaces[1]["virtual-interface"]["dst_ip"],
205                         interfaces[1]["virtual-interface"]["netmask"]))
206
207         vpe_vars = {"port0_local_ip": port0_ip.ip.exploded,
208                     "port0_dst_ip": dst_port0_ip.ip.exploded,
209                     "port0_local_ip_hex":
210                     self._ip_to_hex(port0_ip.ip.exploded),
211                     "port0_prefixlen": port0_ip.network.prefixlen,
212                     "port0_netmask": port0_ip.network.netmask.exploded,
213                     "port0_netmask_hex":
214                     self._ip_to_hex(port0_ip.network.netmask.exploded),
215                     "port0_local_mac":
216                     interfaces[0]["virtual-interface"]["local_mac"],
217                     "port0_dst_mac":
218                     interfaces[0]["virtual-interface"]["dst_mac"],
219                     "port0_gateway":
220                     self._get_ports_gateway(interfaces[0]["name"]),
221                     "port0_local_network":
222                     port0_ip.network.network_address.exploded,
223                     "port0_prefix": port0_ip.network.prefixlen,
224                     "port1_local_ip": port1_ip.ip.exploded,
225                     "port1_dst_ip": dst_port1_ip.ip.exploded,
226                     "port1_local_ip_hex":
227                     self._ip_to_hex(port1_ip.ip.exploded),
228                     "port1_prefixlen": port1_ip.network.prefixlen,
229                     "port1_netmask": port1_ip.network.netmask.exploded,
230                     "port1_netmask_hex":
231                     self._ip_to_hex(port1_ip.network.netmask.exploded),
232                     "port1_local_mac":
233                     interfaces[1]["virtual-interface"]["local_mac"],
234                     "port1_dst_mac":
235                     interfaces[1]["virtual-interface"]["dst_mac"],
236                     "port1_gateway":
237                     self._get_ports_gateway(interfaces[1]["name"]),
238                     "port1_local_network":
239                     port1_ip.network.network_address.exploded,
240                     "port1_prefix": port1_ip.network.prefixlen,
241                     "port0_local_ip6": self._get_port0localip6(),
242                     "port1_local_ip6": self._get_port1localip6(),
243                     "port0_prefixlen6": self._get_port0prefixlen6(),
244                     "port1_prefixlen6": self._get_port1prefixlen6(),
245                     "port0_gateway6": self._get_port0gateway6(),
246                     "port1_gateway6": self._get_port1gateway6(),
247                     "port0_dst_ip_hex6": self._get_port0localip6(),
248                     "port1_dst_ip_hex6": self._get_port1localip6(),
249                     "port0_dst_netmask_hex6": self._get_port0prefixlen6(),
250                     "port1_dst_netmask_hex6": self._get_port1prefixlen6(),
251                     "bin_path": self.bin_path,
252                     "socket": self.socket}
253
254         for cfg in os.listdir(vnf_cfg):
255             vpe_config = ""
256             with open(os.path.join(vnf_cfg, cfg), 'r') as vpe_cfg:
257                 vpe_config = vpe_cfg.read()
258
259             self._provide_config_file(cfg, vpe_config, vpe_vars)
260
261         LOG.info("Provision and start the vPE")
262         tool_path = provision_tool(self.connection,
263                                    os.path.join(self.bin_path, "vPE_vnf"))
264         cmd = VPE_PIPELINE_COMMAND.format(cfg_file="/tmp/vpe_config",
265                                           script="/tmp/vpe_script",
266                                           tool_path=tool_path)
267         self.connection.run(cmd, stdin=filewrapper, stdout=filewrapper,
268                             keep_stdin_open=True, pty=True)
269
270     def _provide_config_file(self, prefix, template, args):
271         cfg, cfg_content = tempfile.mkstemp()
272         cfg = os.fdopen(cfg, "w+")
273         cfg.write(template.format(**args))
274         cfg.close()
275         cfg_file = "/tmp/%s" % prefix
276         self.connection.put(cfg_content, cfg_file)
277         return cfg_file
278
279     def execute_command(self, cmd):
280         ''' send cmd to vnf process '''
281         LOG.info("VPE command: %s", cmd)
282         output = []
283         if self.q_in:
284             self.q_in.put(cmd + "\r\n")
285             time.sleep(3)
286             while self.q_out.qsize() > 0:
287                 output.append(self.q_out.get())
288         return "".join(output)
289
290     def collect_kpi(self):
291         result = self.get_stats_vpe()
292         collect_stats = self._collect_resource_kpi()
293         result["collect_stats"] = collect_stats
294         LOG.debug("vPE collet Kpis: %s", result)
295         return result
296
297     def get_stats_vpe(self):
298         ''' get vpe statistics '''
299         result = {'pkt_in_up_stream': 0, 'pkt_drop_up_stream': 0,
300                   'pkt_in_down_stream': 0, 'pkt_drop_down_stream': 0}
301         up_stat_commands = ['p 5 stats port in 0', 'p 5 stats port out 0',
302                             'p 5 stats port out 1']
303         down_stat_commands = ['p 9 stats port in 0', 'p 9 stats port out 0']
304         pattern = \
305             "Pkts in:\\s(\\d+)\\r\\n\\tPkts dropped by " \
306             "AH:\\s(\\d+)\\r\\n\\tPkts dropped by other:\\s(\\d+)"
307
308         for cmd in up_stat_commands:
309             stats = self.execute_command(cmd)
310             match = re.search(pattern, stats, re.MULTILINE)
311             if match:
312                 result["pkt_in_up_stream"] = \
313                     result.get("pkt_in_up_stream", 0) + int(match.group(1))
314                 result["pkt_drop_up_stream"] = \
315                     result.get("pkt_drop_up_stream", 0) + \
316                     int(match.group(2)) + int(match.group(3))
317
318         for cmd in down_stat_commands:
319             stats = self.execute_command(cmd)
320             match = re.search(pattern, stats, re.MULTILINE)
321             if match:
322                 result["pkt_in_down_stream"] = \
323                     result.get("pkt_in_down_stream", 0) + int(match.group(1))
324                 result["pkt_drop_down_stream"] = \
325                     result.get("pkt_drop_down_stream", 0) + \
326                     int(match.group(2)) + int(match.group(3))
327         return result