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:
parent
4bf8308494
commit
0fc47eabf5
@ -141,3 +141,7 @@ class StaleRevision(ManagerError):
|
||||
|
||||
class NsxL2GWConnectionMappingNotFound(n_exc.NotFound):
|
||||
message = _('Unable to find mapping for L2 gateway connection: %(conn)s')
|
||||
|
||||
|
||||
class NsxL2GWDeviceNotFound(n_exc.NotFound):
|
||||
message = _('Unable to find logical L2 gateway device.')
|
||||
|
@ -29,3 +29,6 @@ INTER_EDGE_PURPOSE = 'inter_edge_net'
|
||||
|
||||
# etc
|
||||
INTERNAL_TENANT_ID = 'a1b2c3d4-e5f6-eeff-ffee-6f5e4d3c2b1a'
|
||||
|
||||
# L2 gateway edge name prefix
|
||||
L2_GATEWAY_EDGE = 'L2 bridging'
|
||||
|
@ -1045,3 +1045,18 @@ class EdgeApplianceDriver(object):
|
||||
status_callback=self._retry_task,
|
||||
userdata=userdata)
|
||||
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)
|
||||
|
@ -61,6 +61,9 @@ DHCP_BINDING_RESOURCE = "bindings"
|
||||
# Syetem control constants
|
||||
SYSCTL_SERVICE = 'systemcontrol/config'
|
||||
|
||||
# L2 gateway constants
|
||||
BRIDGE = "bridging/config"
|
||||
|
||||
|
||||
def retry_upon_exception(exc, delay=500, max_delay=2000,
|
||||
max_attempts=cfg.CONF.nsxv.retries):
|
||||
@ -489,6 +492,19 @@ class Vcns(object):
|
||||
if sg.find('name').text == sg_name:
|
||||
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):
|
||||
"""Creates a layer 3 or layer 2 section in nsx rule table.
|
||||
|
||||
|
0
vmware_nsx/services/l2gateway/nsx_v/__init__.py
Normal file
0
vmware_nsx/services/l2gateway/nsx_v/__init__.py
Normal file
163
vmware_nsx/services/l2gateway/nsx_v/driver.py
Normal file
163
vmware_nsx/services/l2gateway/nsx_v/driver.py
Normal 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'])
|
@ -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")
|
@ -304,6 +304,24 @@ class FakeVcns(object):
|
||||
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):
|
||||
if edge_id not in self._edges:
|
||||
raise Exception(_("Edge %s does not exist") % edge_id)
|
||||
|
Loading…
Reference in New Issue
Block a user