Support for NVP advanced service router
When creating an LR: - deploy an Edge asynchronously - create a L2 switch for connecting LR and Edge - attach a router port to the L2 switch. - assign ip address 169.254.2.1/28 and nexthop 169.254.2.3 to LR When set external gateway: - configure Edge interface and default gateway - Add static routes to Edge for all logic networks attached to LR via nexthop 169.254.2.1 - configure SNAT rules for all logic networks attached to LR When add router interface: - Add static route/SNAT rule for the network attached to LR When associate floating IP address: - configure DNAT rule for the floating ip and the port Tests being done: - Verified Edge is deployed asynchronously and LR is attached to the internal created L2 switch - Manually attach Edge's vNic to the L2 switch and Edge is able to ping 169.254.2.1 - Verified router-delete deletes Edge asynchronously and remove the internal L2 switch - Verified SNAT/DNAT/static-routes rules are configured on Edge in correct order - Verified external vnic ip address/netmask and default gateway is configured Implements: blueprint nvp-service-router Change-Id: If9eff53df4d65cf4e318dedbfaafc742f6c6ab7f
This commit is contained in:
parent
39ef7594bd
commit
f43eb49f56
@ -0,0 +1,69 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# Copyright 2013 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
"""service router
|
||||||
|
|
||||||
|
Revision ID: 4a666eb208c2
|
||||||
|
Revises: 38fc1f6789f8
|
||||||
|
Create Date: 2013-09-03 01:55:57.799217
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '4a666eb208c2'
|
||||||
|
down_revision = '38fc1f6789f8'
|
||||||
|
|
||||||
|
# Change to ['*'] if this migration applies to all plugins
|
||||||
|
|
||||||
|
migration_for_plugins = [
|
||||||
|
'neutron.plugins.nicira.NeutronServicePlugin.NvpAdvancedPlugin'
|
||||||
|
]
|
||||||
|
|
||||||
|
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(
|
||||||
|
'vcns_router_bindings',
|
||||||
|
sa.Column('status', sa.String(length=16), nullable=False),
|
||||||
|
sa.Column('status_description', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('router_id', sa.String(length=36), nullable=False),
|
||||||
|
sa.Column('edge_id', sa.String(length=16), nullable=True),
|
||||||
|
sa.Column('lswitch_id', sa.String(length=36), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('router_id'),
|
||||||
|
mysql_engine='InnoDB'
|
||||||
|
)
|
||||||
|
op.add_column(
|
||||||
|
u'nsxrouterextattributess',
|
||||||
|
sa.Column('service_router',
|
||||||
|
sa.Boolean(),
|
||||||
|
nullable=False))
|
||||||
|
op.execute("UPDATE nsxrouterextattributess set service_router=False")
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade(active_plugins=None, options=None):
|
||||||
|
if not migration.should_run(active_plugins, migration_for_plugins):
|
||||||
|
return
|
||||||
|
|
||||||
|
op.drop_column(u'nsxrouterextattributess', 'service_router')
|
||||||
|
op.drop_table('vcns_router_bindings')
|
@ -54,6 +54,7 @@ from neutron.extensions import portsecurity as psec
|
|||||||
from neutron.extensions import providernet as pnet
|
from neutron.extensions import providernet as pnet
|
||||||
from neutron.extensions import securitygroup as ext_sg
|
from neutron.extensions import securitygroup as ext_sg
|
||||||
from neutron.openstack.common import excutils
|
from neutron.openstack.common import excutils
|
||||||
|
from neutron.plugins.common import constants as plugin_const
|
||||||
from neutron.plugins.nicira.common import config
|
from neutron.plugins.nicira.common import config
|
||||||
from neutron.plugins.nicira.common import exceptions as nvp_exc
|
from neutron.plugins.nicira.common import exceptions as nvp_exc
|
||||||
from neutron.plugins.nicira.common import securitygroups as nvp_sec
|
from neutron.plugins.nicira.common import securitygroups as nvp_sec
|
||||||
@ -78,6 +79,7 @@ NVP_NOSNAT_RULES_ORDER = 10
|
|||||||
NVP_FLOATINGIP_NAT_RULES_ORDER = 224
|
NVP_FLOATINGIP_NAT_RULES_ORDER = 224
|
||||||
NVP_EXTGW_NAT_RULES_ORDER = 255
|
NVP_EXTGW_NAT_RULES_ORDER = 255
|
||||||
NVP_EXT_PATH = os.path.join(os.path.dirname(__file__), 'extensions')
|
NVP_EXT_PATH = os.path.join(os.path.dirname(__file__), 'extensions')
|
||||||
|
NVP_DEFAULT_NEXTHOP = '1.1.1.1'
|
||||||
|
|
||||||
|
|
||||||
# Provider network extension - allowed network types for the NVP Plugin
|
# Provider network extension - allowed network types for the NVP Plugin
|
||||||
@ -1381,6 +1383,45 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
|||||||
else:
|
else:
|
||||||
return super(NvpPluginV2, self).get_router(context, id, fields)
|
return super(NvpPluginV2, self).get_router(context, id, fields)
|
||||||
|
|
||||||
|
def _create_lrouter(self, context, router, nexthop):
|
||||||
|
tenant_id = self._get_tenant_id_for_create(context, router)
|
||||||
|
name = router['name']
|
||||||
|
distributed = router.get('distributed')
|
||||||
|
try:
|
||||||
|
lrouter = nvplib.create_lrouter(
|
||||||
|
self.cluster, tenant_id, name, nexthop,
|
||||||
|
distributed=attr.is_attr_set(distributed) and distributed)
|
||||||
|
except nvp_exc.NvpInvalidVersion:
|
||||||
|
msg = _("Cannot create a distributed router with the NVP "
|
||||||
|
"platform currently in execution. Please, try "
|
||||||
|
"without specifying the 'distributed' attribute.")
|
||||||
|
LOG.exception(msg)
|
||||||
|
raise q_exc.BadRequest(resource='router', msg=msg)
|
||||||
|
except NvpApiClient.NvpApiException:
|
||||||
|
raise nvp_exc.NvpPluginException(
|
||||||
|
err_msg=_("Unable to create logical router on NVP Platform"))
|
||||||
|
|
||||||
|
# Create the port here - and update it later if we have gw_info
|
||||||
|
try:
|
||||||
|
self._create_and_attach_router_port(
|
||||||
|
self.cluster, context, lrouter['uuid'], {'fake_ext_gw': True},
|
||||||
|
"L3GatewayAttachment",
|
||||||
|
self.cluster.default_l3_gw_service_uuid)
|
||||||
|
except nvp_exc.NvpPluginException:
|
||||||
|
LOG.exception(_("Unable to create L3GW port on logical router "
|
||||||
|
"%(router_uuid)s. Verify Default Layer-3 Gateway "
|
||||||
|
"service %(def_l3_gw_svc)s id is correct"),
|
||||||
|
{'router_uuid': lrouter['uuid'],
|
||||||
|
'def_l3_gw_svc':
|
||||||
|
self.cluster.default_l3_gw_service_uuid})
|
||||||
|
# Try and remove logical router from NVP
|
||||||
|
nvplib.delete_lrouter(self.cluster, lrouter['uuid'])
|
||||||
|
# Return user a 500 with an apter message
|
||||||
|
raise nvp_exc.NvpPluginException(
|
||||||
|
err_msg=_("Unable to create router %s") % router['name'])
|
||||||
|
lrouter['status'] = plugin_const.ACTIVE
|
||||||
|
return lrouter
|
||||||
|
|
||||||
def create_router(self, context, router):
|
def create_router(self, context, router):
|
||||||
# NOTE(salvatore-orlando): We completely override this method in
|
# NOTE(salvatore-orlando): We completely override this method in
|
||||||
# order to be able to use the NVP ID as Neutron ID
|
# order to be able to use the NVP ID as Neutron ID
|
||||||
@ -1390,8 +1431,7 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
|||||||
has_gw_info = False
|
has_gw_info = False
|
||||||
tenant_id = self._get_tenant_id_for_create(context, r)
|
tenant_id = self._get_tenant_id_for_create(context, r)
|
||||||
# default value to set - nvp wants it (even if we don't have it)
|
# default value to set - nvp wants it (even if we don't have it)
|
||||||
nexthop = '1.1.1.1'
|
nexthop = NVP_DEFAULT_NEXTHOP
|
||||||
try:
|
|
||||||
# if external gateway info are set, then configure nexthop to
|
# if external gateway info are set, then configure nexthop to
|
||||||
# default external gateway
|
# default external gateway
|
||||||
if 'external_gateway_info' in r and r.get('external_gateway_info'):
|
if 'external_gateway_info' in r and r.get('external_gateway_info'):
|
||||||
@ -1412,44 +1452,14 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
|||||||
if ext_net.subnets:
|
if ext_net.subnets:
|
||||||
ext_subnet = ext_net.subnets[0]
|
ext_subnet = ext_net.subnets[0]
|
||||||
nexthop = ext_subnet.gateway_ip
|
nexthop = ext_subnet.gateway_ip
|
||||||
distributed = r.get('distributed')
|
lrouter = self._create_lrouter(context, r, nexthop)
|
||||||
lrouter = nvplib.create_lrouter(
|
|
||||||
self.cluster, tenant_id, router['router']['name'], nexthop,
|
|
||||||
distributed=attr.is_attr_set(distributed) and distributed)
|
|
||||||
# Use NVP identfier for Neutron resource
|
# Use NVP identfier for Neutron resource
|
||||||
r['id'] = lrouter['uuid']
|
r['id'] = lrouter['uuid']
|
||||||
# Update 'distributed' with value returned from NVP
|
# Update 'distributed' with value returned from NVP
|
||||||
# This will be useful for setting the value if the API request
|
# This will be useful for setting the value if the API request
|
||||||
# did not specify any value for the 'distributed' attribute.
|
# did not specify any value for the 'distributed' attribute
|
||||||
# Platforms older than 3.x do not support the attribute
|
# Platforms older than 3.x do not support the attribute
|
||||||
r['distributed'] = lrouter.get('distributed', False)
|
r['distributed'] = lrouter.get('distributed', False)
|
||||||
except nvp_exc.NvpInvalidVersion:
|
|
||||||
msg = _("Cannot create a distributed router with the NVP "
|
|
||||||
"platform currently in execution. Please, try "
|
|
||||||
"without specifying the 'distributed' attribute.")
|
|
||||||
LOG.exception(msg)
|
|
||||||
raise q_exc.BadRequest(resource='router', msg=msg)
|
|
||||||
except NvpApiClient.NvpApiException:
|
|
||||||
raise nvp_exc.NvpPluginException(
|
|
||||||
err_msg=_("Unable to create logical router on NVP Platform"))
|
|
||||||
# Create the port here - and update it later if we have gw_info
|
|
||||||
try:
|
|
||||||
self._create_and_attach_router_port(
|
|
||||||
self.cluster, context, lrouter['uuid'], {'fake_ext_gw': True},
|
|
||||||
"L3GatewayAttachment",
|
|
||||||
self.cluster.default_l3_gw_service_uuid)
|
|
||||||
except nvp_exc.NvpPluginException:
|
|
||||||
LOG.exception(_("Unable to create L3GW port on logical router "
|
|
||||||
"%(router_uuid)s. Verify Default Layer-3 Gateway "
|
|
||||||
"service %(def_l3_gw_svc)s id is correct"),
|
|
||||||
{'router_uuid': lrouter['uuid'],
|
|
||||||
'def_l3_gw_svc':
|
|
||||||
self.cluster.default_l3_gw_service_uuid})
|
|
||||||
# Try and remove logical router from NVP
|
|
||||||
nvplib.delete_lrouter(self.cluster, lrouter['uuid'])
|
|
||||||
# Return user a 500 with an apter message
|
|
||||||
raise nvp_exc.NvpPluginException(
|
|
||||||
err_msg=_("Unable to create router %s") % r['name'])
|
|
||||||
|
|
||||||
with context.session.begin(subtransactions=True):
|
with context.session.begin(subtransactions=True):
|
||||||
# Transaction nesting is needed to avoid foreign key violations
|
# Transaction nesting is needed to avoid foreign key violations
|
||||||
@ -1459,14 +1469,23 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
|||||||
tenant_id=tenant_id,
|
tenant_id=tenant_id,
|
||||||
name=r['name'],
|
name=r['name'],
|
||||||
admin_state_up=r['admin_state_up'],
|
admin_state_up=r['admin_state_up'],
|
||||||
status="ACTIVE")
|
status=lrouter['status'])
|
||||||
self._process_distributed_router_create(context, router_db, r)
|
self._process_nsx_router_create(context, router_db, r)
|
||||||
context.session.add(router_db)
|
context.session.add(router_db)
|
||||||
if has_gw_info:
|
if has_gw_info:
|
||||||
self._update_router_gw_info(context, router_db['id'], gw_info)
|
self._update_router_gw_info(context, router_db['id'], gw_info)
|
||||||
router = self._make_router_dict(router_db)
|
router = self._make_router_dict(router_db)
|
||||||
return router
|
return router
|
||||||
|
|
||||||
|
def _update_lrouter(self, context, router_id, name, nexthop, routes=None):
|
||||||
|
return nvplib.update_lrouter(
|
||||||
|
self.cluster, router_id, name,
|
||||||
|
nexthop, routes=routes)
|
||||||
|
|
||||||
|
def _update_lrouter_routes(self, router_id, routes):
|
||||||
|
nvplib.update_explicit_routes_lrouter(
|
||||||
|
self.cluster, router_id, routes)
|
||||||
|
|
||||||
def update_router(self, context, router_id, router):
|
def update_router(self, context, router_id, router):
|
||||||
# Either nexthop is updated or should be kept as it was before
|
# Either nexthop is updated or should be kept as it was before
|
||||||
r = router['router']
|
r = router['router']
|
||||||
@ -1494,8 +1513,8 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
|||||||
"this must be updated through the default "
|
"this must be updated through the default "
|
||||||
"gateway attribute")
|
"gateway attribute")
|
||||||
raise q_exc.BadRequest(resource='router', msg=msg)
|
raise q_exc.BadRequest(resource='router', msg=msg)
|
||||||
previous_routes = nvplib.update_lrouter(
|
previous_routes = self._update_lrouter(
|
||||||
self.cluster, router_id, r.get('name'),
|
context, router_id, r.get('name'),
|
||||||
nexthop, routes=r.get('routes'))
|
nexthop, routes=r.get('routes'))
|
||||||
# NOTE(salv-orlando): The exception handling below is not correct, but
|
# NOTE(salv-orlando): The exception handling below is not correct, but
|
||||||
# unfortunately nvplib raises a neutron notfound exception when an
|
# unfortunately nvplib raises a neutron notfound exception when an
|
||||||
@ -1525,8 +1544,11 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
|||||||
extraroute.RoutesExhausted):
|
extraroute.RoutesExhausted):
|
||||||
with excutils.save_and_reraise_exception():
|
with excutils.save_and_reraise_exception():
|
||||||
# revert changes made to NVP
|
# revert changes made to NVP
|
||||||
nvplib.update_explicit_routes_lrouter(
|
self._update_lrouter_routes(
|
||||||
self.cluster, router_id, previous_routes)
|
router_id, previous_routes)
|
||||||
|
|
||||||
|
def _delete_lrouter(self, context, id):
|
||||||
|
nvplib.delete_lrouter(self.cluster, id)
|
||||||
|
|
||||||
def delete_router(self, context, router_id):
|
def delete_router(self, context, router_id):
|
||||||
with context.session.begin(subtransactions=True):
|
with context.session.begin(subtransactions=True):
|
||||||
@ -1544,7 +1566,7 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
|||||||
# allow an extra field for storing the cluster information
|
# allow an extra field for storing the cluster information
|
||||||
# together with the resource
|
# together with the resource
|
||||||
try:
|
try:
|
||||||
nvplib.delete_lrouter(self.cluster, router_id)
|
self._delete_lrouter(context, router_id)
|
||||||
except q_exc.NotFound:
|
except q_exc.NotFound:
|
||||||
LOG.warning(_("Logical router '%s' not found "
|
LOG.warning(_("Logical router '%s' not found "
|
||||||
"on NVP Platform"), router_id)
|
"on NVP Platform"), router_id)
|
||||||
@ -1553,6 +1575,27 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
|||||||
err_msg=(_("Unable to delete logical router '%s' "
|
err_msg=(_("Unable to delete logical router '%s' "
|
||||||
"on NVP Platform") % router_id))
|
"on NVP Platform") % router_id))
|
||||||
|
|
||||||
|
def _add_subnet_snat_rule(self, router, subnet):
|
||||||
|
gw_port = router.gw_port
|
||||||
|
if gw_port and router.enable_snat:
|
||||||
|
# There is a change gw_port might have multiple IPs
|
||||||
|
# In that case we will consider only the first one
|
||||||
|
if gw_port.get('fixed_ips'):
|
||||||
|
snat_ip = gw_port['fixed_ips'][0]['ip_address']
|
||||||
|
cidr_prefix = int(subnet['cidr'].split('/')[1])
|
||||||
|
nvplib.create_lrouter_snat_rule(
|
||||||
|
self.cluster, router['id'], snat_ip, snat_ip,
|
||||||
|
order=NVP_EXTGW_NAT_RULES_ORDER - cidr_prefix,
|
||||||
|
match_criteria={'source_ip_addresses': subnet['cidr']})
|
||||||
|
|
||||||
|
def _delete_subnet_snat_rule(self, router, subnet):
|
||||||
|
# Remove SNAT rule if external gateway is configured
|
||||||
|
if router.gw_port:
|
||||||
|
nvplib.delete_nat_rules_by_match(
|
||||||
|
self.cluster, router['id'], "SourceNatRule",
|
||||||
|
max_num_expected=1, min_num_expected=1,
|
||||||
|
source_ip_addresses=subnet['cidr'])
|
||||||
|
|
||||||
def add_router_interface(self, context, router_id, interface_info):
|
def add_router_interface(self, context, router_id, interface_info):
|
||||||
# When adding interface by port_id we need to create the
|
# When adding interface by port_id we need to create the
|
||||||
# peer port on the nvp logical router in this routine
|
# peer port on the nvp logical router in this routine
|
||||||
@ -1587,17 +1630,7 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
|||||||
# If there is an external gateway we need to configure the SNAT rule.
|
# If there is an external gateway we need to configure the SNAT rule.
|
||||||
# Fetch router from DB
|
# Fetch router from DB
|
||||||
router = self._get_router(context, router_id)
|
router = self._get_router(context, router_id)
|
||||||
gw_port = router.gw_port
|
self._add_subnet_snat_rule(router, subnet)
|
||||||
if gw_port and router.enable_snat:
|
|
||||||
# There is a change gw_port might have multiple IPs
|
|
||||||
# In that case we will consider only the first one
|
|
||||||
if gw_port.get('fixed_ips'):
|
|
||||||
snat_ip = gw_port['fixed_ips'][0]['ip_address']
|
|
||||||
cidr_prefix = int(subnet['cidr'].split('/')[1])
|
|
||||||
nvplib.create_lrouter_snat_rule(
|
|
||||||
self.cluster, router_id, snat_ip, snat_ip,
|
|
||||||
order=NVP_EXTGW_NAT_RULES_ORDER - cidr_prefix,
|
|
||||||
match_criteria={'source_ip_addresses': subnet['cidr']})
|
|
||||||
nvplib.create_lrouter_nosnat_rule(
|
nvplib.create_lrouter_nosnat_rule(
|
||||||
self.cluster, router_id,
|
self.cluster, router_id,
|
||||||
order=NVP_NOSNAT_RULES_ORDER,
|
order=NVP_NOSNAT_RULES_ORDER,
|
||||||
@ -1655,12 +1688,7 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
|||||||
if not subnet:
|
if not subnet:
|
||||||
subnet = self._get_subnet(context, subnet_id)
|
subnet = self._get_subnet(context, subnet_id)
|
||||||
router = self._get_router(context, router_id)
|
router = self._get_router(context, router_id)
|
||||||
# Remove SNAT rule if external gateway is configured
|
self._delete_subnet_snat_rule(router, subnet)
|
||||||
if router.gw_port:
|
|
||||||
nvplib.delete_nat_rules_by_match(
|
|
||||||
self.cluster, router_id, "SourceNatRule",
|
|
||||||
max_num_expected=1, min_num_expected=1,
|
|
||||||
source_ip_addresses=subnet['cidr'])
|
|
||||||
# Relax the minimum expected number as the nosnat rules
|
# Relax the minimum expected number as the nosnat rules
|
||||||
# do not exist in 2.x deployments
|
# do not exist in 2.x deployments
|
||||||
nvplib.delete_nat_rules_by_match(
|
nvplib.delete_nat_rules_by_match(
|
||||||
@ -1677,7 +1705,7 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
|||||||
"on NVP Platform")))
|
"on NVP Platform")))
|
||||||
return info
|
return info
|
||||||
|
|
||||||
def _retrieve_and_delete_nat_rules(self, floating_ip_address,
|
def _retrieve_and_delete_nat_rules(self, context, floating_ip_address,
|
||||||
internal_ip, router_id,
|
internal_ip, router_id,
|
||||||
min_num_rules_expected=0):
|
min_num_rules_expected=0):
|
||||||
try:
|
try:
|
||||||
@ -1720,12 +1748,7 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
|||||||
ips_to_add=[],
|
ips_to_add=[],
|
||||||
ips_to_remove=nvp_floating_ips)
|
ips_to_remove=nvp_floating_ips)
|
||||||
|
|
||||||
def _update_fip_assoc(self, context, fip, floatingip_db, external_port):
|
def _get_fip_assoc_data(self, context, fip, floatingip_db):
|
||||||
"""Update floating IP association data.
|
|
||||||
|
|
||||||
Overrides method from base class.
|
|
||||||
The method is augmented for creating NAT rules in the process.
|
|
||||||
"""
|
|
||||||
if (('fixed_ip_address' in fip and fip['fixed_ip_address']) and
|
if (('fixed_ip_address' in fip and fip['fixed_ip_address']) and
|
||||||
not ('port_id' in fip and fip['port_id'])):
|
not ('port_id' in fip and fip['port_id'])):
|
||||||
msg = _("fixed_ip_address cannot be specified without a port_id")
|
msg = _("fixed_ip_address cannot be specified without a port_id")
|
||||||
@ -1748,7 +1771,6 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
|||||||
fip,
|
fip,
|
||||||
floatingip_db['floating_network_id'])
|
floatingip_db['floating_network_id'])
|
||||||
|
|
||||||
floating_ip = floatingip_db['floating_ip_address']
|
|
||||||
# Retrieve and delete existing NAT rules, if any
|
# Retrieve and delete existing NAT rules, if any
|
||||||
if not router_id and floatingip_db.get('fixed_port_id'):
|
if not router_id and floatingip_db.get('fixed_port_id'):
|
||||||
# This happens if we're disassociating. Need to explicitly
|
# This happens if we're disassociating. Need to explicitly
|
||||||
@ -1757,9 +1779,22 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
|||||||
tmp_fip['port_id'] = floatingip_db['fixed_port_id']
|
tmp_fip['port_id'] = floatingip_db['fixed_port_id']
|
||||||
_pid, internal_ip, router_id = self.get_assoc_data(
|
_pid, internal_ip, router_id = self.get_assoc_data(
|
||||||
context, tmp_fip, floatingip_db['floating_network_id'])
|
context, tmp_fip, floatingip_db['floating_network_id'])
|
||||||
|
|
||||||
|
return (port_id, internal_ip, router_id)
|
||||||
|
|
||||||
|
def _update_fip_assoc(self, context, fip, floatingip_db, external_port):
|
||||||
|
"""Update floating IP association data.
|
||||||
|
|
||||||
|
Overrides method from base class.
|
||||||
|
The method is augmented for creating NAT rules in the process.
|
||||||
|
"""
|
||||||
|
port_id, internal_ip, router_id = self._get_fip_assoc_data(
|
||||||
|
context, fip, floatingip_db)
|
||||||
|
floating_ip = floatingip_db['floating_ip_address']
|
||||||
# If there's no association router_id will be None
|
# If there's no association router_id will be None
|
||||||
if router_id:
|
if router_id:
|
||||||
self._retrieve_and_delete_nat_rules(floating_ip,
|
self._retrieve_and_delete_nat_rules(context,
|
||||||
|
floating_ip,
|
||||||
internal_ip,
|
internal_ip,
|
||||||
router_id)
|
router_id)
|
||||||
# Fetch logical port of router's external gateway
|
# Fetch logical port of router's external gateway
|
||||||
@ -1797,6 +1832,7 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
|||||||
"internal ip:%(internal_ip)s"),
|
"internal ip:%(internal_ip)s"),
|
||||||
{'floating_ip': floating_ip,
|
{'floating_ip': floating_ip,
|
||||||
'internal_ip': internal_ip})
|
'internal_ip': internal_ip})
|
||||||
|
msg = _("Failed to update NAT rules for floatingip update")
|
||||||
raise nvp_exc.NvpPluginException(err_msg=msg)
|
raise nvp_exc.NvpPluginException(err_msg=msg)
|
||||||
elif floatingip_db['fixed_port_id']:
|
elif floatingip_db['fixed_port_id']:
|
||||||
# This is a disassociation.
|
# This is a disassociation.
|
||||||
@ -1816,7 +1852,8 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
|||||||
fip_db = self._get_floatingip(context, id)
|
fip_db = self._get_floatingip(context, id)
|
||||||
# Check whether the floating ip is associated or not
|
# Check whether the floating ip is associated or not
|
||||||
if fip_db.fixed_port_id:
|
if fip_db.fixed_port_id:
|
||||||
self._retrieve_and_delete_nat_rules(fip_db.floating_ip_address,
|
self._retrieve_and_delete_nat_rules(context,
|
||||||
|
fip_db.floating_ip_address,
|
||||||
fip_db.fixed_ip_address,
|
fip_db.fixed_ip_address,
|
||||||
fip_db.router_id,
|
fip_db.router_id,
|
||||||
min_num_rules_expected=1)
|
min_num_rules_expected=1)
|
||||||
@ -1828,7 +1865,8 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
|||||||
try:
|
try:
|
||||||
fip_qry = context.session.query(l3_db.FloatingIP)
|
fip_qry = context.session.query(l3_db.FloatingIP)
|
||||||
fip_db = fip_qry.filter_by(fixed_port_id=port_id).one()
|
fip_db = fip_qry.filter_by(fixed_port_id=port_id).one()
|
||||||
self._retrieve_and_delete_nat_rules(fip_db.floating_ip_address,
|
self._retrieve_and_delete_nat_rules(context,
|
||||||
|
fip_db.floating_ip_address,
|
||||||
fip_db.fixed_ip_address,
|
fip_db.fixed_ip_address,
|
||||||
fip_db.router_id,
|
fip_db.router_id,
|
||||||
min_num_rules_expected=1)
|
min_num_rules_expected=1)
|
||||||
|
808
neutron/plugins/nicira/NeutronServicePlugin.py
Normal file
808
neutron/plugins/nicira/NeutronServicePlugin.py
Normal file
@ -0,0 +1,808 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2013 VMware, Inc.
|
||||||
|
# All Rights Reserved
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
import netaddr
|
||||||
|
from oslo.config import cfg
|
||||||
|
from sqlalchemy.orm import exc as sa_exc
|
||||||
|
|
||||||
|
from neutron.common import exceptions as q_exc
|
||||||
|
from neutron.db import l3_db
|
||||||
|
from neutron.openstack.common import log as logging
|
||||||
|
from neutron.plugins.common import constants as service_constants
|
||||||
|
from neutron.plugins.nicira.common import config # noqa
|
||||||
|
from neutron.plugins.nicira.dbexts import servicerouter as sr_db
|
||||||
|
from neutron.plugins.nicira.dbexts import vcns_db
|
||||||
|
from neutron.plugins.nicira.dbexts import vcns_models
|
||||||
|
from neutron.plugins.nicira.extensions import servicerouter as sr
|
||||||
|
from neutron.plugins.nicira import NeutronPlugin
|
||||||
|
from neutron.plugins.nicira import NvpApiClient
|
||||||
|
from neutron.plugins.nicira import nvplib
|
||||||
|
from neutron.plugins.nicira.vshield.common import (
|
||||||
|
constants as vcns_const)
|
||||||
|
from neutron.plugins.nicira.vshield.common.constants import RouterStatus
|
||||||
|
from neutron.plugins.nicira.vshield.common import exceptions
|
||||||
|
from neutron.plugins.nicira.vshield.tasks.constants import TaskStatus
|
||||||
|
from neutron.plugins.nicira.vshield import vcns_driver
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
ROUTER_TYPE_BASIC = 1
|
||||||
|
ROUTER_TYPE_ADVANCED = 2
|
||||||
|
|
||||||
|
ROUTER_STATUS = [
|
||||||
|
service_constants.ACTIVE,
|
||||||
|
service_constants.DOWN,
|
||||||
|
service_constants.PENDING_CREATE,
|
||||||
|
service_constants.PENDING_DELETE,
|
||||||
|
service_constants.ERROR
|
||||||
|
]
|
||||||
|
|
||||||
|
ROUTER_STATUS_LEVEL = {
|
||||||
|
service_constants.ACTIVE: RouterStatus.ROUTER_STATUS_ACTIVE,
|
||||||
|
service_constants.DOWN: RouterStatus.ROUTER_STATUS_DOWN,
|
||||||
|
service_constants.PENDING_CREATE: (
|
||||||
|
RouterStatus.ROUTER_STATUS_PENDING_CREATE
|
||||||
|
),
|
||||||
|
service_constants.PENDING_DELETE: (
|
||||||
|
RouterStatus.ROUTER_STATUS_PENDING_DELETE
|
||||||
|
),
|
||||||
|
service_constants.ERROR: RouterStatus.ROUTER_STATUS_ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class NvpAdvancedPlugin(sr_db.ServiceRouter_mixin,
|
||||||
|
NeutronPlugin.NvpPluginV2):
|
||||||
|
|
||||||
|
supported_extension_aliases = (
|
||||||
|
NeutronPlugin.NvpPluginV2.supported_extension_aliases + [
|
||||||
|
'service-router'
|
||||||
|
])
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(NvpAdvancedPlugin, self).__init__()
|
||||||
|
|
||||||
|
self._super_create_ext_gw_port = (
|
||||||
|
self._port_drivers['create'][l3_db.DEVICE_OWNER_ROUTER_GW])
|
||||||
|
self._super_delete_ext_gw_port = (
|
||||||
|
self._port_drivers['delete'][l3_db.DEVICE_OWNER_ROUTER_GW])
|
||||||
|
|
||||||
|
self._port_drivers['create'][l3_db.DEVICE_OWNER_ROUTER_GW] = (
|
||||||
|
self._vcns_create_ext_gw_port)
|
||||||
|
self._port_drivers['delete'][l3_db.DEVICE_OWNER_ROUTER_GW] = (
|
||||||
|
self._vcns_delete_ext_gw_port)
|
||||||
|
|
||||||
|
# cache router type based on router id
|
||||||
|
self._router_type = {}
|
||||||
|
self.callbacks = VcnsCallbacks(self)
|
||||||
|
|
||||||
|
# load the vCNS driver
|
||||||
|
self._load_vcns_drivers()
|
||||||
|
|
||||||
|
def _load_vcns_drivers(self):
|
||||||
|
self.vcns_driver = vcns_driver.VcnsDriver(self.callbacks)
|
||||||
|
|
||||||
|
def _set_router_type(self, router_id, router_type):
|
||||||
|
self._router_type[router_id] = router_type
|
||||||
|
|
||||||
|
def _get_router_type(self, context=None, router_id=None, router=None):
|
||||||
|
if not router:
|
||||||
|
if router_id in self._router_type:
|
||||||
|
return self._router_type[router_id]
|
||||||
|
router = self._get_router(context, router_id)
|
||||||
|
|
||||||
|
LOG.debug(_("EDGE: router = %s"), router)
|
||||||
|
if router['nsx_attributes']['service_router']:
|
||||||
|
router_type = ROUTER_TYPE_ADVANCED
|
||||||
|
else:
|
||||||
|
router_type = ROUTER_TYPE_BASIC
|
||||||
|
self._set_router_type(router['id'], router_type)
|
||||||
|
return router_type
|
||||||
|
|
||||||
|
def _find_router_type(self, router):
|
||||||
|
is_service_router = router.get(sr.SERVICE_ROUTER, False)
|
||||||
|
if is_service_router:
|
||||||
|
return ROUTER_TYPE_ADVANCED
|
||||||
|
else:
|
||||||
|
return ROUTER_TYPE_BASIC
|
||||||
|
|
||||||
|
def _is_advanced_service_router(self, context=None, router_id=None,
|
||||||
|
router=None):
|
||||||
|
if router:
|
||||||
|
router_type = self._get_router_type(router=router)
|
||||||
|
else:
|
||||||
|
router_type = self._get_router_type(context, router_id)
|
||||||
|
return (router_type == ROUTER_TYPE_ADVANCED)
|
||||||
|
|
||||||
|
def _vcns_create_ext_gw_port(self, context, port_data):
|
||||||
|
router_id = port_data['device_id']
|
||||||
|
if not self._is_advanced_service_router(context, router_id):
|
||||||
|
self._super_create_ext_gw_port(context, port_data)
|
||||||
|
return
|
||||||
|
|
||||||
|
# NOP for Edge because currently the port will be create internally
|
||||||
|
# by VSM
|
||||||
|
LOG.debug(_("EDGE: _vcns_create_ext_gw_port"))
|
||||||
|
|
||||||
|
def _vcns_delete_ext_gw_port(self, context, port_data):
|
||||||
|
router_id = port_data['device_id']
|
||||||
|
if not self._is_advanced_service_router(context, router_id):
|
||||||
|
self._super_delete_ext_gw_port(context, port_data)
|
||||||
|
return
|
||||||
|
|
||||||
|
# NOP for Edge
|
||||||
|
LOG.debug(_("EDGE: _vcns_delete_ext_gw_port"))
|
||||||
|
|
||||||
|
def _get_external_attachment_info(self, context, router):
|
||||||
|
gw_port = router.gw_port
|
||||||
|
ipaddress = None
|
||||||
|
netmask = None
|
||||||
|
nexthop = None
|
||||||
|
|
||||||
|
if gw_port:
|
||||||
|
# gw_port may have multiple IPs, only configure the first one
|
||||||
|
if gw_port.get('fixed_ips'):
|
||||||
|
ipaddress = gw_port['fixed_ips'][0]['ip_address']
|
||||||
|
|
||||||
|
network_id = gw_port.get('network_id')
|
||||||
|
if network_id:
|
||||||
|
ext_net = self._get_network(context, network_id)
|
||||||
|
if not ext_net.external:
|
||||||
|
msg = (_("Network '%s' is not a valid external "
|
||||||
|
"network") % network_id)
|
||||||
|
raise q_exc.BadRequest(resource='router', msg=msg)
|
||||||
|
if ext_net.subnets:
|
||||||
|
ext_subnet = ext_net.subnets[0]
|
||||||
|
netmask = str(netaddr.IPNetwork(ext_subnet.cidr).netmask)
|
||||||
|
nexthop = ext_subnet.gateway_ip
|
||||||
|
|
||||||
|
return (ipaddress, netmask, nexthop)
|
||||||
|
|
||||||
|
def _get_external_gateway_address(self, context, router):
|
||||||
|
ipaddress, netmask, nexthop = self._get_external_attachment_info(
|
||||||
|
context, router)
|
||||||
|
return nexthop
|
||||||
|
|
||||||
|
def _vcns_update_static_routes(self, context, **kwargs):
|
||||||
|
router = kwargs.get('router')
|
||||||
|
if router is None:
|
||||||
|
router = self._get_router(context, kwargs['router_id'])
|
||||||
|
|
||||||
|
edge_id = kwargs.get('edge_id')
|
||||||
|
if edge_id is None:
|
||||||
|
binding = vcns_db.get_vcns_router_binding(context.session,
|
||||||
|
router['id'])
|
||||||
|
edge_id = binding['edge_id']
|
||||||
|
|
||||||
|
skippable = True
|
||||||
|
if 'nexthop' in kwargs:
|
||||||
|
nexthop = kwargs['nexthop']
|
||||||
|
# The default gateway and vnic config has dependencies, if we
|
||||||
|
# explicitly specify nexthop to change, tell the driver not to
|
||||||
|
# skip this route update
|
||||||
|
skippable = False
|
||||||
|
else:
|
||||||
|
nexthop = self._get_external_gateway_address(context,
|
||||||
|
router)
|
||||||
|
|
||||||
|
if 'subnets' in kwargs:
|
||||||
|
subnets = kwargs['subnets']
|
||||||
|
else:
|
||||||
|
subnets = self._find_router_subnets_cidrs(context.elevated(),
|
||||||
|
router['id'])
|
||||||
|
|
||||||
|
routes = []
|
||||||
|
for subnet in subnets:
|
||||||
|
routes.append({
|
||||||
|
'cidr': subnet,
|
||||||
|
'nexthop': vcns_const.INTEGRATION_LR_IPADDRESS.split('/')[0]
|
||||||
|
})
|
||||||
|
self.vcns_driver.update_routes(router['id'], edge_id, nexthop, routes,
|
||||||
|
skippable)
|
||||||
|
|
||||||
|
def _get_nat_rules(self, context, router):
|
||||||
|
fip_qry = context.session.query(l3_db.FloatingIP)
|
||||||
|
fip_db = fip_qry.filter_by(router_id=router['id']).all()
|
||||||
|
|
||||||
|
dnat = []
|
||||||
|
snat = []
|
||||||
|
for fip in fip_db:
|
||||||
|
if fip.fixed_port_id:
|
||||||
|
dnat.append({
|
||||||
|
'dst': fip.floating_ip_address,
|
||||||
|
'translated': fip.fixed_ip_address
|
||||||
|
})
|
||||||
|
|
||||||
|
gw_port = router.gw_port
|
||||||
|
if gw_port and router.enable_snat:
|
||||||
|
if gw_port.get('fixed_ips'):
|
||||||
|
snat_ip = gw_port['fixed_ips'][0]['ip_address']
|
||||||
|
subnets = self._find_router_subnets_cidrs(context.elevated(),
|
||||||
|
router['id'])
|
||||||
|
for subnet in subnets:
|
||||||
|
snat.append({
|
||||||
|
'src': subnet,
|
||||||
|
'translated': snat_ip
|
||||||
|
})
|
||||||
|
|
||||||
|
return (snat, dnat)
|
||||||
|
|
||||||
|
def _update_nat_rules(self, context, router):
|
||||||
|
snat, dnat = self._get_nat_rules(context, router)
|
||||||
|
binding = vcns_db.get_vcns_router_binding(context.session,
|
||||||
|
router['id'])
|
||||||
|
self.vcns_driver.update_nat_rules(router['id'],
|
||||||
|
binding['edge_id'],
|
||||||
|
snat, dnat)
|
||||||
|
|
||||||
|
def _update_interface(self, context, router):
|
||||||
|
addr, mask, nexthop = self._get_external_attachment_info(
|
||||||
|
context, router)
|
||||||
|
|
||||||
|
secondary = []
|
||||||
|
fip_qry = context.session.query(l3_db.FloatingIP)
|
||||||
|
fip_db = fip_qry.filter_by(router_id=router['id']).all()
|
||||||
|
for fip in fip_db:
|
||||||
|
if fip.fixed_port_id:
|
||||||
|
secondary.append(fip.floating_ip_address)
|
||||||
|
|
||||||
|
binding = vcns_db.get_vcns_router_binding(context.session,
|
||||||
|
router['id'])
|
||||||
|
self.vcns_driver.update_interface(
|
||||||
|
router['id'], binding['edge_id'],
|
||||||
|
vcns_const.EXTERNAL_VNIC_INDEX,
|
||||||
|
self.vcns_driver.external_network,
|
||||||
|
addr, mask, secondary=secondary)
|
||||||
|
|
||||||
|
def _update_router_gw_info(self, context, router_id, info):
|
||||||
|
if not self._is_advanced_service_router(context, router_id):
|
||||||
|
super(NvpAdvancedPlugin, self)._update_router_gw_info(
|
||||||
|
context, router_id, info)
|
||||||
|
return
|
||||||
|
|
||||||
|
# get original gw_port config
|
||||||
|
router = self._get_router(context, router_id)
|
||||||
|
org_ext_net_id = router.gw_port_id and router.gw_port.network_id
|
||||||
|
org_enable_snat = router.enable_snat
|
||||||
|
orgaddr, orgmask, orgnexthop = self._get_external_attachment_info(
|
||||||
|
context, router)
|
||||||
|
|
||||||
|
super(NeutronPlugin.NvpPluginV2, self)._update_router_gw_info(
|
||||||
|
context, router_id, info, router=router)
|
||||||
|
|
||||||
|
new_ext_net_id = router.gw_port_id and router.gw_port.network_id
|
||||||
|
new_enable_snat = router.enable_snat
|
||||||
|
newaddr, newmask, newnexthop = self._get_external_attachment_info(
|
||||||
|
context, router)
|
||||||
|
|
||||||
|
binding = vcns_db.get_vcns_router_binding(context.session, router_id)
|
||||||
|
|
||||||
|
if new_ext_net_id != org_ext_net_id and orgnexthop:
|
||||||
|
# network changed, need to remove default gateway before vnic
|
||||||
|
# can be configured
|
||||||
|
LOG.debug(_("VCNS: delete default gateway %s"), orgnexthop)
|
||||||
|
self._vcns_update_static_routes(context,
|
||||||
|
router=router,
|
||||||
|
edge_id=binding['edge_id'],
|
||||||
|
nexthop=None)
|
||||||
|
|
||||||
|
if orgaddr != newaddr or orgmask != newmask:
|
||||||
|
self.vcns_driver.update_interface(
|
||||||
|
router_id, binding['edge_id'],
|
||||||
|
vcns_const.EXTERNAL_VNIC_INDEX,
|
||||||
|
self.vcns_driver.external_network,
|
||||||
|
newaddr, newmask)
|
||||||
|
|
||||||
|
if orgnexthop != newnexthop:
|
||||||
|
self._vcns_update_static_routes(context,
|
||||||
|
router=router,
|
||||||
|
edge_id=binding['edge_id'],
|
||||||
|
nexthop=newnexthop)
|
||||||
|
|
||||||
|
if (new_ext_net_id == org_ext_net_id and
|
||||||
|
org_enable_snat == new_enable_snat):
|
||||||
|
return
|
||||||
|
|
||||||
|
self._update_nat_rules(context, router)
|
||||||
|
|
||||||
|
def _add_subnet_snat_rule(self, router, subnet):
|
||||||
|
# NOP for service router
|
||||||
|
if not self._is_advanced_service_router(router=router):
|
||||||
|
super(NvpAdvancedPlugin, self)._add_subnet_snat_rule(
|
||||||
|
router, subnet)
|
||||||
|
|
||||||
|
def _delete_subnet_snat_rule(self, router, subnet):
|
||||||
|
# NOP for service router
|
||||||
|
if not self._is_advanced_service_router(router=router):
|
||||||
|
super(NvpAdvancedPlugin, self)._delete_subnet_snat_rule(
|
||||||
|
router, subnet)
|
||||||
|
|
||||||
|
def _remove_floatingip_address(self, context, fip_db):
|
||||||
|
# NOP for service router
|
||||||
|
router_id = fip_db.router_id
|
||||||
|
if not self._is_advanced_service_router(context, router_id):
|
||||||
|
super(NvpAdvancedPlugin, self)._remove_floatingip_address(
|
||||||
|
context, fip_db)
|
||||||
|
|
||||||
|
def _create_advanced_service_router(self, context, name, lrouter, lswitch):
|
||||||
|
|
||||||
|
# store binding
|
||||||
|
binding = vcns_db.add_vcns_router_binding(
|
||||||
|
context.session, lrouter['uuid'], None, lswitch['uuid'],
|
||||||
|
service_constants.PENDING_CREATE)
|
||||||
|
|
||||||
|
# deploy edge
|
||||||
|
jobdata = {
|
||||||
|
'lrouter': lrouter,
|
||||||
|
'lswitch': lswitch,
|
||||||
|
'context': context
|
||||||
|
}
|
||||||
|
|
||||||
|
# deploy and wait until the deploy requeste has been requested
|
||||||
|
# so we will have edge_id ready. The wait here should be fine
|
||||||
|
# as we're not in a database transaction now
|
||||||
|
self.vcns_driver.deploy_edge(
|
||||||
|
lrouter['uuid'], name, lswitch['uuid'], jobdata=jobdata,
|
||||||
|
wait_for_exec=True)
|
||||||
|
|
||||||
|
return binding
|
||||||
|
|
||||||
|
def _create_integration_lswitch(self, tenant_id, name):
|
||||||
|
# use defautl transport zone
|
||||||
|
transport_zone_config = [{
|
||||||
|
"zone_uuid": self.cluster.default_tz_uuid,
|
||||||
|
"transport_type": cfg.CONF.NVP.default_transport_type
|
||||||
|
}]
|
||||||
|
return self.vcns_driver.create_lswitch(name, transport_zone_config)
|
||||||
|
|
||||||
|
def _add_router_integration_interface(self, tenant_id, name,
|
||||||
|
lrouter, lswitch):
|
||||||
|
# create logic switch port
|
||||||
|
try:
|
||||||
|
ls_port = nvplib.create_lport(
|
||||||
|
self.cluster, lswitch['uuid'], tenant_id,
|
||||||
|
'', '', lrouter['uuid'], True)
|
||||||
|
except NvpApiClient.NvpApiException:
|
||||||
|
msg = (_("An exception occured while creating a port "
|
||||||
|
"on lswitch %s") % lswitch['uuid'])
|
||||||
|
LOG.exception(msg)
|
||||||
|
raise q_exc.NeutronException(message=msg)
|
||||||
|
|
||||||
|
# create logic router port
|
||||||
|
try:
|
||||||
|
neutron_port_id = ''
|
||||||
|
pname = name[:36] + '-lp'
|
||||||
|
admin_status_enabled = True
|
||||||
|
lr_port = nvplib.create_router_lport(
|
||||||
|
self.cluster, lrouter['uuid'], tenant_id,
|
||||||
|
neutron_port_id, pname, admin_status_enabled,
|
||||||
|
[vcns_const.INTEGRATION_LR_IPADDRESS])
|
||||||
|
except NvpApiClient.NvpApiException:
|
||||||
|
msg = (_("Unable to create port on NVP logical router %s") % name)
|
||||||
|
LOG.exception(msg)
|
||||||
|
nvplib.delete_port(self.cluster, lswitch['uuid'], ls_port['uuid'])
|
||||||
|
raise q_exc.NeutronException(message=msg)
|
||||||
|
|
||||||
|
# attach logic router port to switch port
|
||||||
|
try:
|
||||||
|
self._update_router_port_attachment(
|
||||||
|
self.cluster, None, lrouter['uuid'], {}, lr_port['uuid'],
|
||||||
|
'PatchAttachment', ls_port['uuid'], None)
|
||||||
|
except NvpApiClient.NvpApiException as e:
|
||||||
|
# lr_port should have been deleted
|
||||||
|
nvplib.delete_port(self.cluster, lswitch['uuid'], ls_port['uuid'])
|
||||||
|
raise e
|
||||||
|
|
||||||
|
def _create_lrouter(self, context, router, nexthop):
|
||||||
|
lrouter = super(NvpAdvancedPlugin, self)._create_lrouter(
|
||||||
|
context, router, vcns_const.INTEGRATION_EDGE_IPADDRESS)
|
||||||
|
|
||||||
|
router_type = self._find_router_type(router)
|
||||||
|
self._set_router_type(lrouter['uuid'], router_type)
|
||||||
|
if router_type == ROUTER_TYPE_BASIC:
|
||||||
|
return lrouter
|
||||||
|
|
||||||
|
tenant_id = self._get_tenant_id_for_create(context, router)
|
||||||
|
name = router['name']
|
||||||
|
try:
|
||||||
|
lsname = name[:36] + '-ls'
|
||||||
|
lswitch = self._create_integration_lswitch(
|
||||||
|
tenant_id, lsname)
|
||||||
|
except Exception:
|
||||||
|
msg = _("Unable to create integration logic switch "
|
||||||
|
"for router %s") % name
|
||||||
|
LOG.exception(msg)
|
||||||
|
nvplib.delete_lrouter(self.cluster, lrouter['uuid'])
|
||||||
|
raise q_exc.NeutronException(message=msg)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._add_router_integration_interface(tenant_id, name,
|
||||||
|
lrouter, lswitch)
|
||||||
|
except Exception:
|
||||||
|
msg = _("Unable to add router interface to integration lswitch "
|
||||||
|
"for router %s") % name
|
||||||
|
LOG.exception(msg)
|
||||||
|
nvplib.delete_lrouter(self.cluster, lrouter['uuid'])
|
||||||
|
raise q_exc.NeutronException(message=msg)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._create_advanced_service_router(
|
||||||
|
context, name, lrouter, lswitch)
|
||||||
|
except Exception:
|
||||||
|
msg = (_("Unable to create advance service router for %s") % name)
|
||||||
|
LOG.exception(msg)
|
||||||
|
self.vcns_driver.delete_lswitch(lswitch('uuid'))
|
||||||
|
nvplib.delete_lrouter(self.cluster, lrouter['uuid'])
|
||||||
|
raise q_exc.NeutronException(message=msg)
|
||||||
|
|
||||||
|
lrouter['status'] = service_constants.PENDING_CREATE
|
||||||
|
return lrouter
|
||||||
|
|
||||||
|
def _delete_lrouter(self, context, id):
|
||||||
|
if not self._is_advanced_service_router(context, id):
|
||||||
|
super(NvpAdvancedPlugin, self)._delete_lrouter(context, id)
|
||||||
|
if id in self._router_type:
|
||||||
|
del self._router_type[id]
|
||||||
|
return
|
||||||
|
|
||||||
|
binding = vcns_db.get_vcns_router_binding(context.session, id)
|
||||||
|
if binding:
|
||||||
|
vcns_db.update_vcns_router_binding(
|
||||||
|
context.session, id, status=service_constants.PENDING_DELETE)
|
||||||
|
|
||||||
|
lswitch_id = binding['lswitch_id']
|
||||||
|
edge_id = binding['edge_id']
|
||||||
|
|
||||||
|
# delete lswitch
|
||||||
|
try:
|
||||||
|
self.vcns_driver.delete_lswitch(lswitch_id)
|
||||||
|
except exceptions.ResourceNotFound:
|
||||||
|
LOG.warning(_("Did not found lswitch %s in NVP"), lswitch_id)
|
||||||
|
|
||||||
|
# delete edge
|
||||||
|
jobdata = {
|
||||||
|
'context': context
|
||||||
|
}
|
||||||
|
self.vcns_driver.delete_edge(id, edge_id, jobdata=jobdata)
|
||||||
|
|
||||||
|
# delete LR
|
||||||
|
nvplib.delete_lrouter(self.cluster, id)
|
||||||
|
if id in self._router_type:
|
||||||
|
del self._router_type[id]
|
||||||
|
|
||||||
|
def _update_lrouter(self, context, router_id, name, nexthop, routes=None):
|
||||||
|
if not self._is_advanced_service_router(context, router_id):
|
||||||
|
return super(NvpAdvancedPlugin, self)._update_lrouter(
|
||||||
|
context, router_id, name, nexthop, routes=routes)
|
||||||
|
|
||||||
|
previous_routes = super(NvpAdvancedPlugin, self)._update_lrouter(
|
||||||
|
context, router_id, name,
|
||||||
|
vcns_const.INTEGRATION_EDGE_IPADDRESS, routes=routes)
|
||||||
|
|
||||||
|
# TODO(fank): Theoretically users can specify extra routes for
|
||||||
|
# physical network, and routes for phyiscal network needs to be
|
||||||
|
# configured on Edge. This can be done by checking if nexthop is in
|
||||||
|
# external network. But for now we only handle routes for logic
|
||||||
|
# space and leave it for future enhancement.
|
||||||
|
|
||||||
|
# Let _update_router_gw_info handle nexthop change
|
||||||
|
#self._vcns_update_static_routes(context, router_id=router_id)
|
||||||
|
|
||||||
|
return previous_routes
|
||||||
|
|
||||||
|
def _retrieve_and_delete_nat_rules(self, context, floating_ip_address,
|
||||||
|
internal_ip, router_id,
|
||||||
|
min_num_rules_expected=0):
|
||||||
|
# NOP for advanced service router
|
||||||
|
if not self._is_advanced_service_router(context, router_id):
|
||||||
|
super(NvpAdvancedPlugin, self)._retrieve_and_delete_nat_rules(
|
||||||
|
context, floating_ip_address, internal_ip, router_id,
|
||||||
|
min_num_rules_expected=min_num_rules_expected)
|
||||||
|
|
||||||
|
def _update_fip_assoc(self, context, fip, floatingip_db, external_port):
|
||||||
|
# Update DB model only for advanced service router
|
||||||
|
router_id = self._get_fip_assoc_data(context, fip, floatingip_db)[2]
|
||||||
|
if (router_id and
|
||||||
|
not self._is_advanced_service_router(context, router_id)):
|
||||||
|
super(NvpAdvancedPlugin, self)._update_fip_assoc(
|
||||||
|
context, fip, floatingip_db, external_port)
|
||||||
|
else:
|
||||||
|
super(NeutronPlugin.NvpPluginV2, self)._update_fip_assoc(
|
||||||
|
context, fip, floatingip_db, external_port)
|
||||||
|
|
||||||
|
def _get_nvp_lrouter_status(self, id):
|
||||||
|
try:
|
||||||
|
lrouter = nvplib.get_lrouter(self.cluster, id)
|
||||||
|
lr_status = lrouter["_relations"]["LogicalRouterStatus"]
|
||||||
|
if lr_status["fabric_status"]:
|
||||||
|
nvp_status = RouterStatus.ROUTER_STATUS_ACTIVE
|
||||||
|
else:
|
||||||
|
nvp_status = RouterStatus.ROUTER_STATUS_DOWN
|
||||||
|
except q_exc.NotFound:
|
||||||
|
nvp_status = RouterStatus.ROUTER_STATUS_ERROR
|
||||||
|
|
||||||
|
return nvp_status
|
||||||
|
|
||||||
|
def _get_vse_status(self, context, id):
|
||||||
|
binding = vcns_db.get_vcns_router_binding(context.session, id)
|
||||||
|
|
||||||
|
edge_status_level = self.vcns_driver.get_edge_status(
|
||||||
|
binding['edge_id'])
|
||||||
|
edge_db_status_level = ROUTER_STATUS_LEVEL[binding.status]
|
||||||
|
|
||||||
|
if edge_status_level > edge_db_status_level:
|
||||||
|
return edge_status_level
|
||||||
|
else:
|
||||||
|
return edge_db_status_level
|
||||||
|
|
||||||
|
def _get_all_nvp_lrouters_statuses(self, tenant_id, fields):
|
||||||
|
# get nvp lrouters status
|
||||||
|
nvp_lrouters = nvplib.get_lrouters(self.cluster,
|
||||||
|
tenant_id,
|
||||||
|
fields)
|
||||||
|
|
||||||
|
nvp_status = {}
|
||||||
|
for nvp_lrouter in nvp_lrouters:
|
||||||
|
if (nvp_lrouter["_relations"]["LogicalRouterStatus"]
|
||||||
|
["fabric_status"]):
|
||||||
|
nvp_status[nvp_lrouter['uuid']] = (
|
||||||
|
RouterStatus.ROUTER_STATUS_ACTIVE
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
nvp_status[nvp_lrouter['uuid']] = (
|
||||||
|
RouterStatus.ROUTER_STATUS_DOWN
|
||||||
|
)
|
||||||
|
|
||||||
|
return nvp_status
|
||||||
|
|
||||||
|
def _get_all_vse_statuses(self, context):
|
||||||
|
bindings = self._model_query(
|
||||||
|
context, vcns_models.VcnsRouterBinding)
|
||||||
|
|
||||||
|
vse_db_status_level = {}
|
||||||
|
edge_id_to_router_id = {}
|
||||||
|
router_ids = []
|
||||||
|
for binding in bindings:
|
||||||
|
if not binding['edge_id']:
|
||||||
|
continue
|
||||||
|
router_id = binding['router_id']
|
||||||
|
router_ids.append(router_id)
|
||||||
|
edge_id_to_router_id[binding['edge_id']] = router_id
|
||||||
|
vse_db_status_level[router_id] = (
|
||||||
|
ROUTER_STATUS_LEVEL[binding['status']])
|
||||||
|
|
||||||
|
if not vse_db_status_level:
|
||||||
|
# no advanced service router, no need to query
|
||||||
|
return {}
|
||||||
|
|
||||||
|
vse_status_level = {}
|
||||||
|
edges_status_level = self.vcns_driver.get_edges_statuses()
|
||||||
|
for edge_id, status_level in edges_status_level.iteritems():
|
||||||
|
if edge_id in edge_id_to_router_id:
|
||||||
|
router_id = edge_id_to_router_id[edge_id]
|
||||||
|
db_status_level = vse_db_status_level[router_id]
|
||||||
|
if status_level > db_status_level:
|
||||||
|
vse_status_level[router_id] = status_level
|
||||||
|
else:
|
||||||
|
vse_status_level[router_id] = db_status_level
|
||||||
|
|
||||||
|
return vse_status_level
|
||||||
|
|
||||||
|
def get_router(self, context, id, fields=None):
|
||||||
|
if fields and 'status' not in fields:
|
||||||
|
return super(NvpAdvancedPlugin, self).get_router(
|
||||||
|
context, id, fields=fields)
|
||||||
|
|
||||||
|
router = super(NvpAdvancedPlugin, self).get_router(context, id)
|
||||||
|
|
||||||
|
router_type = self._find_router_type(router)
|
||||||
|
if router_type == ROUTER_TYPE_ADVANCED:
|
||||||
|
vse_status_level = self._get_vse_status(context, id)
|
||||||
|
if vse_status_level > ROUTER_STATUS_LEVEL[router['status']]:
|
||||||
|
router['status'] = ROUTER_STATUS[vse_status_level]
|
||||||
|
|
||||||
|
return self._fields(router, fields)
|
||||||
|
|
||||||
|
def get_routers(self, context, filters=None, fields=None, **kwargs):
|
||||||
|
routers = super(NvpAdvancedPlugin, self).get_routers(
|
||||||
|
context, filters=filters, **kwargs)
|
||||||
|
|
||||||
|
if fields and 'status' not in fields:
|
||||||
|
# no status checking, just return regular get_routers
|
||||||
|
return [self._fields(router, fields) for router in routers]
|
||||||
|
|
||||||
|
for router in routers:
|
||||||
|
router_type = self._find_router_type(router)
|
||||||
|
if router_type == ROUTER_TYPE_ADVANCED:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# no advanced service router, return here
|
||||||
|
return [self._fields(router, fields) for router in routers]
|
||||||
|
|
||||||
|
vse_status_all = self._get_all_vse_statuses(context)
|
||||||
|
for router in routers:
|
||||||
|
router_type = self._find_router_type(router)
|
||||||
|
if router_type == ROUTER_TYPE_ADVANCED:
|
||||||
|
vse_status_level = vse_status_all.get(router['id'])
|
||||||
|
if vse_status_level is None:
|
||||||
|
vse_status_level = RouterStatus.ROUTER_STATUS_ERROR
|
||||||
|
if vse_status_level > ROUTER_STATUS_LEVEL[router['status']]:
|
||||||
|
router['status'] = ROUTER_STATUS[vse_status_level]
|
||||||
|
|
||||||
|
return [self._fields(router, fields) for router in routers]
|
||||||
|
|
||||||
|
def add_router_interface(self, context, router_id, interface_info):
|
||||||
|
info = super(NvpAdvancedPlugin, self).add_router_interface(
|
||||||
|
context, router_id, interface_info)
|
||||||
|
if self._is_advanced_service_router(context, router_id):
|
||||||
|
router = self._get_router(context, router_id)
|
||||||
|
if router.enable_snat:
|
||||||
|
self._update_nat_rules(context, router)
|
||||||
|
# TODO(fank): do rollback if error, or have a dedicated thread
|
||||||
|
# do sync work (rollback, re-configure, or make router down)
|
||||||
|
self._vcns_update_static_routes(context, router=router)
|
||||||
|
return info
|
||||||
|
|
||||||
|
def remove_router_interface(self, context, router_id, interface_info):
|
||||||
|
info = super(NvpAdvancedPlugin, self).remove_router_interface(
|
||||||
|
context, router_id, interface_info)
|
||||||
|
if self._is_advanced_service_router(context, router_id):
|
||||||
|
router = self._get_router(context, router_id)
|
||||||
|
if router.enable_snat:
|
||||||
|
self._update_nat_rules(context, router)
|
||||||
|
# TODO(fank): do rollback if error, or have a dedicated thread
|
||||||
|
# do sync work (rollback, re-configure, or make router down)
|
||||||
|
self._vcns_update_static_routes(context, router=router)
|
||||||
|
return info
|
||||||
|
|
||||||
|
def create_floatingip(self, context, floatingip):
|
||||||
|
fip = super(NvpAdvancedPlugin, self).create_floatingip(
|
||||||
|
context, floatingip)
|
||||||
|
router_id = fip.get('router_id')
|
||||||
|
if router_id and self._is_advanced_service_router(context, router_id):
|
||||||
|
router = self._get_router(context, router_id)
|
||||||
|
# TODO(fank): do rollback if error, or have a dedicated thread
|
||||||
|
# do sync work (rollback, re-configure, or make router down)
|
||||||
|
self._update_interface(context, router)
|
||||||
|
self._update_nat_rules(context, router)
|
||||||
|
return fip
|
||||||
|
|
||||||
|
def update_floatingip(self, context, id, floatingip):
|
||||||
|
fip = super(NvpAdvancedPlugin, self).update_floatingip(
|
||||||
|
context, id, floatingip)
|
||||||
|
router_id = fip.get('router_id')
|
||||||
|
if router_id and self._is_advanced_service_router(context, router_id):
|
||||||
|
router = self._get_router(context, router_id)
|
||||||
|
# TODO(fank): do rollback if error, or have a dedicated thread
|
||||||
|
# do sync work (rollback, re-configure, or make router down)
|
||||||
|
self._update_interface(context, router)
|
||||||
|
self._update_nat_rules(context, router)
|
||||||
|
return fip
|
||||||
|
|
||||||
|
def delete_floatingip(self, context, id):
|
||||||
|
fip_db = self._get_floatingip(context, id)
|
||||||
|
router_id = None
|
||||||
|
if fip_db.fixed_port_id:
|
||||||
|
router_id = fip_db.router_id
|
||||||
|
super(NvpAdvancedPlugin, self).delete_floatingip(context, id)
|
||||||
|
if router_id and self._is_advanced_service_router(context, router_id):
|
||||||
|
router = self._get_router(context, router_id)
|
||||||
|
# TODO(fank): do rollback if error, or have a dedicated thread
|
||||||
|
# do sync work (rollback, re-configure, or make router down)
|
||||||
|
self._update_interface(context, router)
|
||||||
|
self._update_nat_rules(context, router)
|
||||||
|
|
||||||
|
def disassociate_floatingips(self, context, port_id):
|
||||||
|
try:
|
||||||
|
fip_qry = context.session.query(l3_db.FloatingIP)
|
||||||
|
fip_db = fip_qry.filter_by(fixed_port_id=port_id).one()
|
||||||
|
router_id = fip_db.router_id
|
||||||
|
except sa_exc.NoResultFound:
|
||||||
|
router_id = None
|
||||||
|
super(NvpAdvancedPlugin, self).disassociate_floatingips(context,
|
||||||
|
port_id)
|
||||||
|
if router_id and self._is_advanced_service_router(context, router_id):
|
||||||
|
router = self._get_router(context, router_id)
|
||||||
|
# TODO(fank): do rollback if error, or have a dedicated thread
|
||||||
|
# do sync work (rollback, re-configure, or make router down)
|
||||||
|
self._update_interface(context, router)
|
||||||
|
self._update_nat_rules(context, router)
|
||||||
|
|
||||||
|
|
||||||
|
class VcnsCallbacks(object):
|
||||||
|
"""Edge callback implementation
|
||||||
|
|
||||||
|
Callback functions for asynchronous tasks
|
||||||
|
"""
|
||||||
|
def __init__(self, plugin):
|
||||||
|
self.plugin = plugin
|
||||||
|
|
||||||
|
def edge_deploy_started(self, task):
|
||||||
|
"""callback when deployment task started."""
|
||||||
|
jobdata = task.userdata['jobdata']
|
||||||
|
lrouter = jobdata['lrouter']
|
||||||
|
context = jobdata['context']
|
||||||
|
edge_id = task.userdata.get('edge_id')
|
||||||
|
name = task.userdata['router_name']
|
||||||
|
if edge_id:
|
||||||
|
LOG.debug(_("Start deploying %(edge_id)s for router %(name)s"), {
|
||||||
|
'edge_id': edge_id,
|
||||||
|
'name': name})
|
||||||
|
vcns_db.update_vcns_router_binding(
|
||||||
|
context.session, lrouter['uuid'], edge_id=edge_id)
|
||||||
|
else:
|
||||||
|
LOG.debug(_("Failed to deploy Edge for router %s"), name)
|
||||||
|
vcns_db.update_vcns_router_binding(
|
||||||
|
context.session, lrouter['uuid'],
|
||||||
|
status=service_constants.ERROR)
|
||||||
|
|
||||||
|
def edge_deploy_result(self, task):
|
||||||
|
"""callback when deployment task finished."""
|
||||||
|
jobdata = task.userdata['jobdata']
|
||||||
|
lrouter = jobdata['lrouter']
|
||||||
|
context = jobdata['context']
|
||||||
|
name = task.userdata['router_name']
|
||||||
|
router_db = self.plugin._get_router(context, lrouter['uuid'])
|
||||||
|
if task.status == TaskStatus.COMPLETED:
|
||||||
|
LOG.debug(_("Successfully deployed %(edge_id)s for "
|
||||||
|
"router %(name)s"), {
|
||||||
|
'edge_id': task.userdata['edge_id'],
|
||||||
|
'name': name})
|
||||||
|
if router_db['status'] == service_constants.PENDING_CREATE:
|
||||||
|
router_db['status'] = service_constants.ACTIVE
|
||||||
|
binding = vcns_db.get_vcns_router_binding(
|
||||||
|
context.session, lrouter['uuid'])
|
||||||
|
# only update status to active if its status is pending create
|
||||||
|
if binding['status'] == service_constants.PENDING_CREATE:
|
||||||
|
vcns_db.update_vcns_router_binding(
|
||||||
|
context.session, lrouter['uuid'],
|
||||||
|
status=service_constants.ACTIVE)
|
||||||
|
else:
|
||||||
|
LOG.debug(_("Failed to deploy Edge for router %s"), name)
|
||||||
|
router_db['status'] = service_constants.ERROR
|
||||||
|
vcns_db.update_vcns_router_binding(
|
||||||
|
context.session, lrouter['uuid'],
|
||||||
|
status=service_constants.ERROR)
|
||||||
|
|
||||||
|
def edge_delete_result(self, task):
|
||||||
|
jobdata = task.userdata['jobdata']
|
||||||
|
router_id = task.userdata['router_id']
|
||||||
|
context = jobdata['context']
|
||||||
|
if task.status == TaskStatus.COMPLETED:
|
||||||
|
vcns_db.delete_vcns_router_binding(context.session,
|
||||||
|
router_id)
|
||||||
|
|
||||||
|
def interface_update_result(self, task):
|
||||||
|
LOG.debug(_("interface_update_result %d"), task.status)
|
||||||
|
|
||||||
|
def snat_create_result(self, task):
|
||||||
|
LOG.debug(_("snat_create_result %d"), task.status)
|
||||||
|
|
||||||
|
def snat_delete_result(self, task):
|
||||||
|
LOG.debug(_("snat_delete_result %d"), task.status)
|
||||||
|
|
||||||
|
def dnat_create_result(self, task):
|
||||||
|
LOG.debug(_("dnat_create_result %d"), task.status)
|
||||||
|
|
||||||
|
def dnat_delete_result(self, task):
|
||||||
|
LOG.debug(_("dnat_delete_result %d"), task.status)
|
||||||
|
|
||||||
|
def routes_update_result(self, task):
|
||||||
|
LOG.debug(_("routes_update_result %d"), task.status)
|
||||||
|
|
||||||
|
def nat_update_result(self, task):
|
||||||
|
LOG.debug(_("nat_update_result %d"), task.status)
|
@ -17,53 +17,15 @@
|
|||||||
# @author: Salvatore Orlando, Nicira, Inc
|
# @author: Salvatore Orlando, Nicira, Inc
|
||||||
#
|
#
|
||||||
|
|
||||||
from neutron.db import db_base_plugin_v2
|
from neutron.plugins.nicira.dbexts import nsxrouter
|
||||||
from neutron.extensions import l3
|
|
||||||
from neutron.openstack.common import log as logging
|
|
||||||
from neutron.plugins.nicira.dbexts import nicira_models
|
|
||||||
from neutron.plugins.nicira.extensions import distributedrouter as dist_rtr
|
from neutron.plugins.nicira.extensions import distributedrouter as dist_rtr
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
class DistributedRouter_mixin(nsxrouter.NsxRouterMixin):
|
||||||
class DistributedRouter_mixin(object):
|
|
||||||
"""Mixin class to enable distributed router support."""
|
"""Mixin class to enable distributed router support."""
|
||||||
|
|
||||||
def _extend_router_dict_distributed(self, router_res, router_db):
|
nsx_attributes = (
|
||||||
# Avoid setting attribute to None for routers already existing before
|
nsxrouter.NsxRouterMixin.nsx_attributes + [{
|
||||||
# the data model was extended with the distributed attribute
|
'name': dist_rtr.DISTRIBUTED,
|
||||||
nsx_attrs = router_db['nsx_attributes']
|
'default': False
|
||||||
# Return False if nsx attributes are not definied for this
|
}])
|
||||||
# neutron router
|
|
||||||
router_res[dist_rtr.DISTRIBUTED] = (
|
|
||||||
nsx_attrs and nsx_attrs['distributed'] or False)
|
|
||||||
|
|
||||||
def _process_distributed_router_create(
|
|
||||||
self, context, router_db, router_req):
|
|
||||||
"""Ensures persistency for the 'distributed' attribute.
|
|
||||||
|
|
||||||
Either creates or fetches the nicira extended attributes
|
|
||||||
record for this router and stores the 'distributed'
|
|
||||||
attribute value.
|
|
||||||
This method should be called from within a transaction, as
|
|
||||||
it does not start a new one.
|
|
||||||
"""
|
|
||||||
if not router_db['nsx_attributes']:
|
|
||||||
nsx_attributes = nicira_models.NSXRouterExtAttributes(
|
|
||||||
router_id=router_db['id'],
|
|
||||||
distributed=router_req['distributed'])
|
|
||||||
context.session.add(nsx_attributes)
|
|
||||||
router_db['nsx_attributes'] = nsx_attributes
|
|
||||||
else:
|
|
||||||
# The situation where the record already exists will
|
|
||||||
# be likely once the NSXRouterExtAttributes model
|
|
||||||
# will allow for defining several attributes pertaining
|
|
||||||
# to different extensions
|
|
||||||
router_db['nsx_attributes']['distributed'] = (
|
|
||||||
router_req['distributed'])
|
|
||||||
LOG.debug(_("Distributed router extension successfully processed "
|
|
||||||
"for router:%s"), router_db['id'])
|
|
||||||
|
|
||||||
# Register dict extend functions for ports
|
|
||||||
db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
|
|
||||||
l3.ROUTERS, ['_extend_router_dict_distributed'])
|
|
||||||
|
@ -89,6 +89,7 @@ class NSXRouterExtAttributes(model_base.BASEV2):
|
|||||||
ForeignKey('routers.id', ondelete="CASCADE"),
|
ForeignKey('routers.id', ondelete="CASCADE"),
|
||||||
primary_key=True)
|
primary_key=True)
|
||||||
distributed = Column(Boolean, default=False, nullable=False)
|
distributed = Column(Boolean, default=False, nullable=False)
|
||||||
|
service_router = Column(Boolean, default=False, nullable=False)
|
||||||
# Add a relationship to the Router model in order to instruct
|
# Add a relationship to the Router model in order to instruct
|
||||||
# SQLAlchemy to eagerly load this association
|
# SQLAlchemy to eagerly load this association
|
||||||
router = orm.relationship(
|
router = orm.relationship(
|
||||||
|
70
neutron/plugins/nicira/dbexts/nsxrouter.py
Normal file
70
neutron/plugins/nicira/dbexts/nsxrouter.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2013 Nicira Networks, Inc. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
#
|
||||||
|
# @author: Salvatore Orlando, Nicira, Inc
|
||||||
|
#
|
||||||
|
|
||||||
|
from neutron.db import db_base_plugin_v2
|
||||||
|
from neutron.extensions import l3
|
||||||
|
from neutron.openstack.common import log as logging
|
||||||
|
from neutron.plugins.nicira.dbexts import nicira_models
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class NsxRouterMixin(object):
|
||||||
|
"""Mixin class to enable nsx router support."""
|
||||||
|
|
||||||
|
nsx_attributes = []
|
||||||
|
|
||||||
|
def _extend_nsx_router_dict(self, router_res, router_db):
|
||||||
|
nsx_attrs = router_db['nsx_attributes']
|
||||||
|
# Return False if nsx attributes are not definied for this
|
||||||
|
# neutron router
|
||||||
|
for attr in self.nsx_attributes:
|
||||||
|
name = attr['name']
|
||||||
|
default = attr['default']
|
||||||
|
router_res[name] = (
|
||||||
|
nsx_attrs and nsx_attrs[name] or default)
|
||||||
|
|
||||||
|
def _process_nsx_router_create(
|
||||||
|
self, context, router_db, router_req):
|
||||||
|
if not router_db['nsx_attributes']:
|
||||||
|
kwargs = {}
|
||||||
|
for attr in self.nsx_attributes:
|
||||||
|
name = attr['name']
|
||||||
|
default = attr['default']
|
||||||
|
kwargs[name] = router_req.get(name, default)
|
||||||
|
nsx_attributes = nicira_models.NSXRouterExtAttributes(
|
||||||
|
router_id=router_db['id'], **kwargs)
|
||||||
|
context.session.add(nsx_attributes)
|
||||||
|
router_db['nsx_attributes'] = nsx_attributes
|
||||||
|
else:
|
||||||
|
# The situation where the record already exists will
|
||||||
|
# be likely once the NSXRouterExtAttributes model
|
||||||
|
# will allow for defining several attributes pertaining
|
||||||
|
# to different extensions
|
||||||
|
for attr in self.nsx_attributes:
|
||||||
|
name = attr['name']
|
||||||
|
default = attr['default']
|
||||||
|
router_db['nsx_attributes'][name] = router_req.get(
|
||||||
|
name, default)
|
||||||
|
LOG.debug(_("Nsx router extension successfully processed "
|
||||||
|
"for router:%s"), router_db['id'])
|
||||||
|
|
||||||
|
# Register dict extend functions for ports
|
||||||
|
db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
|
||||||
|
l3.ROUTERS, ['_extend_nsx_router_dict'])
|
29
neutron/plugins/nicira/dbexts/servicerouter.py
Normal file
29
neutron/plugins/nicira/dbexts/servicerouter.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2013 VMware, Inc. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
from neutron.plugins.nicira.dbexts import distributedrouter as dist_rtr
|
||||||
|
from neutron.plugins.nicira.extensions import servicerouter
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceRouter_mixin(dist_rtr.DistributedRouter_mixin):
|
||||||
|
"""Mixin class to enable service router support."""
|
||||||
|
|
||||||
|
nsx_attributes = (
|
||||||
|
dist_rtr.DistributedRouter_mixin.nsx_attributes + [{
|
||||||
|
'name': servicerouter.SERVICE_ROUTER,
|
||||||
|
'default': False
|
||||||
|
}])
|
50
neutron/plugins/nicira/dbexts/vcns_db.py
Normal file
50
neutron/plugins/nicira/dbexts/vcns_db.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2013 Nicira, Inc.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from neutron.plugins.nicira.dbexts import vcns_models
|
||||||
|
|
||||||
|
|
||||||
|
def add_vcns_router_binding(session, router_id, vse_id, lswitch_id, status):
|
||||||
|
with session.begin(subtransactions=True):
|
||||||
|
binding = vcns_models.VcnsRouterBinding(
|
||||||
|
router_id=router_id,
|
||||||
|
edge_id=vse_id,
|
||||||
|
lswitch_id=lswitch_id,
|
||||||
|
status=status)
|
||||||
|
session.add(binding)
|
||||||
|
return binding
|
||||||
|
|
||||||
|
|
||||||
|
def get_vcns_router_binding(session, router_id):
|
||||||
|
with session.begin(subtransactions=True):
|
||||||
|
return (session.query(vcns_models.VcnsRouterBinding).
|
||||||
|
filter_by(router_id=router_id).first())
|
||||||
|
|
||||||
|
|
||||||
|
def update_vcns_router_binding(session, router_id, **kwargs):
|
||||||
|
with session.begin(subtransactions=True):
|
||||||
|
binding = (session.query(vcns_models.VcnsRouterBinding).
|
||||||
|
filter_by(router_id=router_id).one())
|
||||||
|
for key, value in kwargs.iteritems():
|
||||||
|
binding[key] = value
|
||||||
|
|
||||||
|
|
||||||
|
def delete_vcns_router_binding(session, router_id):
|
||||||
|
with session.begin(subtransactions=True):
|
||||||
|
binding = (session.query(vcns_models.VcnsRouterBinding).
|
||||||
|
filter_by(router_id=router_id).one())
|
||||||
|
session.delete(binding)
|
38
neutron/plugins/nicira/dbexts/vcns_models.py
Normal file
38
neutron/plugins/nicira/dbexts/vcns_models.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2013 Nicira, Inc.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
from neutron.db import model_base
|
||||||
|
from neutron.db import models_v2
|
||||||
|
|
||||||
|
|
||||||
|
class VcnsRouterBinding(model_base.BASEV2, models_v2.HasStatusDescription):
|
||||||
|
"""Represents the mapping between neutron router and vShield Edge."""
|
||||||
|
|
||||||
|
__tablename__ = 'vcns_router_bindings'
|
||||||
|
|
||||||
|
# no ForeignKey to routers.id because for now, a router can be removed
|
||||||
|
# from routers when delete_router is executed, but the binding is only
|
||||||
|
# removed after the Edge is deleted
|
||||||
|
router_id = sa.Column(sa.String(36),
|
||||||
|
primary_key=True)
|
||||||
|
edge_id = sa.Column(sa.String(16),
|
||||||
|
nullable=True)
|
||||||
|
lswitch_id = sa.Column(sa.String(36),
|
||||||
|
nullable=False)
|
61
neutron/plugins/nicira/extensions/servicerouter.py
Normal file
61
neutron/plugins/nicira/extensions/servicerouter.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# Copyright 2013 VMware, Inc. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
#
|
||||||
|
# @author: Kaiwei Fan, VMware, Inc
|
||||||
|
|
||||||
|
|
||||||
|
from neutron.api import extensions
|
||||||
|
from neutron.api.v2 import attributes
|
||||||
|
|
||||||
|
|
||||||
|
SERVICE_ROUTER = 'service_router'
|
||||||
|
EXTENDED_ATTRIBUTES_2_0 = {
|
||||||
|
'routers': {
|
||||||
|
SERVICE_ROUTER: {'allow_post': True, 'allow_put': False,
|
||||||
|
'convert_to': attributes.convert_to_boolean,
|
||||||
|
'default': False, 'is_visible': True},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Servicerouter(extensions.ExtensionDescriptor):
|
||||||
|
"""Extension class supporting advanced service router."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_name(cls):
|
||||||
|
return "Service Router"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_alias(cls):
|
||||||
|
return "service-router"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_description(cls):
|
||||||
|
return "Provides service router"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_namespace(cls):
|
||||||
|
return ""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_updated(cls):
|
||||||
|
return "2013-08-08T00:00:00-00:00"
|
||||||
|
|
||||||
|
def get_extended_resources(self, version):
|
||||||
|
if version == "2.0":
|
||||||
|
return EXTENDED_ATTRIBUTES_2_0
|
||||||
|
else:
|
||||||
|
return {}
|
@ -506,6 +506,7 @@ class EdgeApplianceDriver(object):
|
|||||||
LOG.debug(_("VCNS: start updating nat rules: %s"), rules)
|
LOG.debug(_("VCNS: start updating nat rules: %s"), rules)
|
||||||
|
|
||||||
nat = {
|
nat = {
|
||||||
|
'featureType': 'nat',
|
||||||
'rules': {
|
'rules': {
|
||||||
'natRulesDtos': rules
|
'natRulesDtos': rules
|
||||||
}
|
}
|
||||||
@ -565,20 +566,18 @@ class EdgeApplianceDriver(object):
|
|||||||
static_routes = []
|
static_routes = []
|
||||||
for route in routes:
|
for route in routes:
|
||||||
static_routes.append({
|
static_routes.append({
|
||||||
"route": {
|
|
||||||
"description": "",
|
"description": "",
|
||||||
"vnic": vcns_const.INTERNAL_VNIC_INDEX,
|
"vnic": vcns_const.INTERNAL_VNIC_INDEX,
|
||||||
"network": route['cidr'],
|
"network": route['cidr'],
|
||||||
"nextHop": route['nexthop']
|
"nextHop": route['nexthop']
|
||||||
}
|
|
||||||
})
|
})
|
||||||
request = {
|
request = {
|
||||||
"staticRouting": {
|
"staticRoutes": {
|
||||||
"staticRoutes": static_routes,
|
"staticRoutes": static_routes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if gateway:
|
if gateway:
|
||||||
request["staticRouting"]["defaultRoute"] = {
|
request["defaultRoute"] = {
|
||||||
"description": "default-gateway",
|
"description": "default-gateway",
|
||||||
"gatewayAddress": gateway,
|
"gatewayAddress": gateway,
|
||||||
"vnic": vcns_const.EXTERNAL_VNIC_INDEX
|
"vnic": vcns_const.EXTERNAL_VNIC_INDEX
|
||||||
|
@ -37,9 +37,6 @@ class Vcns(object):
|
|||||||
self.password = password
|
self.password = password
|
||||||
self.jsonapi_client = VcnsApiClient.VcnsApiHelper(address, user,
|
self.jsonapi_client = VcnsApiClient.VcnsApiHelper(address, user,
|
||||||
password, 'json')
|
password, 'json')
|
||||||
# TODO(fank): remove this after json syntax is fixed on VSM
|
|
||||||
self.xmlapi_client = VcnsApiClient.VcnsApiHelper(address, user,
|
|
||||||
password, 'xml')
|
|
||||||
|
|
||||||
def do_request(self, method, uri, params=None, format='json', **kwargs):
|
def do_request(self, method, uri, params=None, format='json', **kwargs):
|
||||||
LOG.debug(_("VcnsApiHelper('%(method)s', '%(uri)s', '%(body)s')"), {
|
LOG.debug(_("VcnsApiHelper('%(method)s', '%(uri)s', '%(body)s')"), {
|
||||||
@ -100,7 +97,7 @@ class Vcns(object):
|
|||||||
|
|
||||||
def update_routes(self, edge_id, routes):
|
def update_routes(self, edge_id, routes):
|
||||||
uri = "%s/%s/routing/config/static" % (URI_PREFIX, edge_id)
|
uri = "%s/%s/routing/config/static" % (URI_PREFIX, edge_id)
|
||||||
return self.do_request(HTTP_PUT, uri, routes, format='xml')
|
return self.do_request(HTTP_PUT, uri, routes)
|
||||||
|
|
||||||
def create_lswitch(self, lsconfig):
|
def create_lswitch(self, lsconfig):
|
||||||
uri = "/api/ws.v1/lswitch"
|
uri = "/api/ws.v1/lswitch"
|
||||||
|
@ -20,10 +20,12 @@ import os
|
|||||||
import neutron.plugins.nicira.api_client.client_eventlet as client
|
import neutron.plugins.nicira.api_client.client_eventlet as client
|
||||||
from neutron.plugins.nicira import extensions
|
from neutron.plugins.nicira import extensions
|
||||||
import neutron.plugins.nicira.NeutronPlugin as plugin
|
import neutron.plugins.nicira.NeutronPlugin as plugin
|
||||||
|
import neutron.plugins.nicira.NeutronServicePlugin as service_plugin
|
||||||
import neutron.plugins.nicira.NvpApiClient as nvpapi
|
import neutron.plugins.nicira.NvpApiClient as nvpapi
|
||||||
from neutron.plugins.nicira.vshield import vcns
|
from neutron.plugins.nicira.vshield import vcns
|
||||||
|
|
||||||
nvp_plugin = plugin.NvpPluginV2
|
nvp_plugin = plugin.NvpPluginV2
|
||||||
|
nvp_service_plugin = service_plugin.NvpAdvancedPlugin
|
||||||
api_helper = nvpapi.NVPApiHelper
|
api_helper = nvpapi.NVPApiHelper
|
||||||
nvp_client = client.NvpApiClientEventlet
|
nvp_client = client.NvpApiClientEventlet
|
||||||
vcns_class = vcns.Vcns
|
vcns_class = vcns.Vcns
|
||||||
@ -32,6 +34,8 @@ STUBS_PATH = os.path.join(os.path.dirname(__file__), 'etc')
|
|||||||
NVPEXT_PATH = os.path.dirname(extensions.__file__)
|
NVPEXT_PATH = os.path.dirname(extensions.__file__)
|
||||||
NVPAPI_NAME = '%s.%s' % (api_helper.__module__, api_helper.__name__)
|
NVPAPI_NAME = '%s.%s' % (api_helper.__module__, api_helper.__name__)
|
||||||
PLUGIN_NAME = '%s.%s' % (nvp_plugin.__module__, nvp_plugin.__name__)
|
PLUGIN_NAME = '%s.%s' % (nvp_plugin.__module__, nvp_plugin.__name__)
|
||||||
|
SERVICE_PLUGIN_NAME = '%s.%s' % (nvp_service_plugin.__module__,
|
||||||
|
nvp_service_plugin.__name__)
|
||||||
CLIENT_NAME = '%s.%s' % (nvp_client.__module__, nvp_client.__name__)
|
CLIENT_NAME = '%s.%s' % (nvp_client.__module__, nvp_client.__name__)
|
||||||
VCNS_NAME = '%s.%s' % (vcns_class.__module__, vcns_class.__name__)
|
VCNS_NAME = '%s.%s' % (vcns_class.__module__, vcns_class.__name__)
|
||||||
|
|
||||||
|
205
neutron/tests/unit/nicira/test_edge_router.py
Normal file
205
neutron/tests/unit/nicira/test_edge_router.py
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
# Copyright (c) 2013 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.
|
||||||
|
|
||||||
|
import copy
|
||||||
|
|
||||||
|
from eventlet import greenthread
|
||||||
|
import mock
|
||||||
|
from oslo.config import cfg
|
||||||
|
|
||||||
|
from neutron.api.v2 import attributes
|
||||||
|
from neutron import context
|
||||||
|
from neutron.extensions import l3
|
||||||
|
from neutron.manager import NeutronManager
|
||||||
|
from neutron.openstack.common import uuidutils
|
||||||
|
from neutron.tests.unit.nicira import NVPEXT_PATH
|
||||||
|
from neutron.tests.unit.nicira import SERVICE_PLUGIN_NAME
|
||||||
|
from neutron.tests.unit.nicira import test_nicira_plugin
|
||||||
|
from neutron.tests.unit.nicira import VCNS_NAME
|
||||||
|
from neutron.tests.unit.nicira.vshield import fake_vcns
|
||||||
|
|
||||||
|
_uuid = uuidutils.generate_uuid
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceRouterTestExtensionManager(object):
|
||||||
|
|
||||||
|
def get_resources(self):
|
||||||
|
# If l3 resources have been loaded and updated by main API
|
||||||
|
# router, update the map in the l3 extension so it will load
|
||||||
|
# the same attributes as the API router
|
||||||
|
l3_attr_map = copy.deepcopy(l3.RESOURCE_ATTRIBUTE_MAP)
|
||||||
|
for res in l3.RESOURCE_ATTRIBUTE_MAP.keys():
|
||||||
|
attr_info = attributes.RESOURCE_ATTRIBUTE_MAP.get(res)
|
||||||
|
if attr_info:
|
||||||
|
l3.RESOURCE_ATTRIBUTE_MAP[res] = attr_info
|
||||||
|
resources = l3.L3.get_resources()
|
||||||
|
# restore the original resources once the controllers are created
|
||||||
|
l3.RESOURCE_ATTRIBUTE_MAP = l3_attr_map
|
||||||
|
|
||||||
|
return resources
|
||||||
|
|
||||||
|
def get_actions(self):
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_request_extensions(self):
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
class NvpRouterTestCase(test_nicira_plugin.TestNiciraL3NatTestCase):
|
||||||
|
|
||||||
|
def setUp(self, plugin=None, ext_mgr=None):
|
||||||
|
plugin = plugin or SERVICE_PLUGIN_NAME
|
||||||
|
super(NvpRouterTestCase, self).setUp(plugin=plugin, ext_mgr=ext_mgr)
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceRouterTestCase(NvpRouterTestCase):
|
||||||
|
|
||||||
|
def vcns_patch(self):
|
||||||
|
instance = self.mock_vcns.start()
|
||||||
|
instance.return_value.deploy_edge.side_effect = self.fc2.deploy_edge
|
||||||
|
instance.return_value.get_edge_id.side_effect = self.fc2.get_edge_id
|
||||||
|
instance.return_value.get_edge_deploy_status.side_effect = (
|
||||||
|
self.fc2.get_edge_deploy_status)
|
||||||
|
instance.return_value.delete_edge.side_effect = self.fc2.delete_edge
|
||||||
|
instance.return_value.update_interface.side_effect = (
|
||||||
|
self.fc2.update_interface)
|
||||||
|
instance.return_value.get_nat_config.side_effect = (
|
||||||
|
self.fc2.get_nat_config)
|
||||||
|
instance.return_value.update_nat_config.side_effect = (
|
||||||
|
self.fc2.update_nat_config)
|
||||||
|
instance.return_value.delete_nat_rule.side_effect = (
|
||||||
|
self.fc2.delete_nat_rule)
|
||||||
|
instance.return_value.get_edge_status.side_effect = (
|
||||||
|
self.fc2.get_edge_status)
|
||||||
|
instance.return_value.get_edges.side_effect = self.fc2.get_edges
|
||||||
|
instance.return_value.update_routes.side_effect = (
|
||||||
|
self.fc2.update_routes)
|
||||||
|
instance.return_value.create_lswitch.side_effect = (
|
||||||
|
self.fc2.create_lswitch)
|
||||||
|
instance.return_value.delete_lswitch.side_effect = (
|
||||||
|
self.fc2.delete_lswitch)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
cfg.CONF.set_override('api_extensions_path', NVPEXT_PATH)
|
||||||
|
cfg.CONF.set_override('task_status_check_interval', 100, group="vcns")
|
||||||
|
|
||||||
|
# vcns does not support duplicated router name, ignore router name
|
||||||
|
# validation for unit-test cases
|
||||||
|
self.fc2 = fake_vcns.FakeVcns(unique_router_name=False)
|
||||||
|
self.mock_vcns = mock.patch(VCNS_NAME, autospec=True)
|
||||||
|
self.vcns_patch()
|
||||||
|
|
||||||
|
super(ServiceRouterTestCase, self).setUp(
|
||||||
|
ext_mgr=ServiceRouterTestExtensionManager())
|
||||||
|
|
||||||
|
self.fc2.set_fake_nvpapi(self.fc)
|
||||||
|
self.addCleanup(self.fc2.reset_all)
|
||||||
|
self.addCleanup(self.mock_vcns.stop)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
plugin = NeutronManager.get_plugin()
|
||||||
|
manager = plugin.vcns_driver.task_manager
|
||||||
|
for i in range(20):
|
||||||
|
if not manager.has_pending_task():
|
||||||
|
break
|
||||||
|
greenthread.sleep(0.1)
|
||||||
|
if manager.has_pending_task():
|
||||||
|
manager.show_pending_tasks()
|
||||||
|
raise Exception(_("Tasks not completed"))
|
||||||
|
manager.stop()
|
||||||
|
|
||||||
|
super(ServiceRouterTestCase, self).tearDown()
|
||||||
|
|
||||||
|
def _create_router(self, fmt, tenant_id, name=None,
|
||||||
|
admin_state_up=None, set_context=False,
|
||||||
|
arg_list=None, **kwargs):
|
||||||
|
data = {'router': {'tenant_id': tenant_id}}
|
||||||
|
if name:
|
||||||
|
data['router']['name'] = name
|
||||||
|
if admin_state_up:
|
||||||
|
data['router']['admin_state_up'] = admin_state_up
|
||||||
|
for arg in (('admin_state_up', 'tenant_id') + (arg_list or ())):
|
||||||
|
# Arg must be present and not empty
|
||||||
|
if arg in kwargs and kwargs[arg]:
|
||||||
|
data['router'][arg] = kwargs[arg]
|
||||||
|
data['router']['service_router'] = True
|
||||||
|
router_req = self.new_create_request('routers', data, fmt)
|
||||||
|
if set_context and tenant_id:
|
||||||
|
# create a specific auth context for this request
|
||||||
|
router_req.environ['neutron.context'] = context.Context(
|
||||||
|
'', tenant_id)
|
||||||
|
|
||||||
|
return router_req.get_response(self.ext_api)
|
||||||
|
|
||||||
|
def test_router_create(self):
|
||||||
|
name = 'router1'
|
||||||
|
tenant_id = _uuid()
|
||||||
|
expected_value = [('name', name), ('tenant_id', tenant_id),
|
||||||
|
('admin_state_up', True),
|
||||||
|
('external_gateway_info', None),
|
||||||
|
('service_router', True)]
|
||||||
|
with self.router(name='router1', admin_state_up=True,
|
||||||
|
tenant_id=tenant_id) as router:
|
||||||
|
expected_value_1 = expected_value + [('status', 'PENDING_CREATE')]
|
||||||
|
for k, v in expected_value_1:
|
||||||
|
self.assertEqual(router['router'][k], v)
|
||||||
|
|
||||||
|
# wait ~1 seconds for router status update
|
||||||
|
for i in range(2):
|
||||||
|
greenthread.sleep(0.5)
|
||||||
|
res = self._show('routers', router['router']['id'])
|
||||||
|
if res['router']['status'] == 'ACTIVE':
|
||||||
|
break
|
||||||
|
expected_value_2 = expected_value + [('status', 'ACTIVE')]
|
||||||
|
for k, v in expected_value_2:
|
||||||
|
self.assertEqual(res['router'][k], v)
|
||||||
|
|
||||||
|
# check an integration lswitch is created
|
||||||
|
lswitch_name = "%s-ls" % name
|
||||||
|
for lswitch_id, lswitch in self.fc2._lswitches.iteritems():
|
||||||
|
if lswitch['display_name'] == lswitch_name:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
self.fail("Integration lswitch not found")
|
||||||
|
|
||||||
|
# check an integration lswitch is deleted
|
||||||
|
lswitch_name = "%s-ls" % name
|
||||||
|
for lswitch_id, lswitch in self.fc2._lswitches.iteritems():
|
||||||
|
if lswitch['display_name'] == lswitch_name:
|
||||||
|
self.fail("Integration switch is not deleted")
|
||||||
|
|
||||||
|
def test_router_show(self):
|
||||||
|
name = 'router1'
|
||||||
|
tenant_id = _uuid()
|
||||||
|
expected_value = [('name', name), ('tenant_id', tenant_id),
|
||||||
|
('admin_state_up', True),
|
||||||
|
('status', 'PENDING_CREATE'),
|
||||||
|
('external_gateway_info', None),
|
||||||
|
('service_router', True)]
|
||||||
|
with self.router(name='router1', admin_state_up=True,
|
||||||
|
tenant_id=tenant_id) as router:
|
||||||
|
res = self._show('routers', router['router']['id'])
|
||||||
|
for k, v in expected_value:
|
||||||
|
self.assertEqual(res['router'][k], v)
|
||||||
|
|
||||||
|
def _test_router_create_with_gwinfo_and_l3_ext_net(self, vlan_id=None):
|
||||||
|
super(ServiceRouterTestCase,
|
||||||
|
self)._test_router_create_with_gwinfo_and_l3_ext_net(
|
||||||
|
vlan_id, validate_ext_gw=False)
|
||||||
|
|
||||||
|
def _test_router_update_gateway_on_l3_ext_net(self, vlan_id=None):
|
||||||
|
super(ServiceRouterTestCase,
|
||||||
|
self)._test_router_update_gateway_on_l3_ext_net(
|
||||||
|
vlan_id, validate_ext_gw=False)
|
@ -32,6 +32,7 @@ from neutron.extensions import portbindings
|
|||||||
from neutron.extensions import providernet as pnet
|
from neutron.extensions import providernet as pnet
|
||||||
from neutron.extensions import securitygroup as secgrp
|
from neutron.extensions import securitygroup as secgrp
|
||||||
from neutron import manager
|
from neutron import manager
|
||||||
|
from neutron.manager import NeutronManager
|
||||||
from neutron.openstack.common import uuidutils
|
from neutron.openstack.common import uuidutils
|
||||||
from neutron.plugins.nicira.common import exceptions as nvp_exc
|
from neutron.plugins.nicira.common import exceptions as nvp_exc
|
||||||
from neutron.plugins.nicira.common import sync
|
from neutron.plugins.nicira.common import sync
|
||||||
@ -433,15 +434,21 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
|
|||||||
def _restore_l3_attribute_map(self):
|
def _restore_l3_attribute_map(self):
|
||||||
l3.RESOURCE_ATTRIBUTE_MAP = self._l3_attribute_map_bk
|
l3.RESOURCE_ATTRIBUTE_MAP = self._l3_attribute_map_bk
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self, plugin=None, ext_mgr=None):
|
||||||
self._l3_attribute_map_bk = {}
|
self._l3_attribute_map_bk = {}
|
||||||
for item in l3.RESOURCE_ATTRIBUTE_MAP:
|
for item in l3.RESOURCE_ATTRIBUTE_MAP:
|
||||||
self._l3_attribute_map_bk[item] = (
|
self._l3_attribute_map_bk[item] = (
|
||||||
l3.RESOURCE_ATTRIBUTE_MAP[item].copy())
|
l3.RESOURCE_ATTRIBUTE_MAP[item].copy())
|
||||||
cfg.CONF.set_override('api_extensions_path', NVPEXT_PATH)
|
cfg.CONF.set_override('api_extensions_path', NVPEXT_PATH)
|
||||||
self.addCleanup(self._restore_l3_attribute_map)
|
self.addCleanup(self._restore_l3_attribute_map)
|
||||||
|
ext_mgr = ext_mgr or TestNiciraL3ExtensionManager()
|
||||||
super(TestNiciraL3NatTestCase, self).setUp(
|
super(TestNiciraL3NatTestCase, self).setUp(
|
||||||
ext_mgr=TestNiciraL3ExtensionManager())
|
plugin=plugin, ext_mgr=ext_mgr)
|
||||||
|
plugin_instance = NeutronManager.get_plugin()
|
||||||
|
self._plugin_name = "%s.%s" % (
|
||||||
|
plugin_instance.__module__,
|
||||||
|
plugin_instance.__class__.__name__)
|
||||||
|
self._plugin_class = plugin_instance.__class__
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
super(TestNiciraL3NatTestCase, self).tearDown()
|
super(TestNiciraL3NatTestCase, self).tearDown()
|
||||||
@ -487,7 +494,8 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
|
|||||||
def test_create_l3_ext_network_without_vlan(self):
|
def test_create_l3_ext_network_without_vlan(self):
|
||||||
self._test_create_l3_ext_network()
|
self._test_create_l3_ext_network()
|
||||||
|
|
||||||
def _test_router_create_with_gwinfo_and_l3_ext_net(self, vlan_id=None):
|
def _test_router_create_with_gwinfo_and_l3_ext_net(self, vlan_id=None,
|
||||||
|
validate_ext_gw=True):
|
||||||
with self._create_l3_ext_network(vlan_id) as net:
|
with self._create_l3_ext_network(vlan_id) as net:
|
||||||
with self.subnet(network=net) as s:
|
with self.subnet(network=net) as s:
|
||||||
data = {'router': {'tenant_id': 'whatever'}}
|
data = {'router': {'tenant_id': 'whatever'}}
|
||||||
@ -503,6 +511,7 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
|
|||||||
s['subnet']['network_id'],
|
s['subnet']['network_id'],
|
||||||
(router['router']['external_gateway_info']
|
(router['router']['external_gateway_info']
|
||||||
['network_id']))
|
['network_id']))
|
||||||
|
if validate_ext_gw:
|
||||||
self._nvp_validate_ext_gw(router['router']['id'],
|
self._nvp_validate_ext_gw(router['router']['id'],
|
||||||
'l3_gw_uuid', vlan_id)
|
'l3_gw_uuid', vlan_id)
|
||||||
finally:
|
finally:
|
||||||
@ -584,7 +593,8 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
|
|||||||
uuidutils.generate_uuid(),
|
uuidutils.generate_uuid(),
|
||||||
expected_code=webob.exc.HTTPNotFound.code)
|
expected_code=webob.exc.HTTPNotFound.code)
|
||||||
|
|
||||||
def _test_router_update_gateway_on_l3_ext_net(self, vlan_id=None):
|
def _test_router_update_gateway_on_l3_ext_net(self, vlan_id=None,
|
||||||
|
validate_ext_gw=True):
|
||||||
with self.router() as r:
|
with self.router() as r:
|
||||||
with self.subnet() as s1:
|
with self.subnet() as s1:
|
||||||
with self._create_l3_ext_network(vlan_id) as net:
|
with self._create_l3_ext_network(vlan_id) as net:
|
||||||
@ -609,7 +619,9 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
|
|||||||
['external_gateway_info']['network_id'])
|
['external_gateway_info']['network_id'])
|
||||||
self.assertEqual(net_id,
|
self.assertEqual(net_id,
|
||||||
s2['subnet']['network_id'])
|
s2['subnet']['network_id'])
|
||||||
self._nvp_validate_ext_gw(body['router']['id'],
|
if validate_ext_gw:
|
||||||
|
self._nvp_validate_ext_gw(
|
||||||
|
body['router']['id'],
|
||||||
'l3_gw_uuid', vlan_id)
|
'l3_gw_uuid', vlan_id)
|
||||||
finally:
|
finally:
|
||||||
# Cleanup
|
# Cleanup
|
||||||
@ -635,14 +647,10 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
|
|||||||
self._test_create_l3_ext_network(666)
|
self._test_create_l3_ext_network(666)
|
||||||
|
|
||||||
def test_floatingip_with_assoc_fails(self):
|
def test_floatingip_with_assoc_fails(self):
|
||||||
self._test_floatingip_with_assoc_fails(
|
self._test_floatingip_with_assoc_fails(self._plugin_name)
|
||||||
'neutron.plugins.nicira.'
|
|
||||||
'NeutronPlugin.NvpPluginV2')
|
|
||||||
|
|
||||||
def test_floatingip_with_invalid_create_port(self):
|
def test_floatingip_with_invalid_create_port(self):
|
||||||
self._test_floatingip_with_invalid_create_port(
|
self._test_floatingip_with_invalid_create_port(self._plugin_name)
|
||||||
'neutron.plugins.nicira.'
|
|
||||||
'NeutronPlugin.NvpPluginV2')
|
|
||||||
|
|
||||||
def _nvp_metadata_setup(self):
|
def _nvp_metadata_setup(self):
|
||||||
cfg.CONF.set_override('metadata_mode', 'access_network', 'NVP')
|
cfg.CONF.set_override('metadata_mode', 'access_network', 'NVP')
|
||||||
@ -724,7 +732,7 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
|
|||||||
with self.router() as r:
|
with self.router() as r:
|
||||||
with self.subnet() as s:
|
with self.subnet() as s:
|
||||||
# Raise a NeutronException (eg: NotFound)
|
# Raise a NeutronException (eg: NotFound)
|
||||||
with mock.patch.object(NeutronPlugin.NvpPluginV2,
|
with mock.patch.object(self._plugin_class,
|
||||||
'create_subnet',
|
'create_subnet',
|
||||||
side_effect=ntn_exc.NotFound):
|
side_effect=ntn_exc.NotFound):
|
||||||
self._router_interface_action(
|
self._router_interface_action(
|
||||||
@ -746,7 +754,7 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
|
|||||||
# Raise a NeutronException when adding metadata subnet
|
# Raise a NeutronException when adding metadata subnet
|
||||||
# to router
|
# to router
|
||||||
# save function being mocked
|
# save function being mocked
|
||||||
real_func = NeutronPlugin.NvpPluginV2.add_router_interface
|
real_func = self._plugin_class.add_router_interface
|
||||||
plugin_instance = manager.NeutronManager.get_plugin()
|
plugin_instance = manager.NeutronManager.get_plugin()
|
||||||
|
|
||||||
def side_effect(*args):
|
def side_effect(*args):
|
||||||
@ -756,7 +764,7 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
|
|||||||
# otherwise raise
|
# otherwise raise
|
||||||
raise NvpApiClient.NvpApiException()
|
raise NvpApiClient.NvpApiException()
|
||||||
|
|
||||||
with mock.patch.object(NeutronPlugin.NvpPluginV2,
|
with mock.patch.object(self._plugin_class,
|
||||||
'add_router_interface',
|
'add_router_interface',
|
||||||
side_effect=side_effect):
|
side_effect=side_effect):
|
||||||
self._router_interface_action(
|
self._router_interface_action(
|
||||||
@ -817,7 +825,7 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
|
|||||||
# Raise a NeutronException when removing
|
# Raise a NeutronException when removing
|
||||||
# metadata subnet from router
|
# metadata subnet from router
|
||||||
# save function being mocked
|
# save function being mocked
|
||||||
real_func = NeutronPlugin.NvpPluginV2.remove_router_interface
|
real_func = self._plugin_class.remove_router_interface
|
||||||
plugin_instance = manager.NeutronManager.get_plugin()
|
plugin_instance = manager.NeutronManager.get_plugin()
|
||||||
|
|
||||||
def side_effect(*args):
|
def side_effect(*args):
|
||||||
@ -827,7 +835,7 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
|
|||||||
# otherwise raise
|
# otherwise raise
|
||||||
raise NvpApiClient.NvpApiException()
|
raise NvpApiClient.NvpApiException()
|
||||||
|
|
||||||
with mock.patch.object(NeutronPlugin.NvpPluginV2,
|
with mock.patch.object(self._plugin_class,
|
||||||
'remove_router_interface',
|
'remove_router_interface',
|
||||||
side_effect=side_effect):
|
side_effect=side_effect):
|
||||||
self._router_interface_action('remove', r['router']['id'],
|
self._router_interface_action('remove', r['router']['id'],
|
||||||
|
Loading…
Reference in New Issue
Block a user