Adds support for L3 routing/NAT as a service plugin
- Adds L3 routing/NAT service plugin - Removes L3 routing/NAT from ML2 plugin - Moves "router:external" attribute to new extension "External-net" - Introduces separate RPC topic for L3 callbacks from L3 agent Implements: blueprint quantum-l3-routing-plugin Change-Id: Id9af10c2910f9a1730b163203a68d101ffc3b282
This commit is contained in:
parent
39ef7594bd
commit
715b16aca7
@ -208,7 +208,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
|
||||
raise SystemExit(msg)
|
||||
|
||||
self.context = context.get_admin_context_without_session()
|
||||
self.plugin_rpc = L3PluginApi(topics.PLUGIN, host)
|
||||
self.plugin_rpc = L3PluginApi(topics.L3PLUGIN, host)
|
||||
self.fullsync = True
|
||||
self.updated_routers = set()
|
||||
self.removed_routers = set()
|
||||
|
@ -169,6 +169,18 @@ class ExtensionDescriptor(object):
|
||||
if extended_attrs:
|
||||
attrs.update(extended_attrs)
|
||||
|
||||
def get_alias_namespace_compatibility_map(self):
|
||||
"""Returns mappings between extension aliases and XML namespaces.
|
||||
|
||||
The mappings are XML namespaces that should, for backward compatibility
|
||||
reasons, be added to the XML serialization of extended attributes.
|
||||
This allows an established extended attribute to be provided by
|
||||
another extension than the original one while keeping its old alias
|
||||
in the name.
|
||||
:return: A dictionary of extension_aliases and namespace strings.
|
||||
"""
|
||||
return {}
|
||||
|
||||
|
||||
class ActionExtensionController(wsgi.Controller):
|
||||
|
||||
@ -468,6 +480,13 @@ class ExtensionManager(object):
|
||||
except AttributeError:
|
||||
LOG.exception(_("Error fetching extended attributes for "
|
||||
"extension '%s'"), ext.get_name())
|
||||
try:
|
||||
comp_map = ext.get_alias_namespace_compatibility_map()
|
||||
attributes.EXT_NSES_BC.update(comp_map)
|
||||
except AttributeError:
|
||||
LOG.info(_("Extension '%s' provides no backward "
|
||||
"compatibility map for extended attributes"),
|
||||
ext.get_name())
|
||||
processed_exts.add(ext_name)
|
||||
del exts_to_process[ext_name]
|
||||
if len(processed_exts) == processed_ext_count:
|
||||
|
@ -19,6 +19,7 @@ from neutron.common import utils
|
||||
from neutron import manager
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron.openstack.common.rpc import proxy
|
||||
from neutron.plugins.common import constants as service_constants
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -46,7 +47,8 @@ class L3AgentNotifyAPI(proxy.RpcProxy):
|
||||
operation, data):
|
||||
"""Notify changed routers to hosting l3 agents."""
|
||||
adminContext = context.is_admin and context or context.elevated()
|
||||
plugin = manager.NeutronManager.get_plugin()
|
||||
plugin = manager.NeutronManager.get_service_plugins().get(
|
||||
service_constants.L3_ROUTER_NAT)
|
||||
for router_id in router_ids:
|
||||
l3_agents = plugin.get_l3_agents_hosting_routers(
|
||||
adminContext, [router_id],
|
||||
@ -66,9 +68,14 @@ class L3AgentNotifyAPI(proxy.RpcProxy):
|
||||
|
||||
def _notification(self, context, method, router_ids, operation, data):
|
||||
"""Notify all the agents that are hosting the routers."""
|
||||
plugin = manager.NeutronManager.get_plugin()
|
||||
plugin = manager.NeutronManager.get_service_plugins().get(
|
||||
service_constants.L3_ROUTER_NAT)
|
||||
if not plugin:
|
||||
LOG.error(_('No plugin for L3 routing registered. Cannot notify '
|
||||
'agents with the message %s'), method)
|
||||
return
|
||||
if utils.is_extension_supported(
|
||||
plugin, constants.L3_AGENT_SCHEDULER_EXT_ALIAS):
|
||||
plugin, constants.L3_AGENT_SCHEDULER_EXT_ALIAS):
|
||||
adminContext = (context.is_admin and
|
||||
context or context.elevated())
|
||||
plugin.schedule_routers(adminContext, router_ids)
|
||||
|
@ -704,8 +704,14 @@ PLURALS = {NETWORKS: NETWORK,
|
||||
'extensions': 'extension'}
|
||||
EXT_NSES = {}
|
||||
|
||||
# Namespaces to be added for backward compatibility
|
||||
# when existing extended resource attributes are
|
||||
# provided by other extension than original one.
|
||||
EXT_NSES_BC = {}
|
||||
|
||||
|
||||
def get_attr_metadata():
|
||||
return {'plurals': PLURALS,
|
||||
'xmlns': constants.XML_NS_V20,
|
||||
constants.EXT_NS: EXT_NSES}
|
||||
constants.EXT_NS: EXT_NSES,
|
||||
constants.EXT_NS_COMP: EXT_NSES_BC}
|
||||
|
@ -45,6 +45,7 @@ DHCP_RESPONSE_PORT = 68
|
||||
MIN_VLAN_TAG = 1
|
||||
MAX_VLAN_TAG = 4094
|
||||
|
||||
EXT_NS_COMP = '_backward_comp_e_ns'
|
||||
EXT_NS = '_extension_ns'
|
||||
XML_NS_V20 = 'http://openstack.org/quantum/api/v2.0'
|
||||
XSI_NAMESPACE = "http://www.w3.org/2001/XMLSchema-instance"
|
||||
|
@ -24,6 +24,7 @@ UPDATE = 'update'
|
||||
|
||||
AGENT = 'q-agent-notifier'
|
||||
PLUGIN = 'q-plugin'
|
||||
L3PLUGIN = 'q-l3-plugin'
|
||||
DHCP = 'q-dhcp-notifer'
|
||||
FIREWALL_PLUGIN = 'q-firewall-plugin'
|
||||
METERING_PLUGIN = 'q-metering-plugin'
|
||||
|
@ -24,9 +24,7 @@ from sqlalchemy.orm import joinedload
|
||||
from neutron.common import constants
|
||||
from neutron.db import agents_db
|
||||
from neutron.db import model_base
|
||||
from neutron.db import models_v2
|
||||
from neutron.extensions import dhcpagentscheduler
|
||||
from neutron.extensions import l3agentscheduler
|
||||
from neutron.openstack.common import log as logging
|
||||
|
||||
|
||||
@ -37,14 +35,8 @@ AGENTS_SCHEDULER_OPTS = [
|
||||
default='neutron.scheduler.'
|
||||
'dhcp_agent_scheduler.ChanceScheduler',
|
||||
help=_('Driver to use for scheduling network to DHCP agent')),
|
||||
cfg.StrOpt('router_scheduler_driver',
|
||||
default='neutron.scheduler.l3_agent_scheduler.ChanceScheduler',
|
||||
help=_('Driver to use for scheduling '
|
||||
'router to a default L3 agent')),
|
||||
cfg.BoolOpt('network_auto_schedule', default=True,
|
||||
help=_('Allow auto scheduling networks to DHCP agent.')),
|
||||
cfg.BoolOpt('router_auto_schedule', default=True,
|
||||
help=_('Allow auto scheduling routers to L3 agent.')),
|
||||
cfg.IntOpt('dhcp_agents_per_network', default=1,
|
||||
help=_('Number of DHCP agents scheduled to host a network.')),
|
||||
]
|
||||
@ -65,17 +57,6 @@ class NetworkDhcpAgentBinding(model_base.BASEV2):
|
||||
primary_key=True)
|
||||
|
||||
|
||||
class RouterL3AgentBinding(model_base.BASEV2, models_v2.HasId):
|
||||
"""Represents binding between neutron routers and L3 agents."""
|
||||
|
||||
router_id = sa.Column(sa.String(36),
|
||||
sa.ForeignKey("routers.id", ondelete='CASCADE'))
|
||||
l3_agent = orm.relation(agents_db.Agent)
|
||||
l3_agent_id = sa.Column(sa.String(36),
|
||||
sa.ForeignKey("agents.id",
|
||||
ondelete='CASCADE'))
|
||||
|
||||
|
||||
class AgentSchedulerDbMixin(agents_db.AgentDbMixin):
|
||||
"""Common class for agent scheduler mixins."""
|
||||
|
||||
@ -115,203 +96,6 @@ class AgentSchedulerDbMixin(agents_db.AgentDbMixin):
|
||||
return result
|
||||
|
||||
|
||||
class L3AgentSchedulerDbMixin(l3agentscheduler.L3AgentSchedulerPluginBase,
|
||||
AgentSchedulerDbMixin):
|
||||
"""Mixin class to add l3 agent scheduler extension to db_plugin_base_v2."""
|
||||
|
||||
router_scheduler = None
|
||||
|
||||
def add_router_to_l3_agent(self, context, id, router_id):
|
||||
"""Add a l3 agent to host a router."""
|
||||
router = self.get_router(context, router_id)
|
||||
with context.session.begin(subtransactions=True):
|
||||
agent_db = self._get_agent(context, id)
|
||||
if (agent_db['agent_type'] != constants.AGENT_TYPE_L3 or
|
||||
not agent_db['admin_state_up'] or
|
||||
not self.get_l3_agent_candidates(router, [agent_db])):
|
||||
raise l3agentscheduler.InvalidL3Agent(id=id)
|
||||
query = context.session.query(RouterL3AgentBinding)
|
||||
try:
|
||||
binding = query.filter_by(router_id=router_id).one()
|
||||
|
||||
raise l3agentscheduler.RouterHostedByL3Agent(
|
||||
router_id=router_id,
|
||||
agent_id=binding.l3_agent_id)
|
||||
except exc.NoResultFound:
|
||||
pass
|
||||
|
||||
result = self.auto_schedule_routers(context,
|
||||
agent_db.host,
|
||||
[router_id])
|
||||
if not result:
|
||||
raise l3agentscheduler.RouterSchedulingFailed(
|
||||
router_id=router_id, agent_id=id)
|
||||
|
||||
l3_notifier = self.agent_notifiers.get(constants.AGENT_TYPE_L3)
|
||||
if l3_notifier:
|
||||
l3_notifier.router_added_to_agent(
|
||||
context, [router_id], agent_db.host)
|
||||
|
||||
def remove_router_from_l3_agent(self, context, id, router_id):
|
||||
"""Remove the router from l3 agent.
|
||||
|
||||
After it, the router will be non-hosted until there is update which
|
||||
lead to re schedule or be added to another agent manually.
|
||||
"""
|
||||
agent = self._get_agent(context, id)
|
||||
with context.session.begin(subtransactions=True):
|
||||
query = context.session.query(RouterL3AgentBinding)
|
||||
query = query.filter(
|
||||
RouterL3AgentBinding.router_id == router_id,
|
||||
RouterL3AgentBinding.l3_agent_id == id)
|
||||
try:
|
||||
binding = query.one()
|
||||
except exc.NoResultFound:
|
||||
raise l3agentscheduler.RouterNotHostedByL3Agent(
|
||||
router_id=router_id, agent_id=id)
|
||||
context.session.delete(binding)
|
||||
l3_notifier = self.agent_notifiers.get(constants.AGENT_TYPE_L3)
|
||||
if l3_notifier:
|
||||
l3_notifier.router_removed_from_agent(
|
||||
context, router_id, agent.host)
|
||||
|
||||
def list_routers_on_l3_agent(self, context, id):
|
||||
query = context.session.query(RouterL3AgentBinding.router_id)
|
||||
query = query.filter(RouterL3AgentBinding.l3_agent_id == id)
|
||||
|
||||
router_ids = [item[0] for item in query]
|
||||
if router_ids:
|
||||
return {'routers':
|
||||
self.get_routers(context, filters={'id': router_ids})}
|
||||
else:
|
||||
return {'routers': []}
|
||||
|
||||
def list_active_sync_routers_on_active_l3_agent(
|
||||
self, context, host, router_ids):
|
||||
agent = self._get_agent_by_type_and_host(
|
||||
context, constants.AGENT_TYPE_L3, host)
|
||||
if not agent.admin_state_up:
|
||||
return []
|
||||
query = context.session.query(RouterL3AgentBinding.router_id)
|
||||
query = query.filter(
|
||||
RouterL3AgentBinding.l3_agent_id == agent.id)
|
||||
|
||||
if not router_ids:
|
||||
pass
|
||||
else:
|
||||
query = query.filter(
|
||||
RouterL3AgentBinding.router_id.in_(router_ids))
|
||||
router_ids = [item[0] for item in query]
|
||||
if router_ids:
|
||||
return self.get_sync_data(context, router_ids=router_ids,
|
||||
active=True)
|
||||
else:
|
||||
return []
|
||||
|
||||
def get_l3_agents_hosting_routers(self, context, router_ids,
|
||||
admin_state_up=None,
|
||||
active=None):
|
||||
if not router_ids:
|
||||
return []
|
||||
query = context.session.query(RouterL3AgentBinding)
|
||||
if len(router_ids) > 1:
|
||||
query = query.options(joinedload('l3_agent')).filter(
|
||||
RouterL3AgentBinding.router_id.in_(router_ids))
|
||||
else:
|
||||
query = query.options(joinedload('l3_agent')).filter(
|
||||
RouterL3AgentBinding.router_id == router_ids[0])
|
||||
if admin_state_up is not None:
|
||||
query = (query.filter(agents_db.Agent.admin_state_up ==
|
||||
admin_state_up))
|
||||
l3_agents = [binding.l3_agent for binding in query]
|
||||
if active is not None:
|
||||
l3_agents = [l3_agent for l3_agent in
|
||||
l3_agents if not
|
||||
agents_db.AgentDbMixin.is_agent_down(
|
||||
l3_agent['heartbeat_timestamp'])]
|
||||
return l3_agents
|
||||
|
||||
def _get_l3_bindings_hosting_routers(self, context, router_ids):
|
||||
if not router_ids:
|
||||
return []
|
||||
query = context.session.query(RouterL3AgentBinding)
|
||||
if len(router_ids) > 1:
|
||||
query = query.options(joinedload('l3_agent')).filter(
|
||||
RouterL3AgentBinding.router_id.in_(router_ids))
|
||||
else:
|
||||
query = query.options(joinedload('l3_agent')).filter(
|
||||
RouterL3AgentBinding.router_id == router_ids[0])
|
||||
return query.all()
|
||||
|
||||
def list_l3_agents_hosting_router(self, context, router_id):
|
||||
with context.session.begin(subtransactions=True):
|
||||
bindings = self._get_l3_bindings_hosting_routers(
|
||||
context, [router_id])
|
||||
results = []
|
||||
for binding in bindings:
|
||||
l3_agent_dict = self._make_agent_dict(binding.l3_agent)
|
||||
results.append(l3_agent_dict)
|
||||
if results:
|
||||
return {'agents': results}
|
||||
else:
|
||||
return {'agents': []}
|
||||
|
||||
def get_l3_agents(self, context, active=None, filters=None):
|
||||
query = context.session.query(agents_db.Agent)
|
||||
query = query.filter(
|
||||
agents_db.Agent.agent_type == constants.AGENT_TYPE_L3)
|
||||
if active is not None:
|
||||
query = (query.filter(agents_db.Agent.admin_state_up == active))
|
||||
if filters:
|
||||
for key, value in filters.iteritems():
|
||||
column = getattr(agents_db.Agent, key, None)
|
||||
if column:
|
||||
query = query.filter(column.in_(value))
|
||||
|
||||
return [l3_agent
|
||||
for l3_agent in query
|
||||
if AgentSchedulerDbMixin.is_eligible_agent(active, l3_agent)]
|
||||
|
||||
def get_l3_agent_candidates(self, sync_router, l3_agents):
|
||||
"""Get the valid l3 agents for the router from a list of l3_agents."""
|
||||
candidates = []
|
||||
for l3_agent in l3_agents:
|
||||
if not l3_agent.admin_state_up:
|
||||
continue
|
||||
agent_conf = self.get_configuration_dict(l3_agent)
|
||||
router_id = agent_conf.get('router_id', None)
|
||||
use_namespaces = agent_conf.get('use_namespaces', True)
|
||||
handle_internal_only_routers = agent_conf.get(
|
||||
'handle_internal_only_routers', True)
|
||||
gateway_external_network_id = agent_conf.get(
|
||||
'gateway_external_network_id', None)
|
||||
if not use_namespaces and router_id != sync_router['id']:
|
||||
continue
|
||||
ex_net_id = (sync_router['external_gateway_info'] or {}).get(
|
||||
'network_id')
|
||||
if ((not ex_net_id and not handle_internal_only_routers) or
|
||||
(ex_net_id and gateway_external_network_id and
|
||||
ex_net_id != gateway_external_network_id)):
|
||||
continue
|
||||
candidates.append(l3_agent)
|
||||
return candidates
|
||||
|
||||
def auto_schedule_routers(self, context, host, router_ids):
|
||||
if self.router_scheduler:
|
||||
return self.router_scheduler.auto_schedule_routers(
|
||||
self, context, host, router_ids)
|
||||
|
||||
def schedule_router(self, context, router):
|
||||
if self.router_scheduler:
|
||||
return self.router_scheduler.schedule(
|
||||
self, context, router)
|
||||
|
||||
def schedule_routers(self, context, routers):
|
||||
"""Schedule the routers to l3 agents."""
|
||||
for router in routers:
|
||||
self.schedule_router(context, router)
|
||||
|
||||
|
||||
class DhcpAgentSchedulerDbMixin(dhcpagentscheduler
|
||||
.DhcpAgentSchedulerPluginBase,
|
||||
AgentSchedulerDbMixin):
|
||||
|
@ -59,6 +59,11 @@ class CommonDbMixin(object):
|
||||
# from this class should be invoked
|
||||
_model_query_hooks = {}
|
||||
|
||||
# This dictionary will store methods for extending attributes of
|
||||
# api resources. Mixins can use this dict for adding their own methods
|
||||
# TODO(salvatore-orlando): Avoid using class-level variables
|
||||
_dict_extend_functions = {}
|
||||
|
||||
@classmethod
|
||||
def register_model_query_hook(cls, model, name, query_hook, filter_hook,
|
||||
result_filters=None):
|
||||
@ -218,11 +223,6 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
|
||||
__native_pagination_support = True
|
||||
__native_sorting_support = True
|
||||
|
||||
# This dictionary will store methods for extending attributes of
|
||||
# api resources. Mixins can use this dict for adding their own methods
|
||||
# TODO(salvatore-orlando): Avoid using class-level variables
|
||||
_dict_extend_functions = {}
|
||||
|
||||
def __init__(self):
|
||||
# NOTE(jkoelker) This is an incomplete implementation. Subclasses
|
||||
# must override __init__ and setup the database
|
||||
|
157
neutron/db/external_net_db.py
Normal file
157
neutron/db/external_net_db.py
Normal file
@ -0,0 +1,157 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright (c) 2013 OpenStack Foundation.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy import orm
|
||||
from sqlalchemy.orm import exc
|
||||
from sqlalchemy.sql import expression as expr
|
||||
|
||||
from neutron.api.v2 import attributes
|
||||
from neutron.common import constants as l3_constants
|
||||
from neutron.common import exceptions as q_exc
|
||||
from neutron.db import db_base_plugin_v2
|
||||
from neutron.db import model_base
|
||||
from neutron.db import models_v2
|
||||
from neutron.extensions import external_net
|
||||
|
||||
|
||||
DEVICE_OWNER_ROUTER_GW = l3_constants.DEVICE_OWNER_ROUTER_GW
|
||||
|
||||
|
||||
class ExternalNetwork(model_base.BASEV2):
|
||||
network_id = sa.Column(sa.String(36),
|
||||
sa.ForeignKey('networks.id', ondelete="CASCADE"),
|
||||
primary_key=True)
|
||||
|
||||
# Add a relationship to the Network model in order to instruct
|
||||
# SQLAlchemy to eagerly load this association
|
||||
network = orm.relationship(
|
||||
models_v2.Network,
|
||||
backref=orm.backref("external", lazy='joined',
|
||||
uselist=False, cascade='delete'))
|
||||
|
||||
|
||||
class External_net_db_mixin(object):
|
||||
"""Mixin class to add external network methods to db_plugin_base_v2."""
|
||||
|
||||
def _network_model_hook(self, context, original_model, query):
|
||||
query = query.outerjoin(ExternalNetwork,
|
||||
(original_model.id ==
|
||||
ExternalNetwork.network_id))
|
||||
return query
|
||||
|
||||
def _network_filter_hook(self, context, original_model, conditions):
|
||||
if conditions is not None and not hasattr(conditions, '__iter__'):
|
||||
conditions = (conditions, )
|
||||
# Apply the external network filter only in non-admin context
|
||||
if not context.is_admin and hasattr(original_model, 'tenant_id'):
|
||||
conditions = expr.or_(ExternalNetwork.network_id != expr.null(),
|
||||
*conditions)
|
||||
return conditions
|
||||
|
||||
def _network_result_filter_hook(self, query, filters):
|
||||
vals = filters and filters.get(external_net.EXTERNAL, [])
|
||||
if not vals:
|
||||
return query
|
||||
if vals[0]:
|
||||
return query.filter((ExternalNetwork.network_id != expr.null()))
|
||||
return query.filter((ExternalNetwork.network_id == expr.null()))
|
||||
|
||||
# TODO(salvatore-orlando): Perform this operation without explicitly
|
||||
# referring to db_base_plugin_v2, as plugins that do not extend from it
|
||||
# might exist in the future
|
||||
db_base_plugin_v2.NeutronDbPluginV2.register_model_query_hook(
|
||||
models_v2.Network,
|
||||
"external_net",
|
||||
'_network_model_hook',
|
||||
'_network_filter_hook',
|
||||
'_network_result_filter_hook')
|
||||
|
||||
def _network_is_external(self, context, net_id):
|
||||
try:
|
||||
context.session.query(ExternalNetwork).filter_by(
|
||||
network_id=net_id).one()
|
||||
return True
|
||||
except exc.NoResultFound:
|
||||
return False
|
||||
|
||||
def _extend_network_dict_l3(self, network_res, network_db):
|
||||
# Comparing with None for converting uuid into bool
|
||||
network_res[external_net.EXTERNAL] = network_db.external is not None
|
||||
return network_res
|
||||
|
||||
# Register dict extend functions for networks
|
||||
db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
|
||||
attributes.NETWORKS, ['_extend_network_dict_l3'])
|
||||
|
||||
def _process_l3_create(self, context, net_data, req_data):
|
||||
external = req_data.get(external_net.EXTERNAL)
|
||||
external_set = attributes.is_attr_set(external)
|
||||
|
||||
if not external_set:
|
||||
return
|
||||
|
||||
if external:
|
||||
# expects to be called within a plugin's session
|
||||
context.session.add(ExternalNetwork(network_id=net_data['id']))
|
||||
net_data[external_net.EXTERNAL] = external
|
||||
|
||||
def _process_l3_update(self, context, net_data, req_data):
|
||||
|
||||
new_value = req_data.get(external_net.EXTERNAL)
|
||||
net_id = net_data['id']
|
||||
if not attributes.is_attr_set(new_value):
|
||||
return
|
||||
|
||||
if net_data.get(external_net.EXTERNAL) == new_value:
|
||||
return
|
||||
|
||||
if new_value:
|
||||
context.session.add(ExternalNetwork(network_id=net_id))
|
||||
net_data[external_net.EXTERNAL] = True
|
||||
else:
|
||||
# must make sure we do not have any external gateway ports
|
||||
# (and thus, possible floating IPs) on this network before
|
||||
# allow it to be update to external=False
|
||||
port = context.session.query(models_v2.Port).filter_by(
|
||||
device_owner=DEVICE_OWNER_ROUTER_GW,
|
||||
network_id=net_data['id']).first()
|
||||
if port:
|
||||
raise external_net.ExternalNetworkInUse(net_id=net_id)
|
||||
|
||||
context.session.query(ExternalNetwork).filter_by(
|
||||
network_id=net_id).delete()
|
||||
net_data[external_net.EXTERNAL] = False
|
||||
|
||||
def _filter_nets_l3(self, context, nets, filters):
|
||||
vals = filters and filters.get(external_net.EXTERNAL, [])
|
||||
if not vals:
|
||||
return nets
|
||||
|
||||
ext_nets = set(en['network_id']
|
||||
for en in context.session.query(ExternalNetwork))
|
||||
if vals[0]:
|
||||
return [n for n in nets if n['id'] in ext_nets]
|
||||
else:
|
||||
return [n for n in nets if n['id'] not in ext_nets]
|
||||
|
||||
def get_external_network_id(self, context):
|
||||
nets = self.get_networks(context, {external_net.EXTERNAL: [True]})
|
||||
if len(nets) > 1:
|
||||
raise q_exc.TooManyExternalNetworks()
|
||||
else:
|
||||
return nets[0]['id'] if nets else None
|
@ -91,8 +91,8 @@ class ExtraRoute_db_mixin(l3_db.L3_NAT_db_mixin):
|
||||
# nexthop belongs to one of cidrs of the router ports
|
||||
cidrs = []
|
||||
for port in ports:
|
||||
cidrs += [self._get_subnet(context,
|
||||
ip['subnet_id'])['cidr']
|
||||
cidrs += [self._core_plugin._get_subnet(context,
|
||||
ip['subnet_id'])['cidr']
|
||||
for ip in port['fixed_ips']]
|
||||
if not netaddr.all_matching_cidrs(nexthop, cidrs):
|
||||
raise extraroute.InvalidRoutes(
|
||||
@ -114,7 +114,7 @@ class ExtraRoute_db_mixin(l3_db.L3_NAT_db_mixin):
|
||||
quota=cfg.CONF.max_routes)
|
||||
|
||||
filters = {'device_id': [router_id]}
|
||||
ports = self.get_ports(context, filters)
|
||||
ports = self._core_plugin.get_ports(context, filters)
|
||||
for route in routes:
|
||||
self._validate_routes_nexthop(
|
||||
context, ports, routes, route['nexthop'])
|
||||
@ -171,7 +171,7 @@ class ExtraRoute_db_mixin(l3_db.L3_NAT_db_mixin):
|
||||
subnet_id):
|
||||
super(ExtraRoute_db_mixin, self)._confirm_router_interface_not_in_use(
|
||||
context, router_id, subnet_id)
|
||||
subnet_db = self._get_subnet(context, subnet_id)
|
||||
subnet_db = self._core_plugin._get_subnet(context, subnet_id)
|
||||
subnet_cidr = netaddr.IPNetwork(subnet_db['cidr'])
|
||||
extra_routes = self._get_extra_routes_by_router_id(context, router_id)
|
||||
for route in extra_routes:
|
||||
|
251
neutron/db/l3_agentschedulers_db.py
Normal file
251
neutron/db/l3_agentschedulers_db.py
Normal file
@ -0,0 +1,251 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright (c) 2013 OpenStack Foundation.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo.config import cfg
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy import orm
|
||||
from sqlalchemy.orm import exc
|
||||
from sqlalchemy.orm import joinedload
|
||||
|
||||
from neutron.common import constants
|
||||
from neutron.db import agents_db
|
||||
from neutron.db.agentschedulers_db import AgentSchedulerDbMixin
|
||||
from neutron.db import model_base
|
||||
from neutron.db import models_v2
|
||||
from neutron.extensions import l3agentscheduler
|
||||
|
||||
|
||||
L3_AGENTS_SCHEDULER_OPTS = [
|
||||
cfg.StrOpt('router_scheduler_driver',
|
||||
default='neutron.scheduler.l3_agent_scheduler.ChanceScheduler',
|
||||
help=_('Driver to use for scheduling '
|
||||
'router to a default L3 agent')),
|
||||
cfg.BoolOpt('router_auto_schedule', default=True,
|
||||
help=_('Allow auto scheduling of routers to L3 agent.')),
|
||||
]
|
||||
|
||||
cfg.CONF.register_opts(L3_AGENTS_SCHEDULER_OPTS)
|
||||
|
||||
|
||||
class RouterL3AgentBinding(model_base.BASEV2, models_v2.HasId):
|
||||
"""Represents binding between neutron routers and L3 agents."""
|
||||
|
||||
router_id = sa.Column(sa.String(36),
|
||||
sa.ForeignKey("routers.id", ondelete='CASCADE'))
|
||||
l3_agent = orm.relation(agents_db.Agent)
|
||||
l3_agent_id = sa.Column(sa.String(36),
|
||||
sa.ForeignKey("agents.id",
|
||||
ondelete='CASCADE'))
|
||||
|
||||
|
||||
class L3AgentSchedulerDbMixin(l3agentscheduler.L3AgentSchedulerPluginBase,
|
||||
AgentSchedulerDbMixin):
|
||||
"""Mixin class to add l3 agent scheduler extension to plugins
|
||||
using the l3 agent for routing.
|
||||
"""
|
||||
|
||||
router_scheduler = None
|
||||
|
||||
def add_router_to_l3_agent(self, context, agent_id, router_id):
|
||||
"""Add a l3 agent to host a router."""
|
||||
router = self.get_router(context, router_id)
|
||||
with context.session.begin(subtransactions=True):
|
||||
agent_db = self._get_agent(context, agent_id)
|
||||
if (agent_db['agent_type'] != constants.AGENT_TYPE_L3 or
|
||||
not agent_db['admin_state_up'] or
|
||||
not self.get_l3_agent_candidates(router, [agent_db])):
|
||||
raise l3agentscheduler.InvalidL3Agent(id=agent_id)
|
||||
query = context.session.query(RouterL3AgentBinding)
|
||||
try:
|
||||
binding = query.filter_by(router_id=router_id).one()
|
||||
|
||||
raise l3agentscheduler.RouterHostedByL3Agent(
|
||||
router_id=router_id,
|
||||
agent_id=binding.l3_agent_id)
|
||||
except exc.NoResultFound:
|
||||
pass
|
||||
|
||||
result = self.auto_schedule_routers(context,
|
||||
agent_db.host,
|
||||
[router_id])
|
||||
if not result:
|
||||
raise l3agentscheduler.RouterSchedulingFailed(
|
||||
router_id=router_id, agent_id=agent_id)
|
||||
|
||||
l3_notifier = self.agent_notifiers.get(constants.AGENT_TYPE_L3)
|
||||
if l3_notifier:
|
||||
l3_notifier.router_added_to_agent(
|
||||
context, [router_id], agent_db.host)
|
||||
|
||||
def remove_router_from_l3_agent(self, context, agent_id, router_id):
|
||||
"""Remove the router from l3 agent.
|
||||
|
||||
After removal, the router will be non-hosted until there is update
|
||||
which leads to re-schedule or be added to another agent manually.
|
||||
"""
|
||||
agent = self._get_agent(context, agent_id)
|
||||
with context.session.begin(subtransactions=True):
|
||||
query = context.session.query(RouterL3AgentBinding)
|
||||
query = query.filter(
|
||||
RouterL3AgentBinding.router_id == router_id,
|
||||
RouterL3AgentBinding.l3_agent_id == agent_id)
|
||||
try:
|
||||
binding = query.one()
|
||||
except exc.NoResultFound:
|
||||
raise l3agentscheduler.RouterNotHostedByL3Agent(
|
||||
router_id=router_id, agent_id=agent_id)
|
||||
context.session.delete(binding)
|
||||
l3_notifier = self.agent_notifiers.get(constants.AGENT_TYPE_L3)
|
||||
if l3_notifier:
|
||||
l3_notifier.router_removed_from_agent(
|
||||
context, router_id, agent.host)
|
||||
|
||||
def list_routers_on_l3_agent(self, context, agent_id):
|
||||
query = context.session.query(RouterL3AgentBinding.router_id)
|
||||
query = query.filter(RouterL3AgentBinding.l3_agent_id == agent_id)
|
||||
|
||||
router_ids = [item[0] for item in query]
|
||||
if router_ids:
|
||||
return {'routers':
|
||||
self.get_routers(context, filters={'id': router_ids})}
|
||||
else:
|
||||
return {'routers': []}
|
||||
|
||||
def list_active_sync_routers_on_active_l3_agent(
|
||||
self, context, host, router_ids):
|
||||
agent = self._get_agent_by_type_and_host(
|
||||
context, constants.AGENT_TYPE_L3, host)
|
||||
if not agent.admin_state_up:
|
||||
return []
|
||||
query = context.session.query(RouterL3AgentBinding.router_id)
|
||||
query = query.filter(
|
||||
RouterL3AgentBinding.l3_agent_id == agent.id)
|
||||
|
||||
if not router_ids:
|
||||
pass
|
||||
else:
|
||||
query = query.filter(
|
||||
RouterL3AgentBinding.router_id.in_(router_ids))
|
||||
router_ids = [item[0] for item in query]
|
||||
if router_ids:
|
||||
return self.get_sync_data(context, router_ids=router_ids,
|
||||
active=True)
|
||||
else:
|
||||
return []
|
||||
|
||||
def get_l3_agents_hosting_routers(self, context, router_ids,
|
||||
admin_state_up=None,
|
||||
active=None):
|
||||
if not router_ids:
|
||||
return []
|
||||
query = context.session.query(RouterL3AgentBinding)
|
||||
if len(router_ids) > 1:
|
||||
query = query.options(joinedload('l3_agent')).filter(
|
||||
RouterL3AgentBinding.router_id.in_(router_ids))
|
||||
else:
|
||||
query = query.options(joinedload('l3_agent')).filter(
|
||||
RouterL3AgentBinding.router_id == router_ids[0])
|
||||
if admin_state_up is not None:
|
||||
query = (query.filter(agents_db.Agent.admin_state_up ==
|
||||
admin_state_up))
|
||||
l3_agents = [binding.l3_agent for binding in query]
|
||||
if active is not None:
|
||||
l3_agents = [l3_agent for l3_agent in
|
||||
l3_agents if not
|
||||
agents_db.AgentDbMixin.is_agent_down(
|
||||
l3_agent['heartbeat_timestamp'])]
|
||||
return l3_agents
|
||||
|
||||
def _get_l3_bindings_hosting_routers(self, context, router_ids):
|
||||
if not router_ids:
|
||||
return []
|
||||
query = context.session.query(RouterL3AgentBinding)
|
||||
if len(router_ids) > 1:
|
||||
query = query.options(joinedload('l3_agent')).filter(
|
||||
RouterL3AgentBinding.router_id.in_(router_ids))
|
||||
else:
|
||||
query = query.options(joinedload('l3_agent')).filter(
|
||||
RouterL3AgentBinding.router_id == router_ids[0])
|
||||
return query.all()
|
||||
|
||||
def list_l3_agents_hosting_router(self, context, router_id):
|
||||
with context.session.begin(subtransactions=True):
|
||||
bindings = self._get_l3_bindings_hosting_routers(
|
||||
context, [router_id])
|
||||
results = []
|
||||
for binding in bindings:
|
||||
l3_agent_dict = self._make_agent_dict(binding.l3_agent)
|
||||
results.append(l3_agent_dict)
|
||||
if results:
|
||||
return {'agents': results}
|
||||
else:
|
||||
return {'agents': []}
|
||||
|
||||
def get_l3_agents(self, context, active=None, filters=None):
|
||||
query = context.session.query(agents_db.Agent)
|
||||
query = query.filter(
|
||||
agents_db.Agent.agent_type == constants.AGENT_TYPE_L3)
|
||||
if active is not None:
|
||||
query = (query.filter(agents_db.Agent.admin_state_up == active))
|
||||
if filters:
|
||||
for key, value in filters.iteritems():
|
||||
column = getattr(agents_db.Agent, key, None)
|
||||
if column:
|
||||
query = query.filter(column.in_(value))
|
||||
|
||||
return [l3_agent
|
||||
for l3_agent in query
|
||||
if AgentSchedulerDbMixin.is_eligible_agent(active, l3_agent)]
|
||||
|
||||
def get_l3_agent_candidates(self, sync_router, l3_agents):
|
||||
"""Get the valid l3 agents for the router from a list of l3_agents."""
|
||||
candidates = []
|
||||
for l3_agent in l3_agents:
|
||||
if not l3_agent.admin_state_up:
|
||||
continue
|
||||
agent_conf = self.get_configuration_dict(l3_agent)
|
||||
router_id = agent_conf.get('router_id', None)
|
||||
use_namespaces = agent_conf.get('use_namespaces', True)
|
||||
handle_internal_only_routers = agent_conf.get(
|
||||
'handle_internal_only_routers', True)
|
||||
gateway_external_network_id = agent_conf.get(
|
||||
'gateway_external_network_id', None)
|
||||
if not use_namespaces and router_id != sync_router['id']:
|
||||
continue
|
||||
ex_net_id = (sync_router['external_gateway_info'] or {}).get(
|
||||
'network_id')
|
||||
if ((not ex_net_id and not handle_internal_only_routers) or
|
||||
(ex_net_id and gateway_external_network_id and
|
||||
ex_net_id != gateway_external_network_id)):
|
||||
continue
|
||||
candidates.append(l3_agent)
|
||||
return candidates
|
||||
|
||||
def auto_schedule_routers(self, context, host, router_ids):
|
||||
if self.router_scheduler:
|
||||
return self.router_scheduler.auto_schedule_routers(
|
||||
self, context, host, router_ids)
|
||||
|
||||
def schedule_router(self, context, router):
|
||||
if self.router_scheduler:
|
||||
return self.router_scheduler.schedule(
|
||||
self, context, router)
|
||||
|
||||
def schedule_routers(self, context, routers):
|
||||
"""Schedule the routers to l3 agents."""
|
||||
for router in routers:
|
||||
self.schedule_router(context, router)
|
@ -21,16 +21,15 @@ import netaddr
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy import orm
|
||||
from sqlalchemy.orm import exc
|
||||
from sqlalchemy.sql import expression as expr
|
||||
|
||||
from neutron.api.rpc.agentnotifiers import l3_rpc_agent_api
|
||||
from neutron.api.v2 import attributes
|
||||
from neutron.common import constants as l3_constants
|
||||
from neutron.common import exceptions as q_exc
|
||||
from neutron.db import db_base_plugin_v2
|
||||
from neutron.db import model_base
|
||||
from neutron.db import models_v2
|
||||
from neutron.extensions import l3
|
||||
from neutron import manager
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron.openstack.common.notifier import api as notifier_api
|
||||
from neutron.openstack.common import uuidutils
|
||||
@ -59,19 +58,6 @@ class Router(model_base.BASEV2, models_v2.HasId, models_v2.HasTenant):
|
||||
gw_port = orm.relationship(models_v2.Port)
|
||||
|
||||
|
||||
class ExternalNetwork(model_base.BASEV2):
|
||||
network_id = sa.Column(sa.String(36),
|
||||
sa.ForeignKey('networks.id', ondelete="CASCADE"),
|
||||
primary_key=True)
|
||||
|
||||
# Add a relationship to the Network model in order to instruct
|
||||
# SQLAlchemy to eagerly load this association
|
||||
network = orm.relationship(
|
||||
models_v2.Network,
|
||||
backref=orm.backref("external", lazy='joined',
|
||||
uselist=False, cascade='delete'))
|
||||
|
||||
|
||||
class FloatingIP(model_base.BASEV2, models_v2.HasId, models_v2.HasTenant):
|
||||
"""Represents a floating IP address.
|
||||
|
||||
@ -93,38 +79,9 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
||||
|
||||
l3_rpc_notifier = l3_rpc_agent_api.L3AgentNotify
|
||||
|
||||
def _network_model_hook(self, context, original_model, query):
|
||||
query = query.outerjoin(ExternalNetwork,
|
||||
(original_model.id ==
|
||||
ExternalNetwork.network_id))
|
||||
return query
|
||||
|
||||
def _network_filter_hook(self, context, original_model, conditions):
|
||||
if conditions is not None and not hasattr(conditions, '__iter__'):
|
||||
conditions = (conditions, )
|
||||
# Apply the external network filter only in non-admin context
|
||||
if not context.is_admin and hasattr(original_model, 'tenant_id'):
|
||||
conditions = expr.or_(ExternalNetwork.network_id != expr.null(),
|
||||
*conditions)
|
||||
return conditions
|
||||
|
||||
def _network_result_filter_hook(self, query, filters):
|
||||
vals = filters and filters.get('router:external', [])
|
||||
if not vals:
|
||||
return query
|
||||
if vals[0]:
|
||||
return query.filter((ExternalNetwork.network_id != expr.null()))
|
||||
return query.filter((ExternalNetwork.network_id == expr.null()))
|
||||
|
||||
# TODO(salvatore-orlando): Perform this operation without explicitly
|
||||
# referring to db_base_plugin_v2, as plugins that do not extend from it
|
||||
# might exist in the future
|
||||
db_base_plugin_v2.NeutronDbPluginV2.register_model_query_hook(
|
||||
models_v2.Network,
|
||||
"external_net",
|
||||
'_network_model_hook',
|
||||
'_network_filter_hook',
|
||||
'_network_result_filter_hook')
|
||||
@property
|
||||
def _core_plugin(self):
|
||||
return manager.NeutronManager.get_plugin()
|
||||
|
||||
def _get_router(self, context, id):
|
||||
try:
|
||||
@ -194,7 +151,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
||||
|
||||
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(), {
|
||||
gw_port = self._core_plugin.create_port(context.elevated(), {
|
||||
'port': {'tenant_id': '', # intentionally not set
|
||||
'network_id': network_id,
|
||||
'mac_address': attributes.ATTR_NOT_SPECIFIED,
|
||||
@ -205,15 +162,15 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
||||
'name': ''}})
|
||||
|
||||
if not gw_port['fixed_ips']:
|
||||
self.delete_port(context.elevated(), gw_port['id'],
|
||||
l3_port_check=False)
|
||||
self._core_plugin.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'])
|
||||
router.gw_port = self._core_plugin._get_port(context.elevated(),
|
||||
gw_port['id'])
|
||||
context.session.add(router)
|
||||
|
||||
def _update_router_gw_info(self, context, router_id, info, router=None):
|
||||
@ -225,7 +182,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
||||
# network_id attribute is required by API, so it must be present
|
||||
network_id = info['network_id'] if info else None
|
||||
if network_id:
|
||||
network_db = self._get_network(context, network_id)
|
||||
network_db = self._core_plugin._get_network(context, network_id)
|
||||
if not network_db.external:
|
||||
msg = _("Network %s is not a valid external "
|
||||
"network") % network_id
|
||||
@ -242,13 +199,14 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
||||
with context.session.begin(subtransactions=True):
|
||||
router.gw_port = None
|
||||
context.session.add(router)
|
||||
self.delete_port(context.elevated(), gw_port['id'],
|
||||
l3_port_check=False)
|
||||
self._core_plugin.delete_port(context.elevated(),
|
||||
gw_port['id'],
|
||||
l3_port_check=False)
|
||||
|
||||
if network_id is not None and (gw_port is None or
|
||||
gw_port['network_id'] != network_id):
|
||||
subnets = self._get_subnets_by_network(context,
|
||||
network_id)
|
||||
subnets = self._core_plugin._get_subnets_by_network(context,
|
||||
network_id)
|
||||
for subnet in subnets:
|
||||
self._check_for_dup_router_subnet(context, router_id,
|
||||
network_id, subnet['id'],
|
||||
@ -267,17 +225,19 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
||||
|
||||
device_filter = {'device_id': [id],
|
||||
'device_owner': [DEVICE_OWNER_ROUTER_INTF]}
|
||||
ports = self.get_ports_count(context.elevated(),
|
||||
filters=device_filter)
|
||||
ports = self._core_plugin.get_ports_count(context.elevated(),
|
||||
filters=device_filter)
|
||||
if ports:
|
||||
raise l3.RouterInUse(router_id=id)
|
||||
|
||||
# delete any gw port
|
||||
device_filter = {'device_id': [id],
|
||||
'device_owner': [DEVICE_OWNER_ROUTER_GW]}
|
||||
ports = self.get_ports(context.elevated(), filters=device_filter)
|
||||
ports = self._core_plugin.get_ports(context.elevated(),
|
||||
filters=device_filter)
|
||||
if ports:
|
||||
self._delete_port(context.elevated(), ports[0]['id'])
|
||||
self._core_plugin._delete_port(context.elevated(),
|
||||
ports[0]['id'])
|
||||
|
||||
context.session.delete(router)
|
||||
self.l3_rpc_notifier.router_deleted(context, id)
|
||||
@ -317,8 +277,8 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
||||
% subnet_id)
|
||||
raise q_exc.BadRequest(resource='router', msg=msg)
|
||||
sub_id = ip['subnet_id']
|
||||
cidr = self._get_subnet(context.elevated(),
|
||||
sub_id)['cidr']
|
||||
cidr = self._core_plugin._get_subnet(context.elevated(),
|
||||
sub_id)['cidr']
|
||||
ipnet = netaddr.IPNetwork(cidr)
|
||||
match1 = netaddr.all_matching_cidrs(new_ipnet, [cidr])
|
||||
match2 = netaddr.all_matching_cidrs(ipnet, [subnet_cidr])
|
||||
@ -346,7 +306,8 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
||||
msg = _("Cannot specify both subnet-id and port-id")
|
||||
raise q_exc.BadRequest(resource='router', msg=msg)
|
||||
|
||||
port = self._get_port(context, interface_info['port_id'])
|
||||
port = self._core_plugin._get_port(context,
|
||||
interface_info['port_id'])
|
||||
if port['device_id']:
|
||||
raise q_exc.PortInUse(net_id=port['network_id'],
|
||||
port_id=port['id'],
|
||||
@ -356,7 +317,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
||||
msg = _('Router port must have exactly one fixed IP')
|
||||
raise q_exc.BadRequest(resource='router', msg=msg)
|
||||
subnet_id = fixed_ips[0]['subnet_id']
|
||||
subnet = self._get_subnet(context, subnet_id)
|
||||
subnet = self._core_plugin._get_subnet(context, subnet_id)
|
||||
self._check_for_dup_router_subnet(context, router_id,
|
||||
port['network_id'],
|
||||
subnet['id'],
|
||||
@ -365,7 +326,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
||||
'device_owner': DEVICE_OWNER_ROUTER_INTF})
|
||||
elif 'subnet_id' in interface_info:
|
||||
subnet_id = interface_info['subnet_id']
|
||||
subnet = self._get_subnet(context, subnet_id)
|
||||
subnet = self._core_plugin._get_subnet(context, subnet_id)
|
||||
# Ensure the subnet has a gateway
|
||||
if not subnet['gateway_ip']:
|
||||
msg = _('Subnet for router interface must have a gateway IP')
|
||||
@ -376,7 +337,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
||||
subnet['cidr'])
|
||||
fixed_ip = {'ip_address': subnet['gateway_ip'],
|
||||
'subnet_id': subnet['id']}
|
||||
port = self.create_port(context, {
|
||||
port = self._core_plugin.create_port(context, {
|
||||
'port':
|
||||
{'tenant_id': subnet['tenant_id'],
|
||||
'network_id': subnet['network_id'],
|
||||
@ -402,7 +363,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
||||
|
||||
def _confirm_router_interface_not_in_use(self, context, router_id,
|
||||
subnet_id):
|
||||
subnet_db = self._get_subnet(context, subnet_id)
|
||||
subnet_db = self._core_plugin._get_subnet(context, subnet_id)
|
||||
subnet_cidr = netaddr.IPNetwork(subnet_db['cidr'])
|
||||
fip_qry = context.session.query(FloatingIP)
|
||||
for fip_db in fip_qry.filter_by(router_id=router_id):
|
||||
@ -416,7 +377,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
||||
raise q_exc.BadRequest(resource='router', msg=msg)
|
||||
if 'port_id' in interface_info:
|
||||
port_id = interface_info['port_id']
|
||||
port_db = self._get_port(context, port_id)
|
||||
port_db = self._core_plugin._get_port(context, port_id)
|
||||
if not (port_db['device_owner'] == DEVICE_OWNER_ROUTER_INTF and
|
||||
port_db['device_id'] == router_id):
|
||||
raise l3.RouterInterfaceNotFound(router_id=router_id,
|
||||
@ -428,16 +389,17 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
||||
port_id=port_id,
|
||||
subnet_id=interface_info['subnet_id'])
|
||||
subnet_id = port_db['fixed_ips'][0]['subnet_id']
|
||||
subnet = self._get_subnet(context, subnet_id)
|
||||
subnet = self._core_plugin._get_subnet(context, subnet_id)
|
||||
self._confirm_router_interface_not_in_use(
|
||||
context, router_id, subnet_id)
|
||||
self.delete_port(context, port_db['id'], l3_port_check=False)
|
||||
self._core_plugin.delete_port(context, port_db['id'],
|
||||
l3_port_check=False)
|
||||
elif 'subnet_id' in interface_info:
|
||||
subnet_id = interface_info['subnet_id']
|
||||
self._confirm_router_interface_not_in_use(context, router_id,
|
||||
subnet_id)
|
||||
|
||||
subnet = self._get_subnet(context, subnet_id)
|
||||
subnet = self._core_plugin._get_subnet(context, subnet_id)
|
||||
found = False
|
||||
|
||||
try:
|
||||
@ -450,7 +412,8 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
||||
for p in ports:
|
||||
if p['fixed_ips'][0]['subnet_id'] == subnet_id:
|
||||
port_id = p['id']
|
||||
self.delete_port(context, p['id'], l3_port_check=False)
|
||||
self._core_plugin.delete_port(context, p['id'],
|
||||
l3_port_check=False)
|
||||
found = True
|
||||
break
|
||||
except exc.NoResultFound:
|
||||
@ -492,7 +455,8 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
||||
def _get_router_for_floatingip(self, context, internal_port,
|
||||
internal_subnet_id,
|
||||
external_network_id):
|
||||
subnet_db = self._get_subnet(context, internal_subnet_id)
|
||||
subnet_db = self._core_plugin._get_subnet(context,
|
||||
internal_subnet_id)
|
||||
if not subnet_db['gateway_ip']:
|
||||
msg = (_('Cannot add floating IP to port on subnet %s '
|
||||
'which has no gateway_ip') % internal_subnet_id)
|
||||
@ -526,7 +490,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
||||
Retrieve information concerning the internal port where
|
||||
the floating IP should be associated to.
|
||||
"""
|
||||
internal_port = self._get_port(context, fip['port_id'])
|
||||
internal_port = self._core_plugin._get_port(context, fip['port_id'])
|
||||
if not internal_port['tenant_id'] == fip['tenant_id']:
|
||||
port_id = fip['port_id']
|
||||
if 'id' in fip:
|
||||
@ -633,7 +597,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
||||
fip_id = uuidutils.generate_uuid()
|
||||
|
||||
f_net_id = fip['floating_network_id']
|
||||
if not self._network_is_external(context, f_net_id):
|
||||
if not self._core_plugin._network_is_external(context, f_net_id):
|
||||
msg = _("Network %s is not a valid external network") % f_net_id
|
||||
raise q_exc.BadRequest(resource='floatingip', msg=msg)
|
||||
|
||||
@ -641,7 +605,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
||||
# This external port is never exposed to the tenant.
|
||||
# it is used purely for internal system and admin use when
|
||||
# managing floating IPs.
|
||||
external_port = self.create_port(context.elevated(), {
|
||||
external_port = self._core_plugin.create_port(context.elevated(), {
|
||||
'port':
|
||||
{'tenant_id': '', # tenant intentionally not set
|
||||
'network_id': f_net_id,
|
||||
@ -686,8 +650,8 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
||||
fip_port_id = floatingip_db['floating_port_id']
|
||||
before_router_id = floatingip_db['router_id']
|
||||
self._update_fip_assoc(context, fip, floatingip_db,
|
||||
self.get_port(context.elevated(),
|
||||
fip_port_id))
|
||||
self._core_plugin.get_port(
|
||||
context.elevated(), fip_port_id))
|
||||
router_ids = []
|
||||
if before_router_id:
|
||||
router_ids.append(before_router_id)
|
||||
@ -704,9 +668,9 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
||||
router_id = floatingip['router_id']
|
||||
with context.session.begin(subtransactions=True):
|
||||
context.session.delete(floatingip)
|
||||
self.delete_port(context.elevated(),
|
||||
floatingip['floating_port_id'],
|
||||
l3_port_check=False)
|
||||
self._core_plugin.delete_port(context.elevated(),
|
||||
floatingip['floating_port_id'],
|
||||
l3_port_check=False)
|
||||
if router_id:
|
||||
self.l3_rpc_notifier.routers_updated(
|
||||
context, [router_id],
|
||||
@ -747,7 +711,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
||||
to /ports, but rather via other API calls that perform the proper
|
||||
deletion checks.
|
||||
"""
|
||||
port_db = self._get_port(context, port_id)
|
||||
port_db = self._core_plugin._get_port(context, port_id)
|
||||
if port_db['device_owner'] in [DEVICE_OWNER_ROUTER_INTF,
|
||||
DEVICE_OWNER_ROUTER_GW,
|
||||
DEVICE_OWNER_FLOATINGIP]:
|
||||
@ -782,74 +746,6 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
||||
self.l3_rpc_notifier.routers_updated(
|
||||
context, [router_id])
|
||||
|
||||
def _network_is_external(self, context, net_id):
|
||||
try:
|
||||
context.session.query(ExternalNetwork).filter_by(
|
||||
network_id=net_id).one()
|
||||
return True
|
||||
except exc.NoResultFound:
|
||||
return False
|
||||
|
||||
def _extend_network_dict_l3(self, network_res, network_db):
|
||||
# Comparing with None for converting uuid into bool
|
||||
network_res[l3.EXTERNAL] = network_db.external is not None
|
||||
return network_res
|
||||
|
||||
# Register dict extend functions for networks
|
||||
db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
|
||||
attributes.NETWORKS, ['_extend_network_dict_l3'])
|
||||
|
||||
def _process_l3_create(self, context, net_data, req_data):
|
||||
external = req_data.get(l3.EXTERNAL)
|
||||
external_set = attributes.is_attr_set(external)
|
||||
|
||||
if not external_set:
|
||||
return
|
||||
|
||||
if external:
|
||||
# expects to be called within a plugin's session
|
||||
context.session.add(ExternalNetwork(network_id=net_data['id']))
|
||||
net_data[l3.EXTERNAL] = external
|
||||
|
||||
def _process_l3_update(self, context, net_data, req_data):
|
||||
|
||||
new_value = req_data.get(l3.EXTERNAL)
|
||||
net_id = net_data['id']
|
||||
if not attributes.is_attr_set(new_value):
|
||||
return
|
||||
|
||||
if net_data.get(l3.EXTERNAL) == new_value:
|
||||
return
|
||||
|
||||
if new_value:
|
||||
context.session.add(ExternalNetwork(network_id=net_id))
|
||||
net_data[l3.EXTERNAL] = True
|
||||
else:
|
||||
# must make sure we do not have any external gateway ports
|
||||
# (and thus, possible floating IPs) on this network before
|
||||
# allow it to be update to external=False
|
||||
port = context.session.query(models_v2.Port).filter_by(
|
||||
device_owner=DEVICE_OWNER_ROUTER_GW,
|
||||
network_id=net_data['id']).first()
|
||||
if port:
|
||||
raise l3.ExternalNetworkInUse(net_id=net_id)
|
||||
|
||||
context.session.query(ExternalNetwork).filter_by(
|
||||
network_id=net_id).delete()
|
||||
net_data[l3.EXTERNAL] = False
|
||||
|
||||
def _filter_nets_l3(self, context, nets, filters):
|
||||
vals = filters and filters.get('router:external', [])
|
||||
if not vals:
|
||||
return nets
|
||||
|
||||
ext_nets = set(en['network_id']
|
||||
for en in context.session.query(ExternalNetwork))
|
||||
if vals[0]:
|
||||
return [n for n in nets if n['id'] in ext_nets]
|
||||
else:
|
||||
return [n for n in nets if n['id'] not in ext_nets]
|
||||
|
||||
def _build_routers_list(self, routers, gw_ports):
|
||||
gw_port_id_gw_port_dict = dict((gw_port['id'], gw_port)
|
||||
for gw_port in gw_ports)
|
||||
@ -898,7 +794,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
||||
if not gw_port_ids:
|
||||
return []
|
||||
filters = {'id': gw_port_ids}
|
||||
gw_ports = self.get_ports(context, filters)
|
||||
gw_ports = self._core_plugin.get_ports(context, filters)
|
||||
if gw_ports:
|
||||
self._populate_subnet_for_ports(context, gw_ports)
|
||||
return gw_ports
|
||||
@ -910,7 +806,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
||||
return []
|
||||
filters = {'device_id': router_ids,
|
||||
'device_owner': [device_owner]}
|
||||
interfaces = self.get_ports(context, filters)
|
||||
interfaces = self._core_plugin.get_ports(context, filters)
|
||||
if interfaces:
|
||||
self._populate_subnet_for_ports(context, interfaces)
|
||||
return interfaces
|
||||
@ -943,7 +839,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
||||
return
|
||||
filters = {'id': subnet_id_ports_dict.keys()}
|
||||
fields = ['id', 'cidr', 'gateway_ip']
|
||||
subnet_dicts = self.get_subnets(context, filters, fields)
|
||||
subnet_dicts = self._core_plugin.get_subnets(context, filters, fields)
|
||||
for subnet_dict in subnet_dicts:
|
||||
ports = subnet_id_ports_dict.get(subnet_dict['id'], [])
|
||||
for port in ports:
|
||||
@ -982,10 +878,3 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
||||
floating_ips = self._get_sync_floating_ips(context, router_ids)
|
||||
interfaces = self.get_sync_interfaces(context, router_ids)
|
||||
return self._process_sync_data(routers, interfaces, floating_ips)
|
||||
|
||||
def get_external_network_id(self, context):
|
||||
nets = self.get_networks(context, {'router:external': [True]})
|
||||
if len(nets) > 1:
|
||||
raise q_exc.TooManyExternalNetworks()
|
||||
else:
|
||||
return nets[0]['id'] if nets else None
|
||||
|