Merge "Add send socket commands function"
[yardstick.git] / yardstick / network_services / vnf_generic / vnf / router_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 """ Add generic L3 forwarder implementation based on sample_vnf.py"""
15
16 from __future__ import absolute_import
17 import logging
18 import time
19 import itertools
20
21 import re
22 from netaddr import IPRange
23
24 from six.moves import zip
25
26 from yardstick.benchmark.contexts.base import Context
27 from yardstick.network_services.vnf_generic.vnf.sample_vnf import SampleVNF, \
28     DpdkVnfSetupEnvHelper
29
30 LOG = logging.getLogger(__name__)
31
32
33 class RouterVNF(SampleVNF):
34
35     WAIT_TIME = 1
36
37     def __init__(self, name, vnfd, setup_env_helper_type=None, resource_helper_type=None):
38         if setup_env_helper_type is None:
39             setup_env_helper_type = DpdkVnfSetupEnvHelper
40
41         # For heat test cases
42         vnfd['mgmt-interface'].pop("pkey", "")
43         vnfd['mgmt-interface']['password'] = 'password'
44
45         super(RouterVNF, self).__init__(name, vnfd, setup_env_helper_type, resource_helper_type)
46
47     def instantiate(self, scenario_cfg, context_cfg):
48         self.scenario_helper.scenario_cfg = scenario_cfg
49         self.context_cfg = context_cfg
50         self.configure_routes(self.name, scenario_cfg, context_cfg)
51
52     def wait_for_instantiate(self):
53         time.sleep(self.WAIT_TIME)
54
55     def _run(self):
56         # we can't share ssh paramiko objects to force new connection
57         self.ssh_helper.drop_connection()
58
59     def terminate(self):
60         self._tear_down()
61         self.resource_helper.stop_collect()
62
63     def scale(self, flavor=""):
64         pass
65
66     @staticmethod
67     def row_with_header(header, data):
68         """Returns dictionary per row of values for 'ip show stats'.
69
70         Args:
71             header(str):  output header
72             data(str):  output data
73
74         Returns:
75             dict:  dictionary per row of values for 'ip show stats'
76
77         """
78         prefix, columns = header.strip().split(':')
79         column_names = ["{0}:{1}".format(prefix, h) for h in columns.split()]
80         return dict(list(zip(column_names, data.strip().split())))
81
82     RX_TX_RE = re.compile(r"\s+[RT]X[^:]*:")
83
84     @classmethod
85     def get_stats(cls, stdout):
86         """Returns list of IP statistics.
87
88         Args:
89             stdout(str):  command output
90
91         Returns:
92             dict:  list of IP statistics
93
94         """
95         input_lines = stdout.splitlines()
96         table = {}
97         for n, row in enumerate(input_lines):
98             if cls.RX_TX_RE.match(row):
99                 # use pairs of rows, header and data
100                 table.update(cls.row_with_header(*input_lines[n:n + 2]))
101         return table
102
103     def collect_kpi(self):
104         # Implement stats collection
105         ip_link_stats = '/sbin/ip -s link'
106         stdout = self.ssh_helper.execute(ip_link_stats)[1]
107         link_stats = self.get_stats(stdout)
108         # get RX/TX from link_stats and assign to results
109         physical_node = Context.get_physical_node_from_server(
110             self.scenario_helper.nodes[self.name])
111
112         result = {
113             "physical_node": physical_node,
114             "packets_in": 0,
115             "packets_dropped": 0,
116             "packets_fwd": 0,
117             "link_stats": link_stats
118         }
119
120         LOG.debug("%s collect KPIs %s", "RouterVNF", result)
121         return result
122
123     INTERFACE_WAIT = 2
124
125     def configure_routes(self, node_name, scenario_cfg, context_cfg):
126         # Configure IP of dataplane ports and add static ARP entries
127         #
128         # This function should be modified to configure a 3rd party/commercial VNF.
129         # The current implementation works on a Linux based VNF with "ip" command.
130         #
131         # Flow contains:
132         # {'src_ip': ['152.16.100.26-152.16.100.27'],
133         #  'dst_ip': ['152.16.40.26-152.16.40.27'], 'count': 2}
134
135         ifaces = []
136         dst_macs = []
137
138         ip_cmd_replace = '/sbin/ip addr replace %s/24 dev %s'
139         ip_cmd_up = '/sbin/ip link set %s up'
140         ip_cmd_flush = '/sbin/ip address flush dev %s'
141
142         # Get VNF IPs from test case file
143         for value in context_cfg['nodes'][node_name]['interfaces'].values():
144             dst_macs.append(value['dst_mac'])
145
146             # Get the network interface name using local_mac
147             iname = self.ssh_helper.execute("/sbin/ip a |grep -B 1 %s | head -n 1"
148                                             % (value['local_mac']))
149             iname = iname[1].split(":")[1].strip()
150             ifaces.append(iname)
151
152             self.ssh_helper.execute(ip_cmd_flush % iname)
153
154             # Get the local_ip from context_cfg and assign to the data ports
155             self.ssh_helper.execute(ip_cmd_replace % (str(value['local_ip']),
156                                                       iname))
157             # Enable interface
158             self.ssh_helper.execute(ip_cmd_up % iname)
159             time.sleep(self.INTERFACE_WAIT)
160
161         # Configure static ARP entries for each IP
162         # using SSH or REST API calls
163         try:
164             src_ips = scenario_cfg['options']['flow']['src_ip']
165             dst_ips = scenario_cfg['options']['flow']['dst_ip']
166         except KeyError:
167             raise KeyError("Missing flow definition in scenario section" +
168                            " of the task definition file")
169
170         # Multiport
171         ip_ranges = []
172         for src, dst in zip(src_ips, dst_ips):
173             range1 = itertools.cycle(iter(src.split('-')))
174             range2 = itertools.cycle(iter(dst.split('-')))
175
176             range1 = IPRange(next(range1), next(range1))
177             range2 = IPRange(next(range2), next(range2))
178             ip_ranges.append(range1)
179             ip_ranges.append(range2)
180
181         ip_cmd = '/sbin/ip neigh add %s lladdr %s dev %s nud perm'
182         for idx, iface in enumerate(ifaces):
183             for addr in ip_ranges[idx]:
184                 self.ssh_helper.execute(ip_cmd % (addr, dst_macs[idx], iface))
185
186         arp_status = self.ssh_helper.execute("arp -a -n")
187         LOG.debug('arp %s', arp_status)