NSXv driver for Layer 2 gateway

This patch adds the backend driver to support Layer 2 gateway
API calls for NSXv.

Partial-bug: #1481087

Change-Id: Iea8b5390300dfd653b275c4389bc0d12bc4cc59f
This commit is contained in:
Zhenmei 2015-08-11 07:18:16 +08:00
parent 4bf8308494
commit 0fc47eabf5
8 changed files with 401 additions and 0 deletions

View File

@ -141,3 +141,7 @@ class StaleRevision(ManagerError):
class NsxL2GWConnectionMappingNotFound(n_exc.NotFound): class NsxL2GWConnectionMappingNotFound(n_exc.NotFound):
message = _('Unable to find mapping for L2 gateway connection: %(conn)s') message = _('Unable to find mapping for L2 gateway connection: %(conn)s')
class NsxL2GWDeviceNotFound(n_exc.NotFound):
message = _('Unable to find logical L2 gateway device.')

View File

@ -29,3 +29,6 @@ INTER_EDGE_PURPOSE = 'inter_edge_net'
# etc # etc
INTERNAL_TENANT_ID = 'a1b2c3d4-e5f6-eeff-ffee-6f5e4d3c2b1a' INTERNAL_TENANT_ID = 'a1b2c3d4-e5f6-eeff-ffee-6f5e4d3c2b1a'
# L2 gateway edge name prefix
L2_GATEWAY_EDGE = 'L2 bridging'

View File

@ -1045,3 +1045,18 @@ class EdgeApplianceDriver(object):
status_callback=self._retry_task, status_callback=self._retry_task,
userdata=userdata) userdata=userdata)
self.task_manager.add(task) self.task_manager.add(task)
def create_bridge(self, device_name, bridge):
try:
self.vcns.create_bridge(device_name, bridge)
except exceptions.VcnsApiException:
with excutils.save_and_reraise_exception():
LOG.exception(_LE("Failed to create bridge in the %s"),
device_name)
def delete_bridge(self, device_name):
try:
self.vcns.delete_bridge(device_name)
except exceptions.VcnsApiException:
LOG.exception(_LE("Failed to delete bridge in the %s"),
device_name)

View File

@ -61,6 +61,9 @@ DHCP_BINDING_RESOURCE = "bindings"
# Syetem control constants # Syetem control constants
SYSCTL_SERVICE = 'systemcontrol/config' SYSCTL_SERVICE = 'systemcontrol/config'
# L2 gateway constants
BRIDGE = "bridging/config"
def retry_upon_exception(exc, delay=500, max_delay=2000, def retry_upon_exception(exc, delay=500, max_delay=2000,
max_attempts=cfg.CONF.nsxv.retries): max_attempts=cfg.CONF.nsxv.retries):
@ -489,6 +492,19 @@ class Vcns(object):
if sg.find('name').text == sg_name: if sg.find('name').text == sg_name:
return sg.find('objectId').text return sg.find('objectId').text
@retry_upon_exception(exceptions.VcnsApiException)
def create_bridge(self, edge_id, request):
"""Create a bridge."""
uri = self._build_uri_path(edge_id, BRIDGE)
return self.do_request(HTTP_PUT, uri, request, format='xml',
decode=False)
@retry_upon_exception(exceptions.VcnsApiException)
def delete_bridge(self, edge_id):
"""Delete a bridge."""
uri = self._build_uri_path(edge_id, BRIDGE)
return self.do_request(HTTP_DELETE, uri, format='xml', decode=False)
def create_section(self, type, request): def create_section(self, type, request):
"""Creates a layer 3 or layer 2 section in nsx rule table. """Creates a layer 3 or layer 2 section in nsx rule table.

View File

