Merge "Optimize Core usage for the sampleVNFs"
[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 = 0
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(0)
272         self.update_write_parser(timer_tpl)
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.pipeline_counter = self.read_parser.getint(section, 'core')
292                 self.txrx_pipeline = self.read_parser.getint(section, 'core')
293                 return
294             self.write_parser.add_section(section)
295             for name, value in self.read_parser.items(section):
296                 self.write_parser.set(section, name, value)
297
298     def update_write_parser(self, data):
299         section = "PIPELINE{0}".format(self.pipeline_counter)
300         self.write_parser.add_section(section)
301         for name, value in data.items():
302             self.write_parser.set(section, name, value)
303
304     def get_worker_threads(self, worker_threads):
305         if self.worker_config == '1t':
306             return worker_threads
307         else:
308             return worker_threads - worker_threads % 2
309
310     def generate_next_core_id(self):
311         if self.worker_config == '1t':
312             self.start_core += 1
313             return
314
315         try:
316             self.start_core = '{}h'.format(int(self.start_core))
317         except ValueError:
318             self.start_core = int(self.start_core[:-1]) + 1
319
320     def get_lb_count(self):
321         self.lb_count = int(min(len(self.port_pair_list), self.lb_count))
322
323     def generate_lb_to_port_pair_mapping(self):
324         self.lb_to_port_pair_mapping = defaultdict(int)
325         port_pair_count = len(self.port_pair_list)
326         lb_pair_count = int(port_pair_count / self.lb_count)
327         extra = port_pair_count % self.lb_count
328         extra_iter = repeat(lb_pair_count + 1, extra)
329         norm_iter = repeat(lb_pair_count, port_pair_count - extra)
330         new_values = {i: v for i, v in enumerate(chain(extra_iter, norm_iter), 1)}
331         self.lb_to_port_pair_mapping.update(new_values)
332
333     def set_priv_to_pub_mapping(self):
334         port_nums = [tuple(self.vnfd_helper.port_nums(x)) for x in self.port_pair_list]
335         return "".join(str(y).replace(" ", "") for y in
336                        port_nums)
337
338     def set_priv_que_handler(self):
339         # iterated twice, can't be generator
340         priv_to_pub_map = [tuple(self.vnfd_helper.port_nums(x)) for x in self.port_pairs]
341         # must be list to use .index()
342         port_list = list(chain.from_iterable(priv_to_pub_map))
343         uplink_ports = (x[0] for x in priv_to_pub_map)
344         self.prv_que_handler = '({})'.format(
345             "".join(("{},".format(port_list.index(x)) for x in uplink_ports)))
346
347     def generate_arp_route_tbl(self):
348         arp_route_tbl_tmpl = "({port0_dst_ip_hex},{port0_netmask_hex},{port_num}," \
349                              "{next_hop_ip_hex})"
350
351         def build_arp_config(port):
352             dpdk_port_num = self.vnfd_helper.port_num(port)
353             interface = self.vnfd_helper.find_interface(name=port)["virtual-interface"]
354             # We must use the dst because we are on the VNF and we need to
355             # reach the TG.
356             dst_port0_ip = ipaddress.ip_interface(six.text_type(
357                 "%s/%s" % (interface["dst_ip"], interface["netmask"])))
358
359             arp_vars = {
360                 "port0_dst_ip_hex": ip_to_hex(dst_port0_ip.network.network_address.exploded),
361                 "port0_netmask_hex": ip_to_hex(dst_port0_ip.network.netmask.exploded),
362                 # this is the port num that contains port0 subnet and next_hop_ip_hex
363                 # this is LINKID which should be based on DPDK port number
364                 "port_num": dpdk_port_num,
365                 # next hop is dst in this case
366                 # must be within subnet
367                 "next_hop_ip_hex": ip_to_hex(dst_port0_ip.ip.exploded),
368             }
369             return arp_route_tbl_tmpl.format(**arp_vars)
370
371         return ' '.join(build_arp_config(port) for port in self.all_ports)
372
373     def generate_arpicmp_data(self):
374         swq_in_str = self.make_range_str('SWQ{}', self.swq, offset=self.lb_count)
375         self.swq += self.lb_count
376         swq_out_str = self.make_range_str('SWQ{}', self.swq, offset=self.lb_count)
377         self.swq += self.lb_count
378         # ports_mac_list is disabled for some reason
379
380         # mac_iter = (self.vnfd_helper.find_interface(name=port)['virtual-interface']['local_mac']
381         #             for port in self.all_ports)
382         pktq_in_iter = ('RXQ{}.0'.format(self.vnfd_helper.port_num(x[0])) for x in
383                         self.port_pair_list)
384
385         arpicmp_data = {
386             'core': self.gen_core(0),
387             'pktq_in': swq_in_str,
388             'pktq_out': swq_out_str,
389             # we need to disable ports_mac_list?
390             # it looks like ports_mac_list is no longer required
391             # 'ports_mac_list': ' '.join(mac_iter),
392             'pktq_in_prv': ' '.join(pktq_in_iter),
393             'prv_to_pub_map': self.set_priv_to_pub_mapping(),
394             'arp_route_tbl': self.generate_arp_route_tbl(),
395             # nd_route_tbl must be set or we get segault on random OpenStack IPv6 traffic
396             # 'nd_route_tbl': "(0064:ff9b:0:0:0:0:9810:6414,120,0,0064:ff9b:0:0:0:0:9810:6414)"
397             # safe default?  route discard prefix to localhost
398             'nd_route_tbl': "(0100::,64,0,::1)"
399         }
400         self.pktq_out_os = swq_out_str.split(' ')
401         # HWLB is a run to complition. So override the pktq_in/pktq_out
402         if self.lb_config == self.HW_LB:
403             self.swq = 0
404             swq_in_str = \
405                 self.make_range_str('SWQ{}', self.swq,
406                                     offset=(self.lb_count * self.worker_threads))
407             arpicmp_data['pktq_in'] = swq_in_str
408             # WA: Since port_pairs will not be populated during arp pipeline
409             self.port_pairs = self.port_pair_list
410             port_iter = \
411                 self.make_port_pairs_iter(self.float_x_plus_one_tenth_of_y, [self.mul])
412             pktq_out = self.make_str('TXQ{}', port_iter)
413             arpicmp_data['pktq_out'] = pktq_out
414
415         return arpicmp_data
416
417     def generate_final_txrx_data(self, core=0):
418         swq_start = self.swq - self.ports_len * self.worker_threads
419
420         txq_start = 0
421         txq_end = self.worker_threads
422
423         pktq_out_iter = self.make_range_port_pairs_iter(self.float_x_plus_one_tenth_of_y,
424                                                         txq_start, txq_end)
425
426         swq_str = self.make_range_str('SWQ{}', swq_start, self.swq)
427         txq_str = self.make_str('TXQ{}', pktq_out_iter)
428         rxtx_data = {
429             'pktq_in': swq_str,
430             'pktq_out': txq_str,
431             'pipeline_txrx_type': 'TXTX',
432             'core': self.gen_core(core),
433         }
434         pktq_in = rxtx_data['pktq_in']
435         pktq_in = '{0} {1}'.format(pktq_in, self.pktq_out_os[self.lb_index - 1])
436         rxtx_data['pktq_in'] = pktq_in
437         self.pipeline_counter += 1
438         return rxtx_data
439
440     def generate_initial_txrx_data(self):
441         pktq_iter = self.make_range_port_pairs_iter(self.float_x_plus_one_tenth_of_y,
442                                                     0, self.worker_threads)
443
444         rxq_str = self.make_str('RXQ{}', pktq_iter)
445         swq_str = self.make_range_str('SWQ{}', self.swq, offset=self.ports_len)
446         txrx_data = {
447             'pktq_in': rxq_str,
448             'pktq_out': swq_str + ' SWQ{0}'.format(self.lb_index - 1),
449             'pipeline_txrx_type': 'RXRX',
450             'core': self.gen_core(self.start_core),
451         }
452         self.pipeline_counter += 1
453         return self.start_core, txrx_data
454
455     def generate_lb_data(self):
456         pktq_in = self.make_range_str('SWQ{}', self.swq, offset=self.ports_len)
457         self.swq += self.ports_len
458
459         offset = self.ports_len * self.worker_threads
460         pktq_out = self.make_range_str('SWQ{}', self.swq, offset=offset)
461         self.pktq_out = pktq_out.split()
462
463         self.swq += (self.ports_len * self.worker_threads)
464         lb_data = {
465             'prv_que_handler': self.prv_que_handler,
466             'pktq_in': pktq_in,
467             'pktq_out': pktq_out,
468             'n_vnf_threads': str(self.worker_threads),
469             'core': self.gen_core(self.start_core),
470         }
471         self.pipeline_counter += 1
472         return lb_data
473
474     def generate_vnf_data(self):
475         if self.lb_config == self.HW_LB:
476             port_iter = self.make_port_pairs_iter(self.float_x_plus_one_tenth_of_y, [self.mul])
477             pktq_in = self.make_str('RXQ{}', port_iter)
478
479             self.mul += 1
480             port_iter = self.make_port_pairs_iter(self.float_x_plus_one_tenth_of_y, [self.mul])
481             pktq_out = self.make_str('TXQ{}', port_iter)
482
483             pipe_line_data = {
484                 'pktq_in': pktq_in,
485                 'pktq_out': pktq_out + ' SWQ{0}'.format(self.swq),
486                 'prv_que_handler': self.prv_que_handler,
487                 'core': self.gen_core(self.start_core),
488             }
489             self.swq += 1
490         else:
491             pipe_line_data = {
492                 'pktq_in': ' '.join((self.pktq_out.pop(0) for _ in range(self.ports_len))),
493                 'pktq_out': self.make_range_str('SWQ{}', self.swq, offset=self.ports_len),
494                 'prv_que_handler': self.prv_que_handler,
495                 'core': self.gen_core(self.start_core),
496             }
497             self.swq += self.ports_len
498
499         if self.vnf_type in ('ACL', 'VFW'):
500             pipe_line_data.pop('prv_que_handler')
501
502         if self.vnf_tpl.get('vnf_set'):
503             public_ip_port_range_list = self.vnf_tpl['public_ip_port_range'].split(':')
504             ip_in_hex = '{:x}'.format(int(public_ip_port_range_list[0], 16) + self.lb_index - 1)
505             public_ip_port_range_list[0] = ip_in_hex
506             self.vnf_tpl['public_ip_port_range'] = ':'.join(public_ip_port_range_list)
507
508         self.pipeline_counter += 1
509         return pipe_line_data
510
511     def generate_config_data(self):
512         self.init_write_parser_template()
513
514         # use master core for master, don't use self.start_core
515         self.write_parser.set('PIPELINE0', 'core', self.gen_core(self.master_core))
516         arpicmp_data = self.generate_arpicmp_data()
517         self.arpicmp_tpl.update(arpicmp_data)
518         self.update_write_parser(self.arpicmp_tpl)
519
520         if self.vnf_type == 'CGNAPT':
521             self.pipeline_counter += 1
522             self.update_timer()
523
524         for lb in self.lb_to_port_pair_mapping:
525             self.lb_index = lb
526             self.mul = 0
527             port_pair_count = self.lb_to_port_pair_mapping[lb]
528             if not self.port_pair_list:
529                 continue
530
531             self.port_pairs = self.port_pair_list[:port_pair_count]
532             self.port_pair_list = self.port_pair_list[port_pair_count:]
533             self.ports_len = port_pair_count * 2
534             self.set_priv_que_handler()
535             if self.lb_config == 'SW':
536                 core, txrx_data = self.generate_initial_txrx_data()
537                 self.txrx_tpl.update(txrx_data)
538                 self.update_write_parser(self.txrx_tpl)
539                 self.start_core += 1
540                 lb_data = self.generate_lb_data()
541                 self.loadb_tpl.update(lb_data)
542                 self.update_write_parser(self.loadb_tpl)
543                 self.start_core += 1
544
545             for i in range(self.worker_threads):
546                 vnf_data = self.generate_vnf_data()
547                 if not self.vnf_tpl:
548                     self.vnf_tpl = {}
549                 self.vnf_tpl.update(vnf_data)
550                 self.update_write_parser(self.vnf_tpl)
551                 try:
552                     self.vnf_tpl.pop('vnf_set')
553                 except KeyError:
554                     pass
555                 else:
556                     self.vnf_tpl.pop('public_ip_port_range')
557                 self.generate_next_core_id()
558
559             if self.lb_config == 'SW':
560                 txrx_data = self.generate_final_txrx_data(core)
561                 self.txrx_tpl.update(txrx_data)
562                 self.update_write_parser(self.txrx_tpl)
563             self.vnf_tpl = self.get_config_tpl_data(self.vnf_type)
564
565     def generate_config(self):
566         self._port_pairs = PortPairs(self.vnfd_helper.interfaces)
567         self.port_pair_list = self._port_pairs.port_pair_list
568         self.all_ports = self._port_pairs.all_ports
569
570         self.get_lb_count()
571         self.generate_lb_to_port_pair_mapping()
572         self.generate_config_data()
573         self.write_parser.write(sys.stdout)
574         with open(self.tmp_file, 'a') as tfh:
575             self.write_parser.write(tfh)
576
577     def generate_link_config(self):
578         def build_args(port):
579             # lookup interface by name
580             virtual_interface = self.vnfd_helper.find_interface(name=port)["virtual-interface"]
581             local_ip = virtual_interface["local_ip"]
582             netmask = virtual_interface["netmask"]
583             port_num = self.vnfd_helper.port_num(port)
584             port_ip, prefix_len = self.validate_ip_and_prefixlen(local_ip, netmask)
585             return LINK_CONFIG_TEMPLATE.format(port_num, port_ip, prefix_len)
586
587         return ''.join(build_args(port) for port in self.all_ports)
588
589     def get_route_data(self, src_key, data_key, port):
590         route_list = self.vnfd['vdu'][0].get(src_key, [])
591         try:
592             return next((route[data_key] for route in route_list if route['if'] == port), None)
593         except (TypeError, StopIteration, KeyError):
594             return None
595
596     def get_ports_gateway(self, port):
597         return self.get_route_data('routing_table', 'gateway', port)
598
599     def get_ports_gateway6(self, port):
600         return self.get_route_data('nd_route_tbl', 'gateway', port)
601
602     def get_netmask_gateway(self, port):
603         return self.get_route_data('routing_table', 'netmask', port)
604
605     def get_netmask_gateway6(self, port):
606         return self.get_route_data('nd_route_tbl', 'netmask', port)
607
608     def generate_arp_config(self):
609         arp_config = []
610         for port in self.all_ports:
611             # ignore gateway, always use TG IP
612             # gateway = self.get_ports_gateway(port)
613             vintf = self.vnfd_helper.find_interface(name=port)["virtual-interface"]
614             dst_mac = vintf["dst_mac"]
615             dst_ip = vintf["dst_ip"]
616             # arp_config.append(
617             #     (self.vnfd_helper.port_num(port), gateway, dst_mac, self.txrx_pipeline))
618             # so dst_mac is the TG dest mac, so we need TG dest IP.
619             # should be dpdk_port_num
620             arp_config.append(
621                 (self.vnfd_helper.port_num(port), dst_ip, dst_mac, self.txrx_pipeline))
622
623         return '\n'.join(('p {3} arpadd {0} {1} {2}'.format(*values) for values in arp_config))
624
625     def generate_arp_config6(self):
626         arp_config6 = []
627         for port in self.all_ports:
628             # ignore gateway, always use TG IP
629             # gateway6 = self.get_ports_gateway6(port)
630             vintf = self.vnfd_helper.find_interface(name=port)["virtual-interface"]
631             dst_mac6 = vintf["dst_mac"]
632             dst_ip6 = vintf["dst_ip"]
633             # arp_config6.append(
634             #     (self.vnfd_helper.port_num(port), gateway6, dst_mac6, self.txrx_pipeline))
635             arp_config6.append(
636                 (self.vnfd_helper.port_num(port), dst_ip6, dst_mac6, self.txrx_pipeline))
637
638         return '\n'.join(('p {3} arpadd {0} {1} {2}'.format(*values) for values in arp_config6))
639
640     def generate_action_config(self):
641         port_list = (self.vnfd_helper.port_num(p) for p in self.all_ports)
642         if self.vnf_type == "VFW":
643             template = FW_ACTION_TEMPLATE
644         else:
645             template = ACTION_TEMPLATE
646
647         return ''.join((template.format(port) for port in port_list))
648
649     def get_ip_from_port(self, port):
650         # we can't use gateway because in OpenStack gateways interfer with floating ip routing
651         # return self.make_ip_addr(self.get_ports_gateway(port), self.get_netmask_gateway(port))
652         vintf = self.vnfd_helper.find_interface(name=port)["virtual-interface"]
653         ip = vintf["local_ip"]
654         netmask = vintf["netmask"]
655         return self.make_ip_addr(ip, netmask)
656
657     def get_network_and_prefixlen_from_ip_of_port(self, port):
658         ip_addr = self.get_ip_from_port(port)
659         # handle cases with no gateway
660         if ip_addr:
661             return ip_addr.network.network_address.exploded, ip_addr.network.prefixlen
662         else:
663             return None, None
664
665     def generate_rule_config(self):
666         cmd = 'acl' if self.vnf_type == "ACL" else "vfw"
667         rules_config = self.rules if self.rules else ''
668         new_rules = []
669         new_ipv6_rules = []
670         pattern = 'p {0} add {1} {2} {3} {4} {5} 0 65535 0 65535 0 0 {6}'
671         for src_intf, dst_intf in self.port_pair_list:
672             src_port = self.vnfd_helper.port_num(src_intf)
673             dst_port = self.vnfd_helper.port_num(dst_intf)
674
675             src_net, src_prefix_len = self.get_network_and_prefixlen_from_ip_of_port(src_intf)
676             dst_net, dst_prefix_len = self.get_network_and_prefixlen_from_ip_of_port(dst_intf)
677             # ignore entires with empty values
678             if all((src_net, src_prefix_len, dst_net, dst_prefix_len)):
679                 new_rules.append((cmd, self.txrx_pipeline, src_net, src_prefix_len,
680                                   dst_net, dst_prefix_len, dst_port))
681                 new_rules.append((cmd, self.txrx_pipeline, dst_net, dst_prefix_len,
682                                   src_net, src_prefix_len, src_port))
683
684             # src_net = self.get_ports_gateway6(port_pair[0])
685             # src_prefix_len = self.get_netmask_gateway6(port_pair[0])
686             # dst_net = self.get_ports_gateway6(port_pair[1])
687             # dst_prefix_len = self.get_netmask_gateway6(port_pair[0])
688             # # ignore entires with empty values
689             # if all((src_net, src_prefix_len, dst_net, dst_prefix_len)):
690             #     new_ipv6_rules.append((cmd, self.txrx_pipeline, src_net, src_prefix_len,
691             #                            dst_net, dst_prefix_len, dst_port))
692             #     new_ipv6_rules.append((cmd, self.txrx_pipeline, dst_net, dst_prefix_len,
693             #                            src_net, src_prefix_len, src_port))
694
695         acl_apply = "\np %s applyruleset" % cmd
696         new_rules_config = '\n'.join(pattern.format(*values) for values
697                                      in chain(new_rules, new_ipv6_rules))
698         return ''.join([rules_config, new_rules_config, acl_apply])
699
700     def generate_script_data(self):
701         self._port_pairs = PortPairs(self.vnfd_helper.interfaces)
702         self.port_pair_list = self._port_pairs.port_pair_list
703         self.get_lb_count()
704         script_data = {
705             'link_config': self.generate_link_config(),
706             'arp_config': self.generate_arp_config(),
707             # disable IPv6 for now
708             # 'arp_config6': self.generate_arp_config6(),
709             'arp_config6': "",
710             'actions': '',
711             'rules': '',
712         }
713
714         if self.vnf_type in ('ACL', 'VFW'):
715             script_data.update({
716                 'actions': self.generate_action_config(),
717                 'rules': self.generate_rule_config(),
718             })
719
720         return script_data
721
722     def generate_script(self, vnfd, rules=None):
723         self.vnfd = vnfd
724         self.rules = rules
725         script_data = self.generate_script_data()
726         script = SCRIPT_TPL.format(**script_data)
727         if self.lb_config == self.HW_LB:
728             script += 'set fwd rxonly'
729             hwlb_tpl = """
730 set_sym_hash_ena_per_port {0} enable
731 set_hash_global_config {0} simple_xor ipv4-udp enable
732 set_sym_hash_ena_per_port {1} enable
733 set_hash_global_config {1} simple_xor ipv4-udp enable
734 set_hash_input_set {0} ipv4-udp src-ipv4 udp-src-port add
735 set_hash_input_set {1} ipv4-udp dst-ipv4 udp-dst-port add
736 set_hash_input_set {0} ipv6-udp src-ipv6 udp-src-port add
737 set_hash_input_set {1} ipv6-udp dst-ipv6 udp-dst-port add
738 """
739             for port_pair in self.port_pair_list:
740                 script += hwlb_tpl.format(*(self.vnfd_helper.port_nums(port_pair)))
741         return script