NSXv3: Support network creation options

Handle admin state and provider network options during NSXv3 network creation.

Change-Id: I13a848f943d0e983542d16f26a821bd9a3899442
This commit is contained in:
Janet Yu 2015-07-12 22:53:33 -07:00
parent 865fccf9ab
commit fedfa5962f
8 changed files with 254 additions and 62 deletions

View File

@ -85,11 +85,14 @@ function neutron_plugin_configure_plugin_agent {
}
function neutron_plugin_configure_service {
if [[ "$DEFAULT_TZ_UUID" != "" ]]; then
iniset /$Q_PLUGIN_CONF_FILE DEFAULT default_tz_uuid $DEFAULT_TZ_UUID
if [[ "$DEFAULT_OVERLAY_TZ_UUID" != "" ]]; then
iniset /$Q_PLUGIN_CONF_FILE nsx_v3 default_overlay_tz_uuid $DEFAULT_OVERLAY_TZ_UUID
else
die $LINENO "The VMware NSX plugin won't work without a default transport zone."
fi
if [[ "$DEFAULT_VLAN_TZ_UUID" != "" ]]; then
iniset /$Q_PLUGIN_CONF_FILE nsx_v3 default_vlan_tz_uuid $DEFAULT_VLAN_TZ_UUID
fi
if [[ "$DEFAULT_EDGE_CLUSTER_UUID" != "" ]]; then
iniset /$Q_PLUGIN_CONF_FILE nsx_v3 default_edge_cluster_uuid $DEFAULT_EDGE_CLUSTER_UUID
Q_L3_ENABLED=True

View File

@ -172,9 +172,23 @@ nsx_v3_opts = [
help=_('IP address of the NSX manager')),
cfg.StrOpt('default_edge_cluster_uuid',
help=_("Default edge cluster identifier")),
cfg.StrOpt('default_overlay_tz_uuid',
deprecated_name='default_tz_uuid',
help=_("This is the UUID of the default NSX overlay transport "
"zone that will be used for creating tunneled isolated "
"Neutron networks. It needs to be created in NSX "
"before starting Neutron with the NSX plugin.")),
cfg.StrOpt('default_vlan_tz_uuid',
help=_("This is the UUID of the default NSX VLAN transport "
"zone that will be used for bridging between Neutron "
"networks. It needs to be created in NSX before "
"starting Neutron with the NSX plugin.")),
cfg.StrOpt('default_edge_cluster_uuid',
help=_("Default edge cluster identifier")),
cfg.IntOpt('retries',
default=10,
help=_('Maximum number of times to retry API request'))]
help=_('Maximum number of times to retry API request'))
]
DEFAULT_STATUS_CHECK_INTERVAL = 2000
DEFAULT_MINIMUM_POOLED_EDGES = 1

View File

@ -47,6 +47,14 @@ class NsxVNetworkTypes:
PORTGROUP = 'portgroup'
# Allowed network types for the NSXv3 Plugin
class NsxV3NetworkTypes:
"""Allowed provider network types for the NSXv3 Plugin."""
FLAT = 'flat'
VLAN = 'vlan'
VXLAN = 'vxlan'
def get_tags(**kwargs):
tags = ([dict(tag=value, scope=key)
for key, value in six.iteritems(kwargs)])

View File

