summaryrefslogtreecommitdiffstats
path: root/nfvbench/chain_router.py
blob: ac8947608b41eace3936367d223d803444cff596 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
#!/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."""

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)