NSXv3: Support network creation options
Handle admin state and provider network options during NSXv3 network creation. Change-Id: I13a848f943d0e983542d16f26a821bd9a3899442
This commit is contained in:
parent
865fccf9ab
commit
fedfa5962f
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)])
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
@ -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'])
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user