# # 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()]()