1 # Copyright (c) 2016-2017 Intel 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.
14 """ vPE (Power Edge router) VNF model definitions based on IETS Spec """
16 from __future__ import absolute_import
17 from __future__ import print_function
25 from six.moves import configparser, zip
27 from yardstick.common import utils
28 from yardstick.common.process import check_if_process_failed
29 from yardstick.network_services.helpers.samplevnf_helper import PortPairs
30 from yardstick.network_services.pipeline import PipelineRules
31 from yardstick.network_services.vnf_generic.vnf.sample_vnf import SampleVNF, DpdkVnfSetupEnvHelper
32 from yardstick.benchmark.contexts import base as ctx_base
34 LOG = logging.getLogger(__name__)
36 VPE_PIPELINE_COMMAND = "sudo {tool_path} -p {port_mask_hex} -f {cfg_file} -s {script} {hwlb}"
38 VPE_COLLECT_KPI = """\
39 Pkts in:\\s(\\d+)\r\n\
40 \tPkts dropped by AH:\\s(\\d+)\r\n\
41 \tPkts dropped by other:\\s(\\d+)\
45 class ConfigCreate(object):
48 def vpe_tmq(config, index):
49 tm_q = 'TM{0}'.format(index)
50 config.add_section(tm_q)
51 config.set(tm_q, 'burst_read', '24')
52 config.set(tm_q, 'burst_write', '32')
53 config.set(tm_q, 'cfg', '/tmp/full_tm_profile_10G.cfg')
56 def __init__(self, vnfd_helper, socket):
57 super(ConfigCreate, self).__init__()
61 self.vnfd_helper = vnfd_helper
62 self.uplink_ports = self.vnfd_helper.port_pairs.uplink_ports
63 self.downlink_ports = self.vnfd_helper.port_pairs.downlink_ports
64 self.pipeline_per_port = 9
66 self._dpdk_port_to_link_id_map = None
69 def dpdk_port_to_link_id_map(self):
70 # we need interface name -> DPDK port num (PMD ID) -> LINK ID
71 # LINK ID -> PMD ID is governed by the port mask
72 # LINK instances are created implicitly based on the PORT_MASK application startup
73 # argument. LINK0 is the first port enabled in the PORT_MASK, port 1 is the next one,
74 # etc. The LINK ID is different than the DPDK PMD-level NIC port ID, which is the actual
75 # position in the bitmask mentioned above. For example, if bit 5 is the first bit set
76 # in the bitmask, then LINK0 is having the PMD ID of 5. This mechanism creates a
77 # contiguous LINK ID space and isolates the configuration file against changes in the
78 # board PCIe slots where NICs are plugged in.
79 if self._dpdk_port_to_link_id_map is None:
80 self._dpdk_port_to_link_id_map = {}
81 for link_id, port_name in enumerate(sorted(self.vnfd_helper.port_pairs.all_ports,
82 key=self.vnfd_helper.port_num)):
83 self._dpdk_port_to_link_id_map[port_name] = link_id
84 return self._dpdk_port_to_link_id_map
86 def vpe_initialize(self, config):
87 config.add_section('EAL')
88 config.set('EAL', 'log_level', '0')
90 config.add_section('PIPELINE0')
91 config.set('PIPELINE0', 'type', 'MASTER')
92 config.set('PIPELINE0', 'core', 's%sC0' % self.socket)
94 config.add_section('MEMPOOL0')
95 config.set('MEMPOOL0', 'pool_size', '256K')
97 config.add_section('MEMPOOL1')
98 config.set('MEMPOOL1', 'pool_size', '2M')
101 def vpe_rxq(self, config):
102 for port in self.downlink_ports:
103 new_section = 'RXQ{0}.0'.format(self.dpdk_port_to_link_id_map[port])
104 config.add_section(new_section)
105 config.set(new_section, 'mempool', 'MEMPOOL1')
109 def get_sink_swq(self, parser, pipeline, k, index):
111 pktq = parser.get(pipeline, k)
114 sink = " SINK{0}".format(self.sink_q)
116 sink = " TM{0}".format(index)
117 pktq = "SWQ{0}{1}".format(self.sw_q, sink)
120 def vpe_upstream(self, vnf_cfg, index=0): # pragma: no cover
121 # NOTE(ralonsoh): this function must be covered in UTs.
122 parser = configparser.ConfigParser()
123 parser.read(os.path.join(vnf_cfg, 'vpe_upstream'))
125 for pipeline in parser.sections():
126 for k, v in parser.items(pipeline):
129 port = self.dpdk_port_to_link_id_map[self.uplink_ports[index]]
130 value = "RXQ{0}.0".format(port)
132 value = self.get_sink_swq(parser, pipeline, k, index)
134 parser.set(pipeline, k, value)
136 elif k == "pktq_out":
138 port = self.dpdk_port_to_link_id_map[self.downlink_ports[index]]
139 value = "TXQ{0}.0".format(port)
142 value = self.get_sink_swq(parser, pipeline, k, index)
144 parser.set(pipeline, k, value)
146 new_pipeline = 'PIPELINE{0}'.format(self.n_pipeline)
147 if new_pipeline != pipeline:
148 parser._sections[new_pipeline] = parser._sections[pipeline]
149 parser._sections.pop(pipeline)
153 def vpe_downstream(self, vnf_cfg, index): # pragma: no cover
154 # NOTE(ralonsoh): this function must be covered in UTs.
155 parser = configparser.ConfigParser()
156 parser.read(os.path.join(vnf_cfg, 'vpe_downstream'))
157 for pipeline in parser.sections():
158 for k, v in parser.items(pipeline):
161 port = self.dpdk_port_to_link_id_map[self.downlink_ports[index]]
163 value = self.get_sink_swq(parser, pipeline, k, index)
165 value = "RXQ{0}.0 TM{1}".format(port, index)
167 value = "RXQ{0}.0".format(port)
169 parser.set(pipeline, k, value)
172 port = self.dpdk_port_to_link_id_map[self.uplink_ports[index]]
175 value = self.get_sink_swq(parser, pipeline, k, index)
177 value = "TXQ{0}.0 TM{1}".format(port, index)
179 value = "TXQ{0}.0".format(port)
181 parser.set(pipeline, k, value)
183 new_pipeline = 'PIPELINE{0}'.format(self.n_pipeline)
184 if new_pipeline != pipeline:
185 parser._sections[new_pipeline] = parser._sections[pipeline]
186 parser._sections.pop(pipeline)
190 def create_vpe_config(self, vnf_cfg):
191 config = configparser.ConfigParser()
192 vpe_cfg = os.path.join("/tmp/vpe_config")
193 with open(vpe_cfg, 'w') as cfg_file:
194 config = self.vpe_initialize(config)
195 config = self.vpe_rxq(config)
196 config.write(cfg_file)
197 for index, _ in enumerate(self.uplink_ports):
198 config = self.vpe_upstream(vnf_cfg, index)
199 config.write(cfg_file)
200 config = self.vpe_downstream(vnf_cfg, index)
201 config = self.vpe_tmq(config, index)
202 config.write(cfg_file)
204 def generate_vpe_script(self, interfaces):
205 rules = PipelineRules(pipeline_id=1)
206 for uplink_port, downlink_port in zip(self.uplink_ports, self.downlink_ports):
209 next(intf["virtual-interface"] for intf in interfaces
210 if intf["name"] == uplink_port)
212 next(intf["virtual-interface"] for intf in interfaces
213 if intf["name"] == downlink_port)
215 dst_port0_ip = uplink_intf["dst_ip"]
216 dst_port1_ip = downlink_intf["dst_ip"]
217 dst_port0_mac = uplink_intf["dst_mac"]
218 dst_port1_mac = downlink_intf["dst_mac"]
220 rules.add_firewall_script(dst_port0_ip)
221 rules.next_pipeline()
222 rules.add_flow_classification_script()
223 rules.next_pipeline()
224 rules.add_flow_action()
225 rules.next_pipeline()
226 rules.add_flow_action2()
227 rules.next_pipeline()
228 rules.add_route_script(dst_port1_ip, dst_port1_mac)
229 rules.next_pipeline()
230 rules.add_route_script2(dst_port0_ip, dst_port0_mac)
231 rules.next_pipeline(num=4)
233 return rules.get_string()
235 def generate_tm_cfg(self, vnf_cfg):
236 vnf_cfg = os.path.join(vnf_cfg, "full_tm_profile_10G.cfg")
237 if os.path.exists(vnf_cfg):
238 return open(vnf_cfg).read()
240 class VpeApproxSetupEnvHelper(DpdkVnfSetupEnvHelper):
243 CFG_SCRIPT = "/tmp/vpe_script"
244 TM_CONFIG = "/tmp/full_tm_profile_10G.cfg"
245 CORES = ['0', '1', '2', '3', '4', '5']
246 PIPELINE_COMMAND = VPE_PIPELINE_COMMAND
248 def _build_vnf_ports(self):
249 self._port_pairs = PortPairs(self.vnfd_helper.interfaces)
250 self.uplink_ports = self._port_pairs.uplink_ports
251 self.downlink_ports = self._port_pairs.downlink_ports
252 self.all_ports = self._port_pairs.all_ports
254 def build_config(self):
255 vnf_cfg = self.scenario_helper.vnf_cfg
256 task_path = self.scenario_helper.task_path
257 action_bulk_file = vnf_cfg.get('action_bulk_file', '/tmp/action_bulk_512.txt')
258 full_tm_profile_file = vnf_cfg.get('full_tm_profile_file', '/tmp/full_tm_profile_10G.cfg')
259 config_file = vnf_cfg.get('file', '/tmp/vpe_config')
261 "bin_path": self.ssh_helper.bin_path,
262 "socket": self.socket,
264 self._build_vnf_ports()
265 vpe_conf = ConfigCreate(self.vnfd_helper, self.socket)
267 config_basename = posixpath.basename(config_file)
268 script_basename = posixpath.basename(self.CFG_SCRIPT)
270 with utils.open_relative_file(action_bulk_file, task_path) as handle:
271 action_bulk = handle.read()
273 with utils.open_relative_file(full_tm_profile_file, task_path) as handle:
274 full_tm_profile = handle.read()
276 with utils.open_relative_file(config_file, task_path) as handle:
277 vpe_config = handle.read()
279 # vpe_script needs to be autogenerated
280 vpe_script = vpe_conf.generate_vpe_script(self.vnfd_helper.interfaces)
281 # upload the 4 config files to the target server
282 self.ssh_helper.upload_config_file(config_basename, vpe_config.format(**vpe_vars))
283 self.ssh_helper.upload_config_file(script_basename, vpe_script.format(**vpe_vars))
284 self.ssh_helper.upload_config_file(posixpath.basename(action_bulk_file),
285 action_bulk.format(**vpe_vars))
286 self.ssh_helper.upload_config_file(posixpath.basename(full_tm_profile_file),
287 full_tm_profile.format(**vpe_vars))
289 LOG.info("Provision and start the %s", self.APP_NAME)
290 LOG.info(config_file)
291 LOG.info(self.CFG_SCRIPT)
292 self._build_pipeline_kwargs(cfg_file='/tmp/' + config_basename)
293 return self.PIPELINE_COMMAND.format(**self.pipeline_kwargs)
296 class VpeApproxVnf(SampleVNF):
297 """ This class handles vPE VNF model-driver definitions """
301 COLLECT_KPI = VPE_COLLECT_KPI
304 def __init__(self, name, vnfd, task_id, setup_env_helper_type=None,
305 resource_helper_type=None):
306 if setup_env_helper_type is None:
307 setup_env_helper_type = VpeApproxSetupEnvHelper
308 super(VpeApproxVnf, self).__init__(
309 name, vnfd, task_id, setup_env_helper_type, resource_helper_type)
311 def get_stats(self, *args, **kwargs):
312 raise NotImplementedError
314 def collect_kpi(self):
315 # we can't get KPIs if the VNF is down
316 check_if_process_failed(self._vnf_process)
317 physical_node = ctx_base.Context.get_physical_node_from_server(
318 self.scenario_helper.nodes[self.name])
321 "physical_node": physical_node,
322 'pkt_in_up_stream': 0,
323 'pkt_drop_up_stream': 0,
324 'pkt_in_down_stream': 0,
325 'pkt_drop_down_stream': 0,
326 'collect_stats': self.resource_helper.collect_kpi(),
330 indexes_drop = [2, 3]
331 command = 'p {0} stats port {1} 0'
332 for index, direction in ((5, 'up'), (9, 'down')):
333 key_in = "pkt_in_{0}_stream".format(direction)
334 key_drop = "pkt_drop_{0}_stream".format(direction)
335 for mode in ('in', 'out'):
336 stats = self.vnf_execute(command.format(index, mode))
337 match = re.search(self.COLLECT_KPI, stats, re.MULTILINE)
340 result[key_in] += sum(int(match.group(x)) for x in indexes_in)
341 result[key_drop] += sum(int(match.group(x)) for x in indexes_drop)
343 LOG.debug("%s collect KPIs %s", self.APP_NAME, result)