@ -0,0 +1,163 @@
# Copyright 2015 VMware, 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.
from networking_l2gw.db.l2gateway import l2gateway_db
from networking_l2gw.services.l2gateway.common import constants as l2gw_const
from networking_l2gw.services.l2gateway import exceptions as l2gw_exc
from neutron.common import exceptions as n_exc
from neutron.i18n import _LE
from neutron import manager
from oslo_log import log as logging
from oslo_utils import excutils
from oslo_utils import uuidutils
from vmware_nsx.common import exceptions as nsx_exc
from vmware_nsx.common import nsxv_constants
from vmware_nsx.db import db as nsx_db
from vmware_nsx.db import nsxv_db
from vmware_nsx.plugins.nsx_v.vshield.common import exceptions
LOG = logging.getLogger(__name__)
class NsxvL2GatewayDriver(l2gateway_db.L2GatewayMixin):
def _core_plugin(self):
return manager.NeutronManager.get_plugin()
@property
def _nsxv(self):
return self._core_plugin.nsx_v
@property
def _edge_manager(self):
return self._core_plugin.edge_manager
def _validate_device_list(self, devices):
# In NSX-v, one L2 gateway is mapped to one DLR.
# So we expect only one device to be configured as part of
# a L2 gateway resource.
if len(devices) != 1:
msg = _("Only a single device is supported for one L2 gateway")
raise n_exc.InvalidInput(error_message=msg)
def _validate_interface_list(self, interfaces):
# In NSXv, interface is mapped to a vDS VLAN port group.
# Since HA is not supported, only one interface is expected
if len(interfaces) != 1:
msg = _("Only a single interface is supported for one L2 gateway")
raise n_exc.InvalidInput(error_message=msg)
if not self._nsxv.vcns.validate_network(interfaces[0]['name']):
msg = _("Configured interface not found")
raise n_exc.InvalidInput(error_message=msg)
def create_l2_gateway(self, context, l2_gateway):
"""Create a logical L2 gateway."""
self._admin_check(context, 'CREATE')
gw = l2_gateway[self.gateway_resource]
devices = gw['devices']
self._validate_device_list(devices)
interfaces = devices[0]['interfaces']
self._validate_interface_list(interfaces)
# Create a dedicated DLR
try:
edge_id = self._create_l2_gateway_edge(context)
except nsx_exc.NsxL2GWDeviceNotFound:
LOG.exception(_LE("Failed to create backend device "
"for L2 gateway"))
raise
devices[0]['device_name'] = edge_id
l2_gateway[self.gateway_resource]['devices'] = devices
return super(NsxvL2GatewayDriver, self).create_l2_gateway(context,
l2_gateway)
def _create_l2_gateway_edge(self, context):
# Create a dedicated DLR
lrouter = {'name': nsxv_constants.L2_GATEWAY_EDGE,
'id': uuidutils.generate_uuid()}
self._edge_manager.create_lrouter(context,
lrouter, lswitch=None, dist=True)
edge_binding = nsxv_db.get_nsxv_router_binding(context.session,
lrouter['id'])
if not edge_binding:
raise nsx_exc.NsxL2GWDeviceNotFound()
return edge_binding['edge_id']
def _get_device(self, context, l2gw_id):
devices = self._get_l2_gateway_devices(context, l2gw_id)
return devices[0]
def create_l2_gateway_connection(self, context, l2_gateway_connection):
"""Create a L2 gateway connection."""
gw_connection = l2_gateway_connection.get(l2gw_const.
CONNECTION_RESOURCE_NAME)
l2gw_connection = super(
NsxvL2GatewayDriver, self).create_l2_gateway_connection(
context, l2_gateway_connection)
network_id = gw_connection.get('network_id')
virtual_wire = nsx_db.get_nsx_switch_ids(context.session, network_id)
l2gw_id = gw_connection.get(l2gw_const.L2GATEWAY_ID)
# In NSX-v, there will be only one device configured per L2 gateway.
# The name of the device shall carry the backend DLR.
device = self._get_device(context, l2gw_id)
device_name = device.get('device_name')
device_id = device.get('id')
interface = self._get_l2_gw_interfaces(context, device_id)
interface_name = interface[0].get("interface_name")
bridge_name = "bridge-" + uuidutils.generate_uuid()
bridge_dict = {"bridges":
{"bridge":
{"name": bridge_name,
"virtualWire": virtual_wire[0],
"dvportGroup": interface_name}}}
try:
self._nsxv.create_bridge(device_name, bridge_dict)
except exceptions.VcnsApiException:
with excutils.save_and_reraise_exception():
super(NsxvL2GatewayDriver, self).delete_l2_gateway_connection(
context, l2gw_connection['id'])
LOG.exception(_LE("Failed to update NSX, "
"rolling back changes on neutron"))
return l2gw_connection
def delete_l2_gateway_connection(self, context, l2_gateway_connection):
"""Delete a L2 gateway connection."""
self._admin_check(context, 'DELETE')
gw_connection = self.get_l2_gateway_connection(context,
l2_gateway_connection)
if not gw_connection:
raise l2gw_exc.L2GatewayConnectionNotFound(
l2_gateway_connection)
l2gw_id = gw_connection.get(l2gw_const.L2GATEWAY_ID)
device = self._get_device(context, l2gw_id)
device_name = device.get('device_name')
self._nsxv.delete_bridge(device_name)
return super(NsxvL2GatewayDriver,
self).delete_l2_gateway_connection(context,
l2_gateway_connection)
def delete_l2_gateway(self, context, l2_gateway):
"""Delete a L2 gateway."""
self._admin_check(context, 'DELETE')
device = self._get_device(context, l2_gateway)
super(NsxvL2GatewayDriver, self).delete_l2_gateway(context, l2_gateway)
edge_id = device.get('device_name')
rtr_binding = nsxv_db.get_nsxv_router_binding_by_edge(
context.session, edge_id)
if rtr_binding:
self._edge_manager.delete_lrouter(context,
rtr_binding['router_id'])