@ -26,7 +26,7 @@ LOG = log.getLogger(__name__)
def create_logical_switch(display_name, transport_zone_id, tags,
replication_mode=nsx_constants.MTEP,
admin_state=nsx_constants.ADMIN_STATE_UP):
admin_state=True, vlan_id=None):
# TODO(salv-orlando): Validate Replication mode and admin_state
# NOTE: These checks might be moved to the API client library if one that
# performs such checks in the client is available
@ -34,10 +34,17 @@ def create_logical_switch(display_name, transport_zone_id, tags,
resource = 'logical-switches'
body = {'transport_zone_id': transport_zone_id,
'replication_mode': replication_mode,
'admin_state': admin_state,
'display_name': display_name,
'tags': tags}
if admin_state:
body['admin_state'] = nsx_constants.ADMIN_STATE_UP
else:
body['admin_state'] = nsx_constants.ADMIN_STATE_DOWN
if vlan_id:
body['vlan'] = vlan_id
return client.create_resource(resource, body)

View File

@ -28,8 +28,10 @@ from neutron.api.rpc.handlers import metadata_rpc
from neutron.api.v2 import attributes
from neutron.extensions import l3
from neutron.extensions import portbindings as pbin
from neutron.extensions import providernet as pnet
from neutron.common import constants as const
from neutron.common import exceptions as n_exc
from neutron.common import rpc as n_rpc
from neutron.common import topics
from neutron.db import agents_db
@ -40,6 +42,8 @@ from neutron.db import models_v2
from neutron.db import portbindings_db
from neutron.db import securitygroups_db
from neutron.i18n import _LE, _LW
from neutron.plugins.common import constants as plugin_const
from neutron.plugins.common import utils as n_utils
from vmware_nsx.neutron.plugins.vmware.common import config # noqa
from vmware_nsx.neutron.plugins.vmware.common import exceptions as nsx_exc
@ -65,7 +69,8 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2,
supported_extension_aliases = ["quotas",
"binding",
"security-group",
"router"]
"router",
"provider"]
def __init__(self):
super(NsxV3Plugin, self).__init__()
@ -95,17 +100,130 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2,
self.supported_extension_aliases.extend(
['agent', 'dhcp_agent_scheduler'])
def _validate_provider_create(self, context, network_data):
physical_net = network_data.get(pnet.PHYSICAL_NETWORK)
if not attributes.is_attr_set(physical_net):
physical_net = None
vlan_id = network_data.get(pnet.SEGMENTATION_ID)
if not attributes.is_attr_set(vlan_id):
vlan_id = None
err_msg = None
net_type = network_data.get(pnet.NETWORK_TYPE)
if attributes.is_attr_set(net_type):
if net_type == utils.NsxV3NetworkTypes.FLAT:
if vlan_id is not None:
err_msg = (_("Segmentation ID cannot be specified with "
"%s network type") %
utils.NsxV3NetworkTypes.FLAT)
else:
# Set VLAN id to 0 for flat networks
vlan_id = '0'
if physical_net is None:
physical_net = cfg.CONF.nsx_v3.default_vlan_tz_uuid
elif net_type == utils.NsxV3NetworkTypes.VLAN:
# Use default VLAN transport zone if physical network not given
if physical_net is None:
physical_net = cfg.CONF.nsx_v3.default_vlan_tz_uuid
# Validate VLAN id
if not vlan_id:
err_msg = (_('Segmentation ID must be specified with %s '
'network type') %
utils.NsxV3NetworkTypes.VLAN)
elif not n_utils.is_valid_vlan_tag(vlan_id):
err_msg = (_('Segmentation ID %(segmentation_id)s out of '
'range (%(min_id)s through %(max_id)s)') %
{'segmentation_id': vlan_id,
'min_id': plugin_const.MIN_VLAN_TAG,
'max_id': plugin_const.MAX_VLAN_TAG})
else:
# Verify VLAN id is not already allocated
bindings = (
nsx_db.get_network_bindings_by_vlanid_and_physical_net(
context.session, vlan_id, physical_net)
)
if bindings:
raise n_exc.VlanIdInUse(
vlan_id=vlan_id, physical_network=physical_net)
elif net_type == utils.NsxV3NetworkTypes.VXLAN:
if vlan_id:
err_msg = (_("Segmentation ID cannot be specified with "
"%s network type") %
utils.NsxV3NetworkTypes.VXLAN)
else:
err_msg = (_('%(net_type_param)s %(net_type_value)s not '
'supported') %
{'net_type_param': pnet.NETWORK_TYPE,
'net_type_value': net_type})
else:
net_type = None
if err_msg:
raise n_exc.InvalidInput(error_message=err_msg)
if physical_net is None:
# Default to transport type overlay
physical_net = cfg.CONF.nsx_v3.default_overlay_tz_uuid
return net_type, physical_net, vlan_id
def _extend_network_dict_provider(self, context, network, bindings=None):
if not bindings:
bindings = nsx_db.get_network_bindings(context.session,
network['id'])
# With NSX plugin, "normal" overlay networks will have no binding
if bindings:
# Network came in through provider networks API
network[pnet.NETWORK_TYPE] = bindings[0].binding_type
network[pnet.PHYSICAL_NETWORK] = bindings[0].phy_uuid
network[pnet.SEGMENTATION_ID] = bindings[0].vlan_id
def create_network(self, context, network):
# TODO(jwy): Handle creating external network (--router:external)
is_provider_net = any(attributes.is_attr_set(network['network'].get(f))
for f in (pnet.NETWORK_TYPE,
pnet.PHYSICAL_NETWORK,
pnet.SEGMENTATION_ID))
net_type, physical_net, vlan_id = self._validate_provider_create(
context, network['network'])
net_name = network['network']['name']
tags = utils.build_v3_tags_payload(network['network'])
result = nsxlib.create_logical_switch(
network['network']['name'],
cfg.CONF.default_tz_uuid, tags)
network['network']['id'] = result['id']
admin_state = network['network'].get('admin_state_up', True)
# Create network on the backend
LOG.debug('create_network: %(net_name)s, %(physical_net)s, %(tags)s, '
'%(admin_state)s, %(vlan_id)s',
{'net_name': net_name,
'physical_net': physical_net,
'tags': tags,
'admin_state': admin_state,
'vlan_id': vlan_id})
result = nsxlib.create_logical_switch(net_name, physical_net, tags,
admin_state=admin_state,
vlan_id=vlan_id)
net_id = result['id']
network['network']['id'] = net_id
tenant_id = self._get_tenant_id_for_create(context, network['network'])
self._ensure_default_security_group(context, tenant_id)
network = super(NsxV3Plugin, self).create_network(context, network)
# TODO(salv-orlando): Undo logical switch creation on failure
with context.session.begin(subtransactions=True):
# Create network in Neutron
try:
network = super(NsxV3Plugin, self).create_network(context,
network)
except Exception:
with excutils.save_and_reraise_exception():
# Undo creation on the backend
LOG.exception(_LE('Failed to create network %s'), net_id)
nsxlib.delete_logical_switch(net_id)
if is_provider_net:
# Save provider network fields, needed by get_network()
nsx_db.add_network_binding(context.session, net_id, net_type,
physical_net, vlan_id)
return network
def delete_network(self, context, network_id):
@ -136,6 +254,31 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2,
'ip_address': fixed_ip['ip_address']})
return address_bindings
def get_network(self, context, id, fields=None):
with context.session.begin(subtransactions=True):
# Get network from Neutron database
network = self._get_network(context, id)
# Don't do field selection here otherwise we won't be able to add
# provider networks fields
net = self._make_network_dict(network, context=context)
self._extend_network_dict_provider(context, net)
return self._fields(net, fields)
def get_networks(self, context, filters=None, fields=None,
sorts=None, limit=None, marker=None,
page_reverse=False):
# Get networks from Neutron database
filters = filters or {}
with context.session.begin(subtransactions=True):
networks = (
super(NsxV3Plugin, self).get_networks(
context, filters, fields, sorts,
limit, marker, page_reverse))
# Add provider network fields
for net in networks:
self._extend_network_dict_provider(context, net)
return [self._fields(network, fields) for network in networks]
def create_port(self, context, port):
port_id = uuidutils.generate_uuid()
tags = utils.build_v3_tags_payload(port['port'])

View File

@ -22,18 +22,18 @@ from vmware_nsx.neutron.plugins.vmware.common import nsx_constants
FAKE_NAME = "fake_name"
def create_logical_switch(display_name, transport_zone_id, tags,
replication_mode=nsx_constants.MTEP,
admin_state=nsx_constants.ADMIN_STATE_UP):
FAKE_TZ_UUID = uuidutils.generate_uuid()
FAKE_SWITCH_UUID = uuidutils.generate_uuid()
def make_fake_switch(switch_uuid=None, tz_uuid=None, name=FAKE_NAME):
if not switch_uuid:
switch_uuid = uuidutils.generate_uuid()
if not tz_uuid:
tz_uuid = uuidutils.generate_uuid()
FAKE_SWITCH = {
"id": FAKE_SWITCH_UUID,
"display_name": FAKE_NAME,
fake_switch = {
"id": switch_uuid,
"display_name": name,
"resource_type": "LogicalSwitch",
"address_bindings": [],
"transport_zone_id": FAKE_TZ_UUID,
"transport_zone_id": tz_uuid,
"replication_mode": nsx_constants.MTEP,
"admin_state": nsx_constants.ADMIN_STATE_UP,
"vni": 50056,
@ -60,7 +60,13 @@ def create_logical_switch(display_name, transport_zone_id, tags,
}
],
}
return FAKE_SWITCH
return fake_switch
def create_logical_switch(display_name, transport_zone_id, tags,
replication_mode=nsx_constants.MTEP,
admin_state=True, vlan_id=None):
return make_fake_switch()
def create_logical_port(lswitch_id, vif_uuid, tags,

View File

@ -17,10 +17,12 @@
import mock
from oslo_log import log
from oslo_utils import uuidutils
from vmware_nsx.neutron.plugins.vmware.common import nsx_constants
from vmware_nsx.neutron.plugins.vmware.nsxlib import v3 as nsxlib
from vmware_nsx.neutron.tests.unit.vmware import nsx_v3_mocks
from vmware_nsx.neutron.tests.unit.vmware.nsxlib.v3 import nsxlib_testcase
from vmware_nsx.neutron.tests.unit.vmware import test_constants_v3
LOG = log.getLogger(__name__)
@ -33,9 +35,52 @@ class NsxLibSwitchTestCase(nsxlib_testcase.NsxLibTestCase):
"""
Test creating a switch returns the correct response and 200 status
"""
mock_create_resource.return_value = test_constants_v3.FAKE_SWITCH
tz_uuid = uuidutils.generate_uuid()
fake_switch = nsx_v3_mocks.make_fake_switch(tz_uuid=tz_uuid)
mock_create_resource.return_value = fake_switch
result = nsxlib.create_logical_switch(test_constants_v3.FAKE_NAME,
test_constants_v3.FAKE_TZ_UUID,
tags={})
self.assertEqual(test_constants_v3.FAKE_SWITCH, result)
result = nsxlib.create_logical_switch(nsx_v3_mocks.FAKE_NAME, tz_uuid,
[])
self.assertEqual(fake_switch, result)
@mock.patch("vmware_nsx.neutron.plugins.vmware.nsxlib.v3"
".client.create_resource")
def test_create_logical_switch_admin_down(self, mock_create_resource):
"""
Test creating switch with admin_state down
"""
tz_uuid = uuidutils.generate_uuid()
fake_switch = nsx_v3_mocks.make_fake_switch(tz_uuid=tz_uuid)
fake_switch['admin_state'] = nsx_constants.ADMIN_STATE_DOWN
mock_create_resource.return_value = fake_switch
result = nsxlib.create_logical_switch(nsx_v3_mocks.FAKE_NAME, tz_uuid,
[], admin_state=False)
self.assertEqual(fake_switch, result)
@mock.patch("vmware_nsx.neutron.plugins.vmware.nsxlib.v3"
".client.create_resource")
def test_create_logical_switch_vlan(self, mock_create_resource):
"""
Test creating switch with provider:network_type VLAN
"""
tz_uuid = uuidutils.generate_uuid()
fake_switch = nsx_v3_mocks.make_fake_switch()
fake_switch['vlan_id'] = '123'
mock_create_resource.return_value = fake_switch
result = nsxlib.create_logical_switch(nsx_v3_mocks.FAKE_NAME, tz_uuid,
[])
self.assertEqual(fake_switch, result)
@mock.patch("vmware_nsx.neutron.plugins.vmware.nsxlib.v3"
".client.delete_resource")
def test_delete_logical_switch(self, mock_delete_resource):
"""
Test deleting switch
"""
mock_delete_resource.return_value = None
fake_switch = nsx_v3_mocks.make_fake_switch()
result = nsxlib.delete_logical_switch(fake_switch['id'])
self.assertIsNone(result)

View File

@ -17,42 +17,8 @@
from oslo_utils import uuidutils
FAKE_NAME = "fake_name"
FAKE_TZ_UUID = uuidutils.generate_uuid()
FAKE_SWITCH_UUID = uuidutils.generate_uuid()
FAKE_SWITCH = {
"id": FAKE_SWITCH_UUID,
"display_name": FAKE_NAME,
"resource_type": "LogicalSwitch",
"address_bindings": [],
"transport_zone_id": FAKE_TZ_UUID,
"replication_mode": "MTEP",
"admin_state": "UP",
"vni": 50056,
"switching_profile_ids": [
{
"value": "64814784-7896-3901-9741-badeff705639",
"key": "IpDiscoverySwitchingProfile"
},
{
"value": "fad98876-d7ff-11e4-b9d6-1681e6b88ec1",
"key": "SpoofGuardSwitchingProfile"
},
{
"value": "93b4b7e8-f116-415d-a50c-3364611b5d09",
"key": "PortMirroringSwitchingProfile"
},
{
"value": "fbc4fb17-83d9-4b53-a286-ccdf04301888",
"key": "SwitchSecuritySwitchingProfile"
},
{
"value": "f313290b-eba8-4262-bd93-fab5026e9495",
"key": "QosSwitchingProfile"
}
],
}
FAKE_PORT_UUID = uuidutils.generate_uuid()
FAKE_PORT = {
"id": FAKE_PORT_UUID,