Merge "Configurable external gateway modes"
This commit is contained in:
commit
d1a01265e7
@ -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'])
|
||||||
|
@ -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'
|
||||||
|
@ -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."""
|
||||||
|
68
quantum/db/l3_gwmode_db.py
Normal file
68
quantum/db/l3_gwmode_db.py
Normal 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
|
@ -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')
|
@ -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,
|
||||||
|
73
quantum/extensions/l3_ext_gw_mode.py
Normal file
73
quantum/extensions/l3_ext_gw_mode.py
Normal 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 {}
|
@ -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()
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
@ -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"]
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
@ -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')
|
||||||
|
404
quantum/tests/unit/test_extension_ext_gw_mode.py
Normal file
404
quantum/tests/unit/test_extension_ext_gw_mode.py
Normal 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)
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user