View File

@ -0,0 +1,182 @@
# Copyright 2015 VMware, 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.
import mock
from neutron.common import exceptions as n_exc
from neutron import context
from neutron.tests import base
from networking_l2gw.db.l2gateway import l2gateway_db
from vmware_nsx.common import exceptions as nsx_exc
from vmware_nsx.db import nsxv_db
from vmware_nsx.services.l2gateway.nsx_v import driver as nsx_v_driver
class TestL2gatewayDriver(base.BaseTestCase):
def setUp(self):
super(TestL2gatewayDriver, self).setUp()
self.context = context.get_admin_context()
self.plugin = nsx_v_driver.NsxvL2GatewayDriver()
def test_validate_device_with_multi_devices(self):
fake_l2gw_dict = {"l2_gateway":
{"tenant_id": "fake__tenant_id",
"name": "fake_l2gw",
"devices": [{"interfaces":
[{"name": "fake_inter"}],
"device_name": "fake_dev"},
{"interfaces":
[{"name": "fake_inter_1"}],
"device_name": "fake_dev_1"}]}}
with mock.patch.object(l2gateway_db.L2GatewayMixin, '_admin_check'):
self.assertRaises(n_exc.InvalidInput,
self.plugin.create_l2_gateway,
self.context, fake_l2gw_dict)
def test_validate_interface_with_multi_interfaces(self):
fake_l2gw_dict = {"l2_gateway":
{"tenant_id": "fake_tenant_id",
"name": "fake_l2gw",
"devices": [{"interfaces":
[{"name": "fake_inter_1"},
{"name": "fake_inter_2"}],
"device_name": "fake_dev"}]}}
with mock.patch.object(l2gateway_db.L2GatewayMixin, '_admin_check'):
self.assertRaises(n_exc.InvalidInput,
self.plugin.create_l2_gateway,
self.context, fake_l2gw_dict)
@mock.patch('vmware_nsx.services.l2gateway.'
'nsx_v.driver.NsxvL2GatewayDriver._nsxv')
def test_validate_interface_with_invalid_interfaces(self, _nsxv):
fake_interfaces = [{"name": "fake_inter"}]
_nsxv.vcns.validate_network.return_value = False
self.assertRaises(n_exc.InvalidInput,
self.plugin._validate_interface_list,
fake_interfaces)
@mock.patch('vmware_nsx.services.l2gateway.'
'nsx_v.driver.NsxvL2GatewayDriver._edge_manager')
def test_create_gw_edge_failure(self, edge_manager):
with mock.patch.object(nsxv_db,
'get_nsxv_router_binding',
return_value=None):
self.assertRaises(nsx_exc.NsxL2GWDeviceNotFound,
self.plugin._create_l2_gateway_edge,
self.context)
@mock.patch('networking_l2gw.db.l2gateway.l2gateway_db.'
'L2GatewayMixin._admin_check')
@mock.patch('vmware_nsx.services.l2gateway.'
'nsx_v.driver.NsxvL2GatewayDriver._validate_device_list')
@mock.patch('vmware_nsx.services.l2gateway.'
'nsx_v.driver.NsxvL2GatewayDriver._validate_interface_list')
@mock.patch('vmware_nsx.services.l2gateway.'
'nsx_v.driver.NsxvL2GatewayDriver._create_l2_gateway_edge')
@mock.patch('networking_l2gw.db.l2gateway.l2gateway_db.'
'L2GatewayMixin.create_l2_gateway')
def test_create_l2_gateway_failure(self, create_l2gw, _create_l2gw_edge,
val_inter, val_dev, _admin_check):
fake_l2gw_dict = {"l2_gateway":
{"tenant_id": "fake_teannt_id",
"name": "fake_l2gw",
"devices": [{"interfaces":
[{"name": "fake_inter"}],
"device_name": "fake_dev"}]}}
_create_l2gw_edge.side_effect = nsx_exc.NsxL2GWDeviceNotFound
self.assertRaises(nsx_exc.NsxL2GWDeviceNotFound,
self.plugin.create_l2_gateway,
self.context, fake_l2gw_dict)
@mock.patch('networking_l2gw.db.l2gateway.l2gateway_db.'
'L2GatewayMixin._admin_check')
@mock.patch('vmware_nsx.services.l2gateway.'
'nsx_v.driver.NsxvL2GatewayDriver._validate_device_list')
@mock.patch('vmware_nsx.services.l2gateway.'
'nsx_v.driver.NsxvL2GatewayDriver._validate_interface_list')
@mock.patch('vmware_nsx.services.l2gateway.'
'nsx_v.driver.NsxvL2GatewayDriver._create_l2_gateway_edge')
@mock.patch('networking_l2gw.db.l2gateway.l2gateway_db.'
'L2GatewayMixin.create_l2_gateway')
@mock.patch('vmware_nsx.services.l2gateway.'
'nsx_v.driver.NsxvL2GatewayDriver._edge_manager')
def test_create_l2_gateway(self, edge_manager, create_l2gw,
_create_l2gw_edge,
val_inter, val_dev, _admin_check):
fake_l2gw_dict = {"l2_gateway":
{"tenant_id": "fake_teannt_id",
"name": "fake_l2gw",
"devices": [{"interfaces":
[{"name": "fake_inter"}],
"device_name": "fake_dev"}]}}
fake_devices = [{"interfaces": [{"name": "fake_inter"}],
"device_name": "fake_dev"}]
fake_interfaces = [{"name": "fake_inter"}]
_create_l2gw_edge.return_value = 'fake_dev'
self.plugin.create_l2_gateway(self.context, fake_l2gw_dict)
_admin_check.assert_called_with(self.context, 'CREATE')
val_dev.assert_called_with(fake_devices)
val_inter.assert_called_with(fake_interfaces)
create_l2gw.assert_called_with(self.context, fake_l2gw_dict)
@mock.patch('networking_l2gw.db.l2gateway.l2gateway_db.'
'L2GatewayMixin._admin_check')
@mock.patch('networking_l2gw.db.l2gateway.l2gateway_db.'
'L2GatewayMixin.get_l2_gateway_connection')
@mock.patch('networking_l2gw.db.l2gateway.l2gateway_db.'
'L2GatewayMixin.delete_l2_gateway_connection')
@mock.patch('vmware_nsx.services.l2gateway.'
'nsx_v.driver.NsxvL2GatewayDriver._get_device')
@mock.patch('vmware_nsx.services.l2gateway.'
'nsx_v.driver.NsxvL2GatewayDriver._nsxv')
def test_delete_l2_gateway_connection(self, nsxv, get_devices, del_conn,
get_conn, admin_check):
fake_conn_dict = {'l2_gateway_id': 'fake_l2gw_id'}
fake_device_dict = {'id': 'fake_dev_id',
'device_name': 'fake_dev_name'}
get_conn.return_value = fake_conn_dict
get_devices.return_value = fake_device_dict
self.plugin.delete_l2_gateway_connection(self.context, fake_conn_dict)
admin_check.assert_called_with(self.context, 'DELETE')
get_conn.assert_called_with(self.context, fake_conn_dict)
get_devices.assert_called_with(self.context, 'fake_l2gw_id')
self.plugin._nsxv().del_bridge.asert_called_with('fake_dev_name')
del_conn.assert_called_with(self.context, fake_conn_dict)
@mock.patch('networking_l2gw.db.l2gateway.l2gateway_db.'
'L2GatewayMixin._admin_check')
@mock.patch('vmware_nsx.services.l2gateway.'
'nsx_v.driver.NsxvL2GatewayDriver._get_device')
@mock.patch('vmware_nsx.db.'
'nsxv_db.get_nsxv_router_binding_by_edge')
@mock.patch('networking_l2gw.db.l2gateway.l2gateway_db.'
'L2GatewayMixin.delete_l2_gateway')
@mock.patch('vmware_nsx.services.l2gateway.'
'nsx_v.driver.NsxvL2GatewayDriver._edge_manager')
def test_delete_l2_gateway(self, edge_manager, del_l2gw, get_nsxv_router,
get_devices, admin_check):
fake_device_dict = {"id": "fake_dev_id",
"device_name": "fake_edge_name",
"l2_gateway_id": "fake_l2gw_id"}
fake_rtr_binding = {"router_id": 'fake_router_id'}
get_devices.return_value = fake_device_dict
get_nsxv_router.return_value = fake_rtr_binding
self.plugin.delete_l2_gateway(self.context, 'fake_l2gw_id')
admin_check.assert_called_with(self.context, 'DELETE')
get_devices.assert_called_with(self.context, 'fake_l2gw_id')
del_l2gw.assert_called_with(self.context, 'fake_l2gw_id')
get_nsxv_router.assert_called_with(self.context.session,
"fake_edge_name")

View File

@ -304,6 +304,24 @@ class FakeVcns(object):
response = '' response = ''
return (header, response) return (header, response)
def create_bridge(self, edge_id, request):
if edge_id not in self._edges:
raise Exception(_("Edge %s does not exist") % edge_id)
header = {
'status': 204
}
response = ''
return (header, response)
def delete_bridge(self, edge_id):
if edge_id not in self._edges:
raise Exception(_("Edge %s does not exist") % edge_id)
header = {
'status': 204
}
response = ''
return (header, response)
def get_nat_config(self, edge_id): def get_nat_config(self, edge_id):
if edge_id not in self._edges: if edge_id not in self._edges:
raise Exception(_("Edge %s does not exist") % edge_id) raise Exception(_("Edge %s does not exist") % edge_id)