DHCP Service LoadBalancing Scheduler

In this blueprint, we also propose to write a generic scheduler
framework which can be used to schedule a new resource on
selected least loaded agents.
Currently dhcp_load_type will be fetched from neutron.conf file
and corresponding load is obtained by the agent report state.
The obtained load will be populated in the "load" column of the
agents table.
During scheduling, agent will be selected based on sorting all
the agents of particular type based on load column.
Example dhcp_load_type is networks

DocImpact

Implements: blueprint dhcpservice-loadbalancing

Change-Id: I5ec8adf0c4336f885d603662223caa7694708876
Author: Shivakumar M <shiva.kum.m@hp.com>
Co-Authored-By: Praveen Kumar SM <praveen-sm.kumar@hp.com>
Co-Authored-By: Benjamin GRASSART <benjamin.grassart@thalesgroup.com>
Co-Authored-By: Sourabh Patwardhan <sopatwar@cisco.com>
This commit is contained in:
Shivakumar M 2014-11-25 00:46:07 -08:00 committed by armando-migliaccio
parent 87a534f9b0
commit bc0b8ff8d5
9 changed files with 487 additions and 97 deletions

View File

@ -167,6 +167,23 @@ lock_path = $state_path/lock
# Driver to use for scheduling a loadbalancer pool to an lbaas agent
# loadbalancer_pool_scheduler_driver = neutron.services.loadbalancer.agent_scheduler.ChanceScheduler
# (StrOpt) Representing the resource type whose load is being reported by
# the agent.
# This can be 'networks','subnets' or 'ports'. When specified (Default is networks),
# the server will extract particular load sent as part of its agent configuration object
# from the agent report state, which is the number of resources being consumed, at
# every report_interval.
# dhcp_load_type can be used in combination with network_scheduler_driver =
# neutron.scheduler.dhcp_agent_scheduler.WeightScheduler
# When the network_scheduler_driver is WeightScheduler, dhcp_load_type can
# be configured to represent the choice for the resource being balanced.
# Example: dhcp_load_type = networks
# Values:
# networks - number of networks hosted on the agent
# subnets - number of subnets associated with the networks hosted on the agent
# ports - number of ports associated with the networks hosted on the agent
# dhcp_load_type = networks
# Allow auto scheduling networks to DHCP agent. It will schedule non-hosted
# networks to first DHCP agent which sends get_active_networks message to
# neutron server

View File

