neutron/neutron/scheduler/l3_ovn_scheduler.py

153 lines
5.9 KiB
Python

#
# 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
import copy
import random
from oslo_log import log
from neutron.common.ovn import constants as ovn_const
from neutron.common.ovn import utils
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
LOG = log.getLogger(__name__)
OVN_SCHEDULER_CHANCE = 'chance'
OVN_SCHEDULER_LEAST_LOADED = 'leastloaded'
class OVNGatewayScheduler(object, metaclass=abc.ABCMeta):
def __init__(self):
pass
@abc.abstractmethod
def select(self, nb_idl, sb_idl, gateway_name, candidates=None):
"""Schedule the gateway port of a router to an OVN chassis.
Schedule the gateway router port only if it is not already
scheduled.
"""
def filter_existing_chassis(self, nb_idl, gw_chassis,
physnet, chassis_physnets,
existing_chassis):
chassis_list = copy.copy(existing_chassis)
for chassis_name in existing_chassis:
if utils.is_gateway_chassis_invalid(chassis_name, gw_chassis,
physnet, chassis_physnets):
LOG.debug("Chassis %(chassis)s is invalid for scheduling "
"router in physnet: %(physnet)s.",
{'chassis': chassis_name,
'physnet': physnet})
chassis_list.remove(chassis_name)
return chassis_list
def _schedule_gateway(self, nb_idl, sb_idl, gateway_name, candidates,
existing_chassis):
existing_chassis = existing_chassis or []
candidates = candidates or self._get_chassis_candidates(sb_idl)
candidates = list(set(candidates) - set(existing_chassis))
# If no candidates, or gateway scheduled on MAX_GATEWAY_CHASSIS nodes
# or all candidates in existing_chassis, return existing_chassis.
# Otherwise, if more candidates present, then schedule them.
if existing_chassis:
if not candidates or (
len(existing_chassis) == ovn_const.MAX_GW_CHASSIS):
return existing_chassis
if not candidates:
return [ovn_const.OVN_GATEWAY_INVALID_CHASSIS]
chassis_count = ovn_const.MAX_GW_CHASSIS - len(existing_chassis)
# The actual binding of the gateway to a chassis via the options
# column or gateway_chassis column in the OVN_Northbound is done
# by the caller
chassis = self._select_gateway_chassis(
nb_idl, candidates)[:chassis_count]
# priority of existing chassis is higher than candidates
chassis = existing_chassis + chassis
LOG.debug("Gateway %s scheduled on chassis %s",
gateway_name, chassis)
return chassis
@abc.abstractmethod
def _select_gateway_chassis(self, nb_idl, candidates):
"""Choose a chassis from candidates based on a specific policy."""
def _get_chassis_candidates(self, sb_idl):
# TODO(azbiswas): Allow selection of a specific type of chassis when
# the upstream code merges.
# return (sb_idl.get_all_chassis('gateway_router') or
# sb_idl.get_all_chassis())
return sb_idl.get_all_chassis()
class OVNGatewayChanceScheduler(OVNGatewayScheduler):
"""Randomly select an chassis for a gateway port of a router"""
def select(self, nb_idl, sb_idl, gateway_name, candidates=None,
existing_chassis=None):
return self._schedule_gateway(nb_idl, sb_idl, gateway_name,
candidates, existing_chassis)
def _select_gateway_chassis(self, nb_idl, candidates):
candidates = copy.deepcopy(candidates)
random.shuffle(candidates)
return candidates
class OVNGatewayLeastLoadedScheduler(OVNGatewayScheduler):
"""Select the least loaded chassis for a gateway port of a router"""
def select(self, nb_idl, sb_idl, gateway_name, candidates=None,
existing_chassis=None):
return self._schedule_gateway(nb_idl, sb_idl, gateway_name,
candidates, existing_chassis)
@staticmethod
def _get_chassis_load_by_prios(chassis_info):
"""Retrieve the amount of ports by priorities hosted in the chassis.
@param chassis_info: list of (port, prio) hosted by this chassis
@type chassis_info: []
@return: A list of (prio, number_of_ports) tuples.
"""
chassis_load = {}
for lrp, prio in chassis_info:
chassis_load[prio] = chassis_load.get(prio, 0) + 1
return chassis_load.items()
@staticmethod
def _get_chassis_load(chassis):
chassis_ports_prios = chassis[1]
return sorted(
OVNGatewayLeastLoadedScheduler._get_chassis_load_by_prios(
chassis_ports_prios), reverse=True)
def _select_gateway_chassis(self, nb_idl, candidates):
chassis_bindings = nb_idl.get_all_chassis_gateway_bindings(candidates)
return [chassis for chassis, load in sorted(chassis_bindings.items(),
key=OVNGatewayLeastLoadedScheduler._get_chassis_load)]
OVN_SCHEDULER_STR_TO_CLASS = {
OVN_SCHEDULER_CHANCE: OVNGatewayChanceScheduler,
OVN_SCHEDULER_LEAST_LOADED: OVNGatewayLeastLoadedScheduler}
def get_scheduler():
return OVN_SCHEDULER_STR_TO_CLASS[ovn_conf.get_ovn_l3_scheduler()]()