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.
15 from __future__ import absolute_import
21 from collections import OrderedDict, defaultdict
22 from itertools import chain, repeat
25 from six.moves.configparser import ConfigParser
26 from yardstick.common import utils
28 LOG = logging.getLogger(__name__)
30 LINK_CONFIG_TEMPLATE = """\
32 link {0} config {1} {2}
36 # This sets up a basic passthrough with no rules
53 class PortPairs(object):
58 def __init__(self, interfaces):
59 super(PortPairs, self).__init__()
60 self.interfaces = interfaces
61 self._all_ports = None
62 self._uplink_ports = None
63 self._downlink_ports = None
65 self._port_pair_list = None
66 self._valid_networks = None
70 if self._networks is None:
72 for intf in self.interfaces:
73 vintf = intf['virtual-interface']
75 vld_id = vintf['vld_id']
77 # probably unused port?
78 LOG.warning("intf without vld_id, %s", vintf)
80 self._networks.setdefault(vld_id, []).append(vintf["ifname"])
84 def get_downlink_id(cls, vld_id):
85 # partition returns a tuple
86 parts = list(vld_id.partition(cls.UPLINK))
88 # 'uplink' was not in or not leftmost in the string
90 parts[1] = cls.DOWNLINK
91 public_id = ''.join(parts)
95 # this only works for vnfs that have both uplink and public visible
96 def valid_networks(self):
97 if self._valid_networks is None:
98 self._valid_networks = []
99 for vld_id in self.networks:
100 downlink_id = self.get_downlink_id(vld_id)
101 if downlink_id in self.networks:
102 self._valid_networks.append((vld_id, downlink_id))
103 return self._valid_networks
107 if self._all_ports is None:
108 self._all_ports = sorted(set(self.uplink_ports + self.downlink_ports))
109 return self._all_ports
112 def uplink_ports(self):
113 if self._uplink_ports is None:
114 intfs = chain.from_iterable(
115 intfs for vld_id, intfs in self.networks.items() if
116 vld_id.startswith(self.UPLINK))
117 self._uplink_ports = sorted(set(intfs))
118 return self._uplink_ports
121 def downlink_ports(self):
122 if self._downlink_ports is None:
123 intfs = chain.from_iterable(
124 intfs for vld_id, intfs in self.networks.items() if
125 vld_id.startswith(self.DOWNLINK))
126 self._downlink_ports = sorted(set(intfs))
127 return self._downlink_ports
130 def port_pair_list(self):
131 if self._port_pair_list is None:
132 self._port_pair_list = []
134 for uplink, downlink in self.valid_networks:
135 for uplink_intf in self.networks[uplink]:
136 # only VNFs have uplink, public peers
137 peer_intfs = self.networks.get(downlink, [])
139 for downlink_intf in peer_intfs:
140 port_pair = uplink_intf, downlink_intf
141 self._port_pair_list.append(port_pair)
142 return self._port_pair_list
145 class MultiPortConfig(object):
150 def float_x_plus_one_tenth_of_y(x, y):
151 return float(x) + float(y) / 10.0
154 def make_str(base, iterator):
155 return ' '.join((base.format(x) for x in iterator))
158 def make_range_str(cls, base, start, stop=0, offset=0):
159 if offset and not stop:
160 stop = start + offset
161 return cls.make_str(base, range(start, stop))
164 def parser_get(parser, section, key, default=None):
165 if parser.has_option(section, key):
166 return parser.get(section, key)
170 def validate_ip_and_prefixlen(cls, ip_addr, prefixlen):
171 ip_addr = utils.make_ip_addr(ip_addr, prefixlen)
172 return ip_addr.ip.exploded, ip_addr.network.prefixlen
174 def __init__(self, topology_file, config_tpl, tmp_file, vnfd_helper,
175 vnf_type='CGNAT', lb_count=2, worker_threads=3,
176 worker_config='1C/1T', lb_config='SW', socket=0):
178 super(MultiPortConfig, self).__init__()
179 self.topology_file = topology_file
180 self.worker_config = worker_config.split('/')[1].lower()
181 self.worker_threads = self.get_worker_threads(worker_threads)
182 self.vnf_type = vnf_type
184 self.vnfd_helper = vnfd_helper
185 self.write_parser = ConfigParser()
186 self.read_parser = ConfigParser()
187 self.read_parser.read(config_tpl)
188 self.master_core = self.read_parser.get("PIPELINE0", "core")
189 self.master_tpl = self.get_config_tpl_data('MASTER')
190 self.arpicmp_tpl = self.get_config_tpl_data('ARPICMP')
191 self.txrx_tpl = self.get_config_tpl_data('TXRX')
192 self.loadb_tpl = self.get_config_tpl_data('LOADB')
193 self.vnf_tpl = self.get_config_tpl_data(vnf_type)
195 self.lb_count = int(lb_count)
196 self.lb_config = lb_config
197 self.tmp_file = os.path.join("/tmp", tmp_file)
198 self.pktq_out_os = []
201 self.pipeline_counter = ""
202 self.txrx_pipeline = ""
203 self._port_pairs = None
205 self.port_pair_list = []
206 self.lb_to_port_pair_mapping = {}
213 self.prv_que_handler = None
220 # return "s{}c{}".format(self.socket, core)
221 # don't use sockets for VNFs, because we don't want to have to
222 # adjust VM CPU topology. It is virtual anyway
225 def make_port_pairs_iter(self, operand, iterable):
226 return (operand(self.vnfd_helper.port_num(x), y) for y in iterable for x in
227 chain.from_iterable(self.port_pairs))
229 def make_range_port_pairs_iter(self, operand, start, end):
230 return self.make_port_pairs_iter(operand, range(start, end))
234 vpci = (v['virtual-interface']["vpci"] for v in self.vnfd_helper.interfaces)
235 lines.extend('w = {0}\n'.format(item) for item in vpci)
237 with open(self.tmp_file, 'w') as fh:
240 def update_timer(self):
241 timer_tpl = self.get_config_tpl_data('TIMER')
242 timer_tpl['core'] = self.gen_core(0)
243 self.update_write_parser(timer_tpl)
245 def get_config_tpl_data(self, type_value):
246 for section in self.read_parser.sections():
247 if self.read_parser.has_option(section, 'type'):
248 if type_value == self.read_parser.get(section, 'type'):
249 tpl = OrderedDict(self.read_parser.items(section))
252 def get_txrx_tpl_data(self, value):
253 for section in self.read_parser.sections():
254 if self.read_parser.has_option(section, 'pipeline_txrx_type'):
255 if value == self.read_parser.get(section, 'pipeline_txrx_type'):
256 tpl = OrderedDict(self.read_parser.items(section))
259 def init_write_parser_template(self, type_value='ARPICMP'):
260 for section in self.read_parser.sections():
261 if type_value == self.parser_get(self.read_parser, section, 'type', object()):
262 self.pipeline_counter = self.read_parser.getint(section, 'core')
263 self.txrx_pipeline = self.read_parser.getint(section, 'core')
265 self.write_parser.add_section(section)
266 for name, value in self.read_parser.items(section):
267 self.write_parser.set(section, name, value)
269 def update_write_parser(self, data):
270 section = "PIPELINE{0}".format(self.pipeline_counter)
271 self.write_parser.add_section(section)
272 for name, value in data.items():
273 self.write_parser.set(section, name, value)
275 def get_worker_threads(self, worker_threads):
276 if self.worker_config == '1t':
277 return worker_threads
279 return worker_threads - worker_threads % 2
281 def generate_next_core_id(self):
282 if self.worker_config == '1t':
287 self.start_core = '{}h'.format(int(self.start_core))
289 self.start_core = int(self.start_core[:-1]) + 1
291 def get_lb_count(self):
292 self.lb_count = int(min(len(self.port_pair_list), self.lb_count))
294 def generate_lb_to_port_pair_mapping(self):
295 self.lb_to_port_pair_mapping = defaultdict(int)
296 port_pair_count = len(self.port_pair_list)
297 lb_pair_count = int(port_pair_count / self.lb_count)
298 extra = port_pair_count % self.lb_count
299 extra_iter = repeat(lb_pair_count + 1, extra)
300 norm_iter = repeat(lb_pair_count, port_pair_count - extra)
301 new_values = {i: v for i, v in enumerate(chain(extra_iter, norm_iter), 1)}
302 self.lb_to_port_pair_mapping.update(new_values)
304 def set_priv_to_pub_mapping(self):
305 port_nums = [tuple(self.vnfd_helper.port_nums(x)) for x in self.port_pair_list]
306 return "".join(str(y).replace(" ", "") for y in
309 def set_priv_que_handler(self):
310 # iterated twice, can't be generator
311 priv_to_pub_map = [tuple(self.vnfd_helper.port_nums(x)) for x in self.port_pairs]
312 # must be list to use .index()
313 port_list = list(chain.from_iterable(priv_to_pub_map))
314 uplink_ports = (x[0] for x in priv_to_pub_map)
315 self.prv_que_handler = '({})'.format(
316 "".join(("{},".format(port_list.index(x)) for x in uplink_ports)))
318 def generate_arp_route_tbl(self):
319 arp_route_tbl_tmpl = "routeadd net {port_num} {port_dst_ip} 0x{port_netmask_hex}"
321 def build_arp_config(port):
322 dpdk_port_num = self.vnfd_helper.port_num(port)
323 interface = self.vnfd_helper.find_interface(name=port)["virtual-interface"]
324 # We must use the dst because we are on the VNF and we need to
326 dst_port_ip = ipaddress.ip_interface(six.text_type(
327 "%s/%s" % (interface["dst_ip"], interface["netmask"])))
330 "port_netmask_hex": utils.ip_to_hex(dst_port_ip.network.netmask.exploded),
331 # this is the port num that contains port0 subnet and next_hop_ip_hex
332 # this is LINKID which should be based on DPDK port number
333 "port_num": dpdk_port_num,
334 # next hop is dst in this case
335 # must be within subnet
336 "port_dst_ip": str(dst_port_ip.ip),
338 return arp_route_tbl_tmpl.format(**arp_vars)
340 return '\n'.join(build_arp_config(port) for port in self.all_ports)
342 def generate_arpicmp_data(self):
343 swq_in_str = self.make_range_str('SWQ{}', self.swq, offset=self.lb_count)
344 self.swq += self.lb_count
345 swq_out_str = self.make_range_str('SWQ{}', self.swq, offset=self.lb_count)
346 self.swq += self.lb_count
347 # ports_mac_list is disabled for some reason
349 # mac_iter = (self.vnfd_helper.find_interface(name=port)['virtual-interface']['local_mac']
350 # for port in self.all_ports)
351 pktq_in_iter = ('RXQ{}.0'.format(self.vnfd_helper.port_num(x[0])) for x in
355 'core': self.gen_core(0),
356 'pktq_in': swq_in_str,
357 'pktq_out': swq_out_str,
358 # we need to disable ports_mac_list?
359 # it looks like ports_mac_list is no longer required
360 # 'ports_mac_list': ' '.join(mac_iter),
361 'pktq_in_prv': ' '.join(pktq_in_iter),
362 'prv_to_pub_map': self.set_priv_to_pub_mapping(),
364 self.pktq_out_os = swq_out_str.split(' ')
365 # HWLB is a run to complition. So override the pktq_in/pktq_out
366 if self.lb_config == self.HW_LB:
369 self.make_range_str('SWQ{}', self.swq,
370 offset=(self.lb_count * self.worker_threads))
371 arpicmp_data['pktq_in'] = swq_in_str
372 # WA: Since port_pairs will not be populated during arp pipeline
373 self.port_pairs = self.port_pair_list
375 self.make_port_pairs_iter(self.float_x_plus_one_tenth_of_y, [self.mul])
376 pktq_out = self.make_str('TXQ{}', port_iter)
377 arpicmp_data['pktq_out'] = pktq_out
381 def generate_final_txrx_data(self, core=0):
382 swq_start = self.swq - self.ports_len * self.worker_threads
385 txq_end = self.worker_threads
387 pktq_out_iter = self.make_range_port_pairs_iter(self.float_x_plus_one_tenth_of_y,
390 swq_str = self.make_range_str('SWQ{}', swq_start, self.swq)
391 txq_str = self.make_str('TXQ{}', pktq_out_iter)
395 'pipeline_txrx_type': 'TXTX',
396 'core': self.gen_core(core),
398 pktq_in = rxtx_data['pktq_in']
399 pktq_in = '{0} {1}'.format(pktq_in, self.pktq_out_os[self.lb_index - 1])
400 rxtx_data['pktq_in'] = pktq_in
401 self.pipeline_counter += 1
404 def generate_initial_txrx_data(self):
405 pktq_iter = self.make_range_port_pairs_iter(self.float_x_plus_one_tenth_of_y,
406 0, self.worker_threads)
408 rxq_str = self.make_str('RXQ{}', pktq_iter)
409 swq_str = self.make_range_str('SWQ{}', self.swq, offset=self.ports_len)
412 'pktq_out': swq_str + ' SWQ{0}'.format(self.lb_index - 1),
413 'pipeline_txrx_type': 'RXRX',
414 'core': self.gen_core(self.start_core),
416 self.pipeline_counter += 1
417 return self.start_core, txrx_data
419 def generate_lb_data(self):
420 pktq_in = self.make_range_str('SWQ{}', self.swq, offset=self.ports_len)
421 self.swq += self.ports_len
423 offset = self.ports_len * self.worker_threads
424 pktq_out = self.make_range_str('SWQ{}', self.swq, offset=offset)
425 self.pktq_out = pktq_out.split()
427 self.swq += (self.ports_len * self.worker_threads)
429 'prv_que_handler': self.prv_que_handler,
431 'pktq_out': pktq_out,
432 'n_vnf_threads': str(self.worker_threads),
433 'core': self.gen_core(self.start_core),
435 self.pipeline_counter += 1
438 def generate_vnf_data(self):
439 if self.lb_config == self.HW_LB:
440 port_iter = self.make_port_pairs_iter(self.float_x_plus_one_tenth_of_y, [self.mul])
441 pktq_in = self.make_str('RXQ{}', port_iter)
444 port_iter = self.make_port_pairs_iter(self.float_x_plus_one_tenth_of_y, [self.mul])
445 pktq_out = self.make_str('TXQ{}', port_iter)
449 'pktq_out': pktq_out + ' SWQ{0}'.format(self.swq),
450 'prv_que_handler': self.prv_que_handler,
451 'core': self.gen_core(self.start_core),
456 'pktq_in': ' '.join((self.pktq_out.pop(0) for _ in range(self.ports_len))),
457 'pktq_out': self.make_range_str('SWQ{}', self.swq, offset=self.ports_len),
458 'prv_que_handler': self.prv_que_handler,
459 'core': self.gen_core(self.start_core),
461 self.swq += self.ports_len
463 if self.vnf_type in ('ACL', 'VFW'):
464 pipe_line_data.pop('prv_que_handler')
466 if self.vnf_tpl.get('vnf_set'):
467 public_ip_port_range_list = self.vnf_tpl['public_ip_port_range'].split(':')
468 ip_in_hex = '{:x}'.format(int(public_ip_port_range_list[0], 16) + self.lb_index - 1)
469 public_ip_port_range_list[0] = ip_in_hex
470 self.vnf_tpl['public_ip_port_range'] = ':'.join(public_ip_port_range_list)
472 self.pipeline_counter += 1
473 return pipe_line_data
475 def generate_config_data(self):
476 self.init_write_parser_template()
478 # use master core for master, don't use self.start_core
479 self.write_parser.set('PIPELINE0', 'core', self.gen_core(self.master_core))
480 arpicmp_data = self.generate_arpicmp_data()
481 self.arpicmp_tpl.update(arpicmp_data)
482 self.update_write_parser(self.arpicmp_tpl)
484 if self.vnf_type == 'CGNAPT':
485 self.pipeline_counter += 1
488 if self.lb_config == 'HW':
491 for lb in self.lb_to_port_pair_mapping:
494 port_pair_count = self.lb_to_port_pair_mapping[lb]
495 if not self.port_pair_list:
498 self.port_pairs = self.port_pair_list[:port_pair_count]
499 self.port_pair_list = self.port_pair_list[port_pair_count:]
500 self.ports_len = port_pair_count * 2
501 self.set_priv_que_handler()
502 if self.lb_config == 'SW':
503 core, txrx_data = self.generate_initial_txrx_data()
504 self.txrx_tpl.update(txrx_data)
505 self.update_write_parser(self.txrx_tpl)
507 lb_data = self.generate_lb_data()
508 self.loadb_tpl.update(lb_data)
509 self.update_write_parser(self.loadb_tpl)
512 for _ in range(self.worker_threads):
513 vnf_data = self.generate_vnf_data()
516 self.vnf_tpl.update(vnf_data)
517 self.update_write_parser(self.vnf_tpl)
519 self.vnf_tpl.pop('vnf_set')
523 self.vnf_tpl.pop('public_ip_port_range')
524 self.generate_next_core_id()
526 if self.lb_config == 'SW':
527 txrx_data = self.generate_final_txrx_data(core)
528 self.txrx_tpl.update(txrx_data)
529 self.update_write_parser(self.txrx_tpl)
530 self.vnf_tpl = self.get_config_tpl_data(self.vnf_type)
532 def generate_config(self):
533 self._port_pairs = PortPairs(self.vnfd_helper.interfaces)
534 self.port_pair_list = self._port_pairs.port_pair_list
535 self.all_ports = self._port_pairs.all_ports
538 self.generate_lb_to_port_pair_mapping()
539 self.generate_config_data()
540 self.write_parser.write(sys.stdout)
541 with open(self.tmp_file, 'a') as tfh:
542 self.write_parser.write(tfh)
544 def generate_link_config(self):
545 def build_args(port):
546 # lookup interface by name
547 virtual_interface = self.vnfd_helper.find_interface(name=port)["virtual-interface"]
548 local_ip = virtual_interface["local_ip"]
549 netmask = virtual_interface["netmask"]
550 port_num = self.vnfd_helper.port_num(port)
551 port_ip, prefix_len = self.validate_ip_and_prefixlen(local_ip, netmask)
552 return LINK_CONFIG_TEMPLATE.format(port_num, port_ip, prefix_len)
554 return ''.join(build_args(port) for port in self.all_ports)
556 def get_route_data(self, src_key, data_key, port):
557 route_list = self.vnfd['vdu'][0].get(src_key, [])
559 return next((route[data_key] for route in route_list if route['if'] == port), None)
560 except (TypeError, StopIteration, KeyError):
563 def get_ports_gateway(self, port):
564 return self.get_route_data('routing_table', 'gateway', port)
566 def get_ports_gateway6(self, port):
567 return self.get_route_data('nd_route_tbl', 'gateway', port)
569 def get_netmask_gateway(self, port):
570 return self.get_route_data('routing_table', 'netmask', port)
572 def get_netmask_gateway6(self, port):
573 return self.get_route_data('nd_route_tbl', 'netmask', port)
575 def generate_arp_config(self):
577 for port in self.all_ports:
578 # ignore gateway, always use TG IP
579 # gateway = self.get_ports_gateway(port)
580 vintf = self.vnfd_helper.find_interface(name=port)["virtual-interface"]
581 dst_mac = vintf["dst_mac"]
582 dst_ip = vintf["dst_ip"]
584 # (self.vnfd_helper.port_num(port), gateway, dst_mac, self.txrx_pipeline))
585 # so dst_mac is the TG dest mac, so we need TG dest IP.
586 # should be dpdk_port_num
588 (self.vnfd_helper.port_num(port), dst_ip, dst_mac, self.txrx_pipeline))
590 return '\n'.join(('p {3} arpadd {0} {1} {2}'.format(*values) for values in arp_config))
592 def generate_arp_config6(self):
594 for port in self.all_ports:
595 # ignore gateway, always use TG IP
596 # gateway6 = self.get_ports_gateway6(port)
597 vintf = self.vnfd_helper.find_interface(name=port)["virtual-interface"]
598 dst_mac6 = vintf["dst_mac"]
599 dst_ip6 = vintf["dst_ip"]
600 # arp_config6.append(
601 # (self.vnfd_helper.port_num(port), gateway6, dst_mac6, self.txrx_pipeline))
603 (self.vnfd_helper.port_num(port), dst_ip6, dst_mac6, self.txrx_pipeline))
605 return '\n'.join(('p {3} arpadd {0} {1} {2}'.format(*values) for values in arp_config6))
607 def get_flows_config(self):
608 return self.flows if self.flows else ''
610 def generate_script_data(self):
611 self._port_pairs = PortPairs(self.vnfd_helper.interfaces)
612 self.port_pair_list = self._port_pairs.port_pair_list
615 'link_config': self.generate_link_config(),
616 'arp_config': self.generate_arp_config(),
617 # disable IPv6 for now
618 # 'arp_config6': self.generate_arp_config6(),
620 'arp_route_tbl': self.generate_arp_route_tbl(),
621 'arp_route_tbl6': "",
622 'flows': self.get_flows_config()
626 def generate_script(self, vnfd, flows=None):
629 script_data = self.generate_script_data()
630 script = SCRIPT_TPL.format(**script_data)
631 if self.lb_config == self.HW_LB:
632 script += 'set fwd rxonly'
634 set_sym_hash_ena_per_port {0} enable
635 set_hash_global_config {0} simple_xor ipv4-udp enable
636 set_sym_hash_ena_per_port {1} enable
637 set_hash_global_config {1} simple_xor ipv4-udp enable
638 set_hash_input_set {0} ipv4-udp src-ipv4 udp-src-port add
639 set_hash_input_set {1} ipv4-udp dst-ipv4 udp-dst-port add
640 set_hash_input_set {0} ipv6-udp src-ipv6 udp-src-port add
641 set_hash_input_set {1} ipv6-udp dst-ipv6 udp-dst-port add
643 for port_pair in self.port_pair_list:
644 script += hwlb_tpl.format(*(self.vnfd_helper.port_nums(port_pair)))