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