Merge "Configurable external gateway modes"

This commit is contained in:
Jenkins 2013-05-29 14:40:59 +00:00 committed by Gerrit Code Review
commit d1a01265e7
18 changed files with 894 additions and 141 deletions

View File

@ -92,10 +92,13 @@ class RouterInfo(object):
def __init__(self, router_id, root_helper, use_namespaces, router): def __init__(self, router_id, root_helper, use_namespaces, router):
self.router_id = router_id self.router_id = router_id
self.ex_gw_port = None self.ex_gw_port = None
self._snat_enabled = None
self._snat_action = None
self.internal_ports = [] self.internal_ports = []
self.floating_ips = [] self.floating_ips = []
self.root_helper = root_helper self.root_helper = root_helper
self.use_namespaces = use_namespaces self.use_namespaces = use_namespaces
# Invoke the setter for establishing initial SNAT action
self.router = router self.router = router
self.iptables_manager = iptables_manager.IptablesManager( self.iptables_manager = iptables_manager.IptablesManager(
root_helper=root_helper, root_helper=root_helper,
@ -104,10 +107,37 @@ class RouterInfo(object):
self.routes = [] 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): def ns_name(self):
if self.use_namespaces: if self.use_namespaces:
return NS_PREFIX + self.router_id 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): class L3NATAgent(manager.Manager):
@ -291,7 +321,6 @@ class L3NATAgent(manager.Manager):
port['ip_cidr'] = "%s/%s" % (ips[0]['ip_address'], prefixlen) port['ip_cidr'] = "%s/%s" % (ips[0]['ip_address'], prefixlen)
def process_router(self, ri): def process_router(self, ri):
ex_gw_port = self._get_ex_gw_port(ri) ex_gw_port = self._get_ex_gw_port(ri)
internal_ports = ri.router.get(l3_constants.INTERFACE_KEY, []) internal_ports = ri.router.get(l3_constants.INTERFACE_KEY, [])
existing_port_ids = set([p['id'] for p in ri.internal_ports]) existing_port_ids = set([p['id'] for p in ri.internal_ports])
@ -306,31 +335,53 @@ class L3NATAgent(manager.Manager):
for p in new_ports: for p in new_ports:
self._set_subnet_info(p) self._set_subnet_info(p)
ri.internal_ports.append(p) ri.internal_ports.append(p)
self.internal_network_added(ri, ex_gw_port, self.internal_network_added(ri, p['network_id'], p['id'],
p['network_id'], p['id'],
p['ip_cidr'], p['mac_address']) p['ip_cidr'], p['mac_address'])
for p in old_ports: for p in old_ports:
ri.internal_ports.remove(p) ri.internal_ports.remove(p)
self.internal_network_removed(ri, ex_gw_port, p['id'], self.internal_network_removed(ri, p['id'], p['ip_cidr'])
p['ip_cidr'])
internal_cidrs = [p['ip_cidr'] for p in ri.internal_ports] 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: if ex_gw_port and not ri.ex_gw_port:
self._set_subnet_info(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: elif not ex_gw_port and ri.ex_gw_port:
self.external_gateway_removed(ri, ri.ex_gw_port, self.external_gateway_removed(ri, ri.ex_gw_port,
internal_cidrs) interface_name, internal_cidrs)
if ri.ex_gw_port or ex_gw_port: # Process SNAT rules for external gateway
if ex_gw_port_id:
ri.perform_snat_action(self._handle_router_snat_rules,
internal_cidrs, interface_name)
# Process DNAT rules for floating IPs
if ex_gw_port or ri.ex_gw_port:
self.process_router_floating_ips(ri, ex_gw_port) self.process_router_floating_ips(ri, ex_gw_port)
ri.ex_gw_port = ex_gw_port ri.ex_gw_port = ex_gw_port
ri.enable_snat = ri.router.get('enable_snat')
self.routes_updated(ri) 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): def process_router_floating_ips(self, ri, ex_gw_port):
floating_ips = ri.router.get(l3_constants.FLOATINGIP_KEY, []) floating_ips = ri.router.get(l3_constants.FLOATINGIP_KEY, [])
existing_floating_ip_ids = set([fip['id'] for fip in ri.floating_ips]) existing_floating_ip_ids = set([fip['id'] for fip in ri.floating_ips])
@ -398,10 +449,9 @@ class L3NATAgent(manager.Manager):
def get_external_device_name(self, port_id): def get_external_device_name(self, port_id):
return (EXTERNAL_DEV_PREFIX + port_id)[:self.driver.DEV_NAME_LEN] 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, if not ip_lib.device_exists(interface_name,
root_helper=self.root_helper, root_helper=self.root_helper,
namespace=ri.ns_name()): namespace=ri.ns_name()):
@ -427,15 +477,9 @@ class L3NATAgent(manager.Manager):
utils.execute(cmd, check_exit_code=False, utils.execute(cmd, check_exit_code=False,
root_helper=self.root_helper) root_helper=self.root_helper)
for (c, r) in self.external_gateway_nat_rules(ex_gw_ip, def external_gateway_removed(self, ri, ex_gw_port,
internal_cidrs, interface_name, 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, internal_cidrs):
interface_name = self.get_external_device_name(ex_gw_port['id'])
if ip_lib.device_exists(interface_name, if ip_lib.device_exists(interface_name,
root_helper=self.root_helper, root_helper=self.root_helper,
namespace=ri.ns_name()): namespace=ri.ns_name()):
@ -444,12 +488,6 @@ class L3NATAgent(manager.Manager):
namespace=ri.ns_name(), namespace=ri.ns_name(),
prefix=EXTERNAL_DEV_PREFIX) 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): def metadata_filter_rules(self):
rules = [] rules = []
rules.append(('INPUT', '-s 0.0.0.0/0 -d 127.0.0.1 ' rules.append(('INPUT', '-s 0.0.0.0/0 -d 127.0.0.1 '
@ -474,7 +512,7 @@ class L3NATAgent(manager.Manager):
rules.extend(self.internal_network_nat_rules(ex_gw_ip, cidr)) rules.extend(self.internal_network_nat_rules(ex_gw_ip, cidr))
return rules 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): internal_cidr, mac_address):
interface_name = self.get_internal_device_name(port_id) interface_name = self.get_internal_device_name(port_id)
if not ip_lib.device_exists(interface_name, if not ip_lib.device_exists(interface_name,
@ -489,14 +527,7 @@ class L3NATAgent(manager.Manager):
ip_address = internal_cidr.split('/')[0] ip_address = internal_cidr.split('/')[0]
self._send_gratuitous_arp_packet(ri, interface_name, ip_address) self._send_gratuitous_arp_packet(ri, interface_name, ip_address)
if ex_gw_port: def internal_network_removed(self, ri, port_id, internal_cidr):
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):
interface_name = self.get_internal_device_name(port_id) interface_name = self.get_internal_device_name(port_id)
if ip_lib.device_exists(interface_name, if ip_lib.device_exists(interface_name,
root_helper=self.root_helper, root_helper=self.root_helper,
@ -504,13 +535,6 @@ class L3NATAgent(manager.Manager):
self.driver.unplug(interface_name, namespace=ri.ns_name(), self.driver.unplug(interface_name, namespace=ri.ns_name(),
prefix=INTERNAL_DEV_PREFIX) 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): def internal_network_nat_rules(self, ex_gw_ip, internal_cidr):
rules = [('snat', '-s %s -j SNAT --to-source %s' % rules = [('snat', '-s %s -j SNAT --to-source %s' %
(internal_cidr, ex_gw_ip))] (internal_cidr, ex_gw_ip))]
@ -610,11 +634,9 @@ class L3NATAgent(manager.Manager):
if (not self.conf.use_namespaces and if (not self.conf.use_namespaces and
r['id'] != self.conf.router_id): r['id'] != self.conf.router_id):
continue continue
ex_net_id = (r['external_gateway_info'] or {}).get('network_id') ex_net_id = (r['external_gateway_info'] or {}).get('network_id')
if not ex_net_id and not self.conf.handle_internal_only_routers: if not ex_net_id and not self.conf.handle_internal_only_routers:
continue continue
if ex_net_id and ex_net_id != target_ex_net_id: if ex_net_id and ex_net_id != target_ex_net_id:
continue continue
cur_router_ids.add(r['id']) cur_router_ids.add(r['id'])

View File

@ -84,6 +84,15 @@ def _validate_string(data, max_len=None):
return msg 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): def _validate_range(data, valid_values=None):
min_value = valid_values[0] min_value = valid_values[0]
max_value = valid_values[1] max_value = valid_values[1]
@ -294,7 +303,6 @@ def _validate_dict(data, key_specs=None):
msg = _("'%s' is not a dictionary") % data msg = _("'%s' is not a dictionary") % data
LOG.debug(msg) LOG.debug(msg)
return msg return msg
# Do not perform any further validation, if no constraints are supplied # Do not perform any further validation, if no constraints are supplied
if not key_specs: if not key_specs:
return return
@ -340,6 +348,11 @@ def _validate_dict_or_empty(data, key_specs=None):
return _validate_dict(data, key_specs) 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): def _validate_non_negative(data, valid_values=None):
try: try:
data = int(data) data = int(data)
@ -443,6 +456,7 @@ MAC_PATTERN = "^%s[aceACE02468](:%s{2}){5}$" % (HEX_ELEM, HEX_ELEM)
validators = {'type:dict': _validate_dict, validators = {'type:dict': _validate_dict,
'type:dict_or_none': _validate_dict_or_none, 'type:dict_or_none': _validate_dict_or_none,
'type:dict_or_empty': _validate_dict_or_empty, 'type:dict_or_empty': _validate_dict_or_empty,
'type:dict_or_nodata': _validate_dict_or_nodata,
'type:fixed_ips': _validate_fixed_ips, 'type:fixed_ips': _validate_fixed_ips,
'type:hostroutes': _validate_hostroutes, 'type:hostroutes': _validate_hostroutes,
'type:ip_address': _validate_ip_address, 'type:ip_address': _validate_ip_address,
@ -458,7 +472,8 @@ validators = {'type:dict': _validate_dict,
'type:uuid': _validate_uuid, 'type:uuid': _validate_uuid,
'type:uuid_or_none': _validate_uuid_or_none, 'type:uuid_or_none': _validate_uuid_or_none,
'type:uuid_list': _validate_uuid_list, 'type:uuid_list': _validate_uuid_list,
'type:values': _validate_values} 'type:values': _validate_values,
'type:boolean': _validate_boolean}
# Define constants for base resource name # Define constants for base resource name
NETWORK = 'network' NETWORK = 'network'

View File

@ -41,6 +41,7 @@ LOG = logging.getLogger(__name__)
DEVICE_OWNER_ROUTER_INTF = l3_constants.DEVICE_OWNER_ROUTER_INTF DEVICE_OWNER_ROUTER_INTF = l3_constants.DEVICE_OWNER_ROUTER_INTF
DEVICE_OWNER_ROUTER_GW = l3_constants.DEVICE_OWNER_ROUTER_GW DEVICE_OWNER_ROUTER_GW = l3_constants.DEVICE_OWNER_ROUTER_GW
DEVICE_OWNER_FLOATINGIP = l3_constants.DEVICE_OWNER_FLOATINGIP DEVICE_OWNER_FLOATINGIP = l3_constants.DEVICE_OWNER_FLOATINGIP
EXTERNAL_GW_INFO = l3.EXTERNAL_GW_INFO
# Maps API field to DB column # Maps API field to DB column
# API parameter name and Database column names may differ. # API parameter name and Database column names may differ.
@ -130,11 +131,11 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
'tenant_id': router['tenant_id'], 'tenant_id': router['tenant_id'],
'admin_state_up': router['admin_state_up'], 'admin_state_up': router['admin_state_up'],
'status': router['status'], 'status': router['status'],
'external_gateway_info': None, EXTERNAL_GW_INFO: None,
'gw_port_id': router['gw_port_id']} 'gw_port_id': router['gw_port_id']}
if router['gw_port_id']: if router['gw_port_id']:
nw_id = router.gw_port['network_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: if process_extensions:
for func in self._dict_extend_functions.get(l3.ROUTERS, []): for func in self._dict_extend_functions.get(l3.ROUTERS, []):
func(self, res, router) func(self, res, router)
@ -143,10 +144,10 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
def create_router(self, context, router): def create_router(self, context, router):
r = router['router'] r = router['router']
has_gw_info = False has_gw_info = False
if 'external_gateway_info' in r: if EXTERNAL_GW_INFO in r:
has_gw_info = True has_gw_info = True
gw_info = r['external_gateway_info'] gw_info = r[EXTERNAL_GW_INFO]
del r['external_gateway_info'] del r[EXTERNAL_GW_INFO]
tenant_id = self._get_tenant_id_for_create(context, r) tenant_id = self._get_tenant_id_for_create(context, r)
with context.session.begin(subtransactions=True): with context.session.begin(subtransactions=True):
# pre-generate id so it will be available when # pre-generate id so it will be available when
@ -164,10 +165,10 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
def update_router(self, context, id, router): def update_router(self, context, id, router):
r = router['router'] r = router['router']
has_gw_info = False has_gw_info = False
if 'external_gateway_info' in r: if EXTERNAL_GW_INFO in r:
has_gw_info = True has_gw_info = True
gw_info = r['external_gateway_info'] gw_info = r[EXTERNAL_GW_INFO]
del r['external_gateway_info'] del r[EXTERNAL_GW_INFO]
with context.session.begin(subtransactions=True): with context.session.begin(subtransactions=True):
if has_gw_info: if has_gw_info:
self._update_router_gw_info(context, id, gw_info) self._update_router_gw_info(context, id, gw_info)
@ -180,14 +181,38 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
l3_rpc_agent_api.L3AgentNotify.routers_updated(context, routers) l3_rpc_agent_api.L3AgentNotify.routers_updated(context, routers)
return self._make_router_dict(router_db) 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 # TODO(salvatore-orlando): guarantee atomic behavior also across
# operations that span beyond the model classes handled by this # operations that span beyond the model classes handled by this
# class (e.g.: delete_port) # 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 gw_port = router.gw_port
# network_id attribute is required by API, so it must be present
network_id = info.get('network_id', None) if info else None network_id = info['network_id'] if info else None
if network_id: if network_id:
self._get_network(context, network_id) self._get_network(context, network_id)
if not self._network_is_external(context, network_id): if not self._network_is_external(context, network_id):
@ -202,11 +227,12 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
if fip_count: if fip_count:
raise l3.RouterExternalGatewayInUseByFloatingIp( raise l3.RouterExternalGatewayInUseByFloatingIp(
router_id=router_id, net_id=gw_port['network_id']) router_id=router_id, net_id=gw_port['network_id'])
with context.session.begin(subtransactions=True): if gw_port and gw_port['network_id'] != network_id:
router.gw_port = None with context.session.begin(subtransactions=True):
context.session.add(router) router.gw_port = None
self.delete_port(context.elevated(), gw_port['id'], context.session.add(router)
l3_port_check=False) self.delete_port(context.elevated(), gw_port['id'],
l3_port_check=False)
if network_id is not None and (gw_port is None or if network_id is not None and (gw_port is None or
gw_port['network_id'] != network_id): gw_port['network_id'] != network_id):
@ -216,30 +242,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
self._check_for_dup_router_subnet(context, router_id, self._check_for_dup_router_subnet(context, router_id,
network_id, subnet['id'], network_id, subnet['id'],
subnet['cidr']) subnet['cidr'])
self._create_router_gw_port(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 delete_router(self, context, id): def delete_router(self, context, id):
with context.session.begin(subtransactions=True): with context.session.begin(subtransactions=True):
@ -512,14 +515,11 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
external_network_id=external_network_id, external_network_id=external_network_id,
port_id=internal_port['id']) port_id=internal_port['id'])
def get_assoc_data(self, context, fip, floating_network_id): def _internal_fip_assoc_data(self, context, fip):
"""Determine/extract data associated with the internal port. """Retrieve internal port data for floating IP.
When a floating IP is associated with an internal port, Retrieve information concerning the internal port where
we need to extract/determine some data associated with the the floating IP should be associated to.
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 = self._get_port(context, fip['port_id']) internal_port = self._get_port(context, fip['port_id'])
if not internal_port['tenant_id'] == fip['tenant_id']: if not internal_port['tenant_id'] == fip['tenant_id']:
@ -561,7 +561,19 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
raise q_exc.BadRequest(resource='floatingip', msg=msg) raise q_exc.BadRequest(resource='floatingip', msg=msg)
internal_ip_address = internal_port['fixed_ips'][0]['ip_address'] internal_ip_address = internal_port['fixed_ips'][0]['ip_address']
internal_subnet_id = internal_port['fixed_ips'][0]['subnet_id'] 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, router_id = self._get_router_for_floatingip(context,
internal_port, internal_port,
internal_subnet_id, internal_subnet_id,
@ -838,6 +850,15 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
else: else:
return [n for n in nets if n['id'] not in ext_nets] 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): def _get_sync_routers(self, context, router_ids=None, active=None):
"""Query routers and their gw ports for l3 agent. """Query routers and their gw ports for l3 agent.
@ -865,14 +886,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
gw_ports = [] gw_ports = []
if gw_port_ids: if gw_port_ids:
gw_ports = self.get_sync_gw_ports(context, gw_port_ids) gw_ports = self.get_sync_gw_ports(context, gw_port_ids)
gw_port_id_gw_port_dict = {} return self._build_routers_list(router_dicts, gw_ports)
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
def _get_sync_floating_ips(self, context, router_ids): def _get_sync_floating_ips(self, context, router_ids):
"""Query floating_ips that relate to list of router_ids.""" """Query floating_ips that relate to list of router_ids."""

View File

@ -0,0 +1,68 @@
# 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

View File

@ -0,0 +1,62 @@
# 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')

View File

@ -88,8 +88,8 @@ class RouterExternalGatewayInUseByFloatingIp(qexception.InUse):
"more floating IPs.") "more floating IPs.")
ROUTERS = 'routers' ROUTERS = 'routers'
EXTERNAL_GW_INFO = 'external_gateway_info'
# Attribute Map
RESOURCE_ATTRIBUTE_MAP = { RESOURCE_ATTRIBUTE_MAP = {
ROUTERS: { ROUTERS: {
'id': {'allow_post': False, 'allow_put': False, 'id': {'allow_post': False, 'allow_put': False,
@ -109,8 +109,8 @@ RESOURCE_ATTRIBUTE_MAP = {
'required_by_policy': True, 'required_by_policy': True,
'validate': {'type:string': None}, 'validate': {'type:string': None},
'is_visible': True}, 'is_visible': True},
'external_gateway_info': {'allow_post': True, 'allow_put': True, EXTERNAL_GW_INFO: {'allow_post': True, 'allow_put': True,
'is_visible': True, 'default': None} 'is_visible': True, 'default': None}
}, },
'floatingips': { 'floatingips': {
'id': {'allow_post': False, 'allow_put': False, 'id': {'allow_post': False, 'allow_put': False,

View File

@ -0,0 +1,73 @@
# 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 {}

View File

@ -22,7 +22,7 @@ from quantum.api.v2 import attributes
from quantum.common import exceptions as q_exc from quantum.common import exceptions as q_exc
from quantum.common import topics from quantum.common import topics
from quantum.db import db_base_plugin_v2 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.db import quota_db # noqa
from quantum.extensions import portbindings from quantum.extensions import portbindings
from quantum.extensions import providernet as provider from quantum.extensions import providernet as provider
@ -141,13 +141,14 @@ class VlanNetworkProvider(BaseNetworkProvider):
class HyperVQuantumPlugin(db_base_plugin_v2.QuantumDbPluginV2, 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 # This attribute specifies whether the plugin supports or not
# bulk operations. Name mangling is used in order to ensure it # bulk operations. Name mangling is used in order to ensure it
# is qualified by class # is qualified by class
__native_bulk_support = True __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): def __init__(self, configfile=None):
self._db = hyperv_db.HyperVPluginDB() self._db = hyperv_db.HyperVPluginDB()

View File

@ -32,6 +32,7 @@ from quantum.db import api as db_api
from quantum.db import db_base_plugin_v2 from quantum.db import db_base_plugin_v2
from quantum.db import dhcp_rpc_base from quantum.db import dhcp_rpc_base
from quantum.db import extraroute_db from quantum.db import extraroute_db
from quantum.db import l3_gwmode_db
from quantum.db import l3_rpc_base from quantum.db import l3_rpc_base
from quantum.db import portbindings_db from quantum.db import portbindings_db
from quantum.db import quota_db # noqa from quantum.db import quota_db # noqa
@ -185,6 +186,7 @@ class AgentNotifierApi(proxy.RpcProxy,
class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2, class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2,
extraroute_db.ExtraRoute_db_mixin, extraroute_db.ExtraRoute_db_mixin,
l3_gwmode_db.L3_NAT_db_mixin,
sg_db_rpc.SecurityGroupServerRpcMixin, sg_db_rpc.SecurityGroupServerRpcMixin,
agentschedulers_db.AgentSchedulerDbMixin, agentschedulers_db.AgentSchedulerDbMixin,
portbindings_db.PortBindingMixin): portbindings_db.PortBindingMixin):
@ -211,9 +213,9 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2,
__native_pagination_support = True __native_pagination_support = True
__native_sorting_support = True __native_sorting_support = True
_supported_extension_aliases = ["provider", "router", "binding", "quotas", _supported_extension_aliases = ["provider", "router", "ext-gw-mode",
"security-group", "agent", "extraroute", "binding", "quotas", "security-group",
"agent_scheduler"] "agent", "extraroute", "agent_scheduler"]
@property @property
def supported_extension_aliases(self): def supported_extension_aliases(self):

View File

@ -51,7 +51,8 @@ class MetaPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
LOG.debug(_("Start initializing metaplugin")) LOG.debug(_("Start initializing metaplugin"))
self.supported_extension_aliases = \ self.supported_extension_aliases = \
cfg.CONF.META.supported_extension_aliases.split(',') 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 # Ignore config option overapping
def _is_opt_registered(opts, opt): def _is_opt_registered(opts, opt):

View File

@ -25,6 +25,7 @@ from quantum.db import agents_db
from quantum.db import agentschedulers_db from quantum.db import agentschedulers_db
from quantum.db import dhcp_rpc_base from quantum.db import dhcp_rpc_base
from quantum.db import extraroute_db from quantum.db import extraroute_db
from quantum.db import l3_gwmode_db
from quantum.db import l3_rpc_base from quantum.db import l3_rpc_base
from quantum.db import quota_db # noqa from quantum.db import quota_db # noqa
from quantum.db import securitygroups_rpc_base as sg_db_rpc from quantum.db import securitygroups_rpc_base as sg_db_rpc
@ -59,6 +60,7 @@ class OperationalStatus:
class NECPluginV2(nec_plugin_base.NECPluginV2Base, class NECPluginV2(nec_plugin_base.NECPluginV2Base,
extraroute_db.ExtraRoute_db_mixin, extraroute_db.ExtraRoute_db_mixin,
l3_gwmode_db.L3_NAT_db_mixin,
sg_db_rpc.SecurityGroupServerRpcMixin, sg_db_rpc.SecurityGroupServerRpcMixin,
agentschedulers_db.AgentSchedulerDbMixin): agentschedulers_db.AgentSchedulerDbMixin):
"""NECPluginV2 controls an OpenFlow Controller. """NECPluginV2 controls an OpenFlow Controller.
@ -73,10 +75,9 @@ class NECPluginV2(nec_plugin_base.NECPluginV2Base,
The port binding extension enables an external application relay The port binding extension enables an external application relay
information to and from the plugin. information to and from the plugin.
""" """
_supported_extension_aliases = ["router", "quotas", "binding", _supported_extension_aliases = ["router", "ext-gw-mode", "quotas",
"security-group", "extraroute", "binding", "security-group",
"agent", "agent_scheduler", "extraroute", "agent", "agent_scheduler"]
]
@property @property
def supported_extension_aliases(self): def supported_extension_aliases(self):

View File

@ -38,6 +38,7 @@ from quantum.db import agentschedulers_db
from quantum.db import db_base_plugin_v2 from quantum.db import db_base_plugin_v2
from quantum.db import dhcp_rpc_base from quantum.db import dhcp_rpc_base
from quantum.db import extraroute_db from quantum.db import extraroute_db
from quantum.db import l3_gwmode_db
from quantum.db import l3_rpc_base from quantum.db import l3_rpc_base
from quantum.db import portbindings_db from quantum.db import portbindings_db
from quantum.db import quota_db # noqa from quantum.db import quota_db # noqa
@ -214,6 +215,7 @@ class AgentNotifierApi(proxy.RpcProxy,
class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2, class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
extraroute_db.ExtraRoute_db_mixin, extraroute_db.ExtraRoute_db_mixin,
l3_gwmode_db.L3_NAT_db_mixin,
sg_db_rpc.SecurityGroupServerRpcMixin, sg_db_rpc.SecurityGroupServerRpcMixin,
agentschedulers_db.AgentSchedulerDbMixin, agentschedulers_db.AgentSchedulerDbMixin,
portbindings_db.PortBindingMixin): portbindings_db.PortBindingMixin):
@ -242,7 +244,7 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
__native_pagination_support = True __native_pagination_support = True
__native_sorting_support = True __native_sorting_support = True
_supported_extension_aliases = ["provider", "router", _supported_extension_aliases = ["provider", "router", "ext-gw-mode",
"binding", "quotas", "security-group", "binding", "quotas", "security-group",
"agent", "extraroute", "agent_scheduler"] "agent", "extraroute", "agent_scheduler"]

View File

@ -29,6 +29,7 @@ from quantum.db import api as db
from quantum.db import db_base_plugin_v2 from quantum.db import db_base_plugin_v2
from quantum.db import dhcp_rpc_base from quantum.db import dhcp_rpc_base
from quantum.db import extraroute_db from quantum.db import extraroute_db
from quantum.db import l3_gwmode_db
from quantum.db import l3_rpc_base from quantum.db import l3_rpc_base
from quantum.db import models_v2 from quantum.db import models_v2
from quantum.db import securitygroups_rpc_base as sg_db_rpc from quantum.db import securitygroups_rpc_base as sg_db_rpc
@ -86,9 +87,11 @@ class AgentNotifierApi(proxy.RpcProxy,
class RyuQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2, class RyuQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
extraroute_db.ExtraRoute_db_mixin, extraroute_db.ExtraRoute_db_mixin,
l3_gwmode_db.L3_NAT_db_mixin,
sg_db_rpc.SecurityGroupServerRpcMixin): sg_db_rpc.SecurityGroupServerRpcMixin):
_supported_extension_aliases = ["router", "extraroute", "security-group"] _supported_extension_aliases = ["router", "ext-gw-mode",
"extraroute", "security-group"]
@property @property
def supported_extension_aliases(self): def supported_extension_aliases(self):

View File

@ -15,11 +15,11 @@
# under the License. # under the License.
from quantum.db import db_base_plugin_v2 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, class Fake1(db_base_plugin_v2.QuantumDbPluginV2,
l3_db.L3_NAT_db_mixin): l3_gwmode_db.L3_NAT_db_mixin):
supported_extension_aliases = ['router'] supported_extension_aliases = ['router']
def fake_func(self): def fake_func(self):

View File

@ -45,7 +45,6 @@ from quantum.tests import base
from quantum.tests.unit import test_extensions from quantum.tests.unit import test_extensions
from quantum.tests.unit import testlib_api from quantum.tests.unit import testlib_api
DB_PLUGIN_KLASS = 'quantum.db.db_base_plugin_v2.QuantumDbPluginV2' DB_PLUGIN_KLASS = 'quantum.db.db_base_plugin_v2.QuantumDbPluginV2'
ROOTDIR = os.path.dirname(os.path.dirname(__file__)) ROOTDIR = os.path.dirname(os.path.dirname(__file__))
ETCDIR = os.path.join(ROOTDIR, 'etc') ETCDIR = os.path.join(ROOTDIR, 'etc')

View File

@ -0,0 +1,404 @@
# 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)

View File

@ -92,6 +92,24 @@ class TestBasicRouterOperations(base.BaseTestCase):
self.assertTrue(ri.ns_name().endswith(id)) 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): def testAgentCreate(self):
l3_agent.L3NATAgent(HOSTNAME, self.conf) l3_agent.L3NATAgent(HOSTNAME, self.conf)
@ -104,17 +122,16 @@ class TestBasicRouterOperations(base.BaseTestCase):
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
cidr = '99.0.1.9/24' cidr = '99.0.1.9/24'
mac = 'ca:fe:de:ad:be:ef' mac = 'ca:fe:de:ad:be:ef'
ex_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30'}]}
if action == 'add': if action == 'add':
self.device_exists.return_value = False 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) port_id, cidr, mac)
self.assertEqual(self.mock_driver.plug.call_count, 1) self.assertEqual(self.mock_driver.plug.call_count, 1)
self.assertEqual(self.mock_driver.init_l3.call_count, 1) self.assertEqual(self.mock_driver.init_l3.call_count, 1)
elif action == 'remove': elif action == 'remove':
self.device_exists.return_value = True 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) self.assertEqual(self.mock_driver.unplug.call_count, 1)
else: else:
raise Exception("Invalid action %s" % action) raise Exception("Invalid action %s" % action)
@ -142,7 +159,8 @@ class TestBasicRouterOperations(base.BaseTestCase):
if action == 'add': if action == 'add':
self.device_exists.return_value = False 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.plug.call_count, 1)
self.assertEqual(self.mock_driver.init_l3.call_count, 1) self.assertEqual(self.mock_driver.init_l3.call_count, 1)
arping_cmd = ['arping', '-A', '-U', arping_cmd = ['arping', '-A', '-U',
@ -158,7 +176,8 @@ class TestBasicRouterOperations(base.BaseTestCase):
elif action == 'remove': elif action == 'remove':
self.device_exists.return_value = True 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) self.assertEqual(self.mock_driver.unplug.call_count, 1)
else: else:
raise Exception("Invalid action %s" % action) raise Exception("Invalid action %s" % action)
@ -311,9 +330,28 @@ class TestBasicRouterOperations(base.BaseTestCase):
'via', '10.100.10.30']] 'via', '10.100.10.30']]
self._check_agent_method_called(agent, expected, namespace) self._check_agent_method_called(agent, expected, namespace)
def testProcessRouter(self): 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)
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) def _prepare_router_data(self, enable_snat=True):
router_id = _uuid() router_id = _uuid()
ex_gw_port = {'id': _uuid(), ex_gw_port = {'id': _uuid(),
'network_id': _uuid(), 'network_id': _uuid(),
@ -330,19 +368,23 @@ class TestBasicRouterOperations(base.BaseTestCase):
'subnet': {'cidr': '35.4.4.0/24', 'subnet': {'cidr': '35.4.4.0/24',
'gateway_ip': '35.4.4.1'}} 'gateway_ip': '35.4.4.1'}}
router = {
'id': router_id,
l3_constants.INTERFACE_KEY: [internal_port],
'enable_snat': enable_snat,
'routes': [],
'gw_port': ex_gw_port}
return router
def testProcessRouter(self):
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
router = self._prepare_router_data()
fake_floatingips1 = {'floatingips': [ fake_floatingips1 = {'floatingips': [
{'id': _uuid(), {'id': _uuid(),
'floating_ip_address': '8.8.8.8', 'floating_ip_address': '8.8.8.8',
'fixed_ip_address': '7.7.7.7', 'fixed_ip_address': '7.7.7.7',
'port_id': _uuid()}]} 'port_id': _uuid()}]}
ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
router = {
'id': router_id,
l3_constants.FLOATINGIP_KEY: fake_floatingips1['floatingips'],
l3_constants.INTERFACE_KEY: [internal_port],
'routes': [],
'gw_port': ex_gw_port}
ri = l3_agent.RouterInfo(router_id, self.conf.root_helper,
self.conf.use_namespaces, router=router) self.conf.use_namespaces, router=router)
agent.process_router(ri) agent.process_router(ri)
@ -362,6 +404,44 @@ class TestBasicRouterOperations(base.BaseTestCase):
del router['gw_port'] del router['gw_port']
agent.process_router(ri) 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): def testRoutersWithAdminStateDown(self):
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
self.plugin_api.get_external_network_id.return_value = None self.plugin_api.get_external_network_id.return_value = None

View File

@ -342,10 +342,14 @@ class L3NatTestCaseMixin(object):
return router_req.get_response(self.ext_api) return router_req.get_response(self.ext_api)
def _make_router(self, fmt, tenant_id, name=None, def _make_router(self, fmt, tenant_id, name=None, admin_state_up=None,
admin_state_up=None, set_context=False): 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, 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) return self.deserialize(fmt, res)
def _add_external_gateway_to_router(self, router_id, network_id, def _add_external_gateway_to_router(self, router_id, network_id,
@ -384,9 +388,11 @@ class L3NatTestCaseMixin(object):
@contextlib.contextmanager @contextlib.contextmanager
def router(self, name='router1', admin_state_up=True, 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, 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: try:
yield router yield router
finally: finally: