Merge "Add support for tenant-provided NSX gateways devices"

This commit is contained in:
Jenkins 2014-03-08 03:50:44 +00:00 committed by Gerrit Code Review
commit 1a239b8375
10 changed files with 1281 additions and 215 deletions

View File

@ -46,11 +46,29 @@ def build_resource_info(plural_mappings, resource_map, which_service,
API resource objects for advanced services extensions. Will optionally
translate underscores to dashes in resource names, register the resource,
and accept action information for resources.
:param plural_mappings: mappings between singular and plural forms
:param resource_map: attribute map for the WSGI resources to create
:param which_service: The name of the service for which the WSGI resources
are being created. This name will be used to pass
the appropriate plugin to the WSGI resource.
It can be set to None or "CORE"to create WSGI
resources for the the core plugin
:param action_map: custom resource actions
:param register_quota: it can be set to True to register quotas for the
resource(s) being created
:param translate_name: replaces underscores with dashes
:param allow_bulk: True if bulk create are allowed
"""
resources = []
if not which_service:
which_service = constants.CORE
if action_map is None:
action_map = {}
plugin = manager.NeutronManager.get_service_plugins()[which_service]
if which_service != constants.CORE:
plugin = manager.NeutronManager.get_service_plugins()[which_service]
else:
plugin = manager.NeutronManager.get_plugin()
for collection_name in resource_map:
resource_name = plural_mappings[collection_name]
params = resource_map.get(collection_name, {})

View File

@ -0,0 +1,100 @@
# Copyright 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.
#
"""nsx_gw_devices
Revision ID: 19180cf98af6
Revises: 117643811bca
Create Date: 2014-02-26 02:46:26.151741
"""
# revision identifiers, used by Alembic.
revision = '19180cf98af6'
down_revision = '117643811bca'
# Change to ['*'] if this migration applies to all plugins
migration_for_plugins = [
'neutron.plugins.nicira.NeutronPlugin.NvpPluginV2',
'neutron.plugins.nicira.NeutronServicePlugin.NvpAdvancedPlugin',
'neutron.plugins.vmware.plugin.NsxPlugin',
'neutron.plugins.vmware.plugin.NsxServicePlugin'
]
from alembic import op
import sqlalchemy as sa
from neutron.db import migration
def upgrade(active_plugins=None, options=None):
if not migration.should_run(active_plugins, migration_for_plugins):
return
op.create_table(
'networkgatewaydevicereferences',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('network_gateway_id', sa.String(length=36), nullable=True),
sa.Column('interface_name', sa.String(length=64), nullable=True),
sa.ForeignKeyConstraint(['network_gateway_id'], ['networkgateways.id'],
ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
mysql_engine='InnoDB')
# Copy data from networkgatewaydevices into networkgatewaydevicereference
op.execute("INSERT INTO networkgatewaydevicereferences SELECT "
"id, network_gateway_id, interface_name FROM "
"networkgatewaydevices")
# drop networkgatewaydevices
op.drop_table('networkgatewaydevices')
op.create_table(
'networkgatewaydevices',
sa.Column('tenant_id', sa.String(length=255), nullable=True),
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('nsx_id', sa.String(length=36), nullable=True),
sa.Column('name', sa.String(length=255), nullable=True),
sa.Column('connector_type', sa.String(length=10), nullable=True),
sa.Column('connector_ip', sa.String(length=64), nullable=True),
sa.Column('status', sa.String(length=16), nullable=True),
sa.PrimaryKeyConstraint('id'),
mysql_engine='InnoDB')
# Create a networkgatewaydevice for each existing reference.
# For existing references nsx_id == neutron_id
# Do not fill conenctor info as they would be unknown
op.execute("INSERT INTO networkgatewaydevices (id, nsx_id) SELECT "
"id, id as nsx_id FROM networkgatewaydevicereferences")
def downgrade(active_plugins=None, options=None):
if not migration.should_run(active_plugins, migration_for_plugins):
return
op.drop_table('networkgatewaydevices')
# Re-create previous version of networkgatewaydevices table
op.create_table(
'networkgatewaydevices',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('network_gateway_id', sa.String(length=36), nullable=True),
sa.Column('interface_name', sa.String(length=64), nullable=True),
sa.ForeignKeyConstraint(['network_gateway_id'], ['networkgateways.id'],
ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
mysql_engine='InnoDB')
# Copy from networkgatewaydevicereferences to networkgatewaydevices
op.execute("INSERT INTO networkgatewaydevices SELECT "
"id, network_gateway_id, interface_name FROM "
"networkgatewaydevicereferences")
# Dropt networkgatewaydevicereferences
op.drop_table('networkgatewaydevicereferences')

View File

@ -15,10 +15,14 @@
# License for the specific language governing permissions and limitations
# under the License.
from neutron.common import exceptions as n_exc
from neutron.openstack.common import log
from neutron.plugins.vmware.api_client import client
from neutron.plugins.vmware.api_client import exception as api_exc
from neutron.plugins.vmware.dbexts import db as nsx_db
from neutron.plugins.vmware.dbexts import networkgw_db
from neutron.plugins.vmware import nsx_cluster
from neutron.plugins.vmware.nsxlib import l2gateway as l2gwlib
from neutron.plugins.vmware.nsxlib import router as routerlib
from neutron.plugins.vmware.nsxlib import secgroup as secgrouplib
from neutron.plugins.vmware.nsxlib import switch as switchlib
@ -211,3 +215,35 @@ def create_nsx_cluster(cluster_opts, concurrent_connections, gen_timeout):
concurrent_connections=concurrent_connections,
gen_timeout=gen_timeout)
return cluster
def get_nsx_device_status(cluster, nsx_uuid):
try:
status_up = l2gwlib.get_gateway_device_status(
cluster, nsx_uuid)
if status_up:
return networkgw_db.STATUS_ACTIVE
else:
return networkgw_db.STATUS_DOWN
except api_exc.NsxApiException:
return networkgw_db.STATUS_UNKNOWN
except n_exc.NotFound:
return networkgw_db.ERROR
def get_nsx_device_statuses(cluster, tenant_id):
try:
status_dict = l2gwlib.get_gateway_devices_status(
cluster, tenant_id)
return dict((nsx_device_id,
networkgw_db.STATUS_ACTIVE if connected
else networkgw_db.STATUS_DOWN) for
(nsx_device_id, connected) in status_dict.iteritems())
except api_exc.NsxApiException:
# Do not make a NSX API exception fatal
if tenant_id:
LOG.warn(_("Unable to retrieve operational status for gateway "
"devices belonging to tenant: %s"), tenant_id)
else:
LOG.warn(_("Unable to retrieve operational status for "
"gateway devices"))

View File

@ -27,6 +27,17 @@ MAX_DISPLAY_NAME_LEN = 40
NEUTRON_VERSION = version_info.release_string()
# Allowed network types for the NSX Plugin
class NetworkTypes:
"""Allowed provider network types for the NSX Plugin."""
L3_EXT = 'l3_ext'
STT = 'stt'
GRE = 'gre'
FLAT = 'flat'
VLAN = 'vlan'
BRIDGE = 'bridge'
def get_tags(**kwargs):
tags = ([dict(tag=value, scope=key)
for key, value in kwargs.iteritems()])

View File

@ -11,7 +11,6 @@
# 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 sqlalchemy as sa
@ -37,6 +36,11 @@ SEGMENTATION_ID = 'segmentation_id'
ALLOWED_CONNECTION_ATTRIBUTES = set((NETWORK_ID,
SEGMENTATION_TYPE,
SEGMENTATION_ID))
# Constants for gateway device operational status
STATUS_UNKNOWN = "UNKNOWN"
STATUS_ERROR = "ERROR"
STATUS_ACTIVE = "ACTIVE"
STATUS_DOWN = "DOWN"
class GatewayInUse(exceptions.InUse):
@ -48,6 +52,15 @@ class GatewayNotFound(exceptions.NotFound):
message = _("Network Gateway %(gateway_id)s could not be found")
class GatewayDeviceInUse(exceptions.InUse):
message = _("Network Gateway Device '%(device_id)s' is still used by "
"one or more network gateways.")
class GatewayDeviceNotFound(exceptions.NotFound):
message = _("Network Gateway Device %(device_id)s could not be found.")
class NetworkGatewayPortInUse(exceptions.InUse):
message = _("Port '%(port_id)s' is owned by '%(device_owner)s' and "
"therefore cannot be deleted directly via the port API.")
@ -104,7 +117,7 @@ class NetworkConnection(model_base.BASEV2, models_v2.HasTenant):
primary_key=True)
class NetworkGatewayDevice(model_base.BASEV2):
class NetworkGatewayDeviceReference(model_base.BASEV2):
id = sa.Column(sa.String(36), primary_key=True)
network_gateway_id = sa.Column(sa.String(36),
sa.ForeignKey('networkgateways.id',
@ -112,6 +125,20 @@ class NetworkGatewayDevice(model_base.BASEV2):
interface_name = sa.Column(sa.String(64))
class NetworkGatewayDevice(model_base.BASEV2, models_v2.HasId,
models_v2.HasTenant):
nsx_id = sa.Column(sa.String(36))
# Optional name for the gateway device
name = sa.Column(sa.String(255))
# Transport connector type. Not using enum as range of
# connector types might vary with backend version
connector_type = sa.Column(sa.String(10))
# Transport connector IP Address
connector_ip = sa.Column(sa.String(64))
# operational status
status = sa.Column(sa.String(16))
class NetworkGateway(model_base.BASEV2, models_v2.HasId,
models_v2.HasTenant):
"""Defines the data model for a network gateway."""
@ -119,7 +146,7 @@ class NetworkGateway(model_base.BASEV2, models_v2.HasId,
# Tenant id is nullable for this resource
tenant_id = sa.Column(sa.String(36))
default = sa.Column(sa.Boolean())
devices = orm.relationship(NetworkGatewayDevice,
devices = orm.relationship(NetworkGatewayDeviceReference,
backref='networkgateways',
cascade='all,delete')
network_connections = orm.relationship(NetworkConnection, lazy='joined')
@ -127,7 +154,8 @@ class NetworkGateway(model_base.BASEV2, models_v2.HasId,
class NetworkGatewayMixin(networkgw.NetworkGatewayPluginBase):
resource = networkgw.RESOURCE_NAME.replace('-', '_')
gateway_resource = networkgw.GATEWAY_RESOURCE_NAME
device_resource = networkgw.DEVICE_RESOURCE_NAME
def _get_network_gateway(self, context, gw_id):
try:
@ -222,7 +250,7 @@ class NetworkGatewayMixin(networkgw.NetworkGatewayPluginBase):
device_owner=port['device_owner'])
def create_network_gateway(self, context, network_gateway):
gw_data = network_gateway[self.resource]
gw_data = network_gateway[self.gateway_resource]
tenant_id = self._get_tenant_id_for_create(context, gw_data)
with context.session.begin(subtransactions=True):
gw_db = NetworkGateway(
@ -230,14 +258,17 @@ class NetworkGatewayMixin(networkgw.NetworkGatewayPluginBase):
tenant_id=tenant_id,
name=gw_data.get('name'))
# Device list is guaranteed to be a valid list
gw_db.devices.extend([NetworkGatewayDevice(**device)
# TODO(salv-orlando): Enforce that gateway device identifiers
# in this list are among the tenant's NSX network gateway devices
# to avoid risk a tenant 'guessing' other tenant's network devices
gw_db.devices.extend([NetworkGatewayDeviceReference(**device)
for device in gw_data['devices']])
context.session.add(gw_db)
LOG.debug(_("Created network gateway with id:%s"), gw_db['id'])
return self._make_network_gateway_dict(gw_db)
def update_network_gateway(self, context, id, network_gateway):
gw_data = network_gateway[self.resource]
gw_data = network_gateway[self.gateway_resource]
with context.session.begin(subtransactions=True):
gw_db = self._get_network_gateway(context, id)
if gw_db.default:
@ -363,9 +394,90 @@ class NetworkGatewayMixin(networkgw.NetworkGatewayPluginBase):
raise MultipleGatewayConnections(
gateway_id=network_gateway_id)
# Remove gateway port from network
# FIXME(salvatore-orlando): Ensure state of port in NSX is
# FIXME(salvatore-orlando): Ensure state of port in NVP is
# consistent with outcome of transaction
self.delete_port(context, net_connection['port_id'],
nw_gw_port_check=False)
# Remove NetworkConnection record
context.session.delete(net_connection)
def _make_gateway_device_dict(self, gateway_device, fields=None,
include_nsx_id=False):
res = {'id': gateway_device['id'],
'name': gateway_device['name'],
'status': gateway_device['status'],
'connector_type': gateway_device['connector_type'],
'connector_ip': gateway_device['connector_ip'],
'tenant_id': gateway_device['tenant_id']}
if include_nsx_id:
# Return the NSX mapping as well. This attribute will not be
# returned in the API response anyway. Ensure it will not be
# filtered out in field selection.
if fields:
fields.append('nsx_id')
res['nsx_id'] = gateway_device['nsx_id']
return self._fields(res, fields)
def _get_gateway_device(self, context, device_id):
try:
return self._get_by_id(context, NetworkGatewayDevice, device_id)
except sa_orm_exc.NoResultFound:
raise GatewayDeviceNotFound(device_id=device_id)
def _is_device_in_use(self, context, device_id):
query = self._get_collection_query(
context, NetworkGatewayDeviceReference, {'id': [device_id]})
return query.first()
def get_gateway_device(self, context, device_id, fields=None,
include_nsx_id=False):
return self._make_gateway_device_dict(
self._get_gateway_device(context, device_id),
fields, include_nsx_id)
def get_gateway_devices(self, context, filters=None, fields=None,
include_nsx_id=False):
query = self._get_collection_query(context,
NetworkGatewayDevice,
filters=filters)
return [self._make_gateway_device_dict(row, fields, include_nsx_id)
for row in query]
def create_gateway_device(self, context, gateway_device,
initial_status=STATUS_UNKNOWN):
device_data = gateway_device[self.device_resource]
tenant_id = self._get_tenant_id_for_create(context, device_data)
with context.session.begin(subtransactions=True):
device_db = NetworkGatewayDevice(
id=device_data.get('id', uuidutils.generate_uuid()),
tenant_id=tenant_id,
name=device_data.get('name'),
connector_type=device_data['connector_type'],
connector_ip=device_data['connector_ip'],
status=initial_status)
context.session.add(device_db)
LOG.debug(_("Created network gateway device: %s"), device_db['id'])
return self._make_gateway_device_dict(device_db)
def update_gateway_device(self, context, gateway_device_id,
gateway_device, include_nsx_id=False):
device_data = gateway_device[self.device_resource]
with context.session.begin(subtransactions=True):
device_db = self._get_gateway_device(context, gateway_device_id)
# Ensure there is something to update before doing it
if any([device_db[k] != device_data[k] for k in device_data]):
device_db.update(device_data)
LOG.debug(_("Updated network gateway device: %s"),
gateway_device_id)
return self._make_gateway_device_dict(
device_db, include_nsx_id=include_nsx_id)
def delete_gateway_device(self, context, device_id):
with context.session.begin(subtransactions=True):
# A gateway device should not be deleted
# if it is used in any network gateway service
if self._is_device_in_use(context, device_id):
raise GatewayDeviceInUse(device_id=device_id)
device_db = self._get_gateway_device(context, device_id)
context.session.delete(device_db)
LOG.debug(_("Deleted network gateway device: %s."), device_id)

View File

@ -19,24 +19,23 @@ from abc import abstractmethod
from oslo.config import cfg
from neutron.api import extensions
from neutron.api.v2 import attributes
from neutron.api.v2 import base
from neutron import manager
from neutron import quota
from neutron.api.v2 import resource_helper
from neutron.plugins.vmware.common.utils import NetworkTypes
RESOURCE_NAME = "network_gateway"
GATEWAY_RESOURCE_NAME = "network_gateway"
DEVICE_RESOURCE_NAME = "gateway_device"
# Use dash for alias and collection name
EXT_ALIAS = RESOURCE_NAME.replace('_', '-')
COLLECTION_NAME = "%ss" % EXT_ALIAS
EXT_ALIAS = GATEWAY_RESOURCE_NAME.replace('_', '-')
NETWORK_GATEWAYS = "%ss" % EXT_ALIAS
GATEWAY_DEVICES = "%ss" % DEVICE_RESOURCE_NAME.replace('_', '-')
DEVICE_ID_ATTR = 'id'
IFACE_NAME_ATTR = 'interface_name'
# Attribute Map for Network Gateway Resource
# TODO(salvatore-orlando): add admin state as other neutron resources
RESOURCE_ATTRIBUTE_MAP = {
COLLECTION_NAME: {
NETWORK_GATEWAYS: {
'id': {'allow_post': False, 'allow_put': False,
'is_visible': True},
'name': {'allow_post': True, 'allow_put': True,
@ -54,6 +53,28 @@ RESOURCE_ATTRIBUTE_MAP = {
'validate': {'type:string': None},
'required_by_policy': True,
'is_visible': True}
},
GATEWAY_DEVICES: {
'id': {'allow_post': False, 'allow_put': False,
'is_visible': True},
'name': {'allow_post': True, 'allow_put': True,
'validate': {'type:string': None},
'is_visible': True, 'default': ''},
'client_certificate': {'allow_post': True, 'allow_put': True,
'validate': {'type:string': None},
'is_visible': True},
'connector_type': {'allow_post': True, 'allow_put': True,
'validate': {'type:connector_type': None},
'is_visible': True},
'connector_ip': {'allow_post': True, 'allow_put': True,
'validate': {'type:ip_address': None},
'is_visible': True},
'tenant_id': {'allow_post': True, 'allow_put': False,
'validate': {'type:string': None},
'required_by_policy': True,
'is_visible': True},
'status': {'allow_post': False, 'allow_put': False,
'is_visible': True},
}
}
@ -85,6 +106,23 @@ def _validate_device_list(data, valid_values=None):
return (_("%s: provided data are not iterable") %
_validate_device_list.__name__)
def _validate_connector_type(data, valid_values=None):
if not data:
# A connector type is compulsory
msg = _("A connector type is required to create a gateway device")
return msg
connector_types = (valid_values if valid_values else
[NetworkTypes.GRE,
NetworkTypes.STT,
NetworkTypes.BRIDGE,
'ipsec%s' % NetworkTypes.GRE,
'ipsec%s' % NetworkTypes.STT])
if data not in connector_types:
msg = _("Unknown connector type: %s") % data
return msg
nw_gw_quota_opts = [
cfg.IntOpt('quota_network_gateway',
default=5,
@ -95,6 +133,7 @@ nw_gw_quota_opts = [
cfg.CONF.register_opts(nw_gw_quota_opts, 'QUOTAS')
attributes.validators['type:device_list'] = _validate_device_list
attributes.validators['type:connector_type'] = _validate_connector_type
class Networkgw(object):
@ -132,22 +171,21 @@ class Networkgw(object):
@classmethod
def get_resources(cls):
"""Returns Ext Resources."""
plugin = manager.NeutronManager.get_plugin()
params = RESOURCE_ATTRIBUTE_MAP.get(COLLECTION_NAME, dict())
member_actions = {'connect_network': 'PUT',
'disconnect_network': 'PUT'}
member_actions = {
GATEWAY_RESOURCE_NAME.replace('_', '-'): {
'connect_network': 'PUT',
'disconnect_network': 'PUT'}}
# register quotas for network gateways
quota.QUOTAS.register_resource_by_name(RESOURCE_NAME)
collection_name = COLLECTION_NAME.replace('_', '-')
controller = base.create_resource(collection_name,
RESOURCE_NAME,
plugin, params,
member_actions=member_actions)
return [extensions.ResourceExtension(COLLECTION_NAME,
controller,
member_actions=member_actions)]
plural_mappings = resource_helper.build_plural_mappings(
{}, RESOURCE_ATTRIBUTE_MAP)
return resource_helper.build_resource_info(plural_mappings,
RESOURCE_ATTRIBUTE_MAP,
None,
action_map=member_actions,
register_quota=True,
translate_name=True)
def get_extended_resources(self, version):
if version == "2.0":
@ -187,3 +225,23 @@ class NetworkGatewayPluginBase(object):
def disconnect_network(self, context, network_gateway_id,
network_mapping_info):
pass
@abstractmethod
def create_gateway_device(self, context, gateway_device):
pass
@abstractmethod
def update_gateway_device(self, context, id, gateway_device):
pass
@abstractmethod
def delete_gateway_device(self, context, id):
pass
@abstractmethod
def get_gateway_device(self, context, id, fields=None):
pass
@abstractmethod
def get_gateway_devices(self, context, filters=None, fields=None):
pass

View File

@ -29,6 +29,7 @@ HTTP_DELETE = "DELETE"
HTTP_PUT = "PUT"
GWSERVICE_RESOURCE = "gateway-service"
TRANSPORTNODE_RESOURCE = "transport-node"
LOG = log.getLogger(__name__)
@ -58,7 +59,7 @@ def create_l2_gw_service(cluster, tenant_id, display_name, devices):
"type": "L2GatewayServiceConfig"
}
return do_request(
"POST", _build_uri_path(GWSERVICE_RESOURCE),
HTTP_POST, _build_uri_path(GWSERVICE_RESOURCE),
json.dumps(gwservice_obj), cluster=cluster)
@ -74,8 +75,8 @@ def plug_l2_gw_service(cluster, lswitch_id, lport_id,
def get_l2_gw_service(cluster, gateway_id):
return do_request(
"GET", _build_uri_path(GWSERVICE_RESOURCE,
resource_id=gateway_id),
HTTP_GET, _build_uri_path(GWSERVICE_RESOURCE,
resource_id=gateway_id),
cluster=cluster)
@ -98,12 +99,101 @@ def update_l2_gw_service(cluster, gateway_id, display_name):
# Nothing to update
return gwservice_obj
gwservice_obj["display_name"] = utils.check_and_truncate(display_name)
return do_request("PUT", _build_uri_path(GWSERVICE_RESOURCE,
resource_id=gateway_id),
return do_request(HTTP_PUT, _build_uri_path(GWSERVICE_RESOURCE,
resource_id=gateway_id),
json.dumps(gwservice_obj), cluster=cluster)
def delete_l2_gw_service(cluster, gateway_id):
do_request("DELETE", _build_uri_path(GWSERVICE_RESOURCE,
resource_id=gateway_id),
do_request(HTTP_DELETE, _build_uri_path(GWSERVICE_RESOURCE,
resource_id=gateway_id),
cluster=cluster)
def _build_gateway_device_body(tenant_id, display_name, neutron_id,
connector_type, connector_ip,
client_certificate, tz_uuid):
connector_type_mappings = {
utils.NetworkTypes.STT: "STTConnector",
utils.NetworkTypes.GRE: "GREConnector",
utils.NetworkTypes.BRIDGE: "BridgeConnector",
'ipsec%s' % utils.NetworkTypes.STT: "IPsecSTT",
'ipsec%s' % utils.NetworkTypes.GRE: "IPsecGRE"}
nsx_connector_type = connector_type_mappings[connector_type]
body = {"display_name": utils.check_and_truncate(display_name),
"tags": utils.get_tags(os_tid=tenant_id,
q_gw_dev_id=neutron_id),
"transport_connectors": [
{"transport_zone_uuid": tz_uuid,
"ip_address": connector_ip,
"type": nsx_connector_type}],
"admin_status_enabled": True}
if client_certificate:
body["credential"] = {"client_certificate":
{"pem_encoded": client_certificate},
"type": "SecurityCertificateCredential"}
return body
def create_gateway_device(cluster, tenant_id, display_name, neutron_id,
tz_uuid, connector_type, connector_ip,
client_certificate):
body = _build_gateway_device_body(tenant_id, display_name, neutron_id,
connector_type, connector_ip,
client_certificate, tz_uuid)
return do_request(
HTTP_POST, _build_uri_path(TRANSPORTNODE_RESOURCE),
json.dumps(body), cluster=cluster)
def update_gateway_device(cluster, gateway_id, tenant_id,
display_name, neutron_id,
tz_uuid, connector_type, connector_ip,
client_certificate):
body = _build_gateway_device_body(tenant_id, display_name, neutron_id,
connector_type, connector_ip,
client_certificate, tz_uuid)
return do_request(
HTTP_PUT,
_build_uri_path(TRANSPORTNODE_RESOURCE, resource_id=gateway_id),
json.dumps(body), cluster=cluster)
def delete_gateway_device(cluster, device_uuid):
return do_request(HTTP_DELETE,
_build_uri_path(TRANSPORTNODE_RESOURCE,
device_uuid),
cluster=cluster)
def get_gateway_device_status(cluster, device_uuid):
status_res = do_request(HTTP_GET,
_build_uri_path(TRANSPORTNODE_RESOURCE,
device_uuid,
extra_action='status'),
cluster=cluster)
# Returns the connection status
return status_res['connection']['connected']
def get_gateway_devices_status(cluster, tenant_id=None):
if tenant_id:
gw_device_query_path = _build_uri_path(
TRANSPORTNODE_RESOURCE,
fields="uuid,tags",
relations="TransportNodeStatus",
filters={'tag': tenant_id,
'tag_scope': 'os_tid'})
else:
gw_device_query_path = _build_uri_path(
TRANSPORTNODE_RESOURCE,
fields="uuid,tags",
relations="TransportNodeStatus")
response = get_all_query_pages(gw_device_query_path, cluster)
results = {}
for item in response:
results[item['uuid']] = (item['_relations']['TransportNodeStatus']
['connection']['connected'])
return results

View File

@ -60,6 +60,7 @@ from neutron.plugins.vmware.common import exceptions as nsx_exc
from neutron.plugins.vmware.common import nsx_utils
from neutron.plugins.vmware.common import securitygroups as sg_utils
from neutron.plugins.vmware.common import sync
from neutron.plugins.vmware.common.utils import NetworkTypes
from neutron.plugins.vmware.dbexts import db as nsx_db
from neutron.plugins.vmware.dbexts import distributedrouter as dist_rtr
from neutron.plugins.vmware.dbexts import maclearning as mac_db
@ -83,17 +84,6 @@ NSX_EXTGW_NAT_RULES_ORDER = 255
NSX_DEFAULT_NEXTHOP = '1.1.1.1'
# Provider network extension - allowed network types for the NSX Plugin
class NetworkTypes:
"""Allowed provider network types for the NSX Plugin."""
L3_EXT = 'l3_ext'
STT = 'stt'
GRE = 'gre'
FLAT = 'flat'
VLAN = 'vlan'
BRIDGE = 'bridge'
class NsxPluginV2(addr_pair_db.AllowedAddressPairsMixin,
agentschedulers_db.DhcpAgentSchedulerDbMixin,
db_base_plugin_v2.NeutronDbPluginV2,
@ -205,7 +195,7 @@ class NsxPluginV2(addr_pair_db.AllowedAddressPairsMixin,
def_gw_data = {'id': def_l2_gw_uuid,
'name': 'default L2 gateway service',
'devices': []}
gw_res_name = networkgw.RESOURCE_NAME.replace('-', '_')
gw_res_name = networkgw.GATEWAY_RESOURCE_NAME.replace('-', '_')
def_network_gw = super(
NsxPluginV2, self).create_network_gateway(
ctx, {gw_res_name: def_gw_data})
@ -2009,7 +1999,7 @@ class NsxPluginV2(addr_pair_db.AllowedAddressPairsMixin,
# Ensure the default gateway in the config file is in sync with the db
self._ensure_default_network_gateway()
# Need to re-do authZ checks here in order to avoid creation on NSX
gw_data = network_gateway[networkgw.RESOURCE_NAME.replace('-', '_')]
gw_data = network_gateway[networkgw.GATEWAY_RESOURCE_NAME]
tenant_id = self._get_tenant_id_for_create(context, gw_data)
devices = gw_data['devices']
# Populate default physical network where not specified
@ -2017,8 +2007,15 @@ class NsxPluginV2(addr_pair_db.AllowedAddressPairsMixin,
if not device.get('interface_name'):
device['interface_name'] = self.cluster.default_interface_name
try:
# Replace Neutron device identifiers with NSX identifiers
# TODO(salv-orlando): Make this operation more efficient doing a
# single DB query for all devices
nsx_devices = [{'id': self._get_nsx_device_id(context,
device['id']),
'interface_name': device['interface_name']} for
device in devices]
nsx_res = l2gwlib.create_l2_gw_service(
self.cluster, tenant_id, gw_data['name'], devices)
self.cluster, tenant_id, gw_data['name'], nsx_devices)
nsx_uuid = nsx_res.get('uuid')
except api_exc.Conflict:
raise nsx_exc.L2GatewayAlreadyInUse(gateway=gw_data['name'])
@ -2027,8 +2024,8 @@ class NsxPluginV2(addr_pair_db.AllowedAddressPairsMixin,
LOG.exception(err_msg)
raise nsx_exc.NsxPluginException(err_msg=err_msg)
gw_data['id'] = nsx_uuid
return super(NsxPluginV2, self).create_network_gateway(context,
network_gateway)
return super(NsxPluginV2, self).create_network_gateway(
context, network_gateway)
def delete_network_gateway(self, context, gateway_id):
"""Remove a layer-2 network gateway.
@ -2069,7 +2066,7 @@ class NsxPluginV2(addr_pair_db.AllowedAddressPairsMixin,
# Ensure the default gateway in the config file is in sync with the db
self._ensure_default_network_gateway()
# Update gateway on backend when there's a name change
name = network_gateway[networkgw.RESOURCE_NAME].get('name')
name = network_gateway[networkgw.GATEWAY_RESOURCE_NAME].get('name')
if name:
try:
l2gwlib.update_l2_gw_service(self.cluster, id, name)
@ -2098,6 +2095,205 @@ class NsxPluginV2(addr_pair_db.AllowedAddressPairsMixin,
return super(NsxPluginV2, self).disconnect_network(
context, network_gateway_id, network_mapping_info)
def _get_nsx_device_id(self, context, device_id):
return self._get_gateway_device(context, device_id)['nsx_id']
# TODO(salv-orlando): Handlers for Gateway device operations should be
# moved into the appropriate nsx_handlers package once the code for the
# blueprint nsx-async-backend-communication merges
def create_gateway_device_handler(self, context, gateway_device,
client_certificate):
neutron_id = gateway_device['id']
try:
nsx_res = l2gwlib.create_gateway_device(
self.cluster,
gateway_device['tenant_id'],
gateway_device['name'],
neutron_id,
self.cluster.default_tz_uuid,
gateway_device['connector_type'],
gateway_device['connector_ip'],
client_certificate)
# Fetch status (it needs another NSX API call)
device_status = nsx_utils.get_nsx_device_status(self.cluster,
nsx_res['uuid'])
# set NSX GW device in neutron database and update status
with context.session.begin(subtransactions=True):
query = self._model_query(
context, networkgw_db.NetworkGatewayDevice).filter(
networkgw_db.NetworkGatewayDevice.id == neutron_id)
query.update({'status': device_status,
'nsx_id': nsx_res['uuid']},
synchronize_session=False)
LOG.debug(_("Neutron gateway device: %(neutron_id)s; "
"NSX transport node identifier: %(nsx_id)s; "
"Operational status: %(status)s."),
{'neutron_id': neutron_id,
'nsx_id': nsx_res['uuid'],
'status': device_status})
return device_status
except api_exc.NsxApiException:
# Remove gateway device from neutron database
with excutils.save_and_reraise_exception():
LOG.exception(_("Unable to create gateway device: %s on NSX "
"backend."), neutron_id)
with context.session.begin(subtransactions=True):
query = self._model_query(
context, networkgw_db.NetworkGatewayDevice).filter(
networkgw_db.NetworkGatewayDevice.id == neutron_id)
query.delete(synchronize_session=False)
def update_gateway_device_handler(self, context, gateway_device,
old_gateway_device_data,
client_certificate):
nsx_id = gateway_device['nsx_id']
neutron_id = gateway_device['id']
try:
l2gwlib.update_gateway_device(
self.cluster,
nsx_id,
gateway_device['tenant_id'],
gateway_device['name'],
neutron_id,
self.cluster.default_tz_uuid,
gateway_device['connector_type'],
gateway_device['connector_ip'],
client_certificate)
# Fetch status (it needs another NSX API call)
device_status = nsx_utils.get_nsx_device_status(self.cluster,
nsx_id)
# update status
with context.session.begin(subtransactions=True):
query = self._model_query(
context, networkgw_db.NetworkGatewayDevice).filter(
networkgw_db.NetworkGatewayDevice.id == neutron_id)
query.update({'status': device_status},
synchronize_session=False)
LOG.debug(_("Neutron gateway device: %(neutron_id)s; "
"NSX transport node identifier: %(nsx_id)s; "
"Operational status: %(status)s."),
{'neutron_id': neutron_id,
'nsx_id': nsx_id,
'status': device_status})
return device_status
except api_exc.NsxApiException:
# Rollback gateway device on neutron database
# As the NSX failure could be transient, we don't put the
# gateway device in error status here.
with excutils.save_and_reraise_exception():
LOG.exception(_("Unable to update gateway device: %s on NSX "
"backend."), neutron_id)
super(NsxPluginV2, self).update_gateway_device(
context, neutron_id, old_gateway_device_data)
except n_exc.NotFound:
# The gateway device was probably deleted in the backend.
# The DB change should be rolled back and the status must
# be put in error
with excutils.save_and_reraise_exception():
LOG.exception(_("Unable to update gateway device: %s on NSX "
"backend, as the gateway was not found on "
"the NSX backend."), neutron_id)
with context.session.begin(subtransactions=True):
super(NsxPluginV2, self).update_gateway_device(
context, neutron_id, old_gateway_device_data)
query = self._model_query(
context, networkgw_db.NetworkGatewayDevice).filter(
networkgw_db.NetworkGatewayDevice.id == neutron_id)
query.update({'status': networkgw_db.ERROR},
synchronize_session=False)
def get_gateway_device(self, context, device_id, fields=None):
# Get device from database
gw_device = super(NsxPluginV2, self).get_gateway_device(
context, device_id, fields, include_nsx_id=True)
# Fetch status from NSX
nsx_id = gw_device['nsx_id']
device_status = nsx_utils.get_nsx_device_status(self.cluster, nsx_id)
# TODO(salv-orlando): Asynchronous sync for gateway device status
# Update status in database
with context.session.begin(subtransactions=True):
query = self._model_query(
context, networkgw_db.NetworkGatewayDevice).filter(
networkgw_db.NetworkGatewayDevice.id == device_id)
query.update({'status': device_status},
synchronize_session=False)
gw_device['status'] = device_status
return gw_device
def get_gateway_devices(self, context, filters=None, fields=None):
# Get devices from database
devices = super(NsxPluginV2, self).get_gateway_devices(
context, filters, fields, include_nsx_id=True)
# Fetch operational status from NVP, filter by tenant tag
# TODO(salv-orlando): Asynchronous sync for gateway device status
tenant_id = context.tenant_id if not context.is_admin else None
nsx_statuses = nsx_utils.get_nsx_device_statuses(self.cluster,
tenant_id)
# Update statuses in database
with context.session.begin(subtransactions=True):
for device in devices:
new_status = nsx_statuses.get(device['nsx_id'])
if new_status:
device['status'] = new_status
return devices
def create_gateway_device(self, context, gateway_device):
# NOTE(salv-orlando): client-certificate will not be stored
# in the database
device_data = gateway_device[networkgw.DEVICE_RESOURCE_NAME]
client_certificate = device_data.pop('client_certificate')
gw_device = super(NsxPluginV2, self).create_gateway_device(
context, gateway_device)
# DB operation was successful, perform NSX operation
gw_device['status'] = self.create_gateway_device_handler(
context, gw_device, client_certificate)
return gw_device
def update_gateway_device(self, context, device_id,
gateway_device):
# NOTE(salv-orlando): client-certificate will not be stored
# in the database
client_certificate = (
gateway_device[networkgw.DEVICE_RESOURCE_NAME].pop(
'client_certificate', None))
# Retrive current state from DB in case a rollback should be needed
old_gw_device_data = super(NsxPluginV2, self).get_gateway_device(
context, device_id, include_nsx_id=True)
gw_device = super(NsxPluginV2, self).update_gateway_device(
context, device_id, gateway_device, include_nsx_id=True)
# DB operation was successful, perform NSX operation
gw_device['status'] = self.update_gateway_device_handler(
context, gw_device, old_gw_device_data, client_certificate)
gw_device.pop('nsx_id')
return gw_device
def delete_gateway_device(self, context, device_id):
nsx_device_id = self._get_nsx_device_id(context, device_id)
super(NsxPluginV2, self).delete_gateway_device(
context, device_id)
# DB operation was successful, peform NSX operation
# TODO(salv-orlando): State consistency with neutron DB
# should be ensured even in case of backend failures
try:
l2gwlib.delete_gateway_device(self.cluster, nsx_device_id)
except n_exc.NotFound:
LOG.warn(_("Removal of gateway device: %(neutron_id)s failed on "
"NSX backend (NSX id:%(nsx_id)s) because the NSX "
"resource was not found"),
{'neutron_id': device_id, 'nsx_id': nsx_device_id})
except api_exc.NsxApiException:
LOG.exception(_("Removal of gateway device: %(neutron_id)s "
"failed on NSX backend (NSX id:%(nsx_id)s). "
"Neutron and NSX states have diverged."),
{'neutron_id': device_id,
'nsx_id': nsx_device_id})
# In this case a 500 should be returned
raise
def create_security_group(self, context, security_group, default_sg=False):
"""Create security group.

View File

@ -27,11 +27,11 @@ from neutron import context
from neutron.db import api as db_api
from neutron.db import db_base_plugin_v2
from neutron import manager
from neutron.openstack.common import uuidutils
from neutron.plugins.vmware.api_client import exception as api_exc
from neutron.plugins.vmware.dbexts import networkgw_db
from neutron.plugins.vmware.extensions import networkgw
from neutron.plugins.vmware import nsxlib
from neutron.plugins.vmware.nsxlib import l2gateway as l2gwlib
from neutron import quota
from neutron.tests import base
from neutron.tests.unit import test_api_v2
@ -69,7 +69,8 @@ class NetworkGatewayExtensionTestCase(base.BaseTestCase):
super(NetworkGatewayExtensionTestCase, self).setUp()
plugin = '%s.%s' % (networkgw.__name__,
networkgw.NetworkGatewayPluginBase.__name__)
self._resource = networkgw.RESOURCE_NAME.replace('-', '_')
self._gw_resource = networkgw.GATEWAY_RESOURCE_NAME
self._dev_resource = networkgw.DEVICE_RESOURCE_NAME
# Ensure existing ExtensionManager is not used
extensions.PluginAwareExtensionManager._instance = None
@ -100,67 +101,67 @@ class NetworkGatewayExtensionTestCase(base.BaseTestCase):
def test_network_gateway_create(self):
nw_gw_id = _uuid()
data = {self._resource: {'name': 'nw-gw',
'tenant_id': _uuid(),
'devices': [{'id': _uuid(),
'interface_name': 'xxx'}]}}
return_value = data[self._resource].copy()
data = {self._gw_resource: {'name': 'nw-gw',
'tenant_id': _uuid(),
'devices': [{'id': _uuid(),
'interface_name': 'xxx'}]}}
return_value = data[self._gw_resource].copy()
return_value.update({'id': nw_gw_id})
instance = self.plugin.return_value
instance.create_network_gateway.return_value = return_value
res = self.api.post_json(_get_path(networkgw.COLLECTION_NAME), data)
res = self.api.post_json(_get_path(networkgw.NETWORK_GATEWAYS), data)
instance.create_network_gateway.assert_called_with(
mock.ANY, network_gateway=data)
self.assertEqual(res.status_int, exc.HTTPCreated.code)
self.assertIn(self._resource, res.json)
nw_gw = res.json[self._resource]
self.assertIn(self._gw_resource, res.json)
nw_gw = res.json[self._gw_resource]
self.assertEqual(nw_gw['id'], nw_gw_id)
def _test_network_gateway_create_with_error(
self, data, error_code=exc.HTTPBadRequest.code):
res = self.api.post_json(_get_path(networkgw.COLLECTION_NAME), data,
res = self.api.post_json(_get_path(networkgw.NETWORK_GATEWAYS), data,
expect_errors=True)
self.assertEqual(res.status_int, error_code)
def test_network_gateway_create_invalid_device_spec(self):
data = {self._resource: {'name': 'nw-gw',
'tenant_id': _uuid(),
'devices': [{'id': _uuid(),
'invalid': 'xxx'}]}}
data = {self._gw_resource: {'name': 'nw-gw',
'tenant_id': _uuid(),
'devices': [{'id': _uuid(),
'invalid': 'xxx'}]}}
self._test_network_gateway_create_with_error(data)
def test_network_gateway_create_extra_attr_in_device_spec(self):
data = {self._resource: {'name': 'nw-gw',
'tenant_id': _uuid(),
'devices': [{'id': _uuid(),
'interface_name': 'xxx',
'extra_attr': 'onetoomany'}]}}
data = {self._gw_resource: {'name': 'nw-gw',
'tenant_id': _uuid(),
'devices':
[{'id': _uuid(),
'interface_name': 'xxx',
'extra_attr': 'onetoomany'}]}}
self._test_network_gateway_create_with_error(data)
def test_network_gateway_update(self):
nw_gw_name = 'updated'
data = {self._resource: {'name': nw_gw_name}}
data = {self._gw_resource: {'name': nw_gw_name}}
nw_gw_id = _uuid()
return_value = {'id': nw_gw_id,
'name': nw_gw_name}
instance = self.plugin.return_value
instance.update_network_gateway.return_value = return_value
res = self.api.put_json(_get_path('%s/%s' % (networkgw.COLLECTION_NAME,
nw_gw_id)),
data)
res = self.api.put_json(
_get_path('%s/%s' % (networkgw.NETWORK_GATEWAYS, nw_gw_id)), data)
instance.update_network_gateway.assert_called_with(
mock.ANY, nw_gw_id, network_gateway=data)
self.assertEqual(res.status_int, exc.HTTPOk.code)
self.assertIn(self._resource, res.json)
nw_gw = res.json[self._resource]
self.assertIn(self._gw_resource, res.json)
nw_gw = res.json[self._gw_resource]
self.assertEqual(nw_gw['id'], nw_gw_id)
self.assertEqual(nw_gw['name'], nw_gw_name)
def test_network_gateway_delete(self):
nw_gw_id = _uuid()
instance = self.plugin.return_value
res = self.api.delete(_get_path('%s/%s' % (networkgw.COLLECTION_NAME,
res = self.api.delete(_get_path('%s/%s' % (networkgw.NETWORK_GATEWAYS,
nw_gw_id)))
instance.delete_network_gateway.assert_called_with(mock.ANY,
@ -169,15 +170,15 @@ class NetworkGatewayExtensionTestCase(base.BaseTestCase):
def test_network_gateway_get(self):
nw_gw_id = _uuid()
return_value = {self._resource: {'name': 'test',
'devices':
[{'id': _uuid(),
'interface_name': 'xxx'}],
'id': nw_gw_id}}
return_value = {self._gw_resource: {'name': 'test',
'devices':
[{'id': _uuid(),
'interface_name': 'xxx'}],
'id': nw_gw_id}}
instance = self.plugin.return_value
instance.get_network_gateway.return_value = return_value
res = self.api.get(_get_path('%s/%s' % (networkgw.COLLECTION_NAME,
res = self.api.get(_get_path('%s/%s' % (networkgw.NETWORK_GATEWAYS,
nw_gw_id)))
instance.get_network_gateway.assert_called_with(mock.ANY,
@ -187,15 +188,15 @@ class NetworkGatewayExtensionTestCase(base.BaseTestCase):
def test_network_gateway_list(self):
nw_gw_id = _uuid()
return_value = [{self._resource: {'name': 'test',
'devices':
[{'id': _uuid(),
'interface_name': 'xxx'}],
'id': nw_gw_id}}]
return_value = [{self._gw_resource: {'name': 'test',
'devices':
[{'id': _uuid(),
'interface_name': 'xxx'}],
'id': nw_gw_id}}]
instance = self.plugin.return_value
instance.get_network_gateways.return_value = return_value
res = self.api.get(_get_path(networkgw.COLLECTION_NAME))
res = self.api.get(_get_path(networkgw.NETWORK_GATEWAYS))
instance.get_network_gateways.assert_called_with(mock.ANY,
fields=mock.ANY,
@ -216,7 +217,7 @@ class NetworkGatewayExtensionTestCase(base.BaseTestCase):
instance = self.plugin.return_value
instance.connect_network.return_value = return_value
res = self.api.put_json(_get_path('%s/%s/connect_network' %
(networkgw.COLLECTION_NAME,
(networkgw.NETWORK_GATEWAYS,
nw_gw_id)),
mapping_data)
instance.connect_network.assert_called_with(mock.ANY,
@ -233,7 +234,7 @@ class NetworkGatewayExtensionTestCase(base.BaseTestCase):
mapping_data = {'network_id': nw_id}
instance = self.plugin.return_value
res = self.api.put_json(_get_path('%s/%s/disconnect_network' %
(networkgw.COLLECTION_NAME,
(networkgw.NETWORK_GATEWAYS,
nw_gw_id)),
mapping_data)
instance.disconnect_network.assert_called_with(mock.ANY,
@ -241,6 +242,116 @@ class NetworkGatewayExtensionTestCase(base.BaseTestCase):
mapping_data)
self.assertEqual(res.status_int, exc.HTTPOk.code)
def test_gateway_device_get(self):
gw_dev_id = _uuid()
return_value = {self._dev_resource: {'name': 'test',
'connector_type': 'stt',
'connector_ip': '1.1.1.1',
'id': gw_dev_id}}
instance = self.plugin.return_value
instance.get_gateway_device.return_value = return_value
res = self.api.get(_get_path('%s/%s' % (networkgw.GATEWAY_DEVICES,
gw_dev_id)))
instance.get_gateway_device.assert_called_with(mock.ANY,
gw_dev_id,
fields=mock.ANY)
self.assertEqual(res.status_int, exc.HTTPOk.code)
def test_gateway_device_list(self):
gw_dev_id = _uuid()
return_value = [{self._dev_resource: {'name': 'test',
'connector_type': 'stt',
'connector_ip': '1.1.1.1',
'id': gw_dev_id}}]
instance = self.plugin.return_value
instance.get_gateway_devices.return_value = return_value
res = self.api.get(_get_path(networkgw.GATEWAY_DEVICES))
instance.get_gateway_devices.assert_called_with(mock.ANY,
fields=mock.ANY,
filters=mock.ANY)
self.assertEqual(res.status_int, exc.HTTPOk.code)
def test_gateway_device_create(self):
gw_dev_id = _uuid()
data = {self._dev_resource: {'name': 'test-dev',
'tenant_id': _uuid(),
'client_certificate': 'xyz',
'connector_type': 'stt',
'connector_ip': '1.1.1.1'}}
return_value = data[self._dev_resource].copy()
return_value.update({'id': gw_dev_id})
instance = self.plugin.return_value
instance.create_gateway_device.return_value = return_value
res = self.api.post_json(_get_path(networkgw.GATEWAY_DEVICES), data)
instance.create_gateway_device.assert_called_with(
mock.ANY, gateway_device=data)
self.assertEqual(res.status_int, exc.HTTPCreated.code)
self.assertIn(self._dev_resource, res.json)
gw_dev = res.json[self._dev_resource]
self.assertEqual(gw_dev['id'], gw_dev_id)
def _test_gateway_device_create_with_error(
self, data, error_code=exc.HTTPBadRequest.code):
res = self.api.post_json(_get_path(networkgw.GATEWAY_DEVICES), data,
expect_errors=True)
self.assertEqual(res.status_int, error_code)
def test_gateway_device_create_invalid_connector_type(self):
data = {self._gw_resource: {'name': 'test-dev',
'client_certificate': 'xyz',
'tenant_id': _uuid(),
'connector_type': 'invalid',
'connector_ip': '1.1.1.1'}}
self._test_gateway_device_create_with_error(data)
def test_gateway_device_create_invalid_connector_ip(self):
data = {self._gw_resource: {'name': 'test-dev',
'client_certificate': 'xyz',
'tenant_id': _uuid(),
'connector_type': 'stt',
'connector_ip': 'invalid'}}
self._test_gateway_device_create_with_error(data)
def test_gateway_device_create_extra_attr_in_device_spec(self):
data = {self._gw_resource: {'name': 'test-dev',
'client_certificate': 'xyz',
'tenant_id': _uuid(),
'alien_attribute': 'E.T.',
'connector_type': 'stt',
'connector_ip': '1.1.1.1'}}
self._test_gateway_device_create_with_error(data)
def test_gateway_device_update(self):
gw_dev_name = 'updated'
data = {self._dev_resource: {'name': gw_dev_name}}
gw_dev_id = _uuid()
return_value = {'id': gw_dev_id,
'name': gw_dev_name}
instance = self.plugin.return_value
instance.update_gateway_device.return_value = return_value
res = self.api.put_json(
_get_path('%s/%s' % (networkgw.GATEWAY_DEVICES, gw_dev_id)), data)
instance.update_gateway_device.assert_called_with(
mock.ANY, gw_dev_id, gateway_device=data)
self.assertEqual(res.status_int, exc.HTTPOk.code)
self.assertIn(self._dev_resource, res.json)
gw_dev = res.json[self._dev_resource]
self.assertEqual(gw_dev['id'], gw_dev_id)
self.assertEqual(gw_dev['name'], gw_dev_name)
def test_gateway_device_delete(self):
gw_dev_id = _uuid()
instance = self.plugin.return_value
res = self.api.delete(_get_path('%s/%s' % (networkgw.GATEWAY_DEVICES,
gw_dev_id)))
instance.delete_gateway_device.assert_called_with(mock.ANY, gw_dev_id)
self.assertEqual(res.status_int, exc.HTTPNoContent.code)
class NetworkGatewayDbTestCase(test_db_plugin.NeutronDbPluginV2TestCase):
"""Unit tests for Network Gateway DB support."""
@ -250,21 +361,23 @@ class NetworkGatewayDbTestCase(test_db_plugin.NeutronDbPluginV2TestCase):
plugin = '%s.%s' % (__name__, TestNetworkGatewayPlugin.__name__)
if not ext_mgr:
ext_mgr = TestExtensionManager()
self.resource = networkgw.RESOURCE_NAME.replace('-', '_')
self.gw_resource = networkgw.GATEWAY_RESOURCE_NAME
self.dev_resource = networkgw.DEVICE_RESOURCE_NAME
super(NetworkGatewayDbTestCase, self).setUp(plugin=plugin,
ext_mgr=ext_mgr)
def _create_network_gateway(self, fmt, tenant_id, name=None,
devices=None, arg_list=None, **kwargs):
data = {self.resource: {'tenant_id': tenant_id,
'devices': devices}}
data = {self.gw_resource: {'tenant_id': tenant_id,
'devices': devices}}
if name:
data[self.resource]['name'] = name
data[self.gw_resource]['name'] = name
for arg in arg_list or ():
# Arg must be present and not empty
if arg in kwargs and kwargs[arg]:
data[self.resource][arg] = kwargs[arg]
nw_gw_req = self.new_create_request(networkgw.COLLECTION_NAME,
data[self.gw_resource][arg] = kwargs[arg]
nw_gw_req = self.new_create_request(networkgw.NETWORK_GATEWAYS,
data, fmt)
if (kwargs.get('set_context') and tenant_id):
# create a specific auth context for this request
@ -275,16 +388,89 @@ class NetworkGatewayDbTestCase(test_db_plugin.NeutronDbPluginV2TestCase):
@contextlib.contextmanager
def _network_gateway(self, name='gw1', devices=None,
fmt='json', tenant_id=_uuid()):
device = None
if not devices:
devices = [{'id': _uuid(), 'interface_name': 'xyz'}]
device_res = self._create_gateway_device(
fmt, tenant_id, 'stt', '1.1.1.1', 'xxxxxx',
name='whatever')
if device_res.status_int >= 400:
raise exc.HTTPClientError(code=device_res.status_int)
device = self.deserialize(fmt, device_res)
devices = [{'id': device[self.dev_resource]['id'],
'interface_name': 'xyz'}]
res = self._create_network_gateway(fmt, tenant_id, name=name,
devices=devices)
network_gateway = self.deserialize(fmt, res)
if res.status_int >= 400:
raise exc.HTTPClientError(code=res.status_int)
network_gateway = self.deserialize(fmt, res)
yield network_gateway
self._delete(networkgw.COLLECTION_NAME,
network_gateway[self.resource]['id'])
self._delete(networkgw.NETWORK_GATEWAYS,
network_gateway[self.gw_resource]['id'])
if device:
self._delete(networkgw.GATEWAY_DEVICES,
device[self.dev_resource]['id'])
def _create_gateway_device(self, fmt, tenant_id,
connector_type, connector_ip,
client_certificate, name=None,
set_context=False):
data = {self.dev_resource: {'tenant_id': tenant_id,
'connector_type': connector_type,
'connector_ip': connector_ip,
'client_certificate': client_certificate}}
if name:
data[self.dev_resource]['name'] = name
gw_dev_req = self.new_create_request(networkgw.GATEWAY_DEVICES,
data, fmt)
if (set_context and tenant_id):
# create a specific auth context for this request
gw_dev_req.environ['neutron.context'] = context.Context(
'', tenant_id)
return gw_dev_req.get_response(self.ext_api)
def _update_gateway_device(self, fmt, gateway_device_id,
connector_type=None, connector_ip=None,
client_certificate=None, name=None,
set_context=False, tenant_id=None):
data = {self.dev_resource: {}}
if connector_type:
data[self.dev_resource]['connector_type'] = connector_type
if connector_ip:
data[self.dev_resource]['connector_ip'] = connector_ip
if client_certificate:
data[self.dev_resource]['client_certificate'] = client_certificate
if name:
data[self.dev_resource]['name'] = name
gw_dev_req = self.new_update_request(networkgw.GATEWAY_DEVICES,
data, gateway_device_id, fmt)
if (set_context and tenant_id):
# create a specific auth context for this request
gw_dev_req.environ['neutron.context'] = context.Context(
'', tenant_id)
return gw_dev_req.get_response(self.ext_api)
@contextlib.contextmanager
def _gateway_device(self, name='gw_dev',
connector_type='stt',
connector_ip='1.1.1.1',
client_certificate='xxxxxxxxxxxxxxx',
fmt='json', tenant_id=_uuid()):
res = self._create_gateway_device(
fmt,
tenant_id,
connector_type=connector_type,
connector_ip=connector_ip,
client_certificate=client_certificate,
name=name)
if res.status_int >= 400:
raise exc.HTTPClientError(code=res.status_int)
gateway_device = self.deserialize(fmt, res)
yield gateway_device
self._delete(networkgw.GATEWAY_DEVICES,
gateway_device[self.dev_resource]['id'])
def _gateway_action(self, action, network_gateway_id, network_id,
segmentation_type, segmentation_id=None,
@ -294,7 +480,7 @@ class NetworkGatewayDbTestCase(test_db_plugin.NeutronDbPluginV2TestCase):
if segmentation_id:
connection_data['segmentation_id'] = segmentation_id
req = self.new_action_request(networkgw.COLLECTION_NAME,
req = self.new_action_request(networkgw.NETWORK_GATEWAYS,
connection_data,
network_gateway_id,
"%s_network" % action)
@ -307,7 +493,7 @@ class NetworkGatewayDbTestCase(test_db_plugin.NeutronDbPluginV2TestCase):
with self._network_gateway() as gw:
with self.network() as net:
body = self._gateway_action('connect',
gw[self.resource]['id'],
gw[self.gw_resource]['id'],
net['network']['id'],
segmentation_type,
segmentation_id)
@ -320,10 +506,10 @@ class NetworkGatewayDbTestCase(test_db_plugin.NeutronDbPluginV2TestCase):
gw_port_id = connection_info['port_id']
port_body = self._show('ports', gw_port_id)
self.assertEqual(port_body['port']['device_id'],
gw[self.resource]['id'])
gw[self.gw_resource]['id'])
# Clean up - otherwise delete will fail
body = self._gateway_action('disconnect',
gw[self.resource]['id'],
gw[self.gw_resource]['id'],
net['network']['id'],
segmentation_type,
segmentation_id)
@ -332,90 +518,98 @@ class NetworkGatewayDbTestCase(test_db_plugin.NeutronDbPluginV2TestCase):
expected_code=exc.HTTPNotFound.code)
def test_create_network_gateway(self):
name = 'test-gw'
devices = [{'id': _uuid(), 'interface_name': 'xxx'},
{'id': _uuid(), 'interface_name': 'yyy'}]
keys = [('devices', devices), ('name', name)]
with self._network_gateway(name=name, devices=devices) as gw:
for k, v in keys:
self.assertEqual(gw[self.resource][k], v)
with contextlib.nested(
self._gateway_device(name='dev_1'),
self._gateway_device(name='dev_2')) as (dev_1, dev_2):
name = 'test-gw'
dev_1_id = dev_1[self.dev_resource]['id']
dev_2_id = dev_2[self.dev_resource]['id']
devices = [{'id': dev_1_id, 'interface_name': 'xxx'},
{'id': dev_2_id, 'interface_name': 'yyy'}]
keys = [('devices', devices), ('name', name)]
with self._network_gateway(name=name, devices=devices) as gw:
for k, v in keys:
self.assertEqual(gw[self.gw_resource][k], v)
def test_create_network_gateway_no_interface_name(self):
name = 'test-gw'
devices = [{'id': _uuid()}]
exp_devices = devices
exp_devices[0]['interface_name'] = 'breth0'
keys = [('devices', exp_devices), ('name', name)]
with self._network_gateway(name=name, devices=devices) as gw:
for k, v in keys:
self.assertEqual(gw[self.resource][k], v)
def _test_delete_network_gateway(self, exp_gw_count=0):
name = 'test-gw'
devices = [{'id': _uuid(), 'interface_name': 'xxx'},
{'id': _uuid(), 'interface_name': 'yyy'}]
with self._network_gateway(name=name, devices=devices):
# Nothing to do here - just let the gateway go
pass
# Verify nothing left on db
session = db_api.get_session()
gw_query = session.query(networkgw_db.NetworkGateway)
dev_query = session.query(networkgw_db.NetworkGatewayDevice)
self.assertEqual(exp_gw_count, gw_query.count())
self.assertEqual(0, dev_query.count())
with self._gateway_device() as dev:
name = 'test-gw'
devices = [{'id': dev[self.dev_resource]['id']}]
exp_devices = devices
exp_devices[0]['interface_name'] = 'breth0'
keys = [('devices', exp_devices), ('name', name)]
with self._network_gateway(name=name, devices=devices) as gw:
for k, v in keys:
self.assertEqual(gw[self.gw_resource][k], v)
def test_delete_network_gateway(self):
self._test_delete_network_gateway()
with self._gateway_device() as dev:
name = 'test-gw'
device_id = dev[self.dev_resource]['id']
devices = [{'id': device_id,
'interface_name': 'xxx'}]
with self._network_gateway(name=name, devices=devices) as gw:
# Nothing to do here - just let the gateway go
gw_id = gw[self.gw_resource]['id']
# Verify nothing left on db
session = db_api.get_session()
dev_query = session.query(
networkgw_db.NetworkGatewayDevice).filter(
networkgw_db.NetworkGatewayDevice.id == device_id)
self.assertIsNone(dev_query.first())
gw_query = session.query(networkgw_db.NetworkGateway).filter(
networkgw_db.NetworkGateway.id == gw_id)
self.assertIsNone(gw_query.first())
def test_update_network_gateway(self):
with self._network_gateway() as gw:
data = {self.resource: {'name': 'new_name'}}
req = self.new_update_request(networkgw.COLLECTION_NAME,
data = {self.gw_resource: {'name': 'new_name'}}
req = self.new_update_request(networkgw.NETWORK_GATEWAYS,
data,
gw[self.resource]['id'])
gw[self.gw_resource]['id'])
res = self.deserialize('json', req.get_response(self.ext_api))
self.assertEqual(res[self.resource]['name'],
data[self.resource]['name'])
self.assertEqual(res[self.gw_resource]['name'],
data[self.gw_resource]['name'])
def test_get_network_gateway(self):
with self._network_gateway(name='test-gw') as gw:
req = self.new_show_request(networkgw.COLLECTION_NAME,
gw[self.resource]['id'])
req = self.new_show_request(networkgw.NETWORK_GATEWAYS,
gw[self.gw_resource]['id'])
res = self.deserialize('json', req.get_response(self.ext_api))
self.assertEqual(res[self.resource]['name'],
gw[self.resource]['name'])
self.assertEqual(res[self.gw_resource]['name'],
gw[self.gw_resource]['name'])
def test_list_network_gateways(self):
with self._network_gateway(name='test-gw-1') as gw1:
with self._network_gateway(name='test_gw_2') as gw2:
req = self.new_list_request(networkgw.COLLECTION_NAME)
req = self.new_list_request(networkgw.NETWORK_GATEWAYS)
res = self.deserialize('json', req.get_response(self.ext_api))
key = self.resource + 's'
key = self.gw_resource + 's'
self.assertEqual(len(res[key]), 2)
self.assertEqual(res[key][0]['name'],
gw1[self.resource]['name'])
gw1[self.gw_resource]['name'])
self.assertEqual(res[key][1]['name'],
gw2[self.resource]['name'])
gw2[self.gw_resource]['name'])
def _test_list_network_gateway_with_multiple_connections(
self, expected_gateways=1):
with self._network_gateway() as gw:
with self.network() as net_1:
self._gateway_action('connect',
gw[self.resource]['id'],
gw[self.gw_resource]['id'],
net_1['network']['id'],
'vlan', 555)
self._gateway_action('connect',
gw[self.resource]['id'],
gw[self.gw_resource]['id'],
net_1['network']['id'],
'vlan', 777)
req = self.new_list_request(networkgw.COLLECTION_NAME)
req = self.new_list_request(networkgw.NETWORK_GATEWAYS)
res = self.deserialize('json', req.get_response(self.ext_api))
key = self.resource + 's'
key = self.gw_resource + 's'
self.assertEqual(len(res[key]), expected_gateways)
for item in res[key]:
self.assertIn('ports', item)
if item['id'] == gw[self.resource]['id']:
if item['id'] == gw[self.gw_resource]['id']:
gw_ports = item['ports']
self.assertEqual(len(gw_ports), 2)
segmentation_ids = [555, 777]
@ -425,11 +619,11 @@ class NetworkGatewayDbTestCase(test_db_plugin.NeutronDbPluginV2TestCase):
segmentation_ids.remove(gw_port['segmentation_id'])
# Required cleanup
self._gateway_action('disconnect',
gw[self.resource]['id'],
gw[self.gw_resource]['id'],
net_1['network']['id'],
'vlan', 555)
self._gateway_action('disconnect',
gw[self.resource]['id'],
gw[self.gw_resource]['id'],
net_1['network']['id'],
'vlan', 777)
@ -449,19 +643,19 @@ class NetworkGatewayDbTestCase(test_db_plugin.NeutronDbPluginV2TestCase):
with self._network_gateway() as gw:
with self.network() as net_1:
self._gateway_action('connect',
gw[self.resource]['id'],
gw[self.gw_resource]['id'],
net_1['network']['id'],
'vlan', 555)
self._gateway_action('connect',
gw[self.resource]['id'],
gw[self.gw_resource]['id'],
net_1['network']['id'],
'vlan', 777)
self._gateway_action('disconnect',
gw[self.resource]['id'],
gw[self.gw_resource]['id'],
net_1['network']['id'],
'vlan', 555)
self._gateway_action('disconnect',
gw[self.resource]['id'],
gw[self.gw_resource]['id'],
net_1['network']['id'],
'vlan', 777)
@ -470,19 +664,19 @@ class NetworkGatewayDbTestCase(test_db_plugin.NeutronDbPluginV2TestCase):
with self._network_gateway() as gw_2:
with self.network() as net_1:
self._gateway_action('connect',
gw_1[self.resource]['id'],
gw_1[self.gw_resource]['id'],
net_1['network']['id'],
'vlan', 555)
self._gateway_action('connect',
gw_2[self.resource]['id'],
gw_2[self.gw_resource]['id'],
net_1['network']['id'],
'vlan', 555)
self._gateway_action('disconnect',
gw_1[self.resource]['id'],
gw_1[self.gw_resource]['id'],
net_1['network']['id'],
'vlan', 555)
self._gateway_action('disconnect',
gw_2[self.resource]['id'],
gw_2[self.gw_resource]['id'],
net_1['network']['id'],
'vlan', 555)
@ -490,25 +684,25 @@ class NetworkGatewayDbTestCase(test_db_plugin.NeutronDbPluginV2TestCase):
with self._network_gateway() as gw:
with self.network() as net_1:
self._gateway_action('connect',
gw[self.resource]['id'],
gw[self.gw_resource]['id'],
net_1['network']['id'],
'vlan', 555)
with self.network() as net_2:
self._gateway_action('connect',
gw[self.resource]['id'],
gw[self.gw_resource]['id'],
net_2['network']['id'],
'vlan', 555,
expected_status=exc.HTTPConflict.code)
# Clean up - otherwise delete will fail
self._gateway_action('disconnect',
gw[self.resource]['id'],
gw[self.gw_resource]['id'],
net_1['network']['id'],
'vlan', 555)
def test_connect_invalid_network_returns_400(self):
with self._network_gateway() as gw:
self._gateway_action('connect',
gw[self.resource]['id'],
gw[self.gw_resource]['id'],
'hohoho',
'vlan', 555,
expected_status=exc.HTTPBadRequest.code)
@ -516,7 +710,7 @@ class NetworkGatewayDbTestCase(test_db_plugin.NeutronDbPluginV2TestCase):
def test_connect_unspecified_network_returns_400(self):
with self._network_gateway() as gw:
self._gateway_action('connect',
gw[self.resource]['id'],
gw[self.gw_resource]['id'],
None,
'vlan', 555,
expected_status=exc.HTTPBadRequest.code)
@ -525,25 +719,25 @@ class NetworkGatewayDbTestCase(test_db_plugin.NeutronDbPluginV2TestCase):
with self._network_gateway() as gw:
with self.network() as net_1:
self._gateway_action('connect',
gw[self.resource]['id'],
gw[self.gw_resource]['id'],
net_1['network']['id'],
'vlan', 555)
self._gateway_action('connect',
gw[self.resource]['id'],
gw[self.gw_resource]['id'],
net_1['network']['id'],
'vlan', 777)
# This should raise
self._gateway_action('disconnect',
gw[self.resource]['id'],
gw[self.gw_resource]['id'],
net_1['network']['id'],
'vlan',
expected_status=exc.HTTPConflict.code)
self._gateway_action('disconnect',
gw[self.resource]['id'],
gw[self.gw_resource]['id'],
net_1['network']['id'],
'vlan', 555)
self._gateway_action('disconnect',
gw[self.resource]['id'],
gw[self.gw_resource]['id'],
net_1['network']['id'],
'vlan', 777)
@ -551,7 +745,7 @@ class NetworkGatewayDbTestCase(test_db_plugin.NeutronDbPluginV2TestCase):
with self._network_gateway() as gw:
with self.network() as net_1:
body = self._gateway_action('connect',
gw[self.resource]['id'],
gw[self.gw_resource]['id'],
net_1['network']['id'],
'vlan', 555)
# fetch port id and try to delete it
@ -559,7 +753,7 @@ class NetworkGatewayDbTestCase(test_db_plugin.NeutronDbPluginV2TestCase):
self._delete('ports', gw_port_id,
expected_code=exc.HTTPConflict.code)
body = self._gateway_action('disconnect',
gw[self.resource]['id'],
gw[self.gw_resource]['id'],
net_1['network']['id'],
'vlan', 555)
@ -567,14 +761,14 @@ class NetworkGatewayDbTestCase(test_db_plugin.NeutronDbPluginV2TestCase):
with self._network_gateway() as gw:
with self.network() as net_1:
self._gateway_action('connect',
gw[self.resource]['id'],
gw[self.gw_resource]['id'],
net_1['network']['id'],
'flat')
self._delete(networkgw.COLLECTION_NAME,
gw[self.resource]['id'],
self._delete(networkgw.NETWORK_GATEWAYS,
gw[self.gw_resource]['id'],
expected_code=exc.HTTPConflict.code)
self._gateway_action('disconnect',
gw[self.resource]['id'],
gw[self.gw_resource]['id'],
net_1['network']['id'],
'flat')
@ -582,25 +776,99 @@ class NetworkGatewayDbTestCase(test_db_plugin.NeutronDbPluginV2TestCase):
with self._network_gateway() as gw:
with self.network() as net_1:
self._gateway_action('connect',
gw[self.resource]['id'],
gw[self.gw_resource]['id'],
net_1['network']['id'],
'vlan', 555)
self._gateway_action('disconnect',
gw[self.resource]['id'],
gw[self.gw_resource]['id'],
net_1['network']['id'],
'vlan', 999,
expected_status=exc.HTTPNotFound.code)
self._gateway_action('disconnect',
gw[self.resource]['id'],
gw[self.gw_resource]['id'],
net_1['network']['id'],
'vlan', 555)
def test_create_gateway_device(
self, expected_status=networkgw_db.STATUS_UNKNOWN):
with self._gateway_device(name='test-dev',
connector_type='stt',
connector_ip='1.1.1.1',
client_certificate='xyz') as dev:
self.assertEqual(dev[self.dev_resource]['name'], 'test-dev')
self.assertEqual(dev[self.dev_resource]['connector_type'], 'stt')
self.assertEqual(dev[self.dev_resource]['connector_ip'], '1.1.1.1')
self.assertEqual(dev[self.dev_resource]['status'], expected_status)
def test_get_gateway_device(
self, expected_status=networkgw_db.STATUS_UNKNOWN):
with self._gateway_device(name='test-dev',
connector_type='stt',
connector_ip='1.1.1.1',
client_certificate='xyz') as dev:
req = self.new_show_request(networkgw.GATEWAY_DEVICES,
dev[self.dev_resource]['id'])
res = self.deserialize('json', req.get_response(self.ext_api))
self.assertEqual(res[self.dev_resource]['name'], 'test-dev')
self.assertEqual(res[self.dev_resource]['connector_type'], 'stt')
self.assertEqual(res[self.dev_resource]['connector_ip'], '1.1.1.1')
self.assertEqual(res[self.dev_resource]['status'], expected_status)
def test_update_gateway_device(
self, expected_status=networkgw_db.STATUS_UNKNOWN):
with self._gateway_device(name='test-dev',
connector_type='stt',
connector_ip='1.1.1.1',
client_certificate='xyz') as dev:
self._update_gateway_device('json', dev[self.dev_resource]['id'],
connector_type='stt',
connector_ip='2.2.2.2',
name='test-dev-upd')
req = self.new_show_request(networkgw.GATEWAY_DEVICES,
dev[self.dev_resource]['id'])
res = self.deserialize('json', req.get_response(self.ext_api))
self.assertEqual(res[self.dev_resource]['name'], 'test-dev-upd')
self.assertEqual(res[self.dev_resource]['connector_type'], 'stt')
self.assertEqual(res[self.dev_resource]['connector_ip'], '2.2.2.2')
self.assertEqual(res[self.dev_resource]['status'], expected_status)
def test_delete_gateway_device(self):
with self._gateway_device(name='test-dev',
connector_type='stt',
connector_ip='1.1.1.1',
client_certificate='xyz') as dev:
# Nothing to do here - just note the device id
dev_id = dev[self.dev_resource]['id']
# Verify nothing left on db
session = db_api.get_session()
dev_query = session.query(networkgw_db.NetworkGatewayDevice)
dev_query.filter(networkgw_db.NetworkGatewayDevice.id == dev_id)
self.assertIsNone(dev_query.first())
class TestNetworkGateway(NsxPluginV2TestCase,
NetworkGatewayDbTestCase):
def setUp(self, plugin=PLUGIN_NAME, ext_mgr=None):
cfg.CONF.set_override('api_extensions_path', NSXEXT_PATH)
# Mock l2gwlib calls for gateway devices since this resource is not
# mocked through the fake NVP API client
create_gw_dev_patcher = mock.patch.object(
l2gwlib, 'create_gateway_device')
update_gw_dev_patcher = mock.patch.object(
l2gwlib, 'update_gateway_device')
delete_gw_dev_patcher = mock.patch.object(
l2gwlib, 'delete_gateway_device')
get_gw_dev_status_patcher = mock.patch.object(
l2gwlib, 'get_gateway_device_status')
mock_create_gw_dev = create_gw_dev_patcher.start()
mock_create_gw_dev.return_value = {'uuid': 'callejon'}
update_gw_dev_patcher.start()
delete_gw_dev_patcher.start()
self.mock_get_gw_dev_status = get_gw_dev_status_patcher.start()
self.addCleanup(mock.patch.stopall)
super(TestNetworkGateway,
self).setUp(plugin=plugin, ext_mgr=ext_mgr)
@ -608,15 +876,15 @@ class TestNetworkGateway(NsxPluginV2TestCase,
name = 'this_is_a_gateway_whose_name_is_longer_than_40_chars'
with self._network_gateway(name=name) as nw_gw:
# Assert Neutron name is not truncated
self.assertEqual(nw_gw[self.resource]['name'], name)
self.assertEqual(nw_gw[self.gw_resource]['name'], name)
def test_update_network_gateway_with_name_calls_backend(self):
with mock.patch.object(
nsxlib.l2gateway, 'update_l2_gw_service') as mock_update_gw:
with self._network_gateway(name='cavani') as nw_gw:
nw_gw_id = nw_gw[self.resource]['id']
self._update(networkgw.COLLECTION_NAME, nw_gw_id,
{self.resource: {'name': 'higuain'}})
nw_gw_id = nw_gw[self.gw_resource]['id']
self._update(networkgw.NETWORK_GATEWAYS, nw_gw_id,
{self.gw_resource: {'name': 'higuain'}})
mock_update_gw.assert_called_once_with(
mock.ANY, nw_gw_id, 'higuain')
@ -624,22 +892,22 @@ class TestNetworkGateway(NsxPluginV2TestCase,
with mock.patch.object(
nsxlib.l2gateway, 'update_l2_gw_service') as mock_update_gw:
with self._network_gateway(name='something') as nw_gw:
nw_gw_id = nw_gw[self.resource]['id']
self._update(networkgw.COLLECTION_NAME, nw_gw_id,
{self.resource: {}})
nw_gw_id = nw_gw[self.gw_resource]['id']
self._update(networkgw.NETWORK_GATEWAYS, nw_gw_id,
{self.gw_resource: {}})
self.assertEqual(mock_update_gw.call_count, 0)
def test_update_network_gateway_name_exceeds_40_chars(self):
new_name = 'this_is_a_gateway_whose_name_is_longer_than_40_chars'
with self._network_gateway(name='something') as nw_gw:
nw_gw_id = nw_gw[self.resource]['id']
self._update(networkgw.COLLECTION_NAME, nw_gw_id,
{self.resource: {'name': new_name}})
req = self.new_show_request(networkgw.COLLECTION_NAME,
nw_gw_id = nw_gw[self.gw_resource]['id']
self._update(networkgw.NETWORK_GATEWAYS, nw_gw_id,
{self.gw_resource: {'name': new_name}})
req = self.new_show_request(networkgw.NETWORK_GATEWAYS,
nw_gw_id)
res = self.deserialize('json', req.get_response(self.ext_api))
# Assert Neutron name is not truncated
self.assertEqual(new_name, res[self.resource]['name'])
self.assertEqual(new_name, res[self.gw_resource]['name'])
# Assert NSX name is truncated
self.assertEqual(
new_name[:40],
@ -652,49 +920,77 @@ class TestNetworkGateway(NsxPluginV2TestCase,
with mock.patch.object(nsxlib.l2gateway,
'create_l2_gw_service',
new=raise_nsx_api_exc):
res = self._create_network_gateway(
self.fmt, 'xxx', name='yyy',
devices=[{'id': uuidutils.generate_uuid()}])
with self._gateway_device() as dev:
res = self._create_network_gateway(
self.fmt, 'xxx', name='yyy',
devices=[{'id': dev[self.dev_resource]['id']}])
self.assertEqual(500, res.status_int)
def test_create_network_gateway_nsx_error_returns_409(self):
with mock.patch.object(nsxlib.l2gateway,
'create_l2_gw_service',
side_effect=api_exc.Conflict):
res = self._create_network_gateway(
self.fmt, 'xxx', name='yyy',
devices=[{'id': uuidutils.generate_uuid()}])
with self._gateway_device() as dev:
res = self._create_network_gateway(
self.fmt, 'xxx', name='yyy',
devices=[{'id': dev[self.dev_resource]['id']}])
self.assertEqual(409, res.status_int)
def test_list_network_gateways(self):
with self._network_gateway(name='test-gw-1') as gw1:
with self._network_gateway(name='test_gw_2') as gw2:
req = self.new_list_request(networkgw.COLLECTION_NAME)
req = self.new_list_request(networkgw.NETWORK_GATEWAYS)
res = self.deserialize('json', req.get_response(self.ext_api))
# We expect the default gateway too
key = self.resource + 's'
key = self.gw_resource + 's'
self.assertEqual(len(res[key]), 3)
self.assertEqual(res[key][0]['default'],
True)
self.assertEqual(res[key][1]['name'],
gw1[self.resource]['name'])
gw1[self.gw_resource]['name'])
self.assertEqual(res[key][2]['name'],
gw2[self.resource]['name'])
gw2[self.gw_resource]['name'])
def test_list_network_gateway_with_multiple_connections(self):
self._test_list_network_gateway_with_multiple_connections(
expected_gateways=2)
def test_delete_network_gateway(self):
# The default gateway must still be there
self._test_delete_network_gateway(1)
def test_show_network_gateway_nsx_error_returns_404(self):
invalid_id = 'b5afd4a9-eb71-4af7-a082-8fc625a35b61'
req = self.new_show_request(networkgw.COLLECTION_NAME, invalid_id)
req = self.new_show_request(networkgw.NETWORK_GATEWAYS, invalid_id)
res = req.get_response(self.ext_api)
self.assertEqual(exc.HTTPNotFound.code, res.status_int)
def test_create_gateway_device(self):
self.mock_get_gw_dev_status.return_value = True
super(TestNetworkGateway, self).test_create_gateway_device(
expected_status=networkgw_db.STATUS_ACTIVE)
def test_create_gateway_device_status_down(self):
self.mock_get_gw_dev_status.return_value = False
super(TestNetworkGateway, self).test_create_gateway_device(
expected_status=networkgw_db.STATUS_DOWN)
def test_get_gateway_device(self):
self.mock_get_gw_dev_status.return_value = True
super(TestNetworkGateway, self).test_get_gateway_device(
expected_status=networkgw_db.STATUS_ACTIVE)
def test_get_gateway_device_status_down(self):
self.mock_get_gw_dev_status.return_value = False
super(TestNetworkGateway, self).test_get_gateway_device(
expected_status=networkgw_db.STATUS_DOWN)
def test_update_gateway_device(self):
self.mock_get_gw_dev_status.return_value = True
super(TestNetworkGateway, self).test_update_gateway_device(
expected_status=networkgw_db.STATUS_ACTIVE)
def test_update_gateway_device_status_down(self):
self.mock_get_gw_dev_status.return_value = False
super(TestNetworkGateway, self).test_update_gateway_device(
expected_status=networkgw_db.STATUS_DOWN)
class TestNetworkGatewayPlugin(db_base_plugin_v2.NeutronDbPluginV2,
networkgw_db.NetworkGatewayMixin):

View File

@ -14,7 +14,11 @@
# limitations under the License.
#
import mock
from neutron.openstack.common import jsonutils
from neutron.plugins.vmware.api_client import exception
from neutron.plugins.vmware.common import utils as nsx_utils
from neutron.plugins.vmware import nsxlib
from neutron.plugins.vmware.nsxlib import l2gateway as l2gwlib
from neutron.plugins.vmware.nsxlib import switch as switchlib
@ -145,3 +149,148 @@ class L2GatewayTestCase(base.NsxlibTestCase):
self.assertIn('LogicalPortAttachment', resp_obj)
self.assertEqual(resp_obj['LogicalPortAttachment']['type'],
'L2GatewayAttachment')
def _create_expected_req_body(self, display_name, neutron_id,
connector_type, connector_ip,
client_certificate):
body = {
"display_name": display_name,
"tags": [{"tag": neutron_id, "scope": "q_gw_dev_id"},
{"tag": 'fake_tenant', "scope": "os_tid"},
{"tag": nsx_utils.NEUTRON_VERSION,
"scope": "quantum"}],
"transport_connectors": [
{"transport_zone_uuid": 'fake_tz_uuid',
"ip_address": connector_ip,
"type": '%sConnector' % connector_type}],
"admin_status_enabled": True
}
if client_certificate:
body["credential"] = {
"client_certificate": {
"pem_encoded": client_certificate},
"type": "SecurityCertificateCredential"}
return body
def test_create_gw_device(self):
# NOTE(salv-orlando): This unit test mocks backend calls rather than
# leveraging the fake NVP API client
display_name = 'fake-device'
neutron_id = 'whatever'
connector_type = 'stt'
connector_ip = '1.1.1.1'
client_certificate = 'this_should_be_a_certificate'
with mock.patch.object(l2gwlib, 'do_request') as request_mock:
expected_req_body = self._create_expected_req_body(
display_name, neutron_id, connector_type.upper(),
connector_ip, client_certificate)
l2gwlib.create_gateway_device(
self.fake_cluster, 'fake_tenant', display_name, neutron_id,
'fake_tz_uuid', connector_type, connector_ip,
client_certificate)
request_mock.assert_called_once_with(
"POST",
"/ws.v1/transport-node",
jsonutils.dumps(expected_req_body),
cluster=self.fake_cluster)
def test_update_gw_device(self):
# NOTE(salv-orlando): This unit test mocks backend calls rather than
# leveraging the fake NVP API client
display_name = 'fake-device'
neutron_id = 'whatever'
connector_type = 'stt'
connector_ip = '1.1.1.1'
client_certificate = 'this_should_be_a_certificate'
with mock.patch.object(l2gwlib, 'do_request') as request_mock:
expected_req_body = self._create_expected_req_body(
display_name, neutron_id, connector_type.upper(),
connector_ip, client_certificate)
l2gwlib.update_gateway_device(
self.fake_cluster, 'whatever', 'fake_tenant',
display_name, neutron_id,
'fake_tz_uuid', connector_type, connector_ip,
client_certificate)
request_mock.assert_called_once_with(
"PUT",
"/ws.v1/transport-node/whatever",
jsonutils.dumps(expected_req_body),
cluster=self.fake_cluster)
def test_update_gw_device_without_certificate(self):
# NOTE(salv-orlando): This unit test mocks backend calls rather than
# leveraging the fake NVP API client
display_name = 'fake-device'
neutron_id = 'whatever'
connector_type = 'stt'
connector_ip = '1.1.1.1'
with mock.patch.object(l2gwlib, 'do_request') as request_mock:
expected_req_body = self._create_expected_req_body(
display_name, neutron_id, connector_type.upper(),
connector_ip, None)
l2gwlib.update_gateway_device(
self.fake_cluster, 'whatever', 'fake_tenant',
display_name, neutron_id,
'fake_tz_uuid', connector_type, connector_ip,
client_certificate=None)
request_mock.assert_called_once_with(
"PUT",
"/ws.v1/transport-node/whatever",
jsonutils.dumps(expected_req_body),
cluster=self.fake_cluster)
def test_get_gw_device_status(self):
# NOTE(salv-orlando): This unit test mocks backend calls rather than
# leveraging the fake NVP API client
with mock.patch.object(l2gwlib, 'do_request') as request_mock:
l2gwlib.get_gateway_device_status(self.fake_cluster, 'whatever')
request_mock.assert_called_once_with(
"GET",
"/ws.v1/transport-node/whatever/status",
cluster=self.fake_cluster)
def test_get_gw_devices_status(self):
# NOTE(salv-orlando): This unit test mocks backend calls rather than
# leveraging the fake NVP API client
with mock.patch.object(nsxlib, 'do_request') as request_mock:
request_mock.return_value = {
'results': [],
'page_cursor': None,
'result_count': 0}
l2gwlib.get_gateway_devices_status(self.fake_cluster)
request_mock.assert_called_once_with(
"GET",
("/ws.v1/transport-node?fields=uuid,tags&"
"relations=TransportNodeStatus&"
"_page_length=1000&tag_scope=quantum"),
cluster=self.fake_cluster)
def test_get_gw_devices_status_filter_by_tenant(self):
# NOTE(salv-orlando): This unit test mocks backend calls rather than
# leveraging the fake NVP API client
with mock.patch.object(nsxlib, 'do_request') as request_mock:
request_mock.return_value = {
'results': [],
'page_cursor': None,
'result_count': 0}
l2gwlib.get_gateway_devices_status(self.fake_cluster,
tenant_id='ssc_napoli')
request_mock.assert_called_once_with(
"GET",
("/ws.v1/transport-node?fields=uuid,tags&"
"relations=TransportNodeStatus&"
"tag_scope=os_tid&tag=ssc_napoli&"
"_page_length=1000&tag_scope=quantum"),
cluster=self.fake_cluster)
def test_delete_gw_device(self):
# NOTE(salv-orlando): This unit test mocks backend calls rather than
# leveraging the fake NVP API client
with mock.patch.object(l2gwlib, 'do_request') as request_mock:
l2gwlib.delete_gateway_device(self.fake_cluster, 'whatever')
request_mock.assert_called_once_with(
"DELETE",
"/ws.v1/transport-node/whatever",
cluster=self.fake_cluster)