Add L3 traffic management with Neutron routers
[nfvbench.git] / nfvbench / chaining.py
index 898e9ea..3350299 100644 (file)
@@ -54,13 +54,16 @@ from neutronclient.neutron import client as neutronclient
 from novaclient.client import Client
 
 from attrdict import AttrDict
+from chain_router import ChainRouter
 import compute
 from log import LOG
 from specs import ChainType
-
 # Left and right index for network and port lists
 LEFT = 0
 RIGHT = 1
+# L3 traffic edge networks are at the end of networks list
+EDGE_LEFT = -2
+EDGE_RIGHT = -1
 # Name of the VM config file
 NFVBENCH_CFG_FILENAME = 'nfvbenchvm.conf'
 # full pathame of the VM config in the VM
@@ -76,6 +79,7 @@ class ChainException(Exception):
 
     pass
 
+
 class NetworkEncaps(object):
     """Network encapsulation."""
 
@@ -174,6 +178,10 @@ class ChainVnfPort(object):
         """Get the MAC address for this port."""
         return self.port['mac_address']
 
+    def get_ip(self):
+        """Get the IP address for this port."""
+        return self.port['fixed_ips'][0]['ip_address']
+
     def delete(self):
         """Delete this port instance."""
         if self.reuse or not self.port:
@@ -222,6 +230,8 @@ class ChainNetwork(object):
         self.reuse = False
         self.network = None
         self.vlan = None
+        if manager.config.l3_router and hasattr(network_config, 'router_name'):
+            self.router_name = network_config.router_name
         try:
             self._setup(network_config, lookup_only)
         except Exception:
@@ -294,7 +304,7 @@ class ChainNetwork(object):
                 'network': {
                     'name': self.name,
                     'admin_state_up': True
-                    }
+                }
             }
             if network_config.network_type:
                 body['network']['provider:network_type'] = network_config.network_type
@@ -388,6 +398,7 @@ class ChainVnf(object):
         # For example if 7 idle interfaces are requested, the corresp. ports will be
         # at index 2 to 8
         self.ports = []
+        self.routers = []
         self.status = None
         self.instance = None
         self.reuse = False
@@ -397,7 +408,10 @@ class ChainVnf(object):
         try:
             # the vnf_id is conveniently also the starting index in networks
             # for the left and right networks associated to this VNF
-            self._setup(networks[vnf_id:vnf_id + 2])
+            if self.manager.config.l3_router:
+                self._setup(networks[vnf_id:vnf_id + 4])
+            else:
+                self._setup(networks[vnf_id:vnf_id + 2])
         except Exception:
             LOG.error("Error creating VNF %s", self.name)
             self.delete()
@@ -406,22 +420,52 @@ class ChainVnf(object):
     def _get_vm_config(self, remote_mac_pair):
         config = self.manager.config
         devices = self.manager.generator_config.devices
+
+        if config.l3_router:
+            tg_gateway1_ip = self.routers[LEFT].ports[1]['fixed_ips'][0][
+                'ip_address']  # router edge ip left
+            tg_gateway2_ip = self.routers[RIGHT].ports[1]['fixed_ips'][0][
+                'ip_address']  # router edge ip right
+            tg_mac1 = self.routers[LEFT].ports[1]['mac_address']  # router edge mac left
+            tg_mac2 = self.routers[RIGHT].ports[1]['mac_address']  # router edge mac right
+            # edge cidr mask left
+            vnf_gateway1_cidr = \
+                self.ports[LEFT].get_ip() + self.manager.config.edge_networks.left.cidr[-3:]
+            # edge cidr mask right
+            vnf_gateway2_cidr = \
+                self.ports[RIGHT].get_ip() + self.manager.config.edge_networks.right.cidr[-3:]
+            if config.vm_forwarder != 'vpp':
+                raise ChainException(
+                    'L3 router mode imply to set VPP as VM forwarder.'
+                    'Please update your config file with: vm_forwarder: vpp')
+        else:
+            tg_gateway1_ip = devices[LEFT].tg_gateway_ip_addrs
+            tg_gateway2_ip = devices[RIGHT].tg_gateway_ip_addrs
+            tg_mac1 = remote_mac_pair[0]
+            tg_mac2 = remote_mac_pair[1]
+
+            g1cidr = devices[LEFT].get_gw_ip(
+                self.chain.chain_id) + self.manager.config.internal_networks.left.cidr[-3:]
+            g2cidr = devices[RIGHT].get_gw_ip(
+                self.chain.chain_id) + self.manager.config.internal_networks.right.cidr[-3:]
+
+            vnf_gateway1_cidr = g1cidr
+            vnf_gateway2_cidr = g2cidr
+
         with open(BOOT_SCRIPT_PATHNAME, 'r') as boot_script:
             content = boot_script.read()
-        g1cidr = devices[LEFT].get_gw_ip(self.chain.chain_id) + '/8'
-        g2cidr = devices[RIGHT].get_gw_ip(self.chain.chain_id) + '/8'
         vm_config = {
             'forwarder': config.vm_forwarder,
             'intf_mac1': self.ports[LEFT].get_mac(),
             'intf_mac2': self.ports[RIGHT].get_mac(),
-            'tg_gateway1_ip': devices[LEFT].tg_gateway_ip_addrs,
-            'tg_gateway2_ip': devices[RIGHT].tg_gateway_ip_addrs,
+            'tg_gateway1_ip': tg_gateway1_ip,
+            'tg_gateway2_ip': tg_gateway2_ip,
             'tg_net1': devices[LEFT].ip_addrs,
             'tg_net2': devices[RIGHT].ip_addrs,
-            'vnf_gateway1_cidr': g1cidr,
-            'vnf_gateway2_cidr': g2cidr,
-            'tg_mac1': remote_mac_pair[0],
-            'tg_mac2': remote_mac_pair[1],
+            'vnf_gateway1_cidr': vnf_gateway1_cidr,
+            'vnf_gateway2_cidr': vnf_gateway2_cidr,
+            'tg_mac1': tg_mac1,
+            'tg_mac2': tg_mac2,
             'vif_mq_size': config.vif_multiqueue_size
         }
         return content.format(**vm_config)
@@ -505,21 +549,27 @@ class ChainVnf(object):
         # Check if we can reuse an instance with same name
         for instance in self.manager.existing_instances:
             if instance.name == self.name:
+                instance_left = LEFT
+                instance_right = RIGHT
+                # In case of L3 traffic instance use edge networks
+                if self.manager.config.l3_router:
+                    instance_left = EDGE_LEFT
+                    instance_right = EDGE_RIGHT
                 # Verify that other instance characteristics match
                 if instance.flavor['id'] != flavor_id:
                     self._reuse_exception('Flavor mismatch')
                 if instance.status != "ACTIVE":
                     self._reuse_exception('Matching instance is not in ACTIVE state')
                 # The 2 networks for this instance must also be reused
-                if not networks[LEFT].reuse:
-                    self._reuse_exception('network %s is new' % networks[LEFT].name)
-                if not networks[RIGHT].reuse:
-                    self._reuse_exception('network %s is new' % networks[RIGHT].name)
+                if not networks[instance_left].reuse:
+                    self._reuse_exception('network %s is new' % networks[instance_left].name)
+                if not networks[instance_right].reuse:
+                    self._reuse_exception('network %s is new' % networks[instance_right].name)
                 # instance.networks have the network names as keys:
                 # {'nfvbench-rnet0': ['192.168.2.10'], 'nfvbench-lnet0': ['192.168.1.8']}
-                if networks[LEFT].name not in instance.networks:
+                if networks[instance_left].name not in instance.networks:
                     self._reuse_exception('Left network mismatch')
-                if networks[RIGHT].name not in instance.networks:
+                if networks[instance_right].name not in instance.networks:
                     self._reuse_exception('Right network mismatch')
 
                 self.reuse = True
@@ -527,16 +577,51 @@ class ChainVnf(object):
                 LOG.info('Reusing existing instance %s on %s',
                          self.name, self.get_hypervisor_name())
         # create or reuse/discover 2 ports per instance
-        self.ports = [ChainVnfPort(self.name + '-' + str(index),
-                                   self,
-                                   networks[index],
-                                   self._get_vnic_type(index)) for index in [0, 1]]
+        if self.manager.config.l3_router:
+            self.ports = [ChainVnfPort(self.name + '-' + str(index),
+                                       self,
+                                       networks[index + 2],
+                                       self._get_vnic_type(index)) for index in [0, 1]]
+        else:
+            self.ports = [ChainVnfPort(self.name + '-' + str(index),
+                                       self,
+                                       networks[index],
+                                       self._get_vnic_type(index)) for index in [0, 1]]
 
         # create idle networks and ports only if instance is not reused
         # if reused, we do not care about idle networks/ports
         if not self.reuse:
             self._get_idle_networks_ports()
 
+        # Create neutron routers for L3 traffic use case
+        if self.manager.config.l3_router and self.manager.openstack:
+            internal_nets = networks[:2]
+            if self.manager.config.service_chain == ChainType.PVP:
+                edge_nets = networks[2:]
+            else:
+                edge_nets = networks[3:]
+            subnets_left = [internal_nets[0], edge_nets[0]]
+            routes_left = [{'destination': self.manager.config.traffic_generator.ip_addrs[0],
+                            'nexthop': self.manager.config.traffic_generator.tg_gateway_ip_addrs[
+                                0]},
+                           {'destination': self.manager.config.traffic_generator.ip_addrs[1],
+                            'nexthop': self.ports[0].get_ip()}]
+            self.routers.append(
+                ChainRouter(self.manager, edge_nets[0].router_name, subnets_left, routes_left))
+            subnets_right = [internal_nets[1], edge_nets[1]]
+            routes_right = [{'destination': self.manager.config.traffic_generator.ip_addrs[0],
+                             'nexthop': self.ports[1].get_ip()},
+                            {'destination': self.manager.config.traffic_generator.ip_addrs[1],
+                             'nexthop': self.manager.config.traffic_generator.tg_gateway_ip_addrs[
+                                 1]}]
+            self.routers.append(
+                ChainRouter(self.manager, edge_nets[1].router_name, subnets_right, routes_right))
+            # Overload gateway_ips property with router ip address for ARP and traffic calls
+            self.manager.generator_config.devices[LEFT].set_gw_ip(
+                self.routers[LEFT].ports[0]['fixed_ips'][0]['ip_address'])  # router edge ip left)
+            self.manager.generator_config.devices[RIGHT].set_gw_ip(
+                self.routers[RIGHT].ports[0]['fixed_ips'][0]['ip_address'])  # router edge ip right)
+
         # if no reuse, actual vm creation is deferred after all ports in the chain are created
         # since we need to know the next mac in a multi-vnf chain
 
@@ -659,6 +744,7 @@ class ChainVnf(object):
             for network in self.idle_networks:
                 network.delete()
 
+
 class Chain(object):
     """A class to manage a single chain.
 
@@ -720,7 +806,8 @@ class Chain(object):
 
     def get_length(self):
         """Get the number of VNF in the chain."""
-        return len(self.networks) - 1
+        # Take into account 2 edge networks for routers
+        return len(self.networks) - 3 if self.manager.config.l3_router else len(self.networks) - 1
 
     def _get_remote_mac_pairs(self):
         """Get the list of remote mac pairs for every VNF in the chain.
@@ -1156,6 +1243,10 @@ class ChainManager(object):
                 net_cfg = [int_nets.left, int_nets.right]
             else:
                 net_cfg = [int_nets.left, int_nets.middle, int_nets.right]
+            if self.config.l3_router:
+                edge_nets = self.config.edge_networks
+                net_cfg.append(edge_nets.left)
+                net_cfg.append(edge_nets.right)
         networks = []
         try:
             for cfg in net_cfg: