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