NSB: fix port topology
[yardstick.git] / yardstick / network_services / helpers / samplevnf_helper.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
15 from __future__ import absolute_import
16
17 import ipaddress
18 import logging
19 import os
20 import sys
21 from collections import OrderedDict, defaultdict
22 from itertools import chain, repeat
23
24 import six
25 from six.moves.configparser import ConfigParser
26
27 from yardstick.common.utils import ip_to_hex
28
29 LOG = logging.getLogger(__name__)
30
31 LINK_CONFIG_TEMPLATE = """\
32 link {0} down
33 link {0} config {1} {2}
34 link {0} up
35 """
36
37 ACTION_TEMPLATE = """\
38 p action add {0} accept
39 p action add {0} fwd {0}
40 p action add {0} count
41 """
42
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
48 """
49
50 # This sets up a basic passthrough with no rules
51 SCRIPT_TPL = """
52 {link_config}
53
54 {arp_config}
55
56 {arp_config6}
57
58 {actions}
59
60 {rules}
61
62 """
63
64
65 class PortPairs(object):
66
67     PUBLIC = "public"
68     PRIVATE = "private"
69
70     def __init__(self, interfaces):
71         super(PortPairs, self).__init__()
72         self.interfaces = interfaces
73         self._all_ports = None
74         self._priv_ports = None
75         self._pub_ports = None
76         self._networks = None
77         self._port_pair_list = None
78         self._valid_networks = None
79
80     @property
81     def networks(self):
82         if self._networks is None:
83             self._networks = {}
84             for intf in self.interfaces:
85                 vintf = intf['virtual-interface']
86                 try:
87                     vld_id = vintf['vld_id']
88                 except KeyError:
89                     # probably unused port?
90                     LOG.warning("intf without vld_id, %s", vintf)
91                 else:
92                     self._networks.setdefault(vld_id, []).append(vintf["ifname"])
93         return self._networks
94
95     @classmethod
96     def get_public_id(cls, vld_id):
97         # partition returns a tuple
98         parts = list(vld_id.partition(cls.PRIVATE))
99         if parts[0]:
100             # 'private' was not in or not leftmost in the string
101             return
102         parts[1] = cls.PUBLIC
103         public_id = ''.join(parts)
104         return public_id
105
106     @property
107     # this only works for vnfs that have both private and public visible
108     def valid_networks(self):
109         if self._valid_networks is None:
110             self._valid_networks = []
111             for vld_id in self.networks:
112                 public_id = self.get_public_id(vld_id)
113                 if public_id in self.networks:
114                     self._valid_networks.append((vld_id, public_id))
115         return self._valid_networks
116
117     @property
118     def all_ports(self):
119         if self._all_ports is None:
120             self._all_ports = sorted(set(self.priv_ports + self.pub_ports))
121         return self._all_ports
122
123     @property
124     def priv_ports(self):
125         if self._priv_ports is None:
126             intfs = chain.from_iterable(
127                 intfs for vld_id, intfs in self.networks.items() if
128                 vld_id.startswith(self.PRIVATE))
129             self._priv_ports = sorted(set(intfs))
130         return self._priv_ports
131
132     @property
133     def pub_ports(self):
134         if self._pub_ports is None:
135             intfs = chain.from_iterable(
136                 intfs for vld_id, intfs in self.networks.items() if vld_id.startswith(self.PUBLIC))
137             self._pub_ports = sorted(set(intfs))
138         return self._pub_ports
139
140     @property
141     def port_pair_list(self):
142         if self._port_pair_list is None:
143             self._port_pair_list = []
144
145             for priv, pub in self.valid_networks:
146                 for private_intf in self.networks[priv]:
147                     # only VNFs have private, public peers
148                     peer_intfs = self.networks.get(pub, [])
149                     if peer_intfs:
150                         for public_intf in peer_intfs:
151                             port_pair = private_intf, public_intf
152                             self._port_pair_list.append(port_pair)
153         return self._port_pair_list
154
155
156 class MultiPortConfig(object):
157
158     HW_LB = "HW"
159
160     @staticmethod
161     def float_x_plus_one_tenth_of_y(x, y):
162         return float(x) + float(y) / 10.0
163
164     @staticmethod
165     def make_str(base, iterator):
166         return ' '.join((base.format(x) for x in iterator))
167
168     @classmethod
169     def make_range_str(cls, base, start, stop=0, offset=0):
170         if offset and not stop:
171             stop = start + offset
172         return cls.make_str(base, range(start, stop))
173
174     @staticmethod
175     def parser_get(parser, section, key, default=None):
176         if parser.has_option(section, key):
177             return parser.get(section, key)
178         return default
179
180     @staticmethod
181     def make_ip_addr(ip, mask):
182         """
183         :param ip: ip adddress
184         :type ip: str
185         :param mask: /24 prefix of 255.255.255.0 netmask
186         :type mask: str
187         :return: interface
188         :rtype: IPv4Interface
189         """
190
191         try:
192             return ipaddress.ip_interface(six.text_type('/'.join([ip, mask])))
193         except (TypeError, ValueError):
194             # None so we can skip later
195             return None
196
197     @classmethod
198     def validate_ip_and_prefixlen(cls, ip_addr, prefixlen):
199         ip_addr = cls.make_ip_addr(ip_addr, prefixlen)
200         return ip_addr.ip.exploded, ip_addr.network.prefixlen
201
202     def __init__(self, topology_file, config_tpl, tmp_file, vnfd_helper,
203                  vnf_type='CGNAT', lb_count=2, worker_threads=3,
204                  worker_config='1C/1T', lb_config='SW', socket=0):
205
206         super(MultiPortConfig, self).__init__()
207         self.topology_file = topology_file
208         self.worker_config = worker_config.split('/')[1].lower()
209         self.worker_threads = self.get_worker_threads(worker_threads)
210         self.vnf_type = vnf_type
211         self.pipe_line = 0
212         self.vnfd_helper = vnfd_helper
213         self.write_parser = ConfigParser()
214         self.read_parser = ConfigParser()
215         self.read_parser.read(config_tpl)
216         self.master_core = self.read_parser.get("PIPELINE0", "core")
217         self.master_tpl = self.get_config_tpl_data('MASTER')
218         self.arpicmp_tpl = self.get_config_tpl_data('ARPICMP')
219         self.txrx_tpl = self.get_config_tpl_data('TXRX')
220         self.loadb_tpl = self.get_config_tpl_data('LOADB')
221         self.vnf_tpl = self.get_config_tpl_data(vnf_type)
222         self.swq = 0
223         self.lb_count = int(lb_count)
224         self.lb_config = lb_config
225         self.tmp_file = os.path.join("/tmp", tmp_file)
226         self.pktq_out_os = []
227         self.socket = socket
228         self.start_core = ""
229         self.pipeline_counter = ""
230         self.txrx_pipeline = ""
231         self._port_pairs = None
232         self.all_ports = []
233         self.port_pair_list = []
234         self.lb_to_port_pair_mapping = {}
235         self.init_eal()
236
237         self.lb_index = None
238         self.mul = 0
239         self.port_pairs = []
240         self.ports_len = 0
241         self.prv_que_handler = None
242         self.vnfd = None
243         self.rules = None
244         self.pktq_out = []
245
246     @staticmethod
247     def gen_core(core):
248         # return "s{}c{}".format(self.socket, core)
249         # don't use sockets for VNFs, because we don't want to have to
250         # adjust VM CPU topology.  It is virtual anyway
251         return str(core)
252
253     def make_port_pairs_iter(self, operand, iterable):
254         return (operand(self.vnfd_helper.port_num(x), y) for y in iterable for x in
255                 chain.from_iterable(self.port_pairs))
256
257     def make_range_port_pairs_iter(self, operand, start, end):
258         return self.make_port_pairs_iter(operand, range(start, end))
259
260     def init_eal(self):
261         lines = ['[EAL]\n']
262         vpci = (v['virtual-interface']["vpci"] for v in self.vnfd_helper.interfaces)
263         lines.extend('w = {0}\n'.format(item) for item in vpci)
264         lines.append('\n')
265         with open(self.tmp_file, 'w') as fh:
266             fh.writelines(lines)
267
268     def update_timer(self):
269         timer_tpl = self.get_config_tpl_data('TIMER')
270         timer_tpl['core'] = self.gen_core(self.start_core)
271         self.update_write_parser(timer_tpl)
272         self.start_core += 1
273
274     def get_config_tpl_data(self, type_value):
275         for section in self.read_parser.sections():
276             if self.read_parser.has_option(section, 'type'):
277                 if type_value == self.read_parser.get(section, 'type'):
278                     tpl = OrderedDict(self.read_parser.items(section))
279                     return tpl
280
281     def get_txrx_tpl_data(self, value):
282         for section in self.read_parser.sections():
283             if self.read_parser.has_option(section, 'pipeline_txrx_type'):
284                 if value == self.read_parser.get(section, 'pipeline_txrx_type'):
285                     tpl = OrderedDict(self.read_parser.items(section))
286                     return tpl
287
288     def init_write_parser_template(self, type_value='ARPICMP'):
289         for section in self.read_parser.sections():
290             if type_value == self.parser_get(self.read_parser, section, 'type', object()):
291                 self.start_core = self.read_parser.getint(section, 'core')
292                 self.pipeline_counter = self.read_parser.getint(section, 'core')
293                 self.txrx_pipeline = self.read_parser.getint(section, 'core')
294                 return
295             self.write_parser.add_section(section)
296             for name, value in self.read_parser.items(section):
297                 self.write_parser.set(section, name, value)
298
299     def update_write_parser(self, data):
300         section = "PIPELINE{0}".format(self.pipeline_counter)
301         self.write_parser.add_section(section)
302         for name, value in data.items():
303             self.write_parser.set(section, name, value)
304
305     def get_worker_threads(self, worker_threads):
306         if self.worker_config == '1t':
307             return worker_threads
308         else:
309             return worker_threads - worker_threads % 2
310
311     def generate_next_core_id(self):
312         if self.worker_config == '1t':
313             self.start_core += 1
314             return
315
316         try:
317             self.start_core = '{}h'.format(int(self.start_core))
318         except ValueError:
319             self.start_core = int(self.start_core[:-1]) + 1
320
321     def get_lb_count(self):
322         self.lb_count = int(min(len(self.port_pair_list), self.lb_count))
323
324     def generate_lb_to_port_pair_mapping(self):
325         self.lb_to_port_pair_mapping = defaultdict(int)
326         port_pair_count = len(self.port_pair_list)
327         lb_pair_count = int(port_pair_count / self.lb_count)
328         extra = port_pair_count % self.lb_count
329         extra_iter = repeat(lb_pair_count + 1, extra)
330         norm_iter = repeat(lb_pair_count, port_pair_count - extra)
331         new_values = {i: v for i, v in enumerate(chain(extra_iter, norm_iter), 1)}
332         self.lb_to_port_pair_mapping.update(new_values)
333
334     def set_priv_to_pub_mapping(self):
335         port_nums = [tuple(self.vnfd_helper.port_nums(x)) for x in self.port_pair_list]
336         return "".join(str(y).replace(" ", "") for y in
337                        port_nums)
338
339     def set_priv_que_handler(self):
340         # iterated twice, can't be generator
341         priv_to_pub_map = [tuple(self.vnfd_helper.port_nums(x)) for x in self.port_pairs]
342         # must be list to use .index()
343         port_list = list(chain.from_iterable(priv_to_pub_map))
344         priv_ports = (x[0] for x in priv_to_pub_map)
345         self.prv_que_handler = '({})'.format(
346             "".join(("{},".format(port_list.index(x)) for x in priv_ports)))
347
348     def generate_arp_route_tbl(self):
349         arp_route_tbl_tmpl = "({port0_dst_ip_hex},{port0_netmask_hex},{port_num}," \
350                              "{next_hop_ip_hex})"
351
352         def build_arp_config(port):
353             dpdk_port_num = self.vnfd_helper.port_num(port)
354             interface = self.vnfd_helper.find_interface(name=port)["virtual-interface"]
355             # We must use the dst because we are on the VNF and we need to
356             # reach the TG.
357             dst_port0_ip = ipaddress.ip_interface(six.text_type(
358                 "%s/%s" % (interface["dst_ip"], interface["netmask"])))
359
360             arp_vars = {
361                 "port0_dst_ip_hex": ip_to_hex(dst_port0_ip.network.network_address.exploded),
362                 "port0_netmask_hex": ip_to_hex(dst_port0_ip.network.netmask.exploded),
363                 # this is the port num that contains port0 subnet and next_hop_ip_hex
364                 # this is LINKID which should be based on DPDK port number
365                 "port_num": dpdk_port_num,
366                 # next hop is dst in this case
367                 # must be within subnet
368                 "next_hop_ip_hex": ip_to_hex(dst_port0_ip.ip.exploded),
369             }
370             return arp_route_tbl_tmpl.format(**arp_vars)
371
372         return ' '.join(build_arp_config(port) for port in self.all_ports)
373
374     def generate_arpicmp_data(self):
375         swq_in_str = self.make_range_str('SWQ{}', self.swq, offset=self.lb_count)
376         self.swq += self.lb_count
377         swq_out_str = self.make_range_str('SWQ{}', self.swq, offset=self.lb_count)
378         self.swq += self.lb_count
379         # ports_mac_list is disabled for some reason
380
381         # mac_iter = (self.vnfd_helper.find_interface(name=port)['virtual-interface']['local_mac']
382         #             for port in self.all_ports)
383         pktq_in_iter = ('RXQ{}.0'.format(self.vnfd_helper.port_num(x[0])) for x in
384                         self.port_pair_list)
385
386         arpicmp_data = {
387             'core': self.gen_core(self.start_core),
388             'pktq_in': swq_in_str,
389             'pktq_out': swq_out_str,
390             # we need to disable ports_mac_list?
391             # it looks like ports_mac_list is no longer required
392             # 'ports_mac_list': ' '.join(mac_iter),
393             'pktq_in_prv': ' '.join(pktq_in_iter),
394             'prv_to_pub_map': self.set_priv_to_pub_mapping(),
395             'arp_route_tbl': self.generate_arp_route_tbl(),
396             # nd_route_tbl must be set or we get segault on random OpenStack IPv6 traffic
397             # 'nd_route_tbl': "(0064:ff9b:0:0:0:0:9810:6414,120,0,0064:ff9b:0:0:0:0:9810:6414)"
398             # safe default?  route discard prefix to localhost
399             'nd_route_tbl': "(0100::,64,0,::1)"
400         }
401         self.pktq_out_os = swq_out_str.split(' ')
402         # HWLB is a run to complition. So override the pktq_in/pktq_out
403         if self.lb_config == self.HW_LB:
404             self.swq = 0
405             swq_in_str = \
406                 self.make_range_str('SWQ{}', self.swq,
407                                     offset=(self.lb_count * self.worker_threads))
408             arpicmp_data['pktq_in'] = swq_in_str
409             # WA: Since port_pairs will not be populated during arp pipeline
410             self.port_pairs = self.port_pair_list
411             port_iter = \
412                 self.make_port_pairs_iter(self.float_x_plus_one_tenth_of_y, [self.mul])
413             pktq_out = self.make_str('TXQ{}', port_iter)
414             arpicmp_data['pktq_out'] = pktq_out
415
416         return arpicmp_data
417
418     def generate_final_txrx_data(self):
419         swq_start = self.swq - self.ports_len * self.worker_threads
420
421         txq_start = 0
422         txq_end = self.worker_threads
423
424         pktq_out_iter = self.make_range_port_pairs_iter(self.float_x_plus_one_tenth_of_y,
425                                                         txq_start, txq_end)
426
427         swq_str = self.make_range_str('SWQ{}', swq_start, self.swq)
428         txq_str = self.make_str('TXQ{}', pktq_out_iter)
429         rxtx_data = {
430             'pktq_in': swq_str,
431             'pktq_out': txq_str,
432             'pipeline_txrx_type': 'TXTX',
433             'core': self.gen_core(self.start_core),
434         }
435         pktq_in = rxtx_data['pktq_in']
436         pktq_in = '{0} {1}'.format(pktq_in, self.pktq_out_os[self.lb_index - 1])
437         rxtx_data['pktq_in'] = pktq_in
438         self.pipeline_counter += 1
439         return rxtx_data
440
441     def generate_initial_txrx_data(self):
442         pktq_iter = self.make_range_port_pairs_iter(self.float_x_plus_one_tenth_of_y,
443                                                     0, self.worker_threads)
444
445         rxq_str = self.make_str('RXQ{}', pktq_iter)
446         swq_str = self.make_range_str('SWQ{}', self.swq, offset=self.ports_len)
447         txrx_data = {
448             'pktq_in': rxq_str,
449             'pktq_out': swq_str + ' SWQ{0}'.format(self.lb_index - 1),
450             'pipeline_txrx_type': 'RXRX',
451             'core': self.gen_core(self.start_core),
452         }
453         self.pipeline_counter += 1
454         return txrx_data
455
456     def generate_lb_data(self):
457         pktq_in = self.make_range_str('SWQ{}', self.swq, offset=self.ports_len)
458         self.swq += self.ports_len
459
460         offset = self.ports_len * self.worker_threads
461         pktq_out = self.make_range_str('SWQ{}', self.swq, offset=offset)
462         self.pktq_out = pktq_out.split()
463
464         self.swq += (self.ports_len * self.worker_threads)
465         lb_data = {
466             'prv_que_handler': self.prv_que_handler,
467             'pktq_in': pktq_in,
468             'pktq_out': pktq_out,
469             'n_vnf_threads': str(self.worker_threads),
470             'core': self.gen_core(self.start_core),
471         }
472         self.pipeline_counter += 1
473         return lb_data
474
475     def generate_vnf_data(self):
476         if self.lb_config == self.HW_LB:
477             port_iter = self.make_port_pairs_iter(self.float_x_plus_one_tenth_of_y, [self.mul])
478             pktq_in = self.make_str('RXQ{}', port_iter)
479
480             self.mul += 1
481             port_iter = self.make_port_pairs_iter(self.float_x_plus_one_tenth_of_y, [self.mul])
482             pktq_out = self.make_str('TXQ{}', port_iter)
483
484             pipe_line_data = {
485                 'pktq_in': pktq_in,
486                 'pktq_out': pktq_out + ' SWQ{0}'.format(self.swq),
487                 'prv_que_handler': self.prv_que_handler,
488                 'core': self.gen_core(self.start_core),
489             }
490             self.swq += 1
491         else:
492             pipe_line_data = {
493                 'pktq_in': ' '.join((self.pktq_out.pop(0) for _ in range(self.ports_len))),
494                 'pktq_out': self.make_range_str('SWQ{}', self.swq, offset=self.ports_len),
495                 'prv_que_handler': self.prv_que_handler,
496                 'core': self.gen_core(self.start_core),
497             }
498             self.swq += self.ports_len
499
500         if self.vnf_type in ('ACL', 'VFW'):
501             pipe_line_data.pop('prv_que_handler')
502
503         if self.vnf_tpl.get('vnf_set'):
504             public_ip_port_range_list = self.vnf_tpl['public_ip_port_range'].split(':')
505             ip_in_hex = '{:x}'.format(int(public_ip_port_range_list[0], 16) + self.lb_index - 1)
506             public_ip_port_range_list[0] = ip_in_hex
507             self.vnf_tpl['public_ip_port_range'] = ':'.join(public_ip_port_range_list)
508
509         self.pipeline_counter += 1
510         return pipe_line_data
511
512     def generate_config_data(self):
513         self.init_write_parser_template()
514
515         # use master core for master, don't use self.start_core
516         self.write_parser.set('PIPELINE0', 'core', self.gen_core(self.master_core))
517         arpicmp_data = self.generate_arpicmp_data()
518         self.arpicmp_tpl.update(arpicmp_data)
519         self.update_write_parser(self.arpicmp_tpl)
520
521         self.start_core += 1
522         if self.vnf_type == 'CGNAPT':
523             self.pipeline_counter += 1
524             self.update_timer()
525
526         for lb in self.lb_to_port_pair_mapping:
527             self.lb_index = lb
528             self.mul = 0
529             port_pair_count = self.lb_to_port_pair_mapping[lb]
530             if not self.port_pair_list:
531                 continue
532
533             self.port_pairs = self.port_pair_list[:port_pair_count]
534             self.port_pair_list = self.port_pair_list[port_pair_count:]
535             self.ports_len = port_pair_count * 2
536             self.set_priv_que_handler()
537             if self.lb_config == 'SW':
538                 txrx_data = self.generate_initial_txrx_data()
539                 self.txrx_tpl.update(txrx_data)
540                 self.update_write_parser(self.txrx_tpl)
541                 self.start_core += 1
542                 lb_data = self.generate_lb_data()
543                 self.loadb_tpl.update(lb_data)
544                 self.update_write_parser(self.loadb_tpl)
545                 self.start_core += 1
546
547             for i in range(self.worker_threads):
548                 vnf_data = self.generate_vnf_data()
549                 if not self.vnf_tpl:
550                     self.vnf_tpl = {}
551                 self.vnf_tpl.update(vnf_data)
552                 self.update_write_parser(self.vnf_tpl)
553                 try:
554                     self.vnf_tpl.pop('vnf_set')
555                 except KeyError:
556                     pass
557                 else:
558                     self.vnf_tpl.pop('public_ip_port_range')
559                 self.generate_next_core_id()
560
561             if self.lb_config == 'SW':
562                 txrx_data = self.generate_final_txrx_data()
563                 self.txrx_tpl.update(txrx_data)
564                 self.update_write_parser(self.txrx_tpl)
565                 self.start_core += 1
566             self.vnf_tpl = self.get_config_tpl_data(self.vnf_type)
567
568     def generate_config(self):
569         self._port_pairs = PortPairs(self.vnfd_helper.interfaces)
570         self.port_pair_list = self._port_pairs.port_pair_list
571         self.all_ports = self._port_pairs.all_ports
572
573         self.get_lb_count()
574         self.generate_lb_to_port_pair_mapping()
575         self.generate_config_data()
576         self.write_parser.write(sys.stdout)
577         with open(self.tmp_file, 'a') as tfh:
578             self.write_parser.write(tfh)
579
580     def generate_link_config(self):
581         def build_args(port):
582             # lookup interface by name
583             virtual_interface = self.vnfd_helper.find_interface(name=port)["virtual-interface"]
584             local_ip = virtual_interface["local_ip"]
585             netmask = virtual_interface["netmask"]
586             port_num = self.vnfd_helper.port_num(port)
587             port_ip, prefix_len = self.validate_ip_and_prefixlen(local_ip, netmask)
588             return LINK_CONFIG_TEMPLATE.format(port_num, port_ip, prefix_len)
589
590         return ''.join(build_args(port) for port in self.all_ports)
591
592     def get_route_data(self, src_key, data_key, port):
593         route_list = self.vnfd['vdu'][0].get(src_key, [])
594         try:
595             return next((route[data_key] for route in route_list if route['if'] == port), None)
596         except (TypeError, StopIteration, KeyError):
597             return None
598
599     def get_ports_gateway(self, port):
600         return self.get_route_data('routing_table', 'gateway', port)
601
602     def get_ports_gateway6(self, port):
603         return self.get_route_data('nd_route_tbl', 'gateway', port)
604
605     def get_netmask_gateway(self, port):
606         return self.get_route_data('routing_table', 'netmask', port)
607
608     def get_netmask_gateway6(self, port):
609         return self.get_route_data('nd_route_tbl', 'netmask', port)
610
611     def generate_arp_config(self):
612         arp_config = []
613         for port in self.all_ports:
614             # ignore gateway, always use TG IP
615             # gateway = self.get_ports_gateway(port)
616             vintf = self.vnfd_helper.find_interface(name=port)["virtual-interface"]
617             dst_mac = vintf["dst_mac"]
618             dst_ip = vintf["dst_ip"]
619             # arp_config.append(
620             #     (self.vnfd_helper.port_num(port), gateway, dst_mac, self.txrx_pipeline))
621             # so dst_mac is the TG dest mac, so we need TG dest IP.
622             # should be dpdk_port_num
623             arp_config.append(
624                 (self.vnfd_helper.port_num(port), dst_ip, dst_mac, self.txrx_pipeline))
625
626         return '\n'.join(('p {3} arpadd {0} {1} {2}'.format(*values) for values in arp_config))
627
628     def generate_arp_config6(self):
629         arp_config6 = []
630         for port in self.all_ports:
631             # ignore gateway, always use TG IP
632             # gateway6 = self.get_ports_gateway6(port)
633             vintf = self.vnfd_helper.find_interface(name=port)["virtual-interface"]
634             dst_mac6 = vintf["dst_mac"]
635             dst_ip6 = vintf["dst_ip"]
636             # arp_config6.append(
637             #     (self.vnfd_helper.port_num(port), gateway6, dst_mac6, self.txrx_pipeline))
638             arp_config6.append(
639                 (self.vnfd_helper.port_num(port), dst_ip6, dst_mac6, self.txrx_pipeline))
640
641         return '\n'.join(('p {3} arpadd {0} {1} {2}'.format(*values) for values in arp_config6))
642
643     def generate_action_config(self):
644         port_list = (self.vnfd_helper.port_num(p) for p in self.all_ports)
645         if self.vnf_type == "VFW":
646             template = FW_ACTION_TEMPLATE
647         else:
648             template = ACTION_TEMPLATE
649
650         return ''.join((template.format(port) for port in port_list))
651
652     def get_ip_from_port(self, port):
653         # we can't use gateway because in OpenStack gateways interfer with floating ip routing
654         # return self.make_ip_addr(self.get_ports_gateway(port), self.get_netmask_gateway(port))
655         vintf = self.vnfd_helper.find_interface(name=port)["virtual-interface"]
656         ip = vintf["local_ip"]
657         netmask = vintf["netmask"]
658         return self.make_ip_addr(ip, netmask)
659
660     def get_network_and_prefixlen_from_ip_of_port(self, port):
661         ip_addr = self.get_ip_from_port(port)
662         # handle cases with no gateway
663         if ip_addr:
664             return ip_addr.network.network_address.exploded, ip_addr.network.prefixlen
665         else:
666             return None, None
667
668     def generate_rule_config(self):
669         cmd = 'acl' if self.vnf_type == "ACL" else "vfw"
670         rules_config = self.rules if self.rules else ''
671         new_rules = []
672         new_ipv6_rules = []
673         pattern = 'p {0} add {1} {2} {3} {4} {5} 0 65535 0 65535 0 0 {6}'
674         for src_intf, dst_intf in self.port_pair_list:
675             src_port = self.vnfd_helper.port_num(src_intf)
676             dst_port = self.vnfd_helper.port_num(dst_intf)
677
678             src_net, src_prefix_len = self.get_network_and_prefixlen_from_ip_of_port(src_intf)
679             dst_net, dst_prefix_len = self.get_network_and_prefixlen_from_ip_of_port(dst_intf)
680             # ignore entires with empty values
681             if all((src_net, src_prefix_len, dst_net, dst_prefix_len)):
682                 new_rules.append((cmd, self.txrx_pipeline, src_net, src_prefix_len,
683                                   dst_net, dst_prefix_len, dst_port))
684                 new_rules.append((cmd, self.txrx_pipeline, dst_net, dst_prefix_len,
685                                   src_net, src_prefix_len, src_port))
686
687             # src_net = self.get_ports_gateway6(port_pair[0])
688             # src_prefix_len = self.get_netmask_gateway6(port_pair[0])
689             # dst_net = self.get_ports_gateway6(port_pair[1])
690             # dst_prefix_len = self.get_netmask_gateway6(port_pair[0])
691             # # ignore entires with empty values
692             # if all((src_net, src_prefix_len, dst_net, dst_prefix_len)):
693             #     new_ipv6_rules.append((cmd, self.txrx_pipeline, src_net, src_prefix_len,
694             #                            dst_net, dst_prefix_len, dst_port))
695             #     new_ipv6_rules.append((cmd, self.txrx_pipeline, dst_net, dst_prefix_len,
696             #                            src_net, src_prefix_len, src_port))
697
698         acl_apply = "\np %s applyruleset" % cmd
699         new_rules_config = '\n'.join(pattern.format(*values) for values
700                                      in chain(new_rules, new_ipv6_rules))
701         return ''.join([rules_config, new_rules_config, acl_apply])
702
703     def generate_script_data(self):
704         self._port_pairs = PortPairs(self.vnfd_helper.interfaces)
705         self.port_pair_list = self._port_pairs.port_pair_list
706         self.get_lb_count()
707         script_data = {
708             'link_config': self.generate_link_config(),
709             'arp_config': self.generate_arp_config(),
710             # disable IPv6 for now
711             # 'arp_config6': self.generate_arp_config6(),
712             'arp_config6': "",
713             'actions': '',
714             'rules': '',
715         }
716
717         if self.vnf_type in ('ACL', 'VFW'):
718             script_data.update({
719                 'actions': self.generate_action_config(),
720                 'rules': self.generate_rule_config(),
721             })
722
723         return script_data
724
725     def generate_script(self, vnfd, rules=None):
726         self.vnfd = vnfd
727         self.rules = rules
728         script_data = self.generate_script_data()
729         script = SCRIPT_TPL.format(**script_data)
730         if self.lb_config == self.HW_LB:
731             script += 'set fwd rxonly'
732             hwlb_tpl = """
733 set_sym_hash_ena_per_port {0} enable
734 set_hash_global_config {0} simple_xor ipv4-udp enable
735 set_sym_hash_ena_per_port {1} enable
736 set_hash_global_config {1} simple_xor ipv4-udp enable
737 set_hash_input_set {0} ipv4-udp src-ipv4 udp-src-port add
738 set_hash_input_set {1} ipv4-udp dst-ipv4 udp-dst-port add
739 set_hash_input_set {0} ipv6-udp src-ipv6 udp-src-port add
740 set_hash_input_set {1} ipv6-udp dst-ipv6 udp-dst-port add
741 """
742             for port_pair in self.port_pair_list:
743                 script += hwlb_tpl.format(*(self.vnfd_helper.port_nums(port_pair)))
744         return script