NsxV3: external network support

external network is attached to default tier0 router by default, else
attached to tier0 via --provider:physical_network

DocImpact
Change-Id: I3fa4e1a6955d47f68f86b36f214ce4a43daefe5b
This commit is contained in:
linb 2015-08-03 15:01:43 +08:00
parent f58351ade3
commit 7e2205e9ab
7 changed files with 188 additions and 59 deletions

View File

@ -106,6 +106,7 @@ function neutron_plugin_configure_service {
else else
die $LINENO "The VMware NSX plugin needs at least an NSX controller." die $LINENO "The VMware NSX plugin needs at least an NSX controller."
fi fi
_nsxv3_ini_set default_tier0_router_uuid $DEFAULT_TIER0_ROUTER_UUID
_nsxv3_ini_set nsx_manager $NSX_MANAGER "The VMWare NSX plugin needs a NSX manager." _nsxv3_ini_set nsx_manager $NSX_MANAGER "The VMWare NSX plugin needs a NSX manager."
_nsxv3_ini_set nsx_user $NSX_USER _nsxv3_ini_set nsx_user $NSX_USER
_nsxv3_ini_set nsx_password $NSX_PASSWORD _nsxv3_ini_set nsx_password $NSX_PASSWORD

View File

@ -323,3 +323,7 @@
# If true, the NSX Manager server certificate is not verified. If false, # If true, the NSX Manager server certificate is not verified. If false,
# then the default CA truststore is used for verification. # then the default CA truststore is used for verification.
# insecure = True # insecure = True
# UUID of the default tier0 router that will be used for connecting to
# tier1 logical routers and configuring external network
# default_tier0_router_uuid = 412983fd-9016-45e5-93f2-48ba2a931225

View File

@ -195,6 +195,8 @@ nsx_v3_opts = [
'verified. If false, then the default CA truststore is ' 'verified. If false, then the default CA truststore is '
'used for verification. This option is ignored if ' 'used for verification. This option is ignored if '
'"ca_file" is set.')), '"ca_file" is set.')),
cfg.StrOpt('default_tier0_router_uuid',
help=_("Default tier0 router identifier"))
] ]
DEFAULT_STATUS_CHECK_INTERVAL = 2000 DEFAULT_STATUS_CHECK_INTERVAL = 2000

View File

@ -24,6 +24,11 @@ from vmware_nsx.neutron.plugins.vmware.nsxlib.v3 import client
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
def get_edge_cluster(edge_cluster_uuid):
resource = "edge-clusters/%s" % edge_cluster_uuid
return client.get_resource(resource)
def create_logical_switch(display_name, transport_zone_id, tags, def create_logical_switch(display_name, transport_zone_id, tags,
replication_mode=nsx_constants.MTEP, replication_mode=nsx_constants.MTEP,
admin_state=True, vlan_id=None): admin_state=True, vlan_id=None):
@ -157,6 +162,11 @@ def create_logical_router(display_name, tags, edge_cluster_uuid=None,
return client.create_resource(resource, body) return client.create_resource(resource, body)
def get_logical_router(lrouter_id):
resource = 'logical-routers/%s' % lrouter_id
return client.get_resource(resource)
def delete_logical_router(lrouter_id): def delete_logical_router(lrouter_id):
resource = 'logical-routers/%s/' % lrouter_id resource = 'logical-routers/%s/' % lrouter_id

View File

@ -27,6 +27,7 @@ from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api
from neutron.api.rpc.handlers import dhcp_rpc from neutron.api.rpc.handlers import dhcp_rpc
from neutron.api.rpc.handlers import metadata_rpc from neutron.api.rpc.handlers import metadata_rpc
from neutron.api.v2 import attributes from neutron.api.v2 import attributes
from neutron.extensions import external_net as ext_net_extn
from neutron.extensions import extra_dhcp_opt as edo_ext from neutron.extensions import extra_dhcp_opt as edo_ext
from neutron.extensions import l3 from neutron.extensions import l3
from neutron.extensions import portbindings as pbin from neutron.extensions import portbindings as pbin
@ -39,6 +40,7 @@ from neutron.common import topics
from neutron.db import agents_db from neutron.db import agents_db
from neutron.db import agentschedulers_db from neutron.db import agentschedulers_db
from neutron.db import db_base_plugin_v2 from neutron.db import db_base_plugin_v2
from neutron.db import external_net_db
from neutron.db import extradhcpopt_db from neutron.db import extradhcpopt_db
from neutron.db import l3_db from neutron.db import l3_db
from neutron.db import models_v2 from neutron.db import models_v2
@ -59,6 +61,7 @@ LOG = log.getLogger(__name__)
class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2, class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2,
securitygroups_db.SecurityGroupDbMixin, securitygroups_db.SecurityGroupDbMixin,
external_net_db.External_net_db_mixin,
l3_db.L3_NAT_dbonly_mixin, l3_db.L3_NAT_dbonly_mixin,
portbindings_db.PortBindingMixin, portbindings_db.PortBindingMixin,
agentschedulers_db.DhcpAgentSchedulerDbMixin, agentschedulers_db.DhcpAgentSchedulerDbMixin,
@ -74,8 +77,9 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2,
"binding", "binding",
"extra_dhcp_opt", "extra_dhcp_opt",
"security-group", "security-group",
"router", "provider",
"provider"] "external-net",
"router"]
def __init__(self): def __init__(self):
super(NsxV3Plugin, self).__init__() super(NsxV3Plugin, self).__init__()
@ -87,6 +91,7 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2,
# TODO(rkukura): Replace with new VIF security details # TODO(rkukura): Replace with new VIF security details
pbin.CAP_PORT_FILTER: pbin.CAP_PORT_FILTER:
'security-group' in self.supported_extension_aliases}} 'security-group' in self.supported_extension_aliases}}
self.tier0_groups_dict = {}
self._setup_rpc() self._setup_rpc()
def _setup_rpc(self): def _setup_rpc(self):
@ -174,6 +179,73 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2,
return net_type, physical_net, vlan_id return net_type, physical_net, vlan_id
def _validate_tier0(self, tier0_uuid):
if tier0_uuid in self.tier0_groups_dict:
return
err_msg = None
try:
lrouter = nsxlib.get_logical_router(tier0_uuid)
except nsx_exc.ResourceNotFound:
err_msg = _("Failed to validate tier0 router %s since it is "
"not found at the backend") % tier0_uuid
else:
edge_cluster_uuid = lrouter.get('edge_cluster_id')
if not edge_cluster_uuid:
err_msg = _("Failed to get edge cluster uuid from tier0 "
"router %s at the backend") % lrouter
else:
edge_cluster = nsxlib.get_edge_cluster(edge_cluster_uuid)
member_index_list = [member['member_index']
for member in edge_cluster['members']]
if not member_index_list:
err_msg = _("No edge members found in edge_cluster "
"%(cluster)s from tier0 router %(tier0)s") % {
'cluster': edge_cluster_uuid,
'tier0': tier0_uuid}
if err_msg:
raise n_exc.InvalidInput(error_message=err_msg)
else:
self.tier0_groups_dict[tier0_uuid] = {
'edge_cluster_uuid': edge_cluster_uuid,
'member_index_list': member_index_list}
def _validate_external_net_create(self, net_data):
is_provider_net = False
if not attributes.is_attr_set(net_data.get(pnet.PHYSICAL_NETWORK)):
tier0_uuid = cfg.CONF.nsx_v3.default_tier0_router_uuid
else:
tier0_uuid = net_data[pnet.PHYSICAL_NETWORK]
is_provider_net = True
self._validate_tier0(tier0_uuid)
return (is_provider_net, utils.NetworkTypes.L3_EXT, tier0_uuid, 0)
def _create_network_at_the_backend(self, context, net_data):
is_provider_net = any(
attributes.is_attr_set(net_data.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, net_data)
net_name = net_data['name']
tags = utils.build_v3_tags_payload(net_data)
admin_state = net_data.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)
network_id = result['id']
net_data['id'] = network_id
return (is_provider_net, net_type, physical_net, vlan_id)
def _extend_network_dict_provider(self, context, network, bindings=None): def _extend_network_dict_provider(self, context, network, bindings=None):
if not bindings: if not bindings:
bindings = nsx_db.get_network_bindings(context.session, bindings = nsx_db.get_network_bindings(context.session,
@ -186,58 +258,56 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2,
network[pnet.SEGMENTATION_ID] = bindings[0].vlan_id network[pnet.SEGMENTATION_ID] = bindings[0].vlan_id
def create_network(self, context, network): def create_network(self, context, network):
is_provider_net = any(attributes.is_attr_set(network['network'].get(f)) net_data = network['network']
for f in (pnet.NETWORK_TYPE, external = net_data.get(ext_net_extn.EXTERNAL)
pnet.PHYSICAL_NETWORK, if attributes.is_attr_set(external) and external:
pnet.SEGMENTATION_ID)) is_provider_net, net_type, physical_net, vlan_id = (
net_type, physical_net, vlan_id = self._validate_provider_create( self._validate_external_net_create(net_data))
context, network['network']) else:
net_name = network['network']['name'] is_provider_net, net_type, physical_net, vlan_id = (
tags = utils.build_v3_tags_payload(network['network']) self._create_network_at_the_backend(context, net_data))
admin_state = network['network'].get('admin_state_up', True) tenant_id = self._get_tenant_id_for_create(
context, net_data)
# 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) self._ensure_default_security_group(context, tenant_id)
with context.session.begin(subtransactions=True): with context.session.begin(subtransactions=True):
# Create network in Neutron # Create network in Neutron
try: try:
created_net = super(NsxV3Plugin, self).create_network(context, created_net = super(NsxV3Plugin, self).create_network(context,
network) network)
self._process_l3_create(context, created_net, net_data)
except Exception: except Exception:
with excutils.save_and_reraise_exception(): with excutils.save_and_reraise_exception():
# Undo creation on the backend # Undo creation on the backend
LOG.exception(_LE('Failed to create network %s'), net_id) LOG.exception(_LE('Failed to create network %s'),
nsxlib.delete_logical_switch(net_id) created_net['id'])
if net_type != utils.NetworkTypes.L3_EXT:
nsxlib.delete_logical_switch(created_net['id'])
if is_provider_net: if is_provider_net:
# Save provider network fields, needed by get_network() # Save provider network fields, needed by get_network()
nsx_db.add_network_binding(context.session, net_id, net_type, net_bindings = [nsx_db.add_network_binding(
physical_net, vlan_id) context.session, created_net['id'],
net_type, physical_net, vlan_id)]
self._extend_network_dict_provider(context, created_net,
bindings=net_bindings)
return created_net return created_net
def delete_network(self, context, network_id): def delete_network(self, context, network_id):
# First call DB operation for delete network as it will perform # First call DB operation for delete network as it will perform
# checks on active ports # checks on active ports
ret_val = super(NsxV3Plugin, self).delete_network(context, network_id) with context.session.begin(subtransactions=True):
# TODO(salv-orlando): Handle backend failure, possibly without self._process_l3_delete(context, network_id)
# requiring us to un-delete the DB object. For instance, ignore ret_val = super(NsxV3Plugin, self).delete_network(
# failures occuring if logical switch is not found context, network_id)
nsxlib.delete_logical_switch(network_id) if not self._network_is_external(context, network_id):
# TODO(salv-orlando): Handle backend failure, possibly without
# requiring us to un-delete the DB object. For instance, ignore
# failures occuring if logical switch is not found
nsxlib.delete_logical_switch(network_id)
else:
# TODO(berlin): delete subnets public announce on the network
pass
return ret_val return ret_val
def update_network(self, context, id, network): def update_network(self, context, id, network):
@ -248,7 +318,8 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2,
updated_net = super(NsxV3Plugin, self).update_network(context, id, updated_net = super(NsxV3Plugin, self).update_network(context, id,
network) network)
if 'name' in net_data or 'admin_state_up' in net_data: if (not self._network_is_external(context, id) and
'name' in net_data or 'admin_state_up' in net_data):
try: try:
nsxlib.update_logical_switch( nsxlib.update_logical_switch(
id, name=net_data.get('name'), id, name=net_data.get('name'),
@ -265,6 +336,14 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2,
return updated_net return updated_net
def create_subnet(self, context, subnet):
# TODO(berlin): public external subnet announcement
return super(NsxV3Plugin, self).create_subnet(context, subnet)
def delete_subnet(self, context, subnet_id):
# TODO(berlin): cancel public external subnet announcement
return super(NsxV3Plugin, self).delete_subnet(context, subnet_id)
def _build_address_bindings(self, port): def _build_address_bindings(self, port):
address_bindings = [] address_bindings = []
for fixed_ip in port['fixed_ips']: for fixed_ip in port['fixed_ips']:
@ -340,57 +419,70 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2,
# self.get_port(context, parent_name) # self.get_port(context, parent_name)
return parent_name, tag return parent_name, tag
def _create_port_at_the_backend(self, context, neutron_db, port_data):
tags = utils.build_v3_tags_payload(port_data)
parent_name, tag = self._get_data_from_binding_profile(
context, port_data)
address_bindings = self._build_address_bindings(port_data)
# FIXME(arosen): we might need to pull this out of the
# transaction here later.
result = nsxlib.create_logical_port(
lswitch_id=port_data['network_id'],
vif_uuid=port_data['id'], name=port_data['name'], tags=tags,
admin_state=port_data['admin_state_up'],
address_bindings=address_bindings,
parent_name=parent_name, parent_tag=tag)
# TODO(salv-orlando): The logical switch identifier in the
# mapping object is not necessary anymore.
nsx_db.add_neutron_nsx_port_mapping(
context.session, neutron_db['id'],
neutron_db['network_id'], result['id'])
def create_port(self, context, port): def create_port(self, context, port):
dhcp_opts = port['port'].get(edo_ext.EXTRADHCPOPTS, []) dhcp_opts = port['port'].get(edo_ext.EXTRADHCPOPTS, [])
port_id = uuidutils.generate_uuid() port_id = uuidutils.generate_uuid()
tags = utils.build_v3_tags_payload(port['port'])
port['port']['id'] = port_id port['port']['id'] = port_id
self._ensure_default_security_group_on_port(context, port) self._ensure_default_security_group_on_port(context, port)
# TODO(salv-orlando): Undo logical switch creation on failure # TODO(salv-orlando): Undo logical switch creation on failure
with context.session.begin(subtransactions=True): with context.session.begin(subtransactions=True):
parent_name, tag = self._get_data_from_binding_profile(
context, port['port'])
neutron_db = super(NsxV3Plugin, self).create_port(context, port) neutron_db = super(NsxV3Plugin, self).create_port(context, port)
self._process_portbindings_create_and_update(context, self._process_portbindings_create_and_update(context,
port['port'], port['port'],
neutron_db) neutron_db)
port["port"].update(neutron_db) port["port"].update(neutron_db)
address_bindings = self._build_address_bindings(port['port'])
# FIXME(arosen): we might need to pull this out of the transaction
# here later.
result = nsxlib.create_logical_port(
lswitch_id=port['port']['network_id'],
vif_uuid=port_id, name=port['port']['name'], tags=tags,
admin_state=port['port']['admin_state_up'],
address_bindings=address_bindings,
parent_name=parent_name, parent_tag=tag)
# TODO(salv-orlando): The logical switch identifier in the mapping if not self._network_is_external(
# object is not necessary anymore. context, port['port']['network_id']):
nsx_db.add_neutron_nsx_port_mapping( self._create_port_at_the_backend(
context.session, neutron_db['id'], context, neutron_db, port['port'])
neutron_db['network_id'], result['id'])
self._process_portbindings_create_and_update(context, self._process_portbindings_create_and_update(context,
port['port'], port['port'],
neutron_db) neutron_db)
neutron_db[pbin.VNIC_TYPE] = pbin.VNIC_NORMAL neutron_db[pbin.VNIC_TYPE] = pbin.VNIC_NORMAL
if (pbin.PROFILE in port['port'] and if (pbin.PROFILE in port['port'] and
attributes.is_attr_set(port['port'][pbin.PROFILE])): attributes.is_attr_set(port['port'][pbin.PROFILE])):
neutron_db[pbin.PROFILE] = port['port'][pbin.PROFILE] neutron_db[pbin.PROFILE] = port['port'][pbin.PROFILE]
sgids = self._get_security_groups_on_port(context, port) sgids = self._get_security_groups_on_port(context, port)
self._process_port_create_security_group( self._process_port_create_security_group(
context, neutron_db, sgids) context, neutron_db, sgids)
self._process_port_create_extra_dhcp_opts(context, neutron_db, self._process_port_create_extra_dhcp_opts(context, neutron_db,
dhcp_opts) dhcp_opts)
return neutron_db return neutron_db
def delete_port(self, context, port_id, l3_port_check=True): def delete_port(self, context, port_id, l3_port_check=True):
_net_id, nsx_port_id = nsx_db.get_nsx_switch_and_port_id( # if needed, check to see if this is a port owned by
context.session, port_id) # a l3 router. If so, we should prevent deletion here
nsxlib.delete_logical_port(nsx_port_id) if l3_port_check:
self.prevent_l3_port_deletion(context, port_id)
port = self.get_port(context, port_id)
if not self._network_is_external(context, port['network_id']):
_net_id, nsx_port_id = nsx_db.get_nsx_switch_and_port_id(
context.session, port_id)
nsxlib.delete_logical_port(nsx_port_id)
ret_val = super(NsxV3Plugin, self).delete_port(context, port_id) ret_val = super(NsxV3Plugin, self).delete_port(context, port_id)
return ret_val return ret_val

View File

@ -177,3 +177,19 @@ def update_logical_port(lport_id, name=None, admin_state=None):
else: else:
lport['admin_state'] = nsx_constants.ADMIN_STATE_DOWN lport['admin_state'] = nsx_constants.ADMIN_STATE_DOWN
return lport return lport
def get_edge_cluster(edge_cluster_uuid):
FAKE_CLUSTER = {
"id": edge_cluster_uuid,
"members": [
{"member_index": 0},
{"member_index": 1}]}
return FAKE_CLUSTER
def get_logical_router(lrouter_uuid):
FAKE_LROUTER = {
"id": lrouter_uuid,
"edge_cluster_uuid": uuidutils.generate_uuid()}
return FAKE_LROUTER

View File

@ -46,6 +46,9 @@ class NsxPluginV3TestCase(test_plugin.NeutronDbPluginV2TestCase):
nsxlib.delete_logical_port = mock.Mock() nsxlib.delete_logical_port = mock.Mock()
nsxlib.get_logical_port = nsx_v3_mocks.get_logical_port nsxlib.get_logical_port = nsx_v3_mocks.get_logical_port
nsxlib.update_logical_port = nsx_v3_mocks.update_logical_port nsxlib.update_logical_port = nsx_v3_mocks.update_logical_port
# TODO(berlin): fill valid data
nsxlib.get_edge_cluster = nsx_v3_mocks.get_edge_cluster
nsxlib.get_logical_router = nsx_v3_mocks.get_logical_router
class TestNetworksV2(test_plugin.TestNetworksV2, NsxPluginV3TestCase): class TestNetworksV2(test_plugin.TestNetworksV2, NsxPluginV3TestCase):
@ -63,6 +66,7 @@ class SecurityGroupsTestCase(ext_sg.SecurityGroupDBTestCase):
ext_mgr=None): ext_mgr=None):
nsxlib.create_logical_switch = nsx_v3_mocks.create_logical_switch nsxlib.create_logical_switch = nsx_v3_mocks.create_logical_switch
nsxlib.create_logical_port = nsx_v3_mocks.create_logical_port nsxlib.create_logical_port = nsx_v3_mocks.create_logical_port
nsxlib.update_logical_port = nsx_v3_mocks.update_logical_port
nsxlib.delete_logical_port = mock.Mock() nsxlib.delete_logical_port = mock.Mock()
nsxlib.delete_logical_switch = mock.Mock() nsxlib.delete_logical_switch = mock.Mock()