@ -33,11 +33,31 @@ from neutron.i18n import _LW
from neutron import manager
LOG = logging.getLogger(__name__)
cfg.CONF.register_opt(
AGENT_OPTS = [
cfg.IntOpt('agent_down_time', default=75,
help=_("Seconds to regard the agent is down; should be at "
"least twice report_interval, to be sure the "
"agent is down for good.")))
"agent is down for good.")),
cfg.StrOpt('dhcp_load_type', default='networks',
choices=['networks', 'subnets', 'ports'],
help=_('Representing the resource type whose load is being '
'reported by the agent. This can be "networks", '
'"subnets" or "ports". '
'When specified (Default is networks), the server will '
'extract particular load sent as part of its agent '
'configuration object from the agent report state, '
'which is the number of resources being consumed, at '
'every report_interval.'
'dhcp_load_type can be used in combination with '
'network_scheduler_driver = '
'neutron.scheduler.dhcp_agent_scheduler.WeightScheduler '
'When the network_scheduler_driver is WeightScheduler, '
'dhcp_load_type can be configured to represent the '
'choice for the resource being balanced. '
'Example: dhcp_load_type=networks')),
]
cfg.CONF.register_opts(AGENT_OPTS)
class Agent(model_base.BASEV2, models_v2.HasId):
@ -68,6 +88,8 @@ class Agent(model_base.BASEV2, models_v2.HasId):
description = sa.Column(sa.String(255))
# configurations: a json dict string, I think 4095 is enough
configurations = sa.Column(sa.String(4095), nullable=False)
# load - number of resources hosted by the agent
load = sa.Column(sa.Integer, default=0, nullable=False)
@property
def is_active(self):
@ -117,6 +139,16 @@ class AgentDbMixin(ext_agent.AgentPluginBase):
conf = {}
return conf
def _get_agent_load(self, agent):
configs = agent.get('configurations', {})
load_type = None
load = 0
if(agent['agent_type'] == constants.AGENT_TYPE_DHCP):
load_type = cfg.CONF.dhcp_load_type
if load_type:
load = int(configs.get(load_type, 0))
return load
def _make_agent_dict(self, agent, fields=None):
attr = ext_agent.RESOURCE_ATTRIBUTE_MAP.get(
ext_agent.RESOURCE_NAME + 's')
@ -178,6 +210,7 @@ class AgentDbMixin(ext_agent.AgentPluginBase):
configurations_dict = agent.get('configurations', {})
res['configurations'] = jsonutils.dumps(configurations_dict)
res['load'] = self._get_agent_load(agent)
current_time = timeutils.utcnow()
try:
agent_db = self._get_agent_by_type_and_host(

View File

@ -0,0 +1,39 @@
# Copyright 2015 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.
#
"""weight_scheduler
Revision ID: 1955efc66455
Revises: 35a0f3365720
Create Date: 2015-03-12 22:11:37.607390
"""
# revision identifiers, used by Alembic.
revision = '1955efc66455'
down_revision = '35a0f3365720'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.add_column('agents',
sa.Column('load', sa.Integer(),
default=0, nullable=False))
def downgrade():
op.drop_column('agents', 'load')

View File

@ -1 +1 @@
35a0f3365720
1955efc66455

View File

@ -0,0 +1,40 @@
# Copyright (c) 2015 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 abc
class BaseResourceFilter(object):
"""Encapsulate logic that is specific to the resource type."""
@abc.abstractmethod
def filter_agents(self, plugin, context, resource):
"""Return the agents that can host the resource."""
def bind(self, context, agents, resource_id):
"""Bind the resource to the agents."""
with context.session.begin(subtransactions=True):
res = {}
for agent in agents:
# Load is being incremented here to reflect latest agent load
# even within the agent report interval. This will be very
# much necessary when bulk resource creation happens within a
# agent report interval time.
# NOTE: The resource being bound might or might not be of the
# same type which is accounted for the load. It isn't a
# problem because "+ 1" here does not meant to predict
# precisely what the load of the agent will be. The value will
# be corrected by the agent on the next report interval.
res['load'] = agent.load + 1
agent.update(res)

View File

@ -0,0 +1,77 @@
# Copyright (c) 2015 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 abc
from operator import attrgetter
import random
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
class BaseScheduler(object):
"""The base scheduler (agnostic to resource type).
Child classes of BaseScheduler must define the
self.resource_filter to filter agents of
particular type.
"""
resource_filter = None
@abc.abstractmethod
def select(self, plugin, context, resource_hostable_agents,
num_agents_needed):
"""Return a subset of agents based on the specific scheduling logic."""
def schedule(self, plugin, context, resource):
"""Select and bind agents to a given resource."""
if not self.resource_filter:
return
# filter the agents that can host the resource
filtered_agents_dict = self.resource_filter.filter_agents(
plugin, context, resource)
num_agents = filtered_agents_dict['n_agents']
hostable_agents = filtered_agents_dict['hostable_agents']
chosen_agents = self.select(plugin, context, hostable_agents,
num_agents)
# bind the resource to the agents
self.resource_filter.bind(context, chosen_agents, resource['id'])
return chosen_agents
class BaseChanceScheduler(BaseScheduler):
"""Choose agents randomly."""
def __init__(self, resource_filter):
self.resource_filter = resource_filter
def select(self, plugin, context, resource_hostable_agents,
num_agents_needed):
chosen_agents = random.sample(resource_hostable_agents,
num_agents_needed)
return chosen_agents
class BaseWeightScheduler(BaseScheduler):
"""Choose agents based on load."""
def __init__(self, resource_filter):
self.resource_filter = resource_filter
def select(self, plugin, context, resource_hostable_agents,
num_agents_needed):
chosen_agents = sorted(resource_hostable_agents,
key=attrgetter('load'))[0:num_agents_needed]
return chosen_agents

View File

@ -13,7 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
import random
from oslo_config import cfg
from oslo_db import exception as db_exc
@ -24,80 +23,17 @@ 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 ChanceScheduler(object):
"""Allocate a DHCP agent for a network in a random way.
More sophisticated scheduler (similar to filter scheduler in nova?)
can be introduced later.
"""
def _schedule_bind_network(self, context, agents, network_id):
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()
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})
def schedule(self, plugin, context, network):
"""Schedule the network to active DHCP agent(s).
A list of scheduled agents is returned.
"""
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):
dhcp_agents = plugin.get_dhcp_agents_hosting_networks(
context, [network['id']], active=True)
if len(dhcp_agents) >= agents_per_network:
LOG.debug('Network %s is hosted already',
network['id'])
return
n_agents = agents_per_network - len(dhcp_agents)
enabled_dhcp_agents = plugin.get_agents_db(
context, filters={
'agent_type': [constants.AGENT_TYPE_DHCP],
'admin_state_up': [True]})
if not enabled_dhcp_agents:
LOG.warn(_LW('No more DHCP agents'))
return
active_dhcp_agents = [
agent for agent in set(enabled_dhcp_agents)
if agent not in dhcp_agents and plugin.is_eligible_agent(
context, True, agent)
]
if not active_dhcp_agents:
LOG.warn(_LW('No more DHCP agents'))
return
n_agents = min(len(active_dhcp_agents), n_agents)
chosen_agents = random.sample(active_dhcp_agents, n_agents)
self._schedule_bind_network(context, chosen_agents, network['id'])
return chosen_agents
class AutoScheduler(object):
def auto_schedule_networks(self, plugin, context, host):
"""Schedule non-hosted networks to the DHCP agent on
the specified 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
@ -132,5 +68,108 @@ class ChanceScheduler(object):
# do it outside transaction so particular scheduling results don't
# make other to fail
for agent, net_id in bindings_to_add:
self._schedule_bind_network(context, [agent], net_id)
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']], active=True)
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}

View File

@ -22,16 +22,14 @@ from neutron.db import agentschedulers_db
from neutron.db import common_db_mixin
from neutron.scheduler import dhcp_agent_scheduler
from neutron.tests.unit import test_dhcp_scheduler as test_dhcp_sch
from operator import attrgetter
# Required to generate tests from scenarios. Not compatible with nose.
load_tests = testscenarios.load_tests_apply_scenarios
class TestScheduleNetwork(test_dhcp_sch.TestDhcpSchedulerBaseTestCase,
agentschedulers_db.DhcpAgentSchedulerDbMixin,
agents_db.AgentDbMixin,
common_db_mixin.CommonDbMixin):
"""Test various scenarios for ChanceScheduler.schedule.
class BaseTestScheduleNetwork(object):
"""Base class which defines scenarios for schedulers.
agent_count
Number of dhcp agents (also number of hosts).
@ -96,6 +94,14 @@ class TestScheduleNetwork(test_dhcp_sch.TestDhcpSchedulerBaseTestCase,
expected_scheduled_agent_count=1)),
]
class TestChanceScheduleNetwork(test_dhcp_sch.TestDhcpSchedulerBaseTestCase,
agentschedulers_db.DhcpAgentSchedulerDbMixin,
agents_db.AgentDbMixin,
common_db_mixin.CommonDbMixin,
BaseTestScheduleNetwork):
"""Test various scenarios for ChanceScheduler.schedule."""
def test_schedule_network(self):
self.config(dhcp_agents_per_network=self.max_agents_per_network)
scheduler = dhcp_agent_scheduler.ChanceScheduler()
@ -111,9 +117,8 @@ class TestScheduleNetwork(test_dhcp_sch.TestDhcpSchedulerBaseTestCase,
if self.scheduled_agent_count:
# schedule the network
schedule_agents = active_agents[:self.scheduled_agent_count]
scheduler._schedule_bind_network(self.ctx, schedule_agents,
self.network_id)
scheduler.resource_filter.bind(self.ctx,
schedule_agents, self.network_id)
actual_scheduled_agents = scheduler.schedule(self, self.ctx,
self.network)
if self.expected_scheduled_agent_count:
@ -125,7 +130,53 @@ class TestScheduleNetwork(test_dhcp_sch.TestDhcpSchedulerBaseTestCase,
len(actual_scheduled_agents),
len(hosted_agents['agents']))
else:
self.assertIsNone(actual_scheduled_agents)
self.assertEqual([], actual_scheduled_agents)
class TestWeightScheduleNetwork(test_dhcp_sch.TestDhcpSchedulerBaseTestCase,
agentschedulers_db.DhcpAgentSchedulerDbMixin,
agents_db.AgentDbMixin,
common_db_mixin.CommonDbMixin,
BaseTestScheduleNetwork):
"""Test various scenarios for WeightScheduler.schedule."""
def test_weight_schedule_network(self):
self.config(dhcp_agents_per_network=self.max_agents_per_network)
scheduler = dhcp_agent_scheduler.WeightScheduler()
# create dhcp agents
hosts = ['host-%s' % i for i in range(self.agent_count)]
dhcp_agents = self._create_and_set_agents_down(
hosts, down_agent_count=self.down_agent_count)
active_agents = dhcp_agents[self.down_agent_count:]
unscheduled_active_agents = list(active_agents)
# schedule some agents before calling schedule
if self.scheduled_agent_count:
# schedule the network
schedule_agents = active_agents[:self.scheduled_agent_count]
scheduler.resource_filter.bind(self.ctx,
schedule_agents, self.network_id)
for agent in schedule_agents:
unscheduled_active_agents.remove(agent)
actual_scheduled_agents = scheduler.schedule(self, self.ctx,
self.network)
if self.expected_scheduled_agent_count:
sorted_unscheduled_active_agents = sorted(
unscheduled_active_agents,
key=attrgetter('load'))[0:self.expected_scheduled_agent_count]
self.assertItemsEqual(actual_scheduled_agents,
sorted_unscheduled_active_agents)
self.assertEqual(self.expected_scheduled_agent_count,
len(actual_scheduled_agents))
hosted_agents = self.list_dhcp_agents_hosting_network(
self.ctx, self.network_id)
self.assertEqual(self.scheduled_agent_count +
len(actual_scheduled_agents),
len(hosted_agents['agents']))
else:
self.assertEqual([], actual_scheduled_agents)
class TestAutoSchedule(test_dhcp_sch.TestDhcpSchedulerBaseTestCase,
@ -323,9 +374,9 @@ class TestAutoSchedule(test_dhcp_sch.TestDhcpSchedulerBaseTestCase,
agent_index = self._extract_index(agent)
for net in networks:
net_index = self._extract_index(net)
scheduler._schedule_bind_network(self.ctx,
[dhcp_agents[agent_index]],
self._networks[net_index])
scheduler.resource_filter.bind(self.ctx,
[dhcp_agents[agent_index]],
self._networks[net_index])
retval = scheduler.auto_schedule_networks(self, self.ctx,
hosts[host_index])

View File

@ -17,6 +17,8 @@ import contextlib
import datetime
import mock
from oslo_config import cfg
from oslo_utils import importutils
from oslo_utils import timeutils
import testscenarios
@ -80,7 +82,7 @@ class TestDhcpSchedulerBaseTestCase(testlib_api.SqlTestCase):
def _test_schedule_bind_network(self, agents, network_id):
scheduler = dhcp_agent_scheduler.ChanceScheduler()
scheduler._schedule_bind_network(self.ctx, agents, network_id)
scheduler.resource_filter.bind(self.ctx, agents, network_id)
results = self.ctx.session.query(
sched_db.NetworkDhcpAgentBinding).filter_by(
network_id=network_id).all()
@ -265,14 +267,106 @@ class TestNetworksFailover(TestDhcpSchedulerBaseTestCase,
self.assertIn('foo3', res_ids)
self.assertIn('foo4', res_ids)
def test_remove_networks_from_down_agents_catches_all(self):
with contextlib.nested(
mock.patch.object(
self, 'remove_network_from_dhcp_agent',
side_effect=Exception("Unexpected exception!")),
mock.patch.object(
self, '_filter_bindings',
return_value=[sched_db.NetworkDhcpAgentBinding(
network_id='foo', dhcp_agent_id='bar')])
):
self.remove_networks_from_down_agents()
class DHCPAgentWeightSchedulerTestCase(TestDhcpSchedulerBaseTestCase):
"""Unit test scenarios for WeightScheduler.schedule."""
hostc = {
'binary': 'neutron-dhcp-agent',
'host': 'host-c',
'topic': 'DHCP_AGENT',
'configurations': {'dhcp_driver': 'dhcp_driver',
'networks': 0,
'use_namespaces': True,
},
'agent_type': constants.AGENT_TYPE_DHCP}
hostd = {
'binary': 'neutron-dhcp-agent',
'host': 'host-d',
'topic': 'DHCP_AGENT',
'configurations': {'dhcp_driver': 'dhcp_driver',
'networks': 1,
'use_namespaces': True,
},
'agent_type': constants.AGENT_TYPE_DHCP}
def setUp(self):
super(DHCPAgentWeightSchedulerTestCase, self).setUp()
DB_PLUGIN_KLASS = 'neutron.plugins.ml2.plugin.Ml2Plugin'
cfg.CONF.set_override("core_plugin", DB_PLUGIN_KLASS)
cfg.CONF.set_override("network_scheduler_driver",
'neutron.scheduler.dhcp_agent_scheduler.WeightScheduler')
self.plugin = importutils.import_object('neutron.plugins.ml2.plugin.'
'Ml2Plugin')
self.plugin.network_scheduler = importutils.import_object(
'neutron.scheduler.dhcp_agent_scheduler.WeightScheduler'
)
cfg.CONF.set_override('dhcp_agents_per_network', 1)
cfg.CONF.set_override("dhcp_load_type", "networks")
def test_scheduler_one_agents_per_network(self):
cfg.CONF.set_override('dhcp_agents_per_network', 1)
self._save_networks(['1111'])
agents = self._get_agents(['host-c', 'host-d'])
self._save_agents(agents)
self.plugin.network_scheduler.schedule(self.plugin, self.ctx,
{'id': '1111'})
agents = self.plugin.get_dhcp_agents_hosting_networks(self.ctx,
['1111'])
self.assertEqual(1, len(agents))
def test_scheduler_two_agents_per_network(self):
cfg.CONF.set_override('dhcp_agents_per_network', 2)
self._save_networks(['1111'])
agents = self._get_agents(['host-c', 'host-d'])
self._save_agents(agents)
self.plugin.network_scheduler.schedule(self.plugin, self.ctx,
{'id': '1111'})
agents = self.plugin.get_dhcp_agents_hosting_networks(self.ctx,
['1111'])
self.assertEqual(2, len(agents))
def test_scheduler_no_active_agents(self):
self._save_networks(['1111'])
self.plugin.network_scheduler.schedule(self.plugin, self.ctx,
{'id': '1111'})
agents = self.plugin.get_dhcp_agents_hosting_networks(self.ctx,
['1111'])
self.assertEqual(0, len(agents))
def test_scheduler_equal_distribution(self):
cfg.CONF.set_override('dhcp_agents_per_network', 1)
self._save_networks(['1111', '2222', '3333'])
agents = self._get_agents(['host-c', 'host-d'])
self._save_agents(agents)
callback = agents_db.AgentExtRpcCallback()
callback.report_state(self.ctx,
agent_state={'agent_state': self.hostc},
time=timeutils.strtime())
callback.report_state(self.ctx,
agent_state={'agent_state': self.hostd},
time=timeutils.strtime())
self.plugin.network_scheduler.schedule(self.plugin, self.ctx,
{'id': '1111'})
agent1 = self.plugin.get_dhcp_agents_hosting_networks(self.ctx,
['1111'])
self.hostd['configurations']['networks'] = 2
callback.report_state(self.ctx,
agent_state={'agent_state': self.hostd},
time=timeutils.strtime())
self.plugin.network_scheduler.schedule(self.plugin, self.ctx,
{'id': '2222'})
agent2 = self.plugin.get_dhcp_agents_hosting_networks(self.ctx,
['2222'])
self.hostc['configurations']['networks'] = 4
callback.report_state(self.ctx,
agent_state={'agent_state': self.hostc},
time=timeutils.strtime())
self.plugin.network_scheduler.schedule(self.plugin, self.ctx,
{'id': '3333'})
agent3 = self.plugin.get_dhcp_agents_hosting_networks(self.ctx,
['3333'])
self.assertEqual('host-c', agent1[0]['host'])
self.assertEqual('host-c', agent2[0]['host'])
self.assertEqual('host-d', agent3[0]['host'])