Merge "Improve scheduling L3/DHCP agents, missing lower binding indexes" into stable/wallaby

This commit is contained in:
Zuul 2023-02-20 21:20:13 +00:00 committed by Gerrit Code Review
commit 94c2c92f8c
9 changed files with 75 additions and 30 deletions

View File

@ -78,3 +78,6 @@ IDPOOL_SELECT_SIZE = 100
AUTO_DELETE_PORT_OWNERS = [constants.DEVICE_OWNER_DHCP, AUTO_DELETE_PORT_OWNERS = [constants.DEVICE_OWNER_DHCP,
constants.DEVICE_OWNER_DISTRIBUTED, constants.DEVICE_OWNER_DISTRIBUTED,
constants.DEVICE_OWNER_AGENT_GW] constants.DEVICE_OWNER_AGENT_GW]
# The lowest binding index for L3 agents and DHCP agents.
LOWEST_AGENT_BINDING_INDEX = 1

View File

@ -25,9 +25,9 @@ from oslo_log import log as logging
import oslo_messaging import oslo_messaging
from neutron.agent.common import utils as agent_utils from neutron.agent.common import utils as agent_utils
from neutron.common import _constants as n_const
from neutron.conf.db import l3_agentschedulers_db from neutron.conf.db import l3_agentschedulers_db
from neutron.db import agentschedulers_db from neutron.db import agentschedulers_db
from neutron.db.models import l3agent as rb_model
from neutron.extensions import l3agentscheduler from neutron.extensions import l3agentscheduler
from neutron.extensions import router_availability_zone as router_az from neutron.extensions import router_availability_zone as router_az
from neutron.objects import agent as ag_obj from neutron.objects import agent as ag_obj
@ -522,7 +522,7 @@ class L3AgentSchedulerDbMixin(l3agentscheduler.L3AgentSchedulerPluginBase,
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)
return base_scheduler.get_vacant_binding_index( return base_scheduler.get_vacant_binding_index(
num_agents, bindings, rb_model.LOWEST_BINDING_INDEX, num_agents, bindings, n_const.LOWEST_AGENT_BINDING_INDEX,
force_scheduling=is_manual_scheduling) force_scheduling=is_manual_scheduling)

View File

@ -15,10 +15,9 @@ from neutron_lib.db import model_base
import sqlalchemy as sa import sqlalchemy as sa
from sqlalchemy import orm from sqlalchemy import orm
from neutron.common import _constants as n_const
from neutron.db.models import agent as agent_model from neutron.db.models import agent as agent_model
LOWEST_BINDING_INDEX = 1
class RouterL3AgentBinding(model_base.BASEV2): class RouterL3AgentBinding(model_base.BASEV2):
"""Represents binding between neutron routers and L3 agents.""" """Represents binding between neutron routers and L3 agents."""
@ -37,5 +36,6 @@ class RouterL3AgentBinding(model_base.BASEV2):
l3_agent_id = sa.Column(sa.String(36), l3_agent_id = sa.Column(sa.String(36),
sa.ForeignKey("agents.id", ondelete='CASCADE'), sa.ForeignKey("agents.id", ondelete='CASCADE'),
primary_key=True) primary_key=True)
binding_index = sa.Column(sa.Integer, nullable=False, binding_index = sa.Column(
server_default=str(LOWEST_BINDING_INDEX)) sa.Integer, nullable=False,
server_default=str(n_const.LOWEST_AGENT_BINDING_INDEX))

View File

@ -14,12 +14,10 @@ from neutron_lib.db import model_base
import sqlalchemy as sa import sqlalchemy as sa
from sqlalchemy import orm from sqlalchemy import orm
from neutron.common import _constants as n_const
from neutron.db.models import agent as agent_model from neutron.db.models import agent as agent_model
LOWEST_BINDING_INDEX = 1
class NetworkDhcpAgentBinding(model_base.BASEV2): class NetworkDhcpAgentBinding(model_base.BASEV2):
"""Represents binding between neutron networks and DHCP agents.""" """Represents binding between neutron networks and DHCP agents."""
@ -38,5 +36,6 @@ class NetworkDhcpAgentBinding(model_base.BASEV2):
sa.ForeignKey("agents.id", sa.ForeignKey("agents.id",
ondelete='CASCADE'), ondelete='CASCADE'),
primary_key=True) primary_key=True)
binding_index = sa.Column(sa.Integer, nullable=False, binding_index = sa.Column(
server_default=str(LOWEST_BINDING_INDEX)) sa.Integer, nullable=False,
server_default=str(n_const.LOWEST_AGENT_BINDING_INDEX))

View File

@ -17,6 +17,7 @@ from sqlalchemy.orm import joinedload
from sqlalchemy import sql from sqlalchemy import sql
from neutron.common import _constants as n_const
from neutron.db.models import agent as agent_model from neutron.db.models import agent as agent_model
from neutron.db.models import l3_attrs from neutron.db.models import l3_attrs
from neutron.db.models import l3agent from neutron.db.models import l3agent
@ -36,7 +37,7 @@ class RouterL3AgentBinding(base.NeutronDbObject):
'router_id': common_types.UUIDField(), 'router_id': common_types.UUIDField(),
'l3_agent_id': common_types.UUIDField(), 'l3_agent_id': common_types.UUIDField(),
'binding_index': obj_fields.IntegerField( 'binding_index': obj_fields.IntegerField(
default=l3agent.LOWEST_BINDING_INDEX), default=n_const.LOWEST_AGENT_BINDING_INDEX),
} }
# TODO(ihrachys) return OVO objects not models # TODO(ihrachys) return OVO objects not models

View File

@ -99,24 +99,44 @@ def get_vacant_binding_index(num_agents, bindings, lowest_binding_index,
always return an index, even if this number always return an index, even if this number
exceeds the maximum configured number of agents. exceeds the maximum configured number of agents.
""" """
binding_indices = [b.binding_index for b in bindings] def get_open_slots(binding_indices, lowest_binding_index, max_number):
all_indices = set(range(lowest_binding_index, num_agents + 1)) """Returns an ordered list of free slots
open_slots = sorted(list(all_indices - set(binding_indices)))
This list starts from the lowest available binding index. The number
of open slots and "binding_indices" (those already taken), must be
equal to "max_number". The list returned can be [], if
len(max_number) == len(binding_indices) (that means there are no free
slots).
"""
# NOTE(ralonsoh): check LP#2006496 for more context. The DHCP/router
# binding indexes could not be a sequential list starting from
# lowest_binding_index (that is usually 1).
open_slots = set(binding_indices)
idx = lowest_binding_index
while len(open_slots) < max_number:
# Increase sequentially the "open_slots" set until we have the
# required number of slots, that is "num_agents".
open_slots.add(idx)
idx += 1
# Remove those indices already used.
open_slots -= set(binding_indices)
return sorted(list(open_slots))
binding_indices = [b.binding_index for b in bindings]
open_slots = get_open_slots(binding_indices, lowest_binding_index,
num_agents)
if open_slots: if open_slots:
return open_slots[0] return open_slots[0]
if not force_scheduling: if not force_scheduling:
return -1 return -1
# Last chance: if this is a manual scheduling, we're gonna allow # Last chance: if this is a manual scheduling, we're going to allow
# creation of a binding_index even if it will exceed # creation of a binding_index even if it will exceed
# dhcp_agents_per_network. # dhcp_agents_per_network/max_l3_agents_per_router.
if max(binding_indices) == len(binding_indices): while not open_slots:
return max(binding_indices) + 1 num_agents += 1
else: open_slots = get_open_slots(binding_indices, lowest_binding_index,
# Find binding index set gaps and return first free one. num_agents)
all_indices = set(range(lowest_binding_index, return open_slots[0]
max(binding_indices) + 1))
open_slots = sorted(list(all_indices - set(binding_indices)))
return open_slots[0]

View File

@ -25,7 +25,7 @@ from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
from neutron.agent.common import utils as agent_utils from neutron.agent.common import utils as agent_utils
from neutron.db.network_dhcp_agent_binding import models as ndab_model from neutron.common import _constants as n_const
from neutron.objects import agent as agent_obj from neutron.objects import agent as agent_obj
from neutron.objects import network from neutron.objects import network
from neutron.scheduler import base_resource_filter from neutron.scheduler import base_resource_filter
@ -195,7 +195,7 @@ class DhcpFilter(base_resource_filter.BaseResourceFilter):
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( return base_scheduler.get_vacant_binding_index(
num_agents, bindings, ndab_model.LOWEST_BINDING_INDEX, num_agents, bindings, n_const.LOWEST_AGENT_BINDING_INDEX,
force_scheduling=force_scheduling) force_scheduling=force_scheduling)
def bind(self, context, agents, network_id, force_scheduling=False): def bind(self, context, agents, network_id, force_scheduling=False):
@ -205,7 +205,7 @@ class DhcpFilter(base_resource_filter.BaseResourceFilter):
for agent in agents: for agent in agents:
binding_index = self.get_vacant_network_dhcp_agent_binding_index( binding_index = self.get_vacant_network_dhcp_agent_binding_index(
context, network_id, force_scheduling) context, network_id, force_scheduling)
if binding_index < ndab_model.LOWEST_BINDING_INDEX: if binding_index < n_const.LOWEST_AGENT_BINDING_INDEX:
LOG.debug('Unable to find a vacant binding_index for ' LOG.debug('Unable to find a vacant binding_index for '
'network %(network_id)s and agent %(agent_id)s', 'network %(network_id)s and agent %(agent_id)s',
{'network_id': network_id, {'network_id': network_id,

View File

@ -27,6 +27,7 @@ from oslo_config import cfg
from oslo_db import exception as db_exc from oslo_db import exception as db_exc
from oslo_log import log as logging from oslo_log import log as logging
from neutron.common import _constants as n_const
from neutron.common import utils from neutron.common import utils
from neutron.conf.db import l3_hamode_db from neutron.conf.db import l3_hamode_db
from neutron.db.models import l3agent as rb_model from neutron.db.models import l3agent as rb_model
@ -186,7 +187,7 @@ class L3Scheduler(object, metaclass=abc.ABCMeta):
return return
if not is_ha: if not is_ha:
binding_index = rb_model.LOWEST_BINDING_INDEX binding_index = n_const.LOWEST_AGENT_BINDING_INDEX
if rb_obj.RouterL3AgentBinding.objects_exist( if rb_obj.RouterL3AgentBinding.objects_exist(
context, router_id=router_id, binding_index=binding_index): context, router_id=router_id, binding_index=binding_index):
LOG.debug('Non-HA router %s has already been scheduled', LOG.debug('Non-HA router %s has already been scheduled',
@ -195,7 +196,7 @@ class L3Scheduler(object, metaclass=abc.ABCMeta):
else: else:
binding_index = plugin.get_vacant_binding_index( binding_index = plugin.get_vacant_binding_index(
context, router_id, is_manual_scheduling) context, router_id, is_manual_scheduling)
if binding_index < rb_model.LOWEST_BINDING_INDEX: if binding_index < n_const.LOWEST_AGENT_BINDING_INDEX:
LOG.debug('Unable to find a vacant binding_index for ' LOG.debug('Unable to find a vacant binding_index for '
'router %(router_id)s and agent %(agent_id)s', 'router %(router_id)s and agent %(agent_id)s',
{'router_id': router_id, {'router_id': router_id,

View File

@ -38,6 +38,21 @@ class GetVacantBindingFilterCase(base.BaseTestCase):
3, [mock.Mock(binding_index=1), mock.Mock(binding_index=3)], 1) 3, [mock.Mock(binding_index=1), mock.Mock(binding_index=3)], 1)
self.assertEqual(2, ret) self.assertEqual(2, ret)
# Binding list starting in 2, two elements, required three.
ret = base_scheduler.get_vacant_binding_index(
3, [mock.Mock(binding_index=2), mock.Mock(binding_index=3)], 1)
self.assertEqual(1, ret)
# Binding list starting in 2, two elements, required two.
ret = base_scheduler.get_vacant_binding_index(
2, [mock.Mock(binding_index=2), mock.Mock(binding_index=3)], 1)
self.assertEqual(-1, ret)
# Binding list starting in 2, two elements, required one.
ret = base_scheduler.get_vacant_binding_index(
1, [mock.Mock(binding_index=2), mock.Mock(binding_index=3)], 1)
self.assertEqual(-1, ret)
def test_get_vacant_binding_index_force_scheduling(self): def test_get_vacant_binding_index_force_scheduling(self):
ret = base_scheduler.get_vacant_binding_index( ret = base_scheduler.get_vacant_binding_index(
3, [mock.Mock(binding_index=1), mock.Mock(binding_index=2), 3, [mock.Mock(binding_index=1), mock.Mock(binding_index=2),
@ -50,3 +65,9 @@ class GetVacantBindingFilterCase(base.BaseTestCase):
mock.Mock(binding_index=3), mock.Mock(binding_index=4), mock.Mock(binding_index=3), mock.Mock(binding_index=4),
mock.Mock(binding_index=5)], 1, force_scheduling=True) mock.Mock(binding_index=5)], 1, force_scheduling=True)
self.assertEqual(6, ret) self.assertEqual(6, ret)
ret = base_scheduler.get_vacant_binding_index(
3, [mock.Mock(binding_index=2), mock.Mock(binding_index=3),
mock.Mock(binding_index=4), mock.Mock(binding_index=5),
mock.Mock(binding_index=6)], 1, force_scheduling=True)
self.assertEqual(1, ret)