From dee32d91e00c240cc9bacae30c78cdfb1939e69f Mon Sep 17 00:00:00 2001 From: Shiv Haris Date: Sat, 16 Aug 2014 19:09:52 -0700 Subject: [PATCH] Layer 3 service plugin to support hardware based routing Change-Id: Ie8e97a6d4fde0bda7cf049696e1384586492f676 Implements: blueprint brocade-l3-svi-service-plugin --- neutron/plugins/ml2/drivers/brocade/README.md | 10 + .../ml2/drivers/brocade/nos/nctemplates.py | 189 +++++++++++++- .../ml2/drivers/brocade/nos/nosdriver.py | 171 +++++++++++++ .../services/l3_router/brocade/__init__.py | 0 .../l3_router/brocade/l3_router_plugin.py | 236 ++++++++++++++++++ .../drivers/brocade/test_brocade_l3_plugin.py | 57 +++++ 6 files changed, 661 insertions(+), 2 deletions(-) create mode 100644 neutron/services/l3_router/brocade/__init__.py create mode 100644 neutron/services/l3_router/brocade/l3_router_plugin.py create mode 100644 neutron/tests/unit/ml2/drivers/brocade/test_brocade_l3_plugin.py diff --git a/neutron/plugins/ml2/drivers/brocade/README.md b/neutron/plugins/ml2/drivers/brocade/README.md index 5cf5a757115..9ee4e0b97b6 100644 --- a/neutron/plugins/ml2/drivers/brocade/README.md +++ b/neutron/plugins/ml2/drivers/brocade/README.md @@ -58,3 +58,13 @@ Additionally the brocade mechanism driver needs to be enabled from the ml2 confi Required L2 Agent This mechanism driver works in conjunction with an L2 Agent. The agent should be loaded as well in order for it to configure the virtual network int the host machine. Please see the configuration above. Atleast one of linuxbridge or openvswitch must be specified. + + + +Hardware L3 Router (SVI) + +Brocade Hardaware supports SVI (Switch Virtual Interface) which provides ASIC level routing/gateway functionality in the switch for configured VLANs. This Service plugin provides support for this feature which enables line rate routing/gateway functionality. + +l3_router_plugin.py provides a hardware based l3 router. + +Please refer to: https://blueprints.launchpad.net/neutron/+spec/brocade-l3-svi-service-plugin for more details diff --git a/neutron/plugins/ml2/drivers/brocade/nos/nctemplates.py b/neutron/plugins/ml2/drivers/brocade/nos/nctemplates.py index f885ff82095..08c3121e02b 100644 --- a/neutron/plugins/ml2/drivers/brocade/nos/nctemplates.py +++ b/neutron/plugins/ml2/drivers/brocade/nos/nctemplates.py @@ -217,7 +217,7 @@ xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" nc:operation="delete"> """ -#port-profile domain management commands +# port-profile domain management commands REMOVE_PORTPROFILE_FROM_DOMAIN = """ @@ -228,7 +228,7 @@ REMOVE_PORTPROFILE_FROM_DOMAIN = """ """ -#put port profile in default domain +# put port profile in default domain CONFIGURE_PORTPROFILE_IN_DOMAIN = """ @@ -240,12 +240,197 @@ CONFIGURE_PORTPROFILE_IN_DOMAIN = """ """ +# +# L3 Life-cycle Management Configuration Commands +# + +# Create SVI and assign ippaddres (rbridge_id,vlan_id,ip_address) +CONFIGURE_SVI_WITH_IP_ADDRESS = """ + + + {rbridge_id} + + + {vlan_id} + + +
+
{ip_address}
+
+
+
+
+
+
+
+""" + +# delete SVI (rbridge_id,vlan_id) +DELETE_SVI = """ + + + {rbridge_id} + + + {vlan_id} + + + + +""" + +# Activate SVI (rbridge_id,vlan_id) +ACTIVATE_SVI = """ + + + {rbridge_id} + + + {vlan_id} + + + + + +""" + +# Remove ipaddress from SVI (rbridge_id,vlan_id) +DECONFIGURE_IP_FROM_SVI = """ + + + {rbridge_id} + + + {vlan_id} + + +
+
{gw_ip}
+
+
+
+
+
+
+
+""" + +# create vrf (rbridge_id,vrf_name) +CREATE_VRF = """ + + + {rbridge_id} + + {vrf_name} + + + +""" + + +# delete vrf (rbridge_id,vrf_name) +DELETE_VRF = """ + + + {rbridge_id} + + {vrf_name} + + + +""" + +# configure route distinguisher for vrf (rbridge_id,vrf_name, rd) +CONFIGURE_RD_FOR_VRF = """ + + + {rbridge_id} + + {vrf_name} + {rd} + + + +""" + +# configure address-family for vrf (rbridge_id,vrf_name) +ADD_ADDRESS_FAMILY_FOR_VRF_V1 = """ + + + {rbridge_id} + + {vrf_name} + + + 1200 + + + + + +""" + +# configure address-family for vrf (rbridge_id,vrf_name) +ADD_ADDRESS_FAMILY_FOR_VRF = """ + + + {rbridge_id} + + {vrf_name} + + + + + + + + +""" + +# Bind vrf to SVI (rbridge_id,vlan_idi, vrf) +ADD_VRF_TO_SVI = """ + + + {rbridge_id} + + + {vlan_id} + + {vrf_name} + + + + + +""" + +# unbind vrf from SVI (rbridge_id,vlan_idi, vrf) +DELETE_VRF_FROM_SVI = """ + + + {rbridge_id} + + + {vlan_id} + + {vrf_name} + + + + + +""" + # # Constants # # Port profile naming convention for Neutron networks OS_PORT_PROFILE_NAME = "openstack-profile-{id}" +OS_VRF_NAME = "osv-{id}" # Port profile filter expressions PORT_PROFILE_XPATH_FILTER = "/port-profile" diff --git a/neutron/plugins/ml2/drivers/brocade/nos/nosdriver.py b/neutron/plugins/ml2/drivers/brocade/nos/nosdriver.py index 47d6330951c..3fa22965303 100644 --- a/neutron/plugins/ml2/drivers/brocade/nos/nosdriver.py +++ b/neutron/plugins/ml2/drivers/brocade/nos/nosdriver.py @@ -315,3 +315,174 @@ class NOSdriver(): if vfab_enable is not None: return "enabled" return "disabled" + + def create_svi(self, host, username, password, + rbridge_id, vlan_id, ip_address, router_id): + """create svi on configured rbridge-id.""" + try: + mgr = self.connect(host, username, password) + self.bind_vrf_to_svi(host, username, password, + rbridge_id, vlan_id, router_id) + self.configure_svi_with_ip_address(mgr, + rbridge_id, vlan_id, ip_address) + self.activate_svi(mgr, rbridge_id, vlan_id) + except Exception as ex: + with excutils.save_and_reraise_exception(): + LOG.exception(_("NETCONF error: %s"), ex) + self.close_session() + + def delete_svi(self, host, username, password, + rbridge_id, vlan_id, gw_ip, router_id): + """delete svi from configured rbridge-id.""" + try: + mgr = self.connect(host, username, password) + self.remove_svi(mgr, rbridge_id, vlan_id) + except Exception as ex: + with excutils.save_and_reraise_exception(): + LOG.exception(_("NETCONF error: %s"), ex) + self.close_session() + + def create_router(self, host, username, password, rbridge_id, router_id): + """create vrf and associate vrf.""" + router_id = router_id[0:11] + vrf_name = template.OS_VRF_NAME.format(id=router_id) + rd = router_id + ":" + router_id + try: + mgr = self.connect(host, username, password) + self.create_vrf(mgr, rbridge_id, vrf_name) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_("NETCONF error")) + self.close_session() + try: + # For Nos5.0.0 + self.configure_rd_for_vrf(mgr, rbridge_id, vrf_name, rd) + self.configure_address_family_for_vrf(mgr, rbridge_id, vrf_name) + except Exception: + with excutils.save_and_reraise_exception() as ctxt: + try: + # This is done because on 4.0.0 rd doesnt accept alpha + # character nor hyphen + rd = "".join(i for i in router_id if i in "0123456789") + rd = rd[:4] + ":" + rd[:4] + self.configure_rd_for_vrf(mgr, rbridge_id, vrf_name, rd) + self.configure_address_family_for_vrf_v1(mgr, + rbridge_id, + vrf_name) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_("NETCONF error")) + self.close_session() + + ctxt.reraise = False + + def delete_router(self, host, username, password, rbridge_id, router_id): + """delete router and associated vrf.""" + router_id = router_id[0:11] + vrf_name = template.OS_VRF_NAME.format(id=router_id) + try: + mgr = self.connect(host, username, password) + self.delete_vrf(mgr, rbridge_id, vrf_name) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_("NETCONF error")) + self.close_session() + + def bind_vrf_to_svi(self, host, username, password, rbridge_id, + vlan_id, router_id): + """binds vrf to a svi.""" + router_id = router_id[0:11] + vrf_name = template.OS_VRF_NAME.format(id=router_id) + try: + mgr = self.connect(host, username, password) + self.add_vrf_to_svi(mgr, rbridge_id, vlan_id, vrf_name) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_("NETCONF error")) + self.close_session() + + def unbind_vrf_to_svi(self, host, username, password, rbridge_id, + vlan_id, router_id): + """unbind vrf from the svi.""" + router_id = router_id[0:11] + vrf_name = template.OS_VRF_NAME.format(id=router_id) + try: + mgr = self.connect(host, username, password) + self.delete_vrf_from_svi(mgr, rbridge_id, vlan_id, vrf_name) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_("NETCONF error")) + self.close_session() + + def create_vrf(self, mgr, rbridge_id, vrf_name): + """create vrf on rbridge.""" + confstr = template.CREATE_VRF.format(rbridge_id=rbridge_id, + vrf_name=vrf_name) + mgr.edit_config(target='running', config=confstr) + + def delete_vrf(self, mgr, rbridge_id, vrf_name): + """delete vrf on rbridge.""" + + confstr = template.DELETE_VRF.format(rbridge_id=rbridge_id, + vrf_name=vrf_name) + mgr.edit_config(target='running', config=confstr) + + def configure_rd_for_vrf(self, mgr, rbridge_id, vrf_name, rd): + """configure rd on vrf on rbridge.""" + + confstr = template.CONFIGURE_RD_FOR_VRF.format(rbridge_id=rbridge_id, + vrf_name=vrf_name, + rd=rd) + mgr.edit_config(target='running', config=confstr) + + def configure_address_family_for_vrf_v1(self, mgr, rbridge_id, vrf_name): + """configure ipv4 address family to vrf on rbridge.""" + + confstr = template.ADD_ADDRESS_FAMILY_FOR_VRF_V1.format( + rbridge_id=rbridge_id, + vrf_name=vrf_name) + mgr.edit_config(target='running', config=confstr) + + def configure_address_family_for_vrf(self, mgr, rbridge_id, vrf_name): + """configure ipv4 address family to vrf on rbridge.""" + + confstr = template.ADD_ADDRESS_FAMILY_FOR_VRF.format( + rbridge_id=rbridge_id, vrf_name=vrf_name) + mgr.edit_config(target='running', config=confstr) + + def configure_svi_with_ip_address(self, mgr, rbridge_id, + vlan_id, ip_address): + """configure SVI with ip address on rbridge.""" + + confstr = template.CONFIGURE_SVI_WITH_IP_ADDRESS.format( + rbridge_id=rbridge_id, + vlan_id=vlan_id, + ip_address=ip_address) + + mgr.edit_config(target='running', config=confstr) + + def activate_svi(self, mgr, rbridge_id, vlan_id): + """activate the svi on the rbridge.""" + confstr = template.ACTIVATE_SVI.format(rbridge_id=rbridge_id, + vlan_id=vlan_id) + mgr.edit_config(target='running', config=confstr) + + def add_vrf_to_svi(self, mgr, rbridge_id, vlan_id, vrf_name): + """add vrf to svi on rbridge.""" + confstr = template.ADD_VRF_TO_SVI.format(rbridge_id=rbridge_id, + vlan_id=vlan_id, + vrf_name=vrf_name) + mgr.edit_config(target='running', config=confstr) + + def delete_vrf_from_svi(self, mgr, rbridge_id, vlan_id, vrf_name): + """delete vrf from svi on rbridge.""" + confstr = template.DELETE_VRF_FROM_SVI.format(rbridge_id=rbridge_id, + vlan_id=vlan_id, + vrf_name=vrf_name) + mgr.edit_config(target='running', config=confstr) + + def remove_svi(self, mgr, rbridge_id, vlan_id): + """delete vrf from svi on rbridge.""" + confstr = template.DELETE_SVI.format(rbridge_id=rbridge_id, + vlan_id=vlan_id) + mgr.edit_config(target='running', config=confstr) diff --git a/neutron/services/l3_router/brocade/__init__.py b/neutron/services/l3_router/brocade/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/neutron/services/l3_router/brocade/l3_router_plugin.py b/neutron/services/l3_router/brocade/l3_router_plugin.py new file mode 100644 index 00000000000..56428c8c2f8 --- /dev/null +++ b/neutron/services/l3_router/brocade/l3_router_plugin.py @@ -0,0 +1,236 @@ +# Copyright 2014 Brocade Communications System, 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. +# + + +"""Implentation of Brocade SVI service Plugin.""" + +from oslo.config import cfg + +from neutron.common import constants as l3_constants +from neutron.openstack.common import excutils +from neutron.openstack.common import log as logging +from neutron.plugins.ml2 import db +from neutron.plugins.ml2.drivers.brocade.db import models as brocade_db +from neutron.plugins.ml2.drivers.brocade.nos import nosdriver as driver +from neutron.services.l3_router import l3_router_plugin as router + + +DEVICE_OWNER_ROUTER_INTF = l3_constants.DEVICE_OWNER_ROUTER_INTF +DEVICE_OWNER_ROUTER_GW = l3_constants.DEVICE_OWNER_ROUTER_GW +DEVICE_OWNER_FLOATINGIP = l3_constants.DEVICE_OWNER_FLOATINGIP + +ML2_BROCADE = [cfg.StrOpt('address', default='', + help=_('The address of the host to SSH to')), + cfg.StrOpt('username', default='admin', + help=_('The SSH username to use')), + cfg.StrOpt('password', default='password', secret=True, + help=_('The SSH password to use')), + cfg.StrOpt('rbridge_id', default=1, + help=_('Rbridge id of provider edge router(s)')), + ] + +cfg.CONF.register_opts(ML2_BROCADE, "ml2_brocade") + +LOG = logging.getLogger(__name__) + + +class BrocadeSVIPlugin(router.L3RouterPlugin): + """Brocade SVI service Plugin.""" + + def __init__(self): + """Initialize Brocade Plugin + + Specify switch address and db configuration. + """ + super(BrocadeSVIPlugin, self).__init__() + self._switch = None + self._driver = None + self.brocade_init() + + def brocade_init(self): + """Brocade specific initialization.""" + LOG.debug("brocadeSVIPlugin::brocade_init()") + + self._switch = {'address': cfg.CONF.ml2_brocade.address, + 'username': cfg.CONF.ml2_brocade.username, + 'password': cfg.CONF.ml2_brocade.password, + 'rbridge_id': cfg.CONF.ml2_brocade.rbridge_id + } + self._driver = driver.NOSdriver() + LOG.info(_("rbridge id %s"), self._switch['rbridge_id']) + + def create_router(self, context, router): + """ creates a vrf on NOS device.""" + LOG.debug("BrocadeSVIPlugin.create_router called: ") + with context.session.begin(subtransactions=True): + new_router = super(BrocadeSVIPlugin, self).create_router(context, + router) + # Router on VDX + try: + switch = self._switch + self._driver.create_router(switch['address'], + switch['username'], + switch['password'], + switch['rbridge_id'], + str(new_router['id'])) + except Exception: + with excutils.save_and_reraise_exception(): + with context.session.begin(subtransactions=True): + super(BrocadeSVIPlugin, self).delete_router( + context, + new_router['id']) + + LOG.debug("BrocadeSVIPlugin.create_router: " + "router created on VDX switch") + return new_router + + def delete_router(self, context, router_id): + """ delete a vrf on NOS device.""" + router = super(BrocadeSVIPlugin, self).get_router(context, router_id) + super(BrocadeSVIPlugin, self).delete_router(context, router_id) + try: + switch = self._switch + self._driver.delete_router(switch['address'], + switch['username'], + switch['password'], + switch['rbridge_id'], + str(router['id'])) + except Exception: + excutils.save_and_reraise_exception() + + def add_router_interface(self, context, router_id, interface_info): + """creates svi on NOS device and assigns ip addres to SVI.""" + LOG.debug("BrocadeSVIPlugin.add_router_interface on VDX: " + "router_id=%(router_id)s " + "interface_info=%(interface_info)r", + {'router_id': router_id, 'interface_info': interface_info}) + + with context.session.begin(subtransactions=True): + + info = super(BrocadeSVIPlugin, self).add_router_interface( + context, router_id, interface_info) + + port = db.get_port(context.session, info["port_id"]) + + # shutting down neutron port to allow NOS to do Arp/Routing + port['admin_state_up'] = False + port['port'] = port + self._core_plugin.update_port(context, info["port_id"], port) + + interface_info = info + subnet = self._core_plugin._get_subnet(context, + interface_info["subnet_id"]) + cidr = subnet["cidr"] + net_addr, net_len = self.net_addr(cidr) + gateway_ip = subnet["gateway_ip"] + network_id = subnet['network_id'] + bnet = brocade_db.get_network(context, network_id) + vlan_id = bnet['vlan'] + gateway_ip_cidr = gateway_ip + '/' + str(net_len) + LOG.debug("Allocated cidr %(cidr)s from the pool, " + "network_id %(net_id)s " + "bnet %(bnet)s " + "vlan %(vlan_id)d " % ({'cidr': gateway_ip_cidr, + 'net_id': network_id, + 'bnet': bnet, + 'vlan_id': int(vlan_id)})) + port_filters = {'network_id': [network_id], + 'device_owner': [DEVICE_OWNER_ROUTER_INTF]} + port_count = self._core_plugin.get_ports_count(context, + port_filters) + LOG.info(_("BrocadeSVIPlugin.add_router_interface ports_count %d"), + port_count) + + # port count is checked against 2 since the current port is already + # added to db + if port_count == 2: + # This subnet is already part of some router + # (this is not supported in this version of brocade svi plugin) + LOG.error(_("BrocadeSVIPlugin: adding redundant router " + "interface is not supported")) + raise Exception(_("BrocadeSVIPlugin:adding redundant router " + "interface is not supported")) + + try: + switch = self._switch + self._driver.create_svi(switch['address'], + switch['username'], + switch['password'], + switch['rbridge_id'], + vlan_id, + gateway_ip_cidr, + str(router_id)) + except Exception: + LOG.error(_("Failed to create Brocade resources to add router " + "interface. info=%(info)s, router_id=%(router_id)s"), + {"info": info, "router_id": router_id}) + with excutils.save_and_reraise_exception(): + with context.session.begin(subtransactions=True): + self.remove_router_interface(context, router_id, + interface_info) + return info + + def remove_router_interface(self, context, router_id, interface_info): + """Deletes svi from NOS device.""" + LOG.debug("BrocadeSVIPlugin.remove_router_interface called: " + "router_id=%(router_id)s " + "interface_info=%(interface_info)r", + {'router_id': router_id, 'interface_info': interface_info}) + + with context.session.begin(subtransactions=True): + info = super(BrocadeSVIPlugin, self).remove_router_interface( + context, router_id, interface_info) + try: + subnet = self._core_plugin._get_subnet(context, + info['subnet_id']) + cidr = subnet['cidr'] + net_addr, net_len = self.net_addr(cidr) + gateway_ip = subnet['gateway_ip'] + network_id = subnet['network_id'] + bnet = brocade_db.get_network(context, network_id) + vlan_id = bnet['vlan'] + gateway_ip_cidr = gateway_ip + '/' + str(net_len) + LOG.debug("remove_router_interface removed cidr %(cidr)s" + " from the pool," + " network_id %(net_id)s bnet %(bnet)s" + " vlan %(vlan_id)d" % + ({'cidr': gateway_ip_cidr, + 'net_id': network_id, + 'bnet': bnet, 'vlan_id': int(vlan_id)})) + switch = self._switch + self._driver.delete_svi(switch['address'], + switch['username'], + switch['password'], + switch['rbridge_id'], + vlan_id, + gateway_ip_cidr, + str(router_id)) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.error(_("Fail remove of interface from brocade router " + "interface. info=%(info)s, " + "router_id=%(router_id)s") % + ({"info": info, "router_id": router_id})) + return True + + @staticmethod + def net_addr(addr): + """Get network address prefix and length from a given address.""" + if addr is None: + return None, None + nw_addr, nw_len = addr.split('/') + nw_len = int(nw_len) + return nw_addr, nw_len diff --git a/neutron/tests/unit/ml2/drivers/brocade/test_brocade_l3_plugin.py b/neutron/tests/unit/ml2/drivers/brocade/test_brocade_l3_plugin.py new file mode 100644 index 00000000000..ec8bc5f510a --- /dev/null +++ b/neutron/tests/unit/ml2/drivers/brocade/test_brocade_l3_plugin.py @@ -0,0 +1,57 @@ +# Copyright (c) 2014 OpenStack Foundation +# +# 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. +# +# + +import mock +from oslo.config import cfg + +from neutron.db import api as db +from neutron.openstack.common import context +from neutron.openstack.common import importutils +from neutron.openstack.common import log as logging +from neutron.tests.unit import test_l3_plugin + +LOG = logging.getLogger(__name__) +L3_SVC_PLUGIN = ('neutron.services.l3_router.' + 'brocade.l3_router_plugin.BrocadeSVIPlugin') + + +class BrocadeSVIPlugin_TestCases(test_l3_plugin.TestL3NatBasePlugin): + + def setUp(self): + + def mocked_brocade_init(self): + LOG.debug("brocadeSVIPlugin::mocked_brocade_init()") + + self._switch = {'address': cfg.CONF.ml2_brocade.address, + 'username': cfg.CONF.ml2_brocade.username, + 'password': cfg.CONF.ml2_brocade.password, + 'rbridge_id': cfg.CONF.ml2_brocade.rbridge_id + } + LOG.info(_("rbridge id %s"), self._switch['rbridge_id']) + self._driver = mock.MagicMock() + + self.context = context.get_admin_context() + self.context.session = db.get_session() + self.l3_plugin = importutils.import_object(L3_SVC_PLUGIN) + with mock.patch.object(self.l3_plugin, + 'brocade_init', new=mocked_brocade_init): + super(BrocadeSVIPlugin_TestCases, self).setUp() + + +class TestBrocadeSVINatBase(test_l3_plugin.L3NatExtensionTestCase, + BrocadeSVIPlugin_TestCases): + pass