Merge "DHCP Service LoadBalancing Scheduler"

This commit is contained in:
Jenkins 2015-03-19 00:59:00 +00:00 committed by Gerrit Code Review
commit 1e9bea3a30
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'])