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
25 from six.moves.configparser import ConfigParser
27 from yardstick.common.utils import ip_to_hex
29 LOG = logging.getLogger(__name__)
31 LINK_CONFIG_TEMPLATE = """\
33 link {0} config {1} {2}
37 ACTION_TEMPLATE = """\
38 p action add {0} accept
39 p action add {0} fwd {0}
40 p action add {0} count
43 FW_ACTION_TEMPLATE = """\
44 p action add {0} accept
45 p action add {0} fwd {0}
46 p action add {0} count
47 p action add {0} conntrack
50 # This sets up a basic passthrough with no rules
65 class MultiPortConfig(object):
70 def float_x_plus_one_tenth_of_y(x, y):
71 return float(x) + float(y) / 10.0
74 def make_str(base, iterator):
75 return ' '.join((base.format(x) for x in iterator))
78 def make_range_str(cls, base, start, stop=0, offset=0):
79 if offset and not stop:
81 return cls.make_str(base, range(start, stop))
84 def parser_get(parser, section, key, default=None):
85 if parser.has_option(section, key):
86 return parser.get(section, key)
90 def make_ip_addr(ip, mask):
92 :param ip: ip adddress
94 :param mask: /24 prefix of 255.255.255.0 netmask
101 return ipaddress.ip_interface(six.text_type('/'.join([ip, mask])))
102 except (TypeError, ValueError):
103 # None so we can skip later
107 def validate_ip_and_prefixlen(cls, ip_addr, prefixlen):
108 ip_addr = cls.make_ip_addr(ip_addr, prefixlen)
109 return ip_addr.ip.exploded, ip_addr.network.prefixlen
111 def __init__(self, topology_file, config_tpl, tmp_file, interfaces=None,
112 vnf_type='CGNAT', lb_count=2, worker_threads=3,
113 worker_config='1C/1T', lb_config='SW', socket=0):
115 super(MultiPortConfig, self).__init__()
116 self.topology_file = topology_file
117 self.worker_config = worker_config.split('/')[1].lower()
118 self.worker_threads = self.get_worker_threads(worker_threads)
119 self.vnf_type = vnf_type
121 self.interfaces = interfaces if interfaces else {}
123 self.write_parser = ConfigParser()
124 self.read_parser = ConfigParser()
125 self.read_parser.read(config_tpl)
126 self.master_core = self.read_parser.get("PIPELINE0", "core")
127 self.master_tpl = self.get_config_tpl_data('MASTER')
128 self.arpicmp_tpl = self.get_config_tpl_data('ARPICMP')
129 self.txrx_tpl = self.get_config_tpl_data('TXRX')
130 self.loadb_tpl = self.get_config_tpl_data('LOADB')
131 self.vnf_tpl = self.get_config_tpl_data(vnf_type)
133 self.lb_count = int(lb_count)
134 self.lb_config = lb_config
135 self.tmp_file = os.path.join("/tmp", tmp_file)
136 self.pktq_out_os = []
139 self.pipeline_counter = ""
140 self.txrx_pipeline = ""
141 self.port_pair_list = []
142 self.lb_to_port_pair_mapping = {}
148 self.port_pair_list = []
150 self.prv_que_handler = None
157 # return "s{}c{}".format(self.socket, core)
158 # don't use sockets for VNFs, because we don't want to have to
159 # adjust VM CPU topology. It is virtual anyway
162 def make_port_pairs_iter(self, operand, iterable):
163 return (operand(x[-1], y) for y in iterable for x in chain(*self.port_pairs))
165 def make_range_port_pairs_iter(self, operand, start, end):
166 return self.make_port_pairs_iter(operand, range(start, end))
169 vpci = [v['virtual-interface']["vpci"] for v in self.interfaces]
170 with open(self.tmp_file, 'w') as fh:
173 fh.write('w = {0}\n'.format(item))
176 def update_timer(self):
177 timer_tpl = self.get_config_tpl_data('TIMER')
178 timer_tpl['core'] = self.gen_core(self.start_core)
179 self.update_write_parser(timer_tpl)
182 def get_config_tpl_data(self, type_value):
183 for section in self.read_parser.sections():
184 if self.read_parser.has_option(section, 'type'):
185 if type_value == self.read_parser.get(section, 'type'):
186 tpl = OrderedDict(self.read_parser.items(section))
189 def get_txrx_tpl_data(self, value):
190 for section in self.read_parser.sections():
191 if self.read_parser.has_option(section, 'pipeline_txrx_type'):
192 if value == self.read_parser.get(section, 'pipeline_txrx_type'):
193 tpl = OrderedDict(self.read_parser.items(section))
196 def init_write_parser_template(self, type_value='ARPICMP'):
197 for section in self.read_parser.sections():
198 if type_value == self.parser_get(self.read_parser, section, 'type', object()):
199 self.start_core = self.read_parser.getint(section, 'core')
200 self.pipeline_counter = self.read_parser.getint(section, 'core')
201 self.txrx_pipeline = self.read_parser.getint(section, 'core')
203 self.write_parser.add_section(section)
204 for name, value in self.read_parser.items(section):
205 self.write_parser.set(section, name, value)
207 def update_write_parser(self, data):
208 section = "PIPELINE{0}".format(self.pipeline_counter)
209 self.write_parser.add_section(section)
210 for name, value in data.items():
211 self.write_parser.set(section, name, value)
213 def get_worker_threads(self, worker_threads):
214 if self.worker_config == '1t':
215 return worker_threads
217 return worker_threads - worker_threads % 2
219 def generate_next_core_id(self):
220 if self.worker_config == '1t':
225 self.start_core = '{}h'.format(int(self.start_core))
227 self.start_core = int(self.start_core[:-1]) + 1
230 def get_port_pairs(interfaces):
233 for private_intf in interfaces:
234 vintf = private_intf['virtual-interface']
236 vld_id = vintf['vld_id']
240 networks.setdefault(vld_id, []).append(vintf)
242 for name, net in networks.items():
243 # partition returns a tuple
244 parts = list(name.partition('private'))
246 # 'private' was not in or not leftmost in the string
249 public_id = ''.join(parts)
250 for private_intf in net:
252 public_peer_intfs = networks[public_id]
254 LOG.warning("private network without peer %s, %s not found", name, public_id)
257 for public_intf in public_peer_intfs:
258 port_pair = private_intf["ifname"], public_intf["ifname"]
259 port_pair_list.append(port_pair)
261 return port_pair_list, networks
263 def get_lb_count(self):
264 self.lb_count = int(min(len(self.port_pair_list), self.lb_count))
266 def generate_lb_to_port_pair_mapping(self):
267 self.lb_to_port_pair_mapping = defaultdict(int)
268 port_pair_count = len(self.port_pair_list)
269 lb_pair_count = int(port_pair_count / self.lb_count)
270 for i in range(self.lb_count):
271 self.lb_to_port_pair_mapping[i + 1] = lb_pair_count
272 for i in range(port_pair_count % self.lb_count):
273 self.lb_to_port_pair_mapping[i + 1] += 1
275 def set_priv_to_pub_mapping(self):
276 return "".join(str(y) for y in [(int(x[0][-1]), int(x[1][-1])) for x in
277 self.port_pair_list])
279 def set_priv_que_handler(self):
280 # iterated twice, can't be generator
281 priv_to_pub_map = [(int(x[0][-1]), int(x[1][-1])) for x in self.port_pairs]
282 # must be list to use .index()
283 port_list = list(chain.from_iterable(priv_to_pub_map))
284 priv_ports = (x[0] for x in priv_to_pub_map)
285 self.prv_que_handler = '({})'.format(
286 ",".join((str(port_list.index(x)) for x in priv_ports)))
288 def generate_arp_route_tbl(self):
290 arp_route_tbl_tmpl = "({port0_dst_ip_hex},{port0_netmask_hex},{port_num}," \
292 for port_pair in self.port_pair_list:
293 for port in port_pair:
294 port_num = int(port[-1])
295 interface = self.interfaces[port_num]
296 # We must use the dst because we are on the VNF and we need to
299 ipaddress.ip_interface(six.text_type(
300 "%s/%s" % (interface["virtual-interface"]["dst_ip"],
301 interface["virtual-interface"]["netmask"])))
303 "port0_dst_ip_hex": ip_to_hex(dst_port0_ip.network.network_address.exploded),
304 "port0_netmask_hex": ip_to_hex(dst_port0_ip.network.netmask.exploded),
305 # this is the port num that contains port0 subnet and next_hop_ip_hex
306 "port_num": port_num,
307 # next hop is dst in this case
308 # must be within subnet
309 "next_hop_ip_hex": ip_to_hex(dst_port0_ip.ip.exploded),
311 arp_config.append(arp_route_tbl_tmpl.format(**arp_vars))
313 return ' '.join(arp_config)
315 def generate_arpicmp_data(self):
316 swq_in_str = self.make_range_str('SWQ{}', self.swq, offset=self.lb_count)
317 self.swq += self.lb_count
318 swq_out_str = self.make_range_str('SWQ{}', self.swq, offset=self.lb_count)
319 self.swq += self.lb_count
320 # ports_mac_list is disabled for some reason
321 # mac_iter = (self.interfaces[int(x[-1])]['virtual-interface']['local_mac']
322 # for port_pair in self.port_pair_list for x in port_pair)
323 pktq_in_iter = ('RXQ{}'.format(float(x[0][-1])) for x in self.port_pair_list)
326 'core': self.gen_core(self.start_core),
327 'pktq_in': swq_in_str,
328 'pktq_out': swq_out_str,
329 # we need to disable ports_mac_list?
330 # it looks like ports_mac_list is no longer required
331 # 'ports_mac_list': ' '.join(mac_iter),
332 'pktq_in_prv': ' '.join(pktq_in_iter),
333 'prv_to_pub_map': self.set_priv_to_pub_mapping(),
334 'arp_route_tbl': self.generate_arp_route_tbl(),
335 # nd_route_tbl must be set or we get segault on random OpenStack IPv6 traffic
336 # 'nd_route_tbl': "(0064:ff9b:0:0:0:0:9810:6414,120,0,0064:ff9b:0:0:0:0:9810:6414)"
337 # safe default? route discard prefix to localhost
338 'nd_route_tbl': "(0100::,64,0,::1)"
340 self.pktq_out_os = swq_out_str.split(' ')
341 # HWLB is a run to complition. So override the pktq_in/pktq_out
342 if self.lb_config == self.HW_LB:
345 self.make_range_str('SWQ{}', self.swq,
346 offset=(self.lb_count * self.worker_threads))
347 arpicmp_data['pktq_in'] = swq_in_str
348 # WA: Since port_pairs will not be populated during arp pipeline
349 self.port_pairs = self.port_pair_list
351 self.make_port_pairs_iter(self.float_x_plus_one_tenth_of_y, [self.mul])
352 pktq_out = self.make_str('TXQ{}', port_iter)
353 arpicmp_data['pktq_out'] = pktq_out
357 def generate_final_txrx_data(self):
358 swq_start = self.swq - self.ports_len * self.worker_threads
361 txq_end = self.worker_threads
363 pktq_out_iter = self.make_range_port_pairs_iter(self.float_x_plus_one_tenth_of_y,
366 swq_str = self.make_range_str('SWQ{}', swq_start, self.swq)
367 txq_str = self.make_str('TXQ{}', pktq_out_iter)
371 'pipeline_txrx_type': 'TXTX',
372 'core': self.gen_core(self.start_core),
374 pktq_in = rxtx_data['pktq_in']
375 pktq_in = '{0} {1}'.format(pktq_in, self.pktq_out_os[self.lb_index - 1])
376 rxtx_data['pktq_in'] = pktq_in
377 self.pipeline_counter += 1
380 def generate_initial_txrx_data(self):
381 pktq_iter = self.make_range_port_pairs_iter(self.float_x_plus_one_tenth_of_y,
382 0, self.worker_threads)
384 rxq_str = self.make_str('RXQ{}', pktq_iter)
385 swq_str = self.make_range_str('SWQ{}', self.swq, offset=self.ports_len)
388 'pktq_out': swq_str + ' SWQ{0}'.format(self.lb_index - 1),
389 'pipeline_txrx_type': 'RXRX',
390 'core': self.gen_core(self.start_core),
392 self.pipeline_counter += 1
395 def generate_lb_data(self):
396 pktq_in = self.make_range_str('SWQ{}', self.swq, offset=self.ports_len)
397 self.swq += self.ports_len
399 offset = self.ports_len * self.worker_threads
400 pktq_out = self.make_range_str('SWQ{}', self.swq, offset=offset)
401 self.pktq_out = pktq_out.split()
403 self.swq += (self.ports_len * self.worker_threads)
405 'prv_que_handler': self.prv_que_handler,
407 'pktq_out': pktq_out,
408 'n_vnf_threads': str(self.worker_threads),
409 'core': self.gen_core(self.start_core),
411 self.pipeline_counter += 1
414 def generate_vnf_data(self):
415 if self.lb_config == self.HW_LB:
416 port_iter = self.make_port_pairs_iter(self.float_x_plus_one_tenth_of_y, [self.mul])
417 pktq_in = self.make_str('RXQ{}', port_iter)
420 port_iter = self.make_port_pairs_iter(self.float_x_plus_one_tenth_of_y, [self.mul])
421 pktq_out = self.make_str('TXQ{}', port_iter)
425 'pktq_out': pktq_out + ' SWQ{0}'.format(self.swq),
426 'prv_que_handler': self.prv_que_handler,
427 'core': self.gen_core(self.start_core),
432 'pktq_in': ' '.join((self.pktq_out.pop(0) for _ in range(self.ports_len))),
433 'pktq_out': self.make_range_str('SWQ{}', self.swq, offset=self.ports_len),
434 'prv_que_handler': self.prv_que_handler,
435 'core': self.gen_core(self.start_core),
437 self.swq += self.ports_len
439 if self.vnf_type in ('ACL', 'VFW'):
440 pipe_line_data.pop('prv_que_handler')
442 if self.vnf_tpl.get('vnf_set'):
443 public_ip_port_range_list = self.vnf_tpl['public_ip_port_range'].split(':')
444 ip_in_hex = '{:x}'.format(int(public_ip_port_range_list[0], 16) + self.lb_index - 1)
445 public_ip_port_range_list[0] = ip_in_hex
446 self.vnf_tpl['public_ip_port_range'] = ':'.join(public_ip_port_range_list)
448 self.pipeline_counter += 1
449 return pipe_line_data
451 def generate_config_data(self):
452 self.init_write_parser_template()
454 # use master core for master, don't use self.start_core
455 self.write_parser.set('PIPELINE0', 'core', self.gen_core(self.master_core))
456 arpicmp_data = self.generate_arpicmp_data()
457 self.arpicmp_tpl.update(arpicmp_data)
458 self.update_write_parser(self.arpicmp_tpl)
461 if self.vnf_type == 'CGNAPT':
462 self.pipeline_counter += 1
465 for lb in self.lb_to_port_pair_mapping:
468 port_pair_count = self.lb_to_port_pair_mapping[lb]
469 if not self.port_pair_list:
472 self.port_pairs = self.port_pair_list[:port_pair_count]
473 self.port_pair_list = self.port_pair_list[port_pair_count:]
474 self.ports_len = port_pair_count * 2
475 self.set_priv_que_handler()
476 if self.lb_config == 'SW':
477 txrx_data = self.generate_initial_txrx_data()
478 self.txrx_tpl.update(txrx_data)
479 self.update_write_parser(self.txrx_tpl)
481 lb_data = self.generate_lb_data()
482 self.loadb_tpl.update(lb_data)
483 self.update_write_parser(self.loadb_tpl)
486 for i in range(self.worker_threads):
487 vnf_data = self.generate_vnf_data()
490 self.vnf_tpl.update(vnf_data)
491 self.update_write_parser(self.vnf_tpl)
493 self.vnf_tpl.pop('vnf_set')
497 self.vnf_tpl.pop('public_ip_port_range')
498 self.generate_next_core_id()
500 if self.lb_config == 'SW':
501 txrx_data = self.generate_final_txrx_data()
502 self.txrx_tpl.update(txrx_data)
503 self.update_write_parser(self.txrx_tpl)
505 self.vnf_tpl = self.get_config_tpl_data(self.vnf_type)
507 def generate_config(self):
508 self.port_pair_list, self.networks = self.get_port_pairs(self.interfaces)
510 self.generate_lb_to_port_pair_mapping()
511 self.generate_config_data()
512 self.write_parser.write(sys.stdout)
513 with open(self.tmp_file, 'a') as tfh:
514 self.write_parser.write(tfh)
516 def generate_link_config(self):
519 for port_pair in self.port_pair_list:
520 for port in port_pair:
522 virtual_interface = self.interfaces[int(port)]["virtual-interface"]
523 local_ip = virtual_interface["local_ip"]
524 netmask = virtual_interface["netmask"]
525 port_ip, prefix_len = self.validate_ip_and_prefixlen(local_ip, netmask)
526 link_configs.append(LINK_CONFIG_TEMPLATE.format(port, port_ip, prefix_len))
528 return ''.join(link_configs)
530 def get_route_data(self, src_key, data_key, port):
531 route_list = self.vnfd['vdu'][0].get(src_key, [])
533 return next((route[data_key] for route in route_list if route['if'] == port), None)
534 except (TypeError, StopIteration, KeyError):
537 def get_ports_gateway(self, port):
538 return self.get_route_data('routing_table', 'gateway', port)
540 def get_ports_gateway6(self, port):
541 return self.get_route_data('nd_route_tbl', 'gateway', port)
543 def get_netmask_gateway(self, port):
544 return self.get_route_data('routing_table', 'netmask', port)
546 def get_netmask_gateway6(self, port):
547 return self.get_route_data('nd_route_tbl', 'netmask', port)
549 def generate_arp_config(self):
551 for port_pair in self.port_pair_list:
552 for port in port_pair:
553 # ignore gateway, always use TG IP
554 # gateway = self.get_ports_gateway(port)
555 dst_mac = self.interfaces[int(port[-1])]["virtual-interface"]["dst_mac"]
556 dst_ip = self.interfaces[int(port[-1])]["virtual-interface"]["dst_ip"]
557 # arp_config.append((port[-1], gateway, dst_mac, self.txrx_pipeline))
558 # so dst_mac is the TG dest mac, so we need TG dest IP.
559 arp_config.append((port[-1], dst_ip, dst_mac, self.txrx_pipeline))
561 return '\n'.join(('p {3} arpadd {0} {1} {2}'.format(*values) for values in arp_config))
563 def generate_arp_config6(self):
565 for port_pair in self.port_pair_list:
566 for port in port_pair:
567 # ignore gateway, always use TG IP
568 # gateway6 = self.get_ports_gateway6(port)
569 dst_mac6 = self.interfaces[int(port[-1])]["virtual-interface"]["dst_mac"]
570 dst_ip6 = self.interfaces[int(port[-1])]["virtual-interface"]["dst_ip"]
571 # arp_config6.append((port[-1], gateway6, dst_mac6, self.txrx_pipeline))
572 arp_config6.append((port[-1], dst_ip6, dst_mac6, self.txrx_pipeline))
574 return '\n'.join(('p {3} arpadd {0} {1} {2}'.format(*values) for values in arp_config6))
576 def generate_action_config(self):
578 for port_pair in self.port_pair_list:
579 for port in port_pair:
580 port_list.append(port[-1])
582 if self.vnf_type == "VFW":
583 template = FW_ACTION_TEMPLATE
585 template = ACTION_TEMPLATE
587 return ''.join((template.format(port) for port in port_list))
589 def get_ip_from_port(self, port):
590 # we can't use gateway because in OpenStack gateways interfer with floating ip routing
591 # return self.make_ip_addr(self.get_ports_gateway(port), self.get_netmask_gateway(port))
592 ip = self.interfaces[port]["virtual-interface"]["local_ip"]
593 netmask = self.interfaces[port]["virtual-interface"]["netmask"]
594 return self.make_ip_addr(ip, netmask)
596 def get_network_and_prefixlen_from_ip_of_port(self, port):
597 ip_addr = self.get_ip_from_port(port)
598 # handle cases with no gateway
600 return ip_addr.network.network_address.exploded, ip_addr.network.prefixlen
604 def generate_rule_config(self):
605 cmd = 'acl' if self.vnf_type == "ACL" else "vfw"
606 rules_config = self.rules if self.rules else ''
609 pattern = 'p {0} add {1} {2} {3} {4} {5} 0 65535 0 65535 0 0 {6}'
610 for port_pair in self.port_pair_list:
611 src_port = int(port_pair[0][-1])
612 dst_port = int(port_pair[1][-1])
614 src_net, src_prefix_len = self.get_network_and_prefixlen_from_ip_of_port(src_port)
615 dst_net, dst_prefix_len = self.get_network_and_prefixlen_from_ip_of_port(dst_port)
616 # ignore entires with empty values
617 if all((src_net, src_prefix_len, dst_net, dst_prefix_len)):
618 new_rules.append((cmd, self.txrx_pipeline, src_net, src_prefix_len,
619 dst_net, dst_prefix_len, dst_port))
620 new_rules.append((cmd, self.txrx_pipeline, dst_net, dst_prefix_len,
621 src_net, src_prefix_len, src_port))
623 # src_net = self.get_ports_gateway6(port_pair[0])
624 # src_prefix_len = self.get_netmask_gateway6(port_pair[0])
625 # dst_net = self.get_ports_gateway6(port_pair[1])
626 # dst_prefix_len = self.get_netmask_gateway6(port_pair[0])
627 # # ignore entires with empty values
628 # if all((src_net, src_prefix_len, dst_net, dst_prefix_len)):
629 # new_ipv6_rules.append((cmd, self.txrx_pipeline, src_net, src_prefix_len,
630 # dst_net, dst_prefix_len, dst_port))
631 # new_ipv6_rules.append((cmd, self.txrx_pipeline, dst_net, dst_prefix_len,
632 # src_net, src_prefix_len, src_port))
634 acl_apply = "\np %s applyruleset" % cmd
635 new_rules_config = '\n'.join(pattern.format(*values) for values
636 in chain(new_rules, new_ipv6_rules))
637 return ''.join([rules_config, new_rules_config, acl_apply])
639 def generate_script_data(self):
640 self.port_pair_list, self.networks = self.get_port_pairs(self.interfaces)
643 'link_config': self.generate_link_config(),
644 'arp_config': self.generate_arp_config(),
645 # disable IPv6 for now
646 # 'arp_config6': self.generate_arp_config6(),
652 if self.vnf_type in ('ACL', 'VFW'):
654 'actions': self.generate_action_config(),
655 'rules': self.generate_rule_config(),
660 def generate_script(self, vnfd, rules=None):
663 script_data = self.generate_script_data()
664 script = SCRIPT_TPL.format(**script_data)
665 if self.lb_config == self.HW_LB:
666 script += 'set fwd rxonly'
668 set_sym_hash_ena_per_port {0} enable
669 set_hash_global_config {0} simple_xor ipv4-udp enable
670 set_sym_hash_ena_per_port {1} enable
671 set_hash_global_config {1} simple_xor ipv4-udp enable
672 set_hash_input_set {0} ipv4-udp src-ipv4 udp-src-port add
673 set_hash_input_set {1} ipv4-udp dst-ipv4 udp-dst-port add
674 set_hash_input_set {0} ipv6-udp src-ipv6 udp-src-port add
675 set_hash_input_set {1} ipv6-udp dst-ipv6 udp-dst-port add
677 for port_pair in self.port_pair_list:
678 script += hwlb_tpl.format(port_pair[0][-1], port_pair[1][-1])