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:
Kaiwei Fan 2013-08-22 23:25:52 -07:00
parent a2600c9efd
commit becbff42c4
15 changed files with 1491 additions and 152 deletions

View File

@ -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')

View File

@ -54,6 +54,7 @@ from neutron.extensions import portsecurity as psec
from neutron.extensions import providernet as pnet
from neutron.extensions import securitygroup as ext_sg
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 exceptions as nvp_exc
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_EXTGW_NAT_RULES_ORDER = 255
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
@ -1381,48 +1383,14 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
else:
return super(NvpPluginV2, self).get_router(context, id, fields)
def create_router(self, context, router):
# NOTE(salvatore-orlando): We completely override this method in
# order to be able to use the NVP ID as Neutron ID
# TODO(salvatore-orlando): Propose upstream patch for allowing
# 3rd parties to specify IDs as we do with l2 plugin
r = router['router']
has_gw_info = False
tenant_id = self._get_tenant_id_for_create(context, r)
# default value to set - nvp wants it (even if we don't have it)
nexthop = '1.1.1.1'
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:
# if external gateway info are set, then configure nexthop to
# default external gateway
if 'external_gateway_info' in r and r.get('external_gateway_info'):
has_gw_info = True
gw_info = r['external_gateway_info']
del r['external_gateway_info']
# The following DB read will be performed again when updating
# gateway info. This is not great, but still better than
# creating NVP router here and updating it later
network_id = (gw_info.get('network_id', None) if gw_info
else None)
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]
nexthop = ext_subnet.gateway_ip
distributed = r.get('distributed')
lrouter = nvplib.create_lrouter(
self.cluster, tenant_id, router['router']['name'], nexthop,
self.cluster, tenant_id, name, nexthop,
distributed=attr.is_attr_set(distributed) and distributed)
# Use NVP identfier for Neutron resource
r['id'] = lrouter['uuid']
# Update 'distributed' with value returned from NVP
# This will be useful for setting the value if the API request
# did not specify any value for the 'distributed' attribute.
# Platforms older than 3.x do not support the attribute
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 "
@ -1432,6 +1400,7 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
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(
@ -1449,7 +1418,48 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
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'])
err_msg=_("Unable to create router %s") % router['name'])
lrouter['status'] = plugin_const.ACTIVE
return lrouter
def create_router(self, context, router):
# NOTE(salvatore-orlando): We completely override this method in
# order to be able to use the NVP ID as Neutron ID
# TODO(salvatore-orlando): Propose upstream patch for allowing
# 3rd parties to specify IDs as we do with l2 plugin
r = router['router']
has_gw_info = False
tenant_id = self._get_tenant_id_for_create(context, r)
# default value to set - nvp wants it (even if we don't have it)
nexthop = NVP_DEFAULT_NEXTHOP
# if external gateway info are set, then configure nexthop to
# default external gateway
if 'external_gateway_info' in r and r.get('external_gateway_info'):
has_gw_info = True
gw_info = r['external_gateway_info']
del r['external_gateway_info']
# The following DB read will be performed again when updating
# gateway info. This is not great, but still better than
# creating NVP router here and updating it later
network_id = (gw_info.get('network_id', None) if gw_info
else None)
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]
nexthop = ext_subnet.gateway_ip
lrouter = self._create_lrouter(context, r, nexthop)
# Use NVP identfier for Neutron resource
r['id'] = lrouter['uuid']
# Update 'distributed' with value returned from NVP
# This will be useful for setting the value if the API request
# did not specify any value for the 'distributed' attribute
# Platforms older than 3.x do not support the attribute
r['distributed'] = lrouter.get('distributed', False)
with context.session.begin(subtransactions=True):
# Transaction nesting is needed to avoid foreign key violations
@ -1459,14 +1469,23 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
tenant_id=tenant_id,
name=r['name'],
admin_state_up=r['admin_state_up'],
status="ACTIVE")
self._process_distributed_router_create(context, router_db, r)
status=lrouter['status'])
self._process_nsx_router_create(context, router_db, r)
context.session.add(router_db)
if has_gw_info:
self._update_router_gw_info(context, router_db['id'], gw_info)
router = self._make_router_dict(router_db)
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):
# Either nexthop is updated or should be kept as it was before
r = router['router']
@ -1494,8 +1513,8 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
"this must be updated through the default "
"gateway attribute")
raise q_exc.BadRequest(resource='router', msg=msg)
previous_routes = nvplib.update_lrouter(
self.cluster, router_id, r.get('name'),
previous_routes = self._update_lrouter(
context, router_id, r.get('name'),
nexthop, routes=r.get('routes'))
# NOTE(salv-orlando): The exception handling below is not correct, but
# unfortunately nvplib raises a neutron notfound exception when an
@ -1525,8 +1544,11 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
extraroute.RoutesExhausted):
with excutils.save_and_reraise_exception():
# revert changes made to NVP
nvplib.update_explicit_routes_lrouter(
self.cluster, router_id, previous_routes)
self._update_lrouter_routes(
router_id, previous_routes)
def _delete_lrouter(self, context, id):
nvplib.delete_lrouter(self.cluster, id)
def delete_router(self, context, router_id):
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
# together with the resource
try:
nvplib.delete_lrouter(self.cluster, router_id)
self._delete_lrouter(context, router_id)
except q_exc.NotFound:
LOG.warning(_("Logical router '%s' not found "
"on NVP Platform"), router_id)
@ -1553,6 +1575,27 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
err_msg=(_("Unable to delete logical router '%s' "
"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):
# When adding interface by port_id we need to create the
# 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.
# Fetch router from DB
router = self._get_router(context, router_id)
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']})
self._add_subnet_snat_rule(router, subnet)
nvplib.create_lrouter_nosnat_rule(
self.cluster, router_id,
order=NVP_NOSNAT_RULES_ORDER,
@ -1655,12 +1688,7 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
if not subnet:
subnet = self._get_subnet(context, subnet_id)
router = self._get_router(context, router_id)
# 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'])
self._delete_subnet_snat_rule(router, subnet)
# Relax the minimum expected number as the nosnat rules
# do not exist in 2.x deployments
nvplib.delete_nat_rules_by_match(
@ -1677,7 +1705,7 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
"on NVP Platform")))
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,
min_num_rules_expected=0):
try:
@ -1720,12 +1748,7 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
ips_to_add=[],
ips_to_remove=nvp_floating_ips)
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.
"""
def _get_fip_assoc_data(self, context, fip, floatingip_db):
if (('fixed_ip_address' in fip and fip['fixed_ip_address']) and
not ('port_id' in fip and fip['port_id'])):
msg = _("fixed_ip_address cannot be specified without a port_id")
@ -1748,7 +1771,6 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
fip,
floatingip_db['floating_network_id'])
floating_ip = floatingip_db['floating_ip_address']
# Retrieve and delete existing NAT rules, if any
if not router_id and floatingip_db.get('fixed_port_id'):
# 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']
_pid, internal_ip, router_id = self.get_assoc_data(
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 router_id:
self._retrieve_and_delete_nat_rules(floating_ip,
self._retrieve_and_delete_nat_rules(context,
floating_ip,
internal_ip,
router_id)
# Fetch logical port of router's external gateway
@ -1797,6 +1832,7 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
"internal ip:%(internal_ip)s"),
{'floating_ip': floating_ip,
'internal_ip': internal_ip})
msg = _("Failed to update NAT rules for floatingip update")
raise nvp_exc.NvpPluginException(err_msg=msg)
elif floatingip_db['fixed_port_id']:
# This is a disassociation.
@ -1816,7 +1852,8 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
fip_db = self._get_floatingip(context, id)
# Check whether the floating ip is associated or not
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.router_id,
min_num_rules_expected=1)
@ -1828,7 +1865,8 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
try:
fip_qry = context.session.query(l3_db.FloatingIP)
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.router_id,
min_num_rules_expected=1)

View 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)

View File

@ -17,53 +17,15 @@
# @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
from neutron.plugins.nicira.dbexts import nsxrouter
from neutron.plugins.nicira.extensions import distributedrouter as dist_rtr
LOG = logging.getLogger(__name__)
class DistributedRouter_mixin(object):
class DistributedRouter_mixin(nsxrouter.NsxRouterMixin):
"""Mixin class to enable distributed router support."""
def _extend_router_dict_distributed(self, router_res, router_db):
# Avoid setting attribute to None for routers already existing before
# the data model was extended with the distributed attribute
nsx_attrs = router_db['nsx_attributes']
# 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'])
nsx_attributes = (
nsxrouter.NsxRouterMixin.nsx_attributes + [{
'name': dist_rtr.DISTRIBUTED,
'default': False
}])

View File

@ -89,6 +89,7 @@ class NSXRouterExtAttributes(model_base.BASEV2):
ForeignKey('routers.id', ondelete="CASCADE"),
primary_key=True)
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
# SQLAlchemy to eagerly load this association
router = orm.relationship(

View 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'])

View 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
}])

View 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)

View 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)

View 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 {}

View File

@ -506,6 +506,7 @@ class EdgeApplianceDriver(object):
LOG.debug(_("VCNS: start updating nat rules: %s"), rules)
nat = {
'featureType': 'nat',
'rules': {
'natRulesDtos': rules
}
@ -565,20 +566,18 @@ class EdgeApplianceDriver(object):
static_routes = []
for route in routes:
static_routes.append({
"route": {
"description": "",
"vnic": vcns_const.INTERNAL_VNIC_INDEX,
"network": route['cidr'],
"nextHop": route['nexthop']
}
"description": "",
"vnic": vcns_const.INTERNAL_VNIC_INDEX,
"network": route['cidr'],
"nextHop": route['nexthop']
})
request = {
"staticRouting": {
"staticRoutes": static_routes,
"staticRoutes": {
"staticRoutes": static_routes
}
}
if gateway:
request["staticRouting"]["defaultRoute"] = {
request["defaultRoute"] = {
"description": "default-gateway",
"gatewayAddress": gateway,
"vnic": vcns_const.EXTERNAL_VNIC_INDEX

View File

@ -37,9 +37,6 @@ class Vcns(object):
self.password = password
self.jsonapi_client = VcnsApiClient.VcnsApiHelper(address, user,
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):
LOG.debug(_("VcnsApiHelper('%(method)s', '%(uri)s', '%(body)s')"), {
@ -100,7 +97,7 @@ class Vcns(object):
def update_routes(self, edge_id, routes):
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):
uri = "/api/ws.v1/lswitch"

View File

@ -20,10 +20,12 @@ import os
import neutron.plugins.nicira.api_client.client_eventlet as client
from neutron.plugins.nicira import extensions
import neutron.plugins.nicira.NeutronPlugin as plugin
import neutron.plugins.nicira.NeutronServicePlugin as service_plugin
import neutron.plugins.nicira.NvpApiClient as nvpapi
from neutron.plugins.nicira.vshield import vcns
nvp_plugin = plugin.NvpPluginV2
nvp_service_plugin = service_plugin.NvpAdvancedPlugin
api_helper = nvpapi.NVPApiHelper
nvp_client = client.NvpApiClientEventlet
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__)
NVPAPI_NAME = '%s.%s' % (api_helper.__module__, api_helper.__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__)
VCNS_NAME = '%s.%s' % (vcns_class.__module__, vcns_class.__name__)

View 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)

View File

@ -32,6 +32,7 @@ from neutron.extensions import portbindings
from neutron.extensions import providernet as pnet
from neutron.extensions import securitygroup as secgrp
from neutron import manager
from neutron.manager import NeutronManager
from neutron.openstack.common import uuidutils
from neutron.plugins.nicira.common import exceptions as nvp_exc
from neutron.plugins.nicira.common import sync
@ -433,15 +434,21 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
def _restore_l3_attribute_map(self):
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 = {}
for item in l3.RESOURCE_ATTRIBUTE_MAP:
self._l3_attribute_map_bk[item] = (
l3.RESOURCE_ATTRIBUTE_MAP[item].copy())
cfg.CONF.set_override('api_extensions_path', NVPEXT_PATH)
self.addCleanup(self._restore_l3_attribute_map)
ext_mgr = ext_mgr or TestNiciraL3ExtensionManager()
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):
super(TestNiciraL3NatTestCase, self).tearDown()
@ -487,7 +494,8 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
def test_create_l3_ext_network_without_vlan(self):
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.subnet(network=net) as s:
data = {'router': {'tenant_id': 'whatever'}}
@ -503,8 +511,9 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
s['subnet']['network_id'],
(router['router']['external_gateway_info']
['network_id']))
self._nvp_validate_ext_gw(router['router']['id'],
'l3_gw_uuid', vlan_id)
if validate_ext_gw:
self._nvp_validate_ext_gw(router['router']['id'],
'l3_gw_uuid', vlan_id)
finally:
self._delete('routers', router['router']['id'])
@ -584,7 +593,8 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
uuidutils.generate_uuid(),
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.subnet() as s1:
with self._create_l3_ext_network(vlan_id) as net:
@ -609,8 +619,10 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
['external_gateway_info']['network_id'])
self.assertEqual(net_id,
s2['subnet']['network_id'])
self._nvp_validate_ext_gw(body['router']['id'],
'l3_gw_uuid', vlan_id)
if validate_ext_gw:
self._nvp_validate_ext_gw(
body['router']['id'],
'l3_gw_uuid', vlan_id)
finally:
# Cleanup
self._remove_external_gateway_from_router(
@ -635,14 +647,10 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
self._test_create_l3_ext_network(666)
def test_floatingip_with_assoc_fails(self):
self._test_floatingip_with_assoc_fails(
'neutron.plugins.nicira.'
'NeutronPlugin.NvpPluginV2')
self._test_floatingip_with_assoc_fails(self._plugin_name)
def test_floatingip_with_invalid_create_port(self):
self._test_floatingip_with_invalid_create_port(
'neutron.plugins.nicira.'
'NeutronPlugin.NvpPluginV2')
self._test_floatingip_with_invalid_create_port(self._plugin_name)
def _nvp_metadata_setup(self):
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.subnet() as s:
# Raise a NeutronException (eg: NotFound)
with mock.patch.object(NeutronPlugin.NvpPluginV2,
with mock.patch.object(self._plugin_class,
'create_subnet',
side_effect=ntn_exc.NotFound):
self._router_interface_action(
@ -746,7 +754,7 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
# Raise a NeutronException when adding metadata subnet
# to router
# 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()
def side_effect(*args):
@ -756,7 +764,7 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
# otherwise raise
raise NvpApiClient.NvpApiException()
with mock.patch.object(NeutronPlugin.NvpPluginV2,
with mock.patch.object(self._plugin_class,
'add_router_interface',
side_effect=side_effect):
self._router_interface_action(
@ -817,7 +825,7 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
# Raise a NeutronException when removing
# metadata subnet from router
# 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()
def side_effect(*args):
@ -827,7 +835,7 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
# otherwise raise
raise NvpApiClient.NvpApiException()
with mock.patch.object(NeutronPlugin.NvpPluginV2,
with mock.patch.object(self._plugin_class,
'remove_router_interface',
side_effect=side_effect):
self._router_interface_action('remove', r['router']['id'],