L3 agent scheduler should return a valid index if manual scheduling
When retrieving a vacant L3 agent binding index, if "is_manual_scheduling" is set, the method "get_vacant_binding_index" should always return a valid binding index. If the existing binding indexes are sequentially aligned, the method will return a new one on top; if there is a gap in the binding indexes list, the first free index will be returned. Closes-Bug: #1884906 Change-Id: I0a89bca0734d3e735fb357e488f85589e81d709f
This commit is contained in:
parent
ed56429548
commit
2bb514f2d7
|
@ -34,6 +34,7 @@ from neutron.objects import agent as ag_obj
|
||||||
from neutron.objects import base as base_obj
|
from neutron.objects import base as base_obj
|
||||||
from neutron.objects import l3agent as rb_obj
|
from neutron.objects import l3agent as rb_obj
|
||||||
from neutron.objects import router as l3_objs
|
from neutron.objects import router as l3_objs
|
||||||
|
from neutron.scheduler import base_scheduler
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
@ -520,21 +521,9 @@ class L3AgentSchedulerDbMixin(l3agentscheduler.L3AgentSchedulerPluginBase,
|
||||||
pager = base_obj.Pager(sorts=[('binding_index', True)])
|
pager = base_obj.Pager(sorts=[('binding_index', True)])
|
||||||
bindings = rb_obj.RouterL3AgentBinding.get_objects(
|
bindings = rb_obj.RouterL3AgentBinding.get_objects(
|
||||||
context, _pager=pager, router_id=router_id)
|
context, _pager=pager, router_id=router_id)
|
||||||
binding_indices = [b.binding_index for b in bindings]
|
return base_scheduler.get_vacant_binding_index(
|
||||||
all_indicies = set(range(rb_model.LOWEST_BINDING_INDEX,
|
num_agents, bindings, rb_model.LOWEST_BINDING_INDEX,
|
||||||
num_agents + 1))
|
force_scheduling=is_manual_scheduling)
|
||||||
open_slots = sorted(list(all_indicies - set(binding_indices)))
|
|
||||||
|
|
||||||
if open_slots:
|
|
||||||
return open_slots[0]
|
|
||||||
|
|
||||||
# Last chance: if this is a manual scheduling, we're gonna allow
|
|
||||||
# creation of a binding_index even if it will exceed
|
|
||||||
# max_l3_agents_per_router.
|
|
||||||
if is_manual_scheduling:
|
|
||||||
return max(all_indicies) + 1
|
|
||||||
|
|
||||||
return -1
|
|
||||||
|
|
||||||
|
|
||||||
class AZL3AgentSchedulerDbMixin(L3AgentSchedulerDbMixin,
|
class AZL3AgentSchedulerDbMixin(L3AgentSchedulerDbMixin,
|
||||||
|
|
|
@ -83,3 +83,40 @@ class BaseWeightScheduler(BaseScheduler):
|
||||||
chosen_agents = sorted(resource_hostable_agents,
|
chosen_agents = sorted(resource_hostable_agents,
|
||||||
key=attrgetter('load'))[0:num_agents_needed]
|
key=attrgetter('load'))[0:num_agents_needed]
|
||||||
return chosen_agents
|
return chosen_agents
|
||||||
|
|
||||||
|
|
||||||
|
def get_vacant_binding_index(num_agents, bindings, lowest_binding_index,
|
||||||
|
force_scheduling=False):
|
||||||
|
"""Return a vacant binding_index to use and whether or not it exists.
|
||||||
|
|
||||||
|
This method can be used with DHCP and L3 agent schedulers. It will return
|
||||||
|
the lowest vacant index for one of those agents.
|
||||||
|
:param num_agents: (int) number of agents (DHCP, L3) already scheduled
|
||||||
|
:param bindings: (NetworkDhcpAgentBinding, RouterL3AgentBinding) agent
|
||||||
|
binding object, must have "binding_index" field.
|
||||||
|
:param lowest_binding_index: (int) lowest index number to be scheduled.
|
||||||
|
:param force_scheduling: (optional)(boolean) if enabled, the method will
|
||||||
|
always return an index, even if this number
|
||||||
|
exceeds the maximum configured number of agents.
|
||||||
|
"""
|
||||||
|
binding_indices = [b.binding_index for b in bindings]
|
||||||
|
all_indices = set(range(lowest_binding_index, num_agents + 1))
|
||||||
|
open_slots = sorted(list(all_indices - set(binding_indices)))
|
||||||
|
|
||||||
|
if open_slots:
|
||||||
|
return open_slots[0]
|
||||||
|
|
||||||
|
if not force_scheduling:
|
||||||
|
return -1
|
||||||
|
|
||||||
|
# Last chance: if this is a manual scheduling, we're gonna allow
|
||||||
|
# creation of a binding_index even if it will exceed
|
||||||
|
# dhcp_agents_per_network.
|
||||||
|
if max(binding_indices) == len(binding_indices):
|
||||||
|
return max(binding_indices) + 1
|
||||||
|
else:
|
||||||
|
# Find binding index set gaps and return first free one.
|
||||||
|
all_indices = set(range(lowest_binding_index,
|
||||||
|
max(binding_indices) + 1))
|
||||||
|
open_slots = sorted(list(all_indices - set(binding_indices)))
|
||||||
|
return open_slots[0]
|
||||||
|
|
|
@ -192,32 +192,11 @@ class DhcpFilter(base_resource_filter.BaseResourceFilter):
|
||||||
num_agents = agent_obj.Agent.count(
|
num_agents = agent_obj.Agent.count(
|
||||||
context, agent_type=constants.AGENT_TYPE_DHCP)
|
context, agent_type=constants.AGENT_TYPE_DHCP)
|
||||||
num_agents = min(num_agents, cfg.CONF.dhcp_agents_per_network)
|
num_agents = min(num_agents, cfg.CONF.dhcp_agents_per_network)
|
||||||
|
|
||||||
bindings = network.NetworkDhcpAgentBinding.get_objects(
|
bindings = network.NetworkDhcpAgentBinding.get_objects(
|
||||||
context, network_id=network_id)
|
context, network_id=network_id)
|
||||||
|
return base_scheduler.get_vacant_binding_index(
|
||||||
binding_indices = [b.binding_index for b in bindings]
|
num_agents, bindings, ndab_model.LOWEST_BINDING_INDEX,
|
||||||
all_indices = set(range(ndab_model.LOWEST_BINDING_INDEX,
|
force_scheduling=force_scheduling)
|
||||||
num_agents + 1))
|
|
||||||
open_slots = sorted(list(all_indices - set(binding_indices)))
|
|
||||||
|
|
||||||
if open_slots:
|
|
||||||
return open_slots[0]
|
|
||||||
|
|
||||||
if not force_scheduling:
|
|
||||||
return -1
|
|
||||||
|
|
||||||
# Last chance: if this is a manual scheduling, we're gonna allow
|
|
||||||
# creation of a binding_index even if it will exceed
|
|
||||||
# dhcp_agents_per_network.
|
|
||||||
if max(binding_indices) == len(binding_indices):
|
|
||||||
return max(binding_indices) + 1
|
|
||||||
else:
|
|
||||||
# Find binding index set gaps and return first free one.
|
|
||||||
all_indices = set(range(ndab_model.LOWEST_BINDING_INDEX,
|
|
||||||
max(binding_indices) + 1))
|
|
||||||
open_slots = sorted(list(all_indices - set(binding_indices)))
|
|
||||||
return open_slots[0]
|
|
||||||
|
|
||||||
def bind(self, context, agents, network_id, force_scheduling=False):
|
def bind(self, context, agents, network_id, force_scheduling=False):
|
||||||
"""Bind the network to the agents."""
|
"""Bind the network to the agents."""
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
# Copyright 2020 Red Hat Inc.
|
||||||
|
#
|
||||||
|
# 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 unittest import mock
|
||||||
|
|
||||||
|
from neutron.scheduler import base_scheduler
|
||||||
|
|
||||||
|
from neutron.tests import base
|
||||||
|
|
||||||
|
|
||||||
|
class GetVacantBindingFilterCase(base.BaseTestCase):
|
||||||
|
|
||||||
|
def test_get_vacant_binding_index_no_agents(self):
|
||||||
|
ret = base_scheduler.get_vacant_binding_index(0, [], 1)
|
||||||
|
self.assertEqual(-1, ret)
|
||||||
|
|
||||||
|
def test_get_vacant_binding_index_several_agents(self):
|
||||||
|
ret = base_scheduler.get_vacant_binding_index(1, [], 1)
|
||||||
|
self.assertEqual(1, ret)
|
||||||
|
|
||||||
|
ret = base_scheduler.get_vacant_binding_index(
|
||||||
|
1, [mock.Mock(binding_index=1)], 1)
|
||||||
|
self.assertEqual(-1, ret)
|
||||||
|
|
||||||
|
ret = base_scheduler.get_vacant_binding_index(
|
||||||
|
3, [mock.Mock(binding_index=1), mock.Mock(binding_index=3)], 1)
|
||||||
|
self.assertEqual(2, ret)
|
||||||
|
|
||||||
|
def test_get_vacant_binding_index_force_scheduling(self):
|
||||||
|
ret = base_scheduler.get_vacant_binding_index(
|
||||||
|
3, [mock.Mock(binding_index=1), mock.Mock(binding_index=2),
|
||||||
|
mock.Mock(binding_index=3), mock.Mock(binding_index=5),
|
||||||
|
mock.Mock(binding_index=7)], 1, force_scheduling=True)
|
||||||
|
self.assertEqual(4, ret)
|
||||||
|
|
||||||
|
ret = base_scheduler.get_vacant_binding_index(
|
||||||
|
3, [mock.Mock(binding_index=1), mock.Mock(binding_index=2),
|
||||||
|
mock.Mock(binding_index=3), mock.Mock(binding_index=4),
|
||||||
|
mock.Mock(binding_index=5)], 1, force_scheduling=True)
|
||||||
|
self.assertEqual(6, ret)
|
|
@ -31,7 +31,6 @@ from neutron.objects import agent
|
||||||
from neutron.objects import network as network_obj
|
from neutron.objects import network as network_obj
|
||||||
from neutron.scheduler import dhcp_agent_scheduler
|
from neutron.scheduler import dhcp_agent_scheduler
|
||||||
from neutron.services.segments import db as segments_service_db
|
from neutron.services.segments import db as segments_service_db
|
||||||
from neutron.tests import base
|
|
||||||
from neutron.tests.common import helpers
|
from neutron.tests.common import helpers
|
||||||
from neutron.tests.unit.plugins.ml2 import test_plugin
|
from neutron.tests.unit.plugins.ml2 import test_plugin
|
||||||
from neutron.tests.unit import testlib_api
|
from neutron.tests.unit import testlib_api
|
||||||
|
@ -931,61 +930,3 @@ class DHCPAgentAZAwareWeightSchedulerTestCase(TestDhcpSchedulerBaseTestCase):
|
||||||
# which is also not in the same az as the first selected agent.
|
# which is also not in the same az as the first selected agent.
|
||||||
self.assertEqual('az2-host2', agents_select[1]['host'])
|
self.assertEqual('az2-host2', agents_select[1]['host'])
|
||||||
self.assertEqual('az2', agents_select[1]['availability_zone'])
|
self.assertEqual('az2', agents_select[1]['availability_zone'])
|
||||||
|
|
||||||
|
|
||||||
class DhcpFilterTestCase(base.BaseTestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(DhcpFilterTestCase, self).setUp()
|
|
||||||
self.mock_agent_count = mock.patch.object(agent.Agent, 'count').start()
|
|
||||||
self.mock_dhcpagent_bindings = mock.patch.object(
|
|
||||||
network_obj.NetworkDhcpAgentBinding, 'get_objects').start()
|
|
||||||
cfg.CONF.set_override('dhcp_agents_per_network', 3)
|
|
||||||
self.dhcp_filter = dhcp_agent_scheduler.DhcpFilter()
|
|
||||||
|
|
||||||
def test_get_vacant_network_dhcp_agent_binding_index_no_agents(self):
|
|
||||||
self.mock_agent_count.return_value = 0
|
|
||||||
self.mock_dhcpagent_bindings.return_value = []
|
|
||||||
ret = self.dhcp_filter.get_vacant_network_dhcp_agent_binding_index(
|
|
||||||
mock.ANY, mock.ANY, False)
|
|
||||||
self.assertEqual(-1, ret)
|
|
||||||
|
|
||||||
def test_get_vacant_network_dhcp_agent_binding_index_several_agents(self):
|
|
||||||
self.mock_agent_count.return_value = 1
|
|
||||||
self.mock_dhcpagent_bindings.return_value = []
|
|
||||||
ret = self.dhcp_filter.get_vacant_network_dhcp_agent_binding_index(
|
|
||||||
mock.ANY, mock.ANY, False)
|
|
||||||
self.assertEqual(1, ret)
|
|
||||||
|
|
||||||
self.mock_agent_count.return_value = 1
|
|
||||||
self.mock_dhcpagent_bindings.return_value = [
|
|
||||||
mock.Mock(binding_index=1)]
|
|
||||||
ret = self.dhcp_filter.get_vacant_network_dhcp_agent_binding_index(
|
|
||||||
mock.ANY, mock.ANY, False)
|
|
||||||
self.assertEqual(-1, ret)
|
|
||||||
|
|
||||||
self.mock_agent_count.return_value = 3
|
|
||||||
self.mock_dhcpagent_bindings.return_value = [
|
|
||||||
mock.Mock(binding_index=1), mock.Mock(binding_index=3)]
|
|
||||||
ret = self.dhcp_filter.get_vacant_network_dhcp_agent_binding_index(
|
|
||||||
mock.ANY, mock.ANY, False)
|
|
||||||
self.assertEqual(2, ret)
|
|
||||||
|
|
||||||
def test_get_vacant_network_dhcp_agent_binding_index_force_sched(self):
|
|
||||||
self.mock_agent_count.return_value = 3
|
|
||||||
self.mock_dhcpagent_bindings.return_value = [
|
|
||||||
mock.Mock(binding_index=1), mock.Mock(binding_index=2),
|
|
||||||
mock.Mock(binding_index=3), mock.Mock(binding_index=5),
|
|
||||||
mock.Mock(binding_index=7)]
|
|
||||||
ret = self.dhcp_filter.get_vacant_network_dhcp_agent_binding_index(
|
|
||||||
mock.ANY, mock.ANY, True)
|
|
||||||
self.assertEqual(4, ret)
|
|
||||||
|
|
||||||
self.mock_agent_count.return_value = 3
|
|
||||||
self.mock_dhcpagent_bindings.return_value = [
|
|
||||||
mock.Mock(binding_index=1), mock.Mock(binding_index=2),
|
|
||||||
mock.Mock(binding_index=3), mock.Mock(binding_index=4),
|
|
||||||
mock.Mock(binding_index=5)]
|
|
||||||
ret = self.dhcp_filter.get_vacant_network_dhcp_agent_binding_index(
|
|
||||||
mock.ANY, mock.ANY, True)
|
|
||||||
self.assertEqual(6, ret)
|
|
||||||
|
|
Loading…
Reference in New Issue