neutron/neutron/scheduler/dhcp_agent_scheduler.py

176 lines
7.5 KiB
Python

# 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
from oslo_db import exception as db_exc
from oslo_log import log as logging
from sqlalchemy import sql
from neutron.common import constants
from neutron.db import agents_db
from neutron.db import agentschedulers_db
from neutron.i18n import _LI, _LW
from neutron.scheduler import base_resource_filter
from neutron.scheduler import base_scheduler
LOG = logging.getLogger(__name__)
class AutoScheduler(object):
def auto_schedule_networks(self, plugin, context, host):
"""Schedule non-hosted networks to the DHCP agent on the specified
host.
"""
agents_per_network = cfg.CONF.dhcp_agents_per_network
# a list of (agent, net_ids) tuples
bindings_to_add = []
with context.session.begin(subtransactions=True):
fields = ['network_id', 'enable_dhcp']
subnets = plugin.get_subnets(context, fields=fields)
net_ids = set(s['network_id'] for s in subnets
if s['enable_dhcp'])
if not net_ids:
LOG.debug('No non-hosted networks')
return False
query = context.session.query(agents_db.Agent)
query = query.filter(agents_db.Agent.agent_type ==
constants.AGENT_TYPE_DHCP,
agents_db.Agent.host == host,
agents_db.Agent.admin_state_up == sql.true())
dhcp_agents = query.all()
for dhcp_agent in dhcp_agents:
if agents_db.AgentDbMixin.is_agent_down(
dhcp_agent.heartbeat_timestamp):
LOG.warn(_LW('DHCP agent %s is not active'), dhcp_agent.id)
continue
for net_id in net_ids:
agents = plugin.get_dhcp_agents_hosting_networks(
context, [net_id])
if len(agents) >= agents_per_network:
continue
if any(dhcp_agent.id == agent.id for agent in agents):
continue
bindings_to_add.append((dhcp_agent, net_id))
# do it outside transaction so particular scheduling results don't
# make other to fail
for agent, net_id in bindings_to_add:
self.resource_filter.bind(context, [agent], net_id)
return True
class ChanceScheduler(base_scheduler.BaseChanceScheduler, AutoScheduler):
def __init__(self):
super(ChanceScheduler, self).__init__(DhcpFilter())
class WeightScheduler(base_scheduler.BaseWeightScheduler, AutoScheduler):
def __init__(self):
super(WeightScheduler, self).__init__(DhcpFilter())
class DhcpFilter(base_resource_filter.BaseResourceFilter):
def bind(self, context, agents, network_id):
"""Bind the network to the agents."""
# customize the bind logic
bound_agents = agents[:]
for agent in agents:
context.session.begin(subtransactions=True)
# saving agent_id to use it after rollback to avoid
# DetachedInstanceError
agent_id = agent.id
binding = agentschedulers_db.NetworkDhcpAgentBinding()
binding.dhcp_agent_id = agent_id
binding.network_id = network_id
try:
context.session.add(binding)
# try to actually write the changes and catch integrity
# DBDuplicateEntry
context.session.commit()
except db_exc.DBDuplicateEntry:
# it's totally ok, someone just did our job!
context.session.rollback()
bound_agents.remove(agent)
LOG.info(_LI('Agent %s already present'), agent_id)
LOG.debug('Network %(network_id)s is scheduled to be '
'hosted by DHCP agent %(agent_id)s',
{'network_id': network_id,
'agent_id': agent_id})
super(DhcpFilter, self).bind(context, bound_agents, network_id)
def filter_agents(self, plugin, context, network):
"""Return the agents that can host the network."""
agents_dict = self._get_network_hostable_dhcp_agents(
plugin, context, network)
if not agents_dict['hostable_agents'] or agents_dict['n_agents'] <= 0:
return {'n_agents': 0, 'hostable_agents': []}
return agents_dict
def _get_dhcp_agents_hosting_network(self, plugin, context, network):
"""Return dhcp agents hosting the given network or None if a given
network is already hosted by enough number of agents.
"""
agents_per_network = cfg.CONF.dhcp_agents_per_network
#TODO(gongysh) don't schedule the networks with only
# subnets whose enable_dhcp is false
with context.session.begin(subtransactions=True):
network_hosted_agents = plugin.get_dhcp_agents_hosting_networks(
context, [network['id']])
if len(network_hosted_agents) >= agents_per_network:
LOG.debug('Network %s is already hosted by enough agents.',
network['id'])
return
return network_hosted_agents
def _get_active_agents(self, plugin, context):
"""Return a list of active dhcp agents."""
with context.session.begin(subtransactions=True):
active_dhcp_agents = plugin.get_agents_db(
context, filters={
'agent_type': [constants.AGENT_TYPE_DHCP],
'admin_state_up': [True]})
if not active_dhcp_agents:
LOG.warn(_LW('No more DHCP agents'))
return []
return active_dhcp_agents
def _get_network_hostable_dhcp_agents(self, plugin, context, network):
"""Return number of agents which will actually host the given network
and a list of dhcp agents which can host the given network
"""
hosted_agents = self._get_dhcp_agents_hosting_network(plugin,
context, network)
if hosted_agents is None:
return {'n_agents': 0, 'hostable_agents': []}
n_agents = cfg.CONF.dhcp_agents_per_network - len(hosted_agents)
active_dhcp_agents = self._get_active_agents(plugin, context)
if not active_dhcp_agents:
return {'n_agents': 0, 'hostable_agents': []}
hostable_dhcp_agents = [
agent for agent in set(active_dhcp_agents)
if agent not in hosted_agents and plugin.is_eligible_agent(
context, True, agent)
]
if not hostable_dhcp_agents:
return {'n_agents': 0, 'hostable_agents': []}
n_agents = min(len(hostable_dhcp_agents), n_agents)
return {'n_agents': n_agents, 'hostable_agents':
hostable_dhcp_agents}