def __init__(self, router_id, root_helper, use_namespaces, router):
self.router_id = router_id
self.ex_gw_port = None
+ self._snat_enabled = None
+ self._snat_action = None
self.internal_ports = []
self.floating_ips = []
self.root_helper = root_helper
self.use_namespaces = use_namespaces
+ # Invoke the setter for establishing initial SNAT action
self.router = router
self.iptables_manager = iptables_manager.IptablesManager(
root_helper=root_helper,
self.routes = []
+ @property
+ def router(self):
+ return self._router
+
+ @router.setter
+ def router(self, value):
+ self._router = value
+ if not self._router:
+ return
+ # Set a SNAT action for the router
+ if self._router.get('gw_port'):
+ if (self._router.get('enable_snat') and not self._snat_enabled):
+ self._snat_action = 'add_rule'
+ elif (self._snat_enabled and
+ not self._router.get('enable_snat')):
+ self._snat_action = 'remove_rule'
+ elif self.ex_gw_port:
+ self._snat_action = 'remove_rule'
+ self._snat_enabled = self._router.get('enable_snat')
+
def ns_name(self):
if self.use_namespaces:
return NS_PREFIX + self.router_id
+ def perform_snat_action(self, snat_callback, *args):
+ # Process SNAT rules for attached subnets
+ if self._snat_action:
+ snat_callback(self, self._router.get('gw_port'),
+ *args, action=self._snat_action)
+ self._snat_action = None
+
class L3NATAgent(manager.Manager):
port['ip_cidr'] = "%s/%s" % (ips[0]['ip_address'], prefixlen)
def process_router(self, ri):
-
ex_gw_port = self._get_ex_gw_port(ri)
internal_ports = ri.router.get(l3_constants.INTERFACE_KEY, [])
existing_port_ids = set([p['id'] for p in ri.internal_ports])
for p in new_ports:
self._set_subnet_info(p)
ri.internal_ports.append(p)
- self.internal_network_added(ri, ex_gw_port,
- p['network_id'], p['id'],
+ self.internal_network_added(ri, p['network_id'], p['id'],
p['ip_cidr'], p['mac_address'])
for p in old_ports:
ri.internal_ports.remove(p)
- self.internal_network_removed(ri, ex_gw_port, p['id'],
- p['ip_cidr'])
+ self.internal_network_removed(ri, p['id'], p['ip_cidr'])
internal_cidrs = [p['ip_cidr'] for p in ri.internal_ports]
+ # TODO(salv-orlando): RouterInfo would be a better place for
+ # this logic too
+ ex_gw_port_id = (ex_gw_port and ex_gw_port['id'] or
+ ri.ex_gw_port and ri.ex_gw_port['id'])
+ if ex_gw_port_id:
+ interface_name = self.get_external_device_name(ex_gw_port_id)
if ex_gw_port and not ri.ex_gw_port:
self._set_subnet_info(ex_gw_port)
- self.external_gateway_added(ri, ex_gw_port, internal_cidrs)
+ self.external_gateway_added(ri, ex_gw_port,
+ interface_name, internal_cidrs)
elif not ex_gw_port and ri.ex_gw_port:
self.external_gateway_removed(ri, ri.ex_gw_port,
- internal_cidrs)
+ interface_name, internal_cidrs)
+
+ # Process SNAT rules for external gateway
+ if ex_gw_port_id:
+ ri.perform_snat_action(self._handle_router_snat_rules,
+ internal_cidrs, interface_name)
- if ri.ex_gw_port or ex_gw_port:
+ # Process DNAT rules for floating IPs
+ if ex_gw_port or ri.ex_gw_port:
self.process_router_floating_ips(ri, ex_gw_port)
ri.ex_gw_port = ex_gw_port
-
+ ri.enable_snat = ri.router.get('enable_snat')
self.routes_updated(ri)
+ def _handle_router_snat_rules(self, ri, ex_gw_port, internal_cidrs,
+ interface_name, action):
+ ex_gw_ip = ex_gw_port['fixed_ips'][0]['ip_address']
+ for rule in self.external_gateway_nat_rules(ex_gw_ip,
+ internal_cidrs,
+ interface_name):
+ # This is an internal method so we can assume the caller
+ # knows which actions are valid and which not
+ getattr(ri.iptables_manager.ipv4['nat'], action)(*rule)
+ ri.iptables_manager.apply()
+
def process_router_floating_ips(self, ri, ex_gw_port):
floating_ips = ri.router.get(l3_constants.FLOATINGIP_KEY, [])
existing_floating_ip_ids = set([fip['id'] for fip in ri.floating_ips])
def get_external_device_name(self, port_id):
return (EXTERNAL_DEV_PREFIX + port_id)[:self.driver.DEV_NAME_LEN]
- def external_gateway_added(self, ri, ex_gw_port, internal_cidrs):
+ def external_gateway_added(self, ri, ex_gw_port,
+ interface_name, internal_cidrs):
- interface_name = self.get_external_device_name(ex_gw_port['id'])
- ex_gw_ip = ex_gw_port['fixed_ips'][0]['ip_address']
if not ip_lib.device_exists(interface_name,
root_helper=self.root_helper,
namespace=ri.ns_name()):
utils.execute(cmd, check_exit_code=False,
root_helper=self.root_helper)
- for (c, r) in self.external_gateway_nat_rules(ex_gw_ip,
- internal_cidrs,
- interface_name):
- ri.iptables_manager.ipv4['nat'].add_rule(c, r)
- ri.iptables_manager.apply()
+ def external_gateway_removed(self, ri, ex_gw_port,
+ interface_name, internal_cidrs):
- def external_gateway_removed(self, ri, ex_gw_port, internal_cidrs):
-
- interface_name = self.get_external_device_name(ex_gw_port['id'])
if ip_lib.device_exists(interface_name,
root_helper=self.root_helper,
namespace=ri.ns_name()):
namespace=ri.ns_name(),
prefix=EXTERNAL_DEV_PREFIX)
- ex_gw_ip = ex_gw_port['fixed_ips'][0]['ip_address']
- for c, r in self.external_gateway_nat_rules(ex_gw_ip, internal_cidrs,
- interface_name):
- ri.iptables_manager.ipv4['nat'].remove_rule(c, r)
- ri.iptables_manager.apply()
-
def metadata_filter_rules(self):
rules = []
rules.append(('INPUT', '-s 0.0.0.0/0 -d 127.0.0.1 '
rules.extend(self.internal_network_nat_rules(ex_gw_ip, cidr))
return rules
- def internal_network_added(self, ri, ex_gw_port, network_id, port_id,
+ def internal_network_added(self, ri, network_id, port_id,
internal_cidr, mac_address):
interface_name = self.get_internal_device_name(port_id)
if not ip_lib.device_exists(interface_name,
ip_address = internal_cidr.split('/')[0]
self._send_gratuitous_arp_packet(ri, interface_name, ip_address)
- if ex_gw_port:
- ex_gw_ip = ex_gw_port['fixed_ips'][0]['ip_address']
- for c, r in self.internal_network_nat_rules(ex_gw_ip,
- internal_cidr):
- ri.iptables_manager.ipv4['nat'].add_rule(c, r)
- ri.iptables_manager.apply()
-
- def internal_network_removed(self, ri, ex_gw_port, port_id, internal_cidr):
+ def internal_network_removed(self, ri, port_id, internal_cidr):
interface_name = self.get_internal_device_name(port_id)
if ip_lib.device_exists(interface_name,
root_helper=self.root_helper,
self.driver.unplug(interface_name, namespace=ri.ns_name(),
prefix=INTERNAL_DEV_PREFIX)
- if ex_gw_port:
- ex_gw_ip = ex_gw_port['fixed_ips'][0]['ip_address']
- for c, r in self.internal_network_nat_rules(ex_gw_ip,
- internal_cidr):
- ri.iptables_manager.ipv4['nat'].remove_rule(c, r)
- ri.iptables_manager.apply()
-
def internal_network_nat_rules(self, ex_gw_ip, internal_cidr):
rules = [('snat', '-s %s -j SNAT --to-source %s' %
(internal_cidr, ex_gw_ip))]
if (not self.conf.use_namespaces and
r['id'] != self.conf.router_id):
continue
-
ex_net_id = (r['external_gateway_info'] or {}).get('network_id')
if not ex_net_id and not self.conf.handle_internal_only_routers:
continue
-
if ex_net_id and ex_net_id != target_ex_net_id:
continue
cur_router_ids.add(r['id'])
return msg
+def _validate_boolean(data, valid_values=None):
+ try:
+ convert_to_boolean(data)
+ except q_exc.InvalidInput:
+ msg = _("'%s' is not a valid boolean value") % data
+ LOG.debug(msg)
+ return msg
+
+
def _validate_range(data, valid_values=None):
min_value = valid_values[0]
max_value = valid_values[1]
msg = _("'%s' is not a dictionary") % data
LOG.debug(msg)
return msg
-
# Do not perform any further validation, if no constraints are supplied
if not key_specs:
return
return _validate_dict(data, key_specs)
+def _validate_dict_or_nodata(data, key_specs=None):
+ if data:
+ return _validate_dict(data, key_specs)
+
+
def _validate_non_negative(data, valid_values=None):
try:
data = int(data)
validators = {'type:dict': _validate_dict,
'type:dict_or_none': _validate_dict_or_none,
'type:dict_or_empty': _validate_dict_or_empty,
+ 'type:dict_or_nodata': _validate_dict_or_nodata,
'type:fixed_ips': _validate_fixed_ips,
'type:hostroutes': _validate_hostroutes,
'type:ip_address': _validate_ip_address,
'type:uuid': _validate_uuid,
'type:uuid_or_none': _validate_uuid_or_none,
'type:uuid_list': _validate_uuid_list,
- 'type:values': _validate_values}
+ 'type:values': _validate_values,
+ 'type:boolean': _validate_boolean}
# Define constants for base resource name
NETWORK = 'network'
DEVICE_OWNER_ROUTER_INTF = l3_constants.DEVICE_OWNER_ROUTER_INTF
DEVICE_OWNER_ROUTER_GW = l3_constants.DEVICE_OWNER_ROUTER_GW
DEVICE_OWNER_FLOATINGIP = l3_constants.DEVICE_OWNER_FLOATINGIP
+EXTERNAL_GW_INFO = l3.EXTERNAL_GW_INFO
# Maps API field to DB column
# API parameter name and Database column names may differ.
'tenant_id': router['tenant_id'],
'admin_state_up': router['admin_state_up'],
'status': router['status'],
- 'external_gateway_info': None,
+ EXTERNAL_GW_INFO: None,
'gw_port_id': router['gw_port_id']}
if router['gw_port_id']:
nw_id = router.gw_port['network_id']
- res['external_gateway_info'] = {'network_id': nw_id}
+ res[EXTERNAL_GW_INFO] = {'network_id': nw_id}
if process_extensions:
for func in self._dict_extend_functions.get(l3.ROUTERS, []):
func(self, res, router)
def create_router(self, context, router):
r = router['router']
has_gw_info = False
- if 'external_gateway_info' in r:
+ if EXTERNAL_GW_INFO in r:
has_gw_info = True
- gw_info = r['external_gateway_info']
- del r['external_gateway_info']
+ gw_info = r[EXTERNAL_GW_INFO]
+ del r[EXTERNAL_GW_INFO]
tenant_id = self._get_tenant_id_for_create(context, r)
with context.session.begin(subtransactions=True):
# pre-generate id so it will be available when
def update_router(self, context, id, router):
r = router['router']
has_gw_info = False
- if 'external_gateway_info' in r:
+ if EXTERNAL_GW_INFO in r:
has_gw_info = True
- gw_info = r['external_gateway_info']
- del r['external_gateway_info']
+ gw_info = r[EXTERNAL_GW_INFO]
+ del r[EXTERNAL_GW_INFO]
with context.session.begin(subtransactions=True):
if has_gw_info:
self._update_router_gw_info(context, id, gw_info)
l3_rpc_agent_api.L3AgentNotify.routers_updated(context, routers)
return self._make_router_dict(router_db)
- def _update_router_gw_info(self, context, router_id, info):
+ def _create_router_gw_port(self, context, router, network_id):
+ # Port has no 'tenant-id', as it is hidden from user
+ gw_port = self.create_port(context.elevated(), {
+ 'port': {'tenant_id': '', # intentionally not set
+ 'network_id': network_id,
+ 'mac_address': attributes.ATTR_NOT_SPECIFIED,
+ 'fixed_ips': attributes.ATTR_NOT_SPECIFIED,
+ 'device_id': router['id'],
+ 'device_owner': DEVICE_OWNER_ROUTER_GW,
+ 'admin_state_up': True,
+ 'name': ''}})
+
+ if not gw_port['fixed_ips']:
+ self.delete_port(context.elevated(), gw_port['id'],
+ l3_port_check=False)
+ msg = (_('No IPs available for external network %s') %
+ network_id)
+ raise q_exc.BadRequest(resource='router', msg=msg)
+
+ with context.session.begin(subtransactions=True):
+ router.gw_port = self._get_port(context.elevated(),
+ gw_port['id'])
+ context.session.add(router)
+
+ def _update_router_gw_info(self, context, router_id, info, router=None):
# TODO(salvatore-orlando): guarantee atomic behavior also across
# operations that span beyond the model classes handled by this
# class (e.g.: delete_port)
- router = self._get_router(context, router_id)
+ router = router or self._get_router(context, router_id)
gw_port = router.gw_port
-
- network_id = info.get('network_id', None) if info else None
+ # network_id attribute is required by API, so it must be present
+ network_id = info['network_id'] if info else None
if network_id:
self._get_network(context, network_id)
if not self._network_is_external(context, network_id):
if fip_count:
raise l3.RouterExternalGatewayInUseByFloatingIp(
router_id=router_id, net_id=gw_port['network_id'])
- with context.session.begin(subtransactions=True):
- router.gw_port = None
- context.session.add(router)
- self.delete_port(context.elevated(), gw_port['id'],
- l3_port_check=False)
+ if gw_port and gw_port['network_id'] != network_id:
+ with context.session.begin(subtransactions=True):
+ router.gw_port = None
+ context.session.add(router)
+ self.delete_port(context.elevated(), gw_port['id'],
+ l3_port_check=False)
if network_id is not None and (gw_port is None or
gw_port['network_id'] != network_id):
self._check_for_dup_router_subnet(context, router_id,
network_id, subnet['id'],
subnet['cidr'])
-
- # Port has no 'tenant-id', as it is hidden from user
- gw_port = self.create_port(context.elevated(), {
- 'port':
- {'tenant_id': '', # intentionally not set
- 'network_id': network_id,
- 'mac_address': attributes.ATTR_NOT_SPECIFIED,
- 'fixed_ips': attributes.ATTR_NOT_SPECIFIED,
- 'device_id': router_id,
- 'device_owner': DEVICE_OWNER_ROUTER_GW,
- 'admin_state_up': True,
- 'name': ''}})
-
- if not gw_port['fixed_ips']:
- self.delete_port(context.elevated(), gw_port['id'],
- l3_port_check=False)
- msg = (_('No IPs available for external network %s') %
- network_id)
- raise q_exc.BadRequest(resource='router', msg=msg)
-
- with context.session.begin(subtransactions=True):
- router.gw_port = self._get_port(context.elevated(),
- gw_port['id'])
- context.session.add(router)
+ self._create_router_gw_port(context, router, network_id)
def delete_router(self, context, id):
with context.session.begin(subtransactions=True):
external_network_id=external_network_id,
port_id=internal_port['id'])
- def get_assoc_data(self, context, fip, floating_network_id):
- """Determine/extract data associated with the internal port.
+ def _internal_fip_assoc_data(self, context, fip):
+ """Retrieve internal port data for floating IP.
- When a floating IP is associated with an internal port,
- we need to extract/determine some data associated with the
- internal port, including the internal_ip_address, and router_id.
- We also need to confirm that this internal port is owned by the
- tenant who owns the floating IP.
+ Retrieve information concerning the internal port where
+ the floating IP should be associated to.
"""
internal_port = self._get_port(context, fip['port_id'])
if not internal_port['tenant_id'] == fip['tenant_id']:
raise q_exc.BadRequest(resource='floatingip', msg=msg)
internal_ip_address = internal_port['fixed_ips'][0]['ip_address']
internal_subnet_id = internal_port['fixed_ips'][0]['subnet_id']
+ return internal_port, internal_subnet_id, internal_ip_address
+
+ def get_assoc_data(self, context, fip, floating_network_id):
+ """Determine/extract data associated with the internal port.
+ When a floating IP is associated with an internal port,
+ we need to extract/determine some data associated with the
+ internal port, including the internal_ip_address, and router_id.
+ We also need to confirm that this internal port is owned by the
+ tenant who owns the floating IP.
+ """
+ (internal_port, internal_subnet_id,
+ internal_ip_address) = self._internal_fip_assoc_data(context, fip)
router_id = self._get_router_for_floatingip(context,
internal_port,
internal_subnet_id,
else:
return [n for n in nets if n['id'] not in ext_nets]
+ def _build_routers_list(self, routers, gw_ports):
+ gw_port_id_gw_port_dict = dict((gw_port['id'], gw_port)
+ for gw_port in gw_ports)
+ for router in routers:
+ gw_port_id = router['gw_port_id']
+ if gw_port_id:
+ router['gw_port'] = gw_port_id_gw_port_dict[gw_port_id]
+ return routers
+
def _get_sync_routers(self, context, router_ids=None, active=None):
"""Query routers and their gw ports for l3 agent.
gw_ports = []
if gw_port_ids:
gw_ports = self.get_sync_gw_ports(context, gw_port_ids)
- gw_port_id_gw_port_dict = {}
- for gw_port in gw_ports:
- gw_port_id_gw_port_dict[gw_port['id']] = gw_port
- for router_dict in router_dicts:
- gw_port_id = router_dict['gw_port_id']
- if gw_port_id:
- router_dict['gw_port'] = gw_port_id_gw_port_dict[gw_port_id]
- return router_dicts
+ return self._build_routers_list(router_dicts, gw_ports)
def _get_sync_floating_ips(self, context, router_ids):
"""Query floating_ips that relate to list of router_ids."""
--- /dev/null
+# 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
+#
+
+import sqlalchemy as sa
+
+from quantum.db import l3_db
+from quantum.extensions import l3
+from quantum.openstack.common import log as logging
+
+
+LOG = logging.getLogger(__name__)
+EXTERNAL_GW_INFO = l3.EXTERNAL_GW_INFO
+
+# Modify the Router Data Model adding the enable_snat attribute
+setattr(l3_db.Router, 'enable_snat',
+ sa.Column(sa.Boolean, default=True, nullable=False))
+
+
+class L3_NAT_db_mixin(l3_db.L3_NAT_db_mixin):
+ """Mixin class to add configurable gateway modes."""
+
+ def _make_router_dict(self, router, fields=None):
+ res = super(L3_NAT_db_mixin, self)._make_router_dict(router)
+ if router['gw_port_id']:
+ nw_id = router.gw_port['network_id']
+ res[EXTERNAL_GW_INFO] = {'network_id': nw_id,
+ 'enable_snat': router.enable_snat}
+ return self._fields(res, fields)
+
+ def _update_router_gw_info(self, context, router_id, info):
+ router = self._get_router(context, router_id)
+ # if enable_snat is not specified use the value
+ # stored in the database (default:True)
+ enable_snat = not info or info.get('enable_snat', router.enable_snat)
+ with context.session.begin(subtransactions=True):
+ router.enable_snat = enable_snat
+
+ # Calls superclass, pass router db object for avoiding re-loading
+ super(L3_NAT_db_mixin, self)._update_router_gw_info(
+ context, router_id, info, router=router)
+
+ def _build_routers_list(self, routers, gw_ports):
+ gw_port_id_gw_port_dict = {}
+ for gw_port in gw_ports:
+ gw_port_id_gw_port_dict[gw_port['id']] = gw_port
+ for rtr in routers:
+ gw_port_id = rtr['gw_port_id']
+ if gw_port_id:
+ rtr['gw_port'] = gw_port_id_gw_port_dict[gw_port_id]
+ # Add enable_snat key
+ rtr['enable_snat'] = rtr[EXTERNAL_GW_INFO]['enable_snat']
+ return routers
--- /dev/null
+# 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.
+#
+
+"""ext_gw_mode
+
+Revision ID: 128e042a2b68
+Revises: 176a85fc7d79
+Create Date: 2013-03-27 00:35:17.323280
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '128e042a2b68'
+down_revision = '176a85fc7d79'
+
+# Change to ['*'] if this migration applies to all plugins
+
+migration_for_plugins = [
+ 'quantum.plugins.hyperv.hyperv_quantum_plugin.HyperVQuantumPlugin',
+ 'quantum.plugins.linuxbridge.lb_quantum_plugin.LinuxBridgePluginV2',
+ 'quantum.plugins.metaplugin.meta_quantum_plugin.MetaPluginV2',
+ 'quantum.plugins.nec.nec_plugin.NECPluginV2',
+ 'quantum.plugins.openvswitch.ovs_quantum_plugin.OVSQuantumPluginV2',
+ 'quantum.plugins.ryu.ryu_quantum_plugin.RyuQuantumPluginV2'
+]
+
+from alembic import op
+import sqlalchemy as sa
+
+
+from quantum.db import migration
+
+
+def upgrade(active_plugin=None, options=None):
+ if not migration.should_run(active_plugin, migration_for_plugins):
+ return
+
+ op.add_column('routers', sa.Column('enable_snat', sa.Boolean(),
+ nullable=False, default=True))
+ # Set enable_snat to True for existing routers
+ op.execute("UPDATE routers SET enable_snat=True")
+
+
+def downgrade(active_plugin=None, options=None):
+ if not migration.should_run(active_plugin, migration_for_plugins):
+ return
+
+ op.drop_column('routers', 'enable_snat')
"more floating IPs.")
ROUTERS = 'routers'
+EXTERNAL_GW_INFO = 'external_gateway_info'
-# Attribute Map
RESOURCE_ATTRIBUTE_MAP = {
ROUTERS: {
'id': {'allow_post': False, 'allow_put': False,
'required_by_policy': True,
'validate': {'type:string': None},
'is_visible': True},
- 'external_gateway_info': {'allow_post': True, 'allow_put': True,
- 'is_visible': True, 'default': None}
+ EXTERNAL_GW_INFO: {'allow_post': True, 'allow_put': True,
+ 'is_visible': True, 'default': None}
},
'floatingips': {
'id': {'allow_post': False, 'allow_put': False,
--- /dev/null
+# 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 quantum.api import extensions
+from quantum.common import exceptions as qexception
+from quantum.extensions import l3
+
+
+class RouterDNatDisabled(qexception.BadRequest):
+ message = _("DNat is disabled for the router %(router_id)s. Floating IPs "
+ "cannot be associated.")
+
+EXTENDED_ATTRIBUTES_2_0 = {
+ 'routers': {l3.EXTERNAL_GW_INFO:
+ {'allow_post': True,
+ 'allow_put': True,
+ 'is_visible': True,
+ 'default': None,
+ 'validate':
+ {'type:dict_or_nodata':
+ {'network_id': {'type:uuid': None, 'required': True},
+ 'enable_snat': {'type:boolean': None, 'required': False}}
+ }}}}
+
+
+class L3_ext_gw_mode(extensions.ExtensionDescriptor):
+
+ @classmethod
+ def get_name(cls):
+ return "Quantum L3 Configurable external gateway mode"
+
+ @classmethod
+ def get_alias(cls):
+ return "ext-gw-mode"
+
+ @classmethod
+ def get_description(cls):
+ return ("Extension of the router abstraction for specifying whether "
+ "SNAT, DNAT or both should occur on the external gateway")
+
+ @classmethod
+ def get_namespace(cls):
+ return "http://docs.openstack.org/ext/quantum/ext-gw-mode/api/v1.0"
+
+ @classmethod
+ def get_updated(cls):
+ return "2013-03-28T10:00:00-00:00"
+
+ def get_required_extensions(self):
+ return ["router"]
+
+ def get_extended_resources(self, version):
+ if version == "2.0":
+ return dict(EXTENDED_ATTRIBUTES_2_0.items())
+ else:
+ return {}
from quantum.common import exceptions as q_exc
from quantum.common import topics
from quantum.db import db_base_plugin_v2
-from quantum.db import l3_db
+from quantum.db import l3_gwmode_db
from quantum.db import quota_db # noqa
from quantum.extensions import portbindings
from quantum.extensions import providernet as provider
class HyperVQuantumPlugin(db_base_plugin_v2.QuantumDbPluginV2,
- l3_db.L3_NAT_db_mixin):
+ l3_gwmode_db.L3_NAT_db_mixin):
# This attribute specifies whether the plugin supports or not
# bulk operations. Name mangling is used in order to ensure it
# is qualified by class
__native_bulk_support = True
- supported_extension_aliases = ["provider", "router", "binding", "quotas"]
+ supported_extension_aliases = ["provider", "router", "ext-gw-mode",
+ "binding", "quotas"]
def __init__(self, configfile=None):
self._db = hyperv_db.HyperVPluginDB()
from quantum.db import db_base_plugin_v2
from quantum.db import dhcp_rpc_base
from quantum.db import extraroute_db
+from quantum.db import l3_gwmode_db
from quantum.db import l3_rpc_base
from quantum.db import portbindings_db
from quantum.db import quota_db # noqa
class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2,
extraroute_db.ExtraRoute_db_mixin,
+ l3_gwmode_db.L3_NAT_db_mixin,
sg_db_rpc.SecurityGroupServerRpcMixin,
agentschedulers_db.AgentSchedulerDbMixin,
portbindings_db.PortBindingMixin):
__native_pagination_support = True
__native_sorting_support = True
- _supported_extension_aliases = ["provider", "router", "binding", "quotas",
- "security-group", "agent", "extraroute",
- "agent_scheduler"]
+ _supported_extension_aliases = ["provider", "router", "ext-gw-mode",
+ "binding", "quotas", "security-group",
+ "agent", "extraroute", "agent_scheduler"]
@property
def supported_extension_aliases(self):
LOG.debug(_("Start initializing metaplugin"))
self.supported_extension_aliases = \
cfg.CONF.META.supported_extension_aliases.split(',')
- self.supported_extension_aliases += ['flavor', 'router', 'extraroute']
+ self.supported_extension_aliases += ['flavor', 'router',
+ 'ext-gw-mode', 'extraroute']
# Ignore config option overapping
def _is_opt_registered(opts, opt):
from quantum.db import agentschedulers_db
from quantum.db import dhcp_rpc_base
from quantum.db import extraroute_db
+from quantum.db import l3_gwmode_db
from quantum.db import l3_rpc_base
from quantum.db import quota_db # noqa
from quantum.db import securitygroups_rpc_base as sg_db_rpc
class NECPluginV2(nec_plugin_base.NECPluginV2Base,
extraroute_db.ExtraRoute_db_mixin,
+ l3_gwmode_db.L3_NAT_db_mixin,
sg_db_rpc.SecurityGroupServerRpcMixin,
agentschedulers_db.AgentSchedulerDbMixin):
"""NECPluginV2 controls an OpenFlow Controller.
The port binding extension enables an external application relay
information to and from the plugin.
"""
- _supported_extension_aliases = ["router", "quotas", "binding",
- "security-group", "extraroute",
- "agent", "agent_scheduler",
- ]
+ _supported_extension_aliases = ["router", "ext-gw-mode", "quotas",
+ "binding", "security-group",
+ "extraroute", "agent", "agent_scheduler"]
@property
def supported_extension_aliases(self):
from quantum.db import db_base_plugin_v2
from quantum.db import dhcp_rpc_base
from quantum.db import extraroute_db
+from quantum.db import l3_gwmode_db
from quantum.db import l3_rpc_base
from quantum.db import portbindings_db
from quantum.db import quota_db # noqa
class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
extraroute_db.ExtraRoute_db_mixin,
+ l3_gwmode_db.L3_NAT_db_mixin,
sg_db_rpc.SecurityGroupServerRpcMixin,
agentschedulers_db.AgentSchedulerDbMixin,
portbindings_db.PortBindingMixin):
__native_pagination_support = True
__native_sorting_support = True
- _supported_extension_aliases = ["provider", "router",
+ _supported_extension_aliases = ["provider", "router", "ext-gw-mode",
"binding", "quotas", "security-group",
"agent", "extraroute", "agent_scheduler"]
from quantum.db import db_base_plugin_v2
from quantum.db import dhcp_rpc_base
from quantum.db import extraroute_db
+from quantum.db import l3_gwmode_db
from quantum.db import l3_rpc_base
from quantum.db import models_v2
from quantum.db import securitygroups_rpc_base as sg_db_rpc
class RyuQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
extraroute_db.ExtraRoute_db_mixin,
+ l3_gwmode_db.L3_NAT_db_mixin,
sg_db_rpc.SecurityGroupServerRpcMixin):
- _supported_extension_aliases = ["router", "extraroute", "security-group"]
+ _supported_extension_aliases = ["router", "ext-gw-mode",
+ "extraroute", "security-group"]
@property
def supported_extension_aliases(self):
# under the License.
from quantum.db import db_base_plugin_v2
-from quantum.db import l3_db
+from quantum.db import l3_gwmode_db
class Fake1(db_base_plugin_v2.QuantumDbPluginV2,
- l3_db.L3_NAT_db_mixin):
+ l3_gwmode_db.L3_NAT_db_mixin):
supported_extension_aliases = ['router']
def fake_func(self):
from quantum.tests.unit import test_extensions
from quantum.tests.unit import testlib_api
-
DB_PLUGIN_KLASS = 'quantum.db.db_base_plugin_v2.QuantumDbPluginV2'
ROOTDIR = os.path.dirname(os.path.dirname(__file__))
ETCDIR = os.path.join(ROOTDIR, 'etc')
--- /dev/null
+# 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
+#
+
+import stubout
+
+import fixtures
+import mock
+from oslo.config import cfg
+from webob import exc
+
+from quantum.common import constants
+from quantum.common.test_lib import test_config
+from quantum.db import api as db_api
+from quantum.db import l3_db
+from quantum.db import l3_gwmode_db
+from quantum.db import models_v2
+from quantum.extensions import l3
+from quantum.extensions import l3_ext_gw_mode
+from quantum.openstack.common import uuidutils
+from quantum.tests import base
+from quantum.tests.unit import test_db_plugin
+from quantum.tests.unit import test_l3_plugin
+
+_uuid = uuidutils.generate_uuid
+FAKE_GW_PORT_ID = _uuid()
+FAKE_GW_PORT_MAC = 'aa:bb:cc:dd:ee:ff'
+FAKE_FIP_EXT_PORT_ID = _uuid()
+FAKE_FIP_EXT_PORT_MAC = '11:22:33:44:55:66'
+FAKE_FIP_INT_PORT_ID = _uuid()
+FAKE_FIP_INT_PORT_MAC = 'aa:aa:aa:aa:aa:aa'
+FAKE_ROUTER_PORT_ID = _uuid()
+FAKE_ROUTER_PORT_MAC = 'bb:bb:bb:bb:bb:bb'
+
+
+class StuboutFixture(fixtures.Fixture):
+ """Setup stubout and add unsetAll to cleanup."""
+
+ def setUp(self):
+ super(StuboutFixture, self).setUp()
+ self.stubs = stubout.StubOutForTesting()
+ self.addCleanup(self.stubs.UnsetAll)
+ self.addCleanup(self.stubs.SmartUnsetAll)
+
+
+def stubout_floating_ip_calls(stubs, fake_count=0):
+
+ def get_floatingips_count(_1, _2, filters):
+ return fake_count
+
+ stubs.Set(l3_db.L3_NAT_db_mixin, 'get_floatingips_count',
+ get_floatingips_count)
+
+
+class TestExtensionManager(object):
+
+ def get_resources(self):
+ # Simulate extension of L3 attribute map
+ for key in l3.RESOURCE_ATTRIBUTE_MAP.keys():
+ l3.RESOURCE_ATTRIBUTE_MAP[key].update(
+ l3_ext_gw_mode.EXTENDED_ATTRIBUTES_2_0.get(key, {}))
+ return l3.L3.get_resources()
+
+ def get_actions(self):
+ return []
+
+ def get_request_extensions(self):
+ return []
+
+
+# A simple class for making a concrete class out of the mixin
+class TestDbPlugin(test_l3_plugin.TestL3NatPlugin,
+ l3_gwmode_db.L3_NAT_db_mixin):
+
+ supported_extension_aliases = ["router", "ext-gw-mode"]
+
+
+class TestL3GwModeMixin(base.BaseTestCase):
+
+ def setUp(self):
+ super(TestL3GwModeMixin, self).setUp()
+ stubout_fixture = self.useFixture(StuboutFixture())
+ self.stubs = stubout_fixture.stubs
+ self.target_object = TestDbPlugin()
+ # Patch the context
+ ctx_patcher = mock.patch('quantum.context', autospec=True)
+ mock_context = ctx_patcher.start()
+ self.addCleanup(db_api.clear_db)
+ self.addCleanup(ctx_patcher.stop)
+ self.context = mock_context.get_admin_context()
+ # This ensure also calls to elevated work in unit tests
+ self.context.elevated.return_value = self.context
+ self.context.session = db_api.get_session()
+ # Create sample data for tests
+ self.ext_net_id = _uuid()
+ self.int_net_id = _uuid()
+ self.int_sub_id = _uuid()
+ self.tenant_id = 'the_tenant'
+ self.network = models_v2.Network(
+ id=self.ext_net_id,
+ tenant_id=self.tenant_id,
+ admin_state_up=True,
+ status=constants.NET_STATUS_ACTIVE)
+ self.net_ext = l3_db.ExternalNetwork(network_id=self.ext_net_id)
+ self.context.session.add(self.network)
+ # The following is to avoid complains from sqlite on
+ # foreign key violations
+ self.context.session.flush()
+ self.context.session.add(self.net_ext)
+ self.router = l3_db.Router(
+ id=_uuid(),
+ name=None,
+ tenant_id=self.tenant_id,
+ admin_state_up=True,
+ status=constants.NET_STATUS_ACTIVE,
+ enable_snat=True,
+ gw_port_id=None)
+ self.context.session.add(self.router)
+ self.context.session.flush()
+ self.router_gw_port = models_v2.Port(
+ id=FAKE_GW_PORT_ID,
+ tenant_id=self.tenant_id,
+ device_id=self.router.id,
+ device_owner=l3_db.DEVICE_OWNER_ROUTER_GW,
+ admin_state_up=True,
+ status=constants.PORT_STATUS_ACTIVE,
+ mac_address=FAKE_GW_PORT_MAC,
+ network_id=self.ext_net_id)
+ self.router.gw_port_id = self.router_gw_port.id
+ self.context.session.add(self.router)
+ self.context.session.add(self.router_gw_port)
+ self.context.session.flush()
+ self.fip_ext_port = models_v2.Port(
+ id=FAKE_FIP_EXT_PORT_ID,
+ tenant_id=self.tenant_id,
+ admin_state_up=True,
+ device_id=self.router.id,
+ device_owner=l3_db.DEVICE_OWNER_FLOATINGIP,
+ status=constants.PORT_STATUS_ACTIVE,
+ mac_address=FAKE_FIP_EXT_PORT_MAC,
+ network_id=self.ext_net_id)
+ self.context.session.add(self.fip_ext_port)
+ self.context.session.flush()
+ self.int_net = models_v2.Network(
+ id=self.int_net_id,
+ tenant_id=self.tenant_id,
+ admin_state_up=True,
+ status=constants.NET_STATUS_ACTIVE)
+ self.int_sub = models_v2.Subnet(
+ id=self.int_sub_id,
+ tenant_id=self.tenant_id,
+ ip_version=4,
+ cidr='3.3.3.0/24',
+ gateway_ip='3.3.3.1',
+ network_id=self.int_net_id)
+ self.router_port = models_v2.Port(
+ id=FAKE_ROUTER_PORT_ID,
+ tenant_id=self.tenant_id,
+ admin_state_up=True,
+ device_id=self.router.id,
+ device_owner=l3_db.DEVICE_OWNER_ROUTER_INTF,
+ status=constants.PORT_STATUS_ACTIVE,
+ mac_address=FAKE_ROUTER_PORT_MAC,
+ network_id=self.int_net_id)
+ self.router_port_ip_info = models_v2.IPAllocation(
+ port_id=self.router_port.id,
+ network_id=self.int_net.id,
+ subnet_id=self.int_sub_id,
+ ip_address='3.3.3.1')
+ self.context.session.add(self.int_net)
+ self.context.session.add(self.int_sub)
+ self.context.session.add(self.router_port)
+ self.context.session.add(self.router_port_ip_info)
+ self.context.session.flush()
+ self.fip_int_port = models_v2.Port(
+ id=FAKE_FIP_INT_PORT_ID,
+ tenant_id=self.tenant_id,
+ admin_state_up=True,
+ device_id='something',
+ device_owner='compute:nova',
+ status=constants.PORT_STATUS_ACTIVE,
+ mac_address=FAKE_FIP_INT_PORT_MAC,
+ network_id=self.int_net_id)
+ self.fip_int_ip_info = models_v2.IPAllocation(
+ port_id=self.fip_int_port.id,
+ network_id=self.int_net.id,
+ subnet_id=self.int_sub_id,
+ ip_address='3.3.3.3')
+ self.fip = l3_db.FloatingIP(
+ id=_uuid(),
+ floating_ip_address='1.1.1.2',
+ floating_network_id=self.ext_net_id,
+ floating_port_id=FAKE_FIP_EXT_PORT_ID,
+ fixed_port_id=None,
+ fixed_ip_address=None,
+ router_id=None)
+ self.context.session.add(self.fip_int_port)
+ self.context.session.add(self.fip_int_ip_info)
+ self.context.session.add(self.fip)
+ self.context.session.flush()
+ self.fip_request = {'port_id': FAKE_FIP_INT_PORT_ID,
+ 'tenant_id': self.tenant_id}
+
+ def _reset_ext_gw(self):
+ # Reset external gateway
+ self.router.gw_port_id = None
+ self.context.session.add(self.router)
+ self.context.session.flush()
+
+ def _test_update_router_gw(self, gw_info, expected_enable_snat):
+ self.target_object._update_router_gw_info(
+ self.context, self.router.id, gw_info)
+ router = self.target_object._get_router(
+ self.context, self.router.id)
+ try:
+ self.assertEqual(FAKE_GW_PORT_ID,
+ router.gw_port.id)
+ self.assertEqual(FAKE_GW_PORT_MAC,
+ router.gw_port.mac_address)
+ except AttributeError:
+ self.assertIsNone(router.gw_port)
+ self.assertEqual(expected_enable_snat, router.enable_snat)
+
+ def test_update_router_gw_with_gw_info_none(self):
+ self._test_update_router_gw(None, True)
+
+ def test_update_router_gw_with_network_only(self):
+ info = {'network_id': self.ext_net_id}
+ self._test_update_router_gw(info, True)
+
+ def test_update_router_gw_with_snat_disabled(self):
+ info = {'network_id': self.ext_net_id,
+ 'enable_snat': False}
+ self._test_update_router_gw(info, False)
+
+ def test_make_router_dict_no_ext_gw(self):
+ self._reset_ext_gw()
+ router_dict = self.target_object._make_router_dict(self.router)
+ self.assertEqual(None, router_dict[l3.EXTERNAL_GW_INFO])
+
+ def test_make_router_dict_with_ext_gw(self):
+ router_dict = self.target_object._make_router_dict(self.router)
+ self.assertEqual({'network_id': self.ext_net_id,
+ 'enable_snat': True},
+ router_dict[l3.EXTERNAL_GW_INFO])
+
+ def test_make_router_dict_with_ext_gw_snat_disabled(self):
+ self.router.enable_snat = False
+ router_dict = self.target_object._make_router_dict(self.router)
+ self.assertEqual({'network_id': self.ext_net_id,
+ 'enable_snat': False},
+ router_dict[l3.EXTERNAL_GW_INFO])
+
+ def test_build_routers_list_no_ext_gw(self):
+ self._reset_ext_gw()
+ router_dict = self.target_object._make_router_dict(self.router)
+ routers = self.target_object._build_routers_list([router_dict], [])
+ self.assertEqual(1, len(routers))
+ router = routers[0]
+ self.assertIsNone(router.get('gw_port'))
+ self.assertIsNone(router.get('enable_snat'))
+
+ def test_build_routers_list_with_ext_gw(self):
+ router_dict = self.target_object._make_router_dict(self.router)
+ routers = self.target_object._build_routers_list(
+ [router_dict], [self.router.gw_port])
+ self.assertEqual(1, len(routers))
+ router = routers[0]
+ self.assertIsNotNone(router.get('gw_port'))
+ self.assertEqual(FAKE_GW_PORT_ID, router['gw_port']['id'])
+ self.assertTrue(router.get('enable_snat'))
+
+ def test_build_routers_list_with_ext_gw_snat_disabled(self):
+ self.router.enable_snat = False
+ router_dict = self.target_object._make_router_dict(self.router)
+ routers = self.target_object._build_routers_list(
+ [router_dict], [self.router.gw_port])
+ self.assertEqual(1, len(routers))
+ router = routers[0]
+ self.assertIsNotNone(router.get('gw_port'))
+ self.assertEqual(FAKE_GW_PORT_ID, router['gw_port']['id'])
+ self.assertFalse(router.get('enable_snat'))
+
+
+class ExtGwModeTestCase(test_db_plugin.QuantumDbPluginV2TestCase,
+ test_l3_plugin.L3NatTestCaseMixin):
+
+ def setUp(self):
+ # Store l3 resource attribute map as it's will be updated
+ self._l3_attribute_map_bk = {}
+ for item in l3.RESOURCE_ATTRIBUTE_MAP:
+ self._l3_attribute_map_bk[item] = (
+ l3.RESOURCE_ATTRIBUTE_MAP[item].copy())
+ test_config['plugin_name_v2'] = (
+ 'quantum.tests.unit.test_extension_ext_gw_mode.TestDbPlugin')
+ test_config['extension_manager'] = TestExtensionManager()
+ # for these tests we need to enable overlapping ips
+ cfg.CONF.set_default('allow_overlapping_ips', True)
+ super(ExtGwModeTestCase, self).setUp()
+ self.addCleanup(self.restore_l3_attribute_map)
+
+ def restore_l3_attribute_map(self):
+ l3.RESOURCE_ATTRIBUTE_MAP = self._l3_attribute_map_bk
+
+ def tearDown(self):
+ super(ExtGwModeTestCase, self).tearDown()
+
+ def _set_router_external_gateway(self, router_id, network_id,
+ snat_enabled=None,
+ expected_code=exc.HTTPOk.code,
+ quantum_context=None):
+ ext_gw_info = {'network_id': network_id}
+ if snat_enabled in (True, False):
+ ext_gw_info['enable_snat'] = snat_enabled
+ return self._update('routers', router_id,
+ {'router': {'external_gateway_info':
+ ext_gw_info}},
+ expected_code=expected_code,
+ quantum_context=quantum_context)
+
+ def test_router_create_show_no_ext_gwinfo(self):
+ name = 'router1'
+ tenant_id = _uuid()
+ expected_value = [('name', name), ('tenant_id', tenant_id),
+ ('admin_state_up', True), ('status', 'ACTIVE'),
+ ('external_gateway_info', None)]
+ with self.router(name=name, 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_show_ext_gwinfo(self, snat_input_value,
+ snat_expected_value):
+ name = 'router1'
+ tenant_id = _uuid()
+ with self.subnet() as s:
+ ext_net_id = s['subnet']['network_id']
+ self._set_net_external(ext_net_id)
+ input_value = {'network_id': ext_net_id}
+ if snat_input_value in (True, False):
+ input_value['enable_snat'] = snat_input_value
+ expected_value = [('name', name), ('tenant_id', tenant_id),
+ ('admin_state_up', True), ('status', 'ACTIVE'),
+ ('external_gateway_info',
+ {'network_id': ext_net_id,
+ 'enable_snat': snat_expected_value})]
+ with self.router(
+ name=name, admin_state_up=True, tenant_id=tenant_id,
+ external_gateway_info=input_value) 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_show_ext_gwinfo_default(self):
+ self._test_router_create_show_ext_gwinfo(None, True)
+
+ def test_router_create_show_ext_gwinfo_with_snat_enabled(self):
+ self._test_router_create_show_ext_gwinfo(True, True)
+
+ def test_router_create_show_ext_gwinfo_with_snat_disabled(self):
+ self._test_router_create_show_ext_gwinfo(False, False)
+
+ def _test_router_update_ext_gwinfo(self, snat_input_value,
+ snat_expected_value):
+ with self.router() as r:
+ with self.subnet() as s:
+ ext_net_id = s['subnet']['network_id']
+ self._set_net_external(ext_net_id)
+ self._set_router_external_gateway(
+ r['router']['id'], ext_net_id,
+ snat_enabled=snat_input_value)
+ body = self._show('routers', r['router']['id'])
+ res_gw_info = body['router']['external_gateway_info']
+ self.assertEqual(res_gw_info['network_id'], ext_net_id)
+ self.assertEqual(res_gw_info['enable_snat'],
+ snat_expected_value)
+ self._remove_external_gateway_from_router(
+ r['router']['id'], ext_net_id)
+
+ def test_router_update_ext_gwinfo_default(self):
+ self._test_router_update_ext_gwinfo(None, True)
+
+ def test_router_update_ext_gwinfo_with_snat_enabled(self):
+ self._test_router_update_ext_gwinfo(True, True)
+
+ def test_router_update_ext_gwinfo_with_snat_disabled(self):
+ self._test_router_update_ext_gwinfo(False, False)
self.assertTrue(ri.ns_name().endswith(id))
+ def test_router_info_create_with_router(self):
+ id = _uuid()
+ ex_gw_port = {'id': _uuid(),
+ 'network_id': _uuid(),
+ 'fixed_ips': [{'ip_address': '19.4.4.4',
+ 'subnet_id': _uuid()}],
+ 'subnet': {'cidr': '19.4.4.0/24',
+ 'gateway_ip': '19.4.4.1'}}
+ router = {
+ 'id': _uuid(),
+ 'enable_snat': True,
+ 'routes': [],
+ 'gw_port': ex_gw_port}
+ ri = l3_agent.RouterInfo(id, self.conf.root_helper,
+ self.conf.use_namespaces, router)
+ self.assertTrue(ri.ns_name().endswith(id))
+ self.assertEqual(ri.router, router)
+
def testAgentCreate(self):
l3_agent.L3NATAgent(HOSTNAME, self.conf)
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
cidr = '99.0.1.9/24'
mac = 'ca:fe:de:ad:be:ef'
- ex_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30'}]}
if action == 'add':
self.device_exists.return_value = False
- agent.internal_network_added(ri, ex_gw_port, network_id,
+ agent.internal_network_added(ri, network_id,
port_id, cidr, mac)
self.assertEqual(self.mock_driver.plug.call_count, 1)
self.assertEqual(self.mock_driver.init_l3.call_count, 1)
elif action == 'remove':
self.device_exists.return_value = True
- agent.internal_network_removed(ri, ex_gw_port, port_id, cidr)
+ agent.internal_network_removed(ri, port_id, cidr)
self.assertEqual(self.mock_driver.unplug.call_count, 1)
else:
raise Exception("Invalid action %s" % action)
if action == 'add':
self.device_exists.return_value = False
- agent.external_gateway_added(ri, ex_gw_port, internal_cidrs)
+ agent.external_gateway_added(ri, ex_gw_port,
+ interface_name, internal_cidrs)
self.assertEqual(self.mock_driver.plug.call_count, 1)
self.assertEqual(self.mock_driver.init_l3.call_count, 1)
arping_cmd = ['arping', '-A', '-U',
elif action == 'remove':
self.device_exists.return_value = True
- agent.external_gateway_removed(ri, ex_gw_port, internal_cidrs)
+ agent.external_gateway_removed(ri, ex_gw_port,
+ interface_name, internal_cidrs)
self.assertEqual(self.mock_driver.unplug.call_count, 1)
else:
raise Exception("Invalid action %s" % action)
'via', '10.100.10.30']]
self._check_agent_method_called(agent, expected, namespace)
- def testProcessRouter(self):
-
- agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+ def _verify_snat_rules(self, rules, router):
+ interfaces = router[l3_constants.INTERFACE_KEY]
+ source_cidrs = []
+ for interface in interfaces:
+ prefix = interface['subnet']['cidr'].split('/')[1]
+ source_cidr = "%s/%s" % (interface['fixed_ips'][0]['ip_address'],
+ prefix)
+ source_cidrs.append(source_cidr)
+ source_nat_ip = router['gw_port']['fixed_ips'][0]['ip_address']
+ interface_name = ('qg-%s' % router['gw_port']['id'])[:14]
+ expected_rules = [
+ '! -i %s ! -o %s -m conntrack ! --ctstate DNAT -j ACCEPT' %
+ (interface_name, interface_name)]
+ for source_cidr in source_cidrs:
+ value_dict = {'source_cidr': source_cidr,
+ 'source_nat_ip': source_nat_ip}
+ expected_rules.append('-s %(source_cidr)s -j SNAT --to-source '
+ '%(source_nat_ip)s' % value_dict)
+ for r in rules:
+ self.assertIn(r.rule, expected_rules)
+
+ def _prepare_router_data(self, enable_snat=True):
router_id = _uuid()
ex_gw_port = {'id': _uuid(),
'network_id': _uuid(),
'subnet': {'cidr': '35.4.4.0/24',
'gateway_ip': '35.4.4.1'}}
- fake_floatingips1 = {'floatingips': [
- {'id': _uuid(),
- 'floating_ip_address': '8.8.8.8',
- 'fixed_ip_address': '7.7.7.7',
- 'port_id': _uuid()}]}
-
router = {
'id': router_id,
- l3_constants.FLOATINGIP_KEY: fake_floatingips1['floatingips'],
l3_constants.INTERFACE_KEY: [internal_port],
+ 'enable_snat': enable_snat,
'routes': [],
'gw_port': ex_gw_port}
- ri = l3_agent.RouterInfo(router_id, self.conf.root_helper,
+ return router
+
+ def testProcessRouter(self):
+ agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+ router = self._prepare_router_data()
+ fake_floatingips1 = {'floatingips': [
+ {'id': _uuid(),
+ 'floating_ip_address': '8.8.8.8',
+ 'fixed_ip_address': '7.7.7.7',
+ 'port_id': _uuid()}]}
+ ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
self.conf.use_namespaces, router=router)
agent.process_router(ri)
del router['gw_port']
agent.process_router(ri)
+ def test_process_router_snat_disabled(self):
+
+ agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+ router = self._prepare_router_data()
+ ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
+ self.conf.use_namespaces, router=router)
+ # Process with NAT
+ agent.process_router(ri)
+ orig_nat_rules = ri.iptables_manager.ipv4['nat'].rules[:]
+ # Reprocess without NAT
+ router['enable_snat'] = False
+ # Reassign the router object to RouterInfo
+ ri.router = router
+ agent.process_router(ri)
+ nat_rules_delta = (set(orig_nat_rules) -
+ set(ri.iptables_manager.ipv4['nat'].rules))
+ self.assertEqual(len(nat_rules_delta), 2)
+ self._verify_snat_rules(nat_rules_delta, router)
+
+ def test_process_router_snat_enabled(self):
+
+ agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+ router = self._prepare_router_data(enable_snat=False)
+ ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
+ self.conf.use_namespaces, router=router)
+ # Process with NAT
+ agent.process_router(ri)
+ orig_nat_rules = ri.iptables_manager.ipv4['nat'].rules[:]
+ # Reprocess without NAT
+ router['enable_snat'] = True
+ # Reassign the router object to RouterInfo
+ ri.router = router
+ agent.process_router(ri)
+ nat_rules_delta = (set(ri.iptables_manager.ipv4['nat'].rules) -
+ set(orig_nat_rules))
+ self.assertEqual(len(nat_rules_delta), 2)
+ self._verify_snat_rules(nat_rules_delta, router)
+
def testRoutersWithAdminStateDown(self):
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
self.plugin_api.get_external_network_id.return_value = None
return router_req.get_response(self.ext_api)
- def _make_router(self, fmt, tenant_id, name=None,
- admin_state_up=None, set_context=False):
+ def _make_router(self, fmt, tenant_id, name=None, admin_state_up=None,
+ external_gateway_info=None, set_context=False):
+ arg_list = (external_gateway_info and
+ ('external_gateway_info', ) or None)
res = self._create_router(fmt, tenant_id, name,
- admin_state_up, set_context)
+ admin_state_up, set_context,
+ arg_list=arg_list,
+ external_gateway_info=external_gateway_info)
return self.deserialize(fmt, res)
def _add_external_gateway_to_router(self, router_id, network_id,
@contextlib.contextmanager
def router(self, name='router1', admin_state_up=True,
- fmt=None, tenant_id=_uuid(), set_context=False):
+ fmt=None, tenant_id=_uuid(),
+ external_gateway_info=None, set_context=False):
router = self._make_router(fmt or self.fmt, tenant_id, name,
- admin_state_up, set_context)
+ admin_state_up, external_gateway_info,
+ set_context)
try:
yield router
finally: