Add L3 traffic management with Neutron routers
[nfvbench.git] / nfvbench / chain_router.py
diff --git a/nfvbench/chain_router.py b/nfvbench/chain_router.py
new file mode 100644 (file)
index 0000000..9372716
--- /dev/null
@@ -0,0 +1,186 @@
+#!/usr/bin/env python
+# Copyright 2018 Cisco Systems, Inc.  All rights reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+#
+
+# This module takes care of chaining routers
+#
+"""NFVBENCH CHAIN DISCOVERY/STAGING.
+
+This module takes care of staging/discovering resources that are participating in a
+L3 benchmarking session: routers, networks, ports, routes.
+If a resource is discovered with the same name, it will be reused.
+Otherwise it will be created.
+
+Once created/discovered, instances are checked to be in the active state (ready to pass traffic)
+Configuration parameters that will influence how these resources are staged/related:
+- openstack or no openstack
+- chain type
+- number of chains
+- number of VNF in each chain (PVP, PVVP)
+- SRIOV and middle port SRIOV for port types
+- whether networks are shared across chains or not
+
+There is not traffic generation involved in this module.
+"""
+import time
+
+from netaddr import IPAddress
+from netaddr import IPNetwork
+
+from log import LOG
+
+
+class ChainException(Exception):
+    """Exception while operating the chains."""
+
+    pass
+
+
+class ChainRouter(object):
+    """Could be a shared router across all chains or a chain private router."""
+
+    def __init__(self, manager, name, subnets, routes):
+        """Create a router for given chain."""
+        self.manager = manager
+        self.subnets = subnets
+        self.routes = routes
+        self.name = name
+        self.ports = [None, None]
+        self.reuse = False
+        self.router = None
+        try:
+            self._setup()
+        except Exception:
+            LOG.error("Error creating router %s", self.name)
+            self.delete()
+            raise
+
+    def _setup(self):
+        # Lookup if there is a matching router with same name
+        routers = self.manager.neutron_client.list_routers(name=self.name)
+
+        if routers['routers']:
+            router = routers['routers'][0]
+            # a router of same name already exists, we need to verify it has the same
+            # characteristics
+            if self.subnets:
+                for subnet in self.subnets:
+                    if not self.get_router_interface(router['id'], subnet.network['subnets'][0]):
+                        raise ChainException("Mismatch of 'subnet_id' for reused "
+                                             "router '{router}'.Router has no subnet id '{sub_id}'."
+                                             .format(router=self.name,
+                                                     sub_id=subnet.network['subnets'][0]))
+                interfaces = self.manager.neutron_client.list_ports(device_id=router['id'])['ports']
+                for interface in interfaces:
+                    if self.is_ip_in_network(
+                            interface['fixed_ips'][0]['ip_address'],
+                            self.manager.config.traffic_generator.tg_gateway_ip_cidrs[0]) \
+                        or self.is_ip_in_network(
+                            interface['fixed_ips'][0]['ip_address'],
+                            self.manager.config.traffic_generator.tg_gateway_ip_cidrs[1]):
+                        self.ports[0] = interface
+                    else:
+                        self.ports[1] = interface
+            if self.routes:
+                for route in self.routes:
+                    if route not in router['routes']:
+                        LOG.info("Mismatch of 'router' for reused router '%s'."
+                                 "Router has no existing route destination '%s', "
+                                 "and nexthop '%s'.", self.name,
+                                 route['destination'],
+                                 route['nexthop'])
+                        LOG.info("New route added to router %s for reused ", self.name)
+                        body = {
+                            'router': {
+                                'routes': self.routes
+                            }
+                        }
+                        self.manager.neutron_client.update_router(router['id'], body)
+
+            LOG.info('Reusing existing router: %s', self.name)
+            self.reuse = True
+            self.router = router
+            return
+
+        body = {
+            'router': {
+                'name': self.name,
+                'admin_state_up': True
+            }
+        }
+        router = self.manager.neutron_client.create_router(body)['router']
+        router_id = router['id']
+
+        if self.subnets:
+            for subnet in self.subnets:
+                router_interface = {'subnet_id': subnet.network['subnets'][0]}
+                self.manager.neutron_client.add_interface_router(router_id, router_interface)
+            interfaces = self.manager.neutron_client.list_ports(device_id=router_id)['ports']
+            for interface in interfaces:
+                itf = interface['fixed_ips'][0]['ip_address']
+                cidr0 = self.manager.config.traffic_generator.tg_gateway_ip_cidrs[0]
+                cidr1 = self.manager.config.traffic_generator.tg_gateway_ip_cidrs[1]
+                if self.is_ip_in_network(itf, cidr0) or self.is_ip_in_network(itf, cidr1):
+                    self.ports[0] = interface
+                else:
+                    self.ports[1] = interface
+
+        if self.routes:
+            body = {
+                'router': {
+                    'routes': self.routes
+                }
+            }
+            self.manager.neutron_client.update_router(router_id, body)
+
+        LOG.info('Created router: %s.', self.name)
+        self.router = self.manager.neutron_client.show_router(router_id)
+
+    def get_uuid(self):
+        """
+        Extract UUID of this router.
+
+        :return: UUID of this router
+        """
+        return self.router['id']
+
+    def get_router_interface(self, router_id, subnet_id):
+        interfaces = self.manager.neutron_client.list_ports(device_id=router_id)['ports']
+        matching_interface = None
+        for interface in interfaces:
+            if interface['fixed_ips'][0]['subnet_id'] == subnet_id:
+                matching_interface = interface
+        return matching_interface
+
+    def is_ip_in_network(self, interface_ip, cidr):
+        return IPAddress(interface_ip) in IPNetwork(cidr)
+
+    def delete(self):
+        """Delete this router."""
+        if not self.reuse and self.router:
+            retry = 0
+            while retry < self.manager.config.generic_retry_count:
+                try:
+                    self.manager.neutron_client.delete_router(self.router['id'])
+                    LOG.info("Deleted router: %s", self.name)
+                    return
+                except Exception:
+                    retry += 1
+                    LOG.info('Error deleting router %s (retry %d/%d)...',
+                             self.name,
+                             retry,
+                             self.manager.config.generic_retry_count)
+                    time.sleep(self.manager.config.generic_poll_sec)
+            LOG.error('Unable to delete router: %s', self.name)