Merge "[OVN][Placement] Add a SB Chassis event to track changes in BW config"
This commit is contained in:
commit
2a3f7aac7f
|
@ -276,6 +276,28 @@ neutron-openvswitch-agent. However look out for:
|
||||||
resource_provider_bandwidths = ens5:40000000:40000000,ens6:40000000:40000000,...
|
resource_provider_bandwidths = ens5:40000000:40000000,ens6:40000000:40000000,...
|
||||||
#resource_provider_inventory_defaults = step_size:1000,...
|
#resource_provider_inventory_defaults = step_size:1000,...
|
||||||
|
|
||||||
|
OVN chassis config
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Bandwidth config values are stored in each SB chassis register, in
|
||||||
|
"external_ids:ovn-cms-options". The configuration options are the same as in
|
||||||
|
SR-IOV and OVS agents. This is how the values are registered:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ root@dev20:~# ovs-vsctl list Open_vSwitch
|
||||||
|
...
|
||||||
|
external_ids : {hostname=dev20.fistro.com, \
|
||||||
|
ovn-cms-options="resource_provider_bandwidths=br-ex:1001:2000;br-ex2:3000:4000, \
|
||||||
|
resource_provider_inventory_defaults=allocation_ratio:1.0;min_unit:10, \
|
||||||
|
resource_provider_hypervisors=br-ex:dev20.fistro.com;br-ex2:dev20.fistro.com", \
|
||||||
|
rundir="/var/run/openvswitch", \
|
||||||
|
system-id="029e7d3d-d2ab-4f2c-bc92-ec58c94a8fc1"}
|
||||||
|
...
|
||||||
|
|
||||||
|
Each configuration option defined in "external_ids:ovn-cms-options" is divided
|
||||||
|
by commas.
|
||||||
|
|
||||||
Propagation of resource information
|
Propagation of resource information
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import uuid
|
||||||
|
|
||||||
from neutron_lib.api.definitions import portbindings
|
from neutron_lib.api.definitions import portbindings
|
||||||
from neutron_lib import constants as const
|
from neutron_lib import constants as const
|
||||||
|
@ -298,3 +299,16 @@ CMS_OPT_AVAILABILITY_ZONES = 'availability-zones'
|
||||||
|
|
||||||
# OVN vlan transparency option
|
# OVN vlan transparency option
|
||||||
VLAN_PASSTHRU = 'vlan-passthru'
|
VLAN_PASSTHRU = 'vlan-passthru'
|
||||||
|
|
||||||
|
# OVN Placement API; used for minimum bandwidth scheduling allocation.
|
||||||
|
# NOTE(ralonsoh): rehome to neutron-lib
|
||||||
|
RP_HYPERVISORS = 'resource_provider_hypervisors'
|
||||||
|
|
||||||
|
# OVN mechanism driver constants.
|
||||||
|
OVN_RP_UUID = uuid.UUID('5533233b-800c-11eb-b1f4-000056b2f5b8')
|
||||||
|
OVN_SUPPORTED_VNIC_TYPES = [portbindings.VNIC_NORMAL,
|
||||||
|
portbindings.VNIC_DIRECT,
|
||||||
|
portbindings.VNIC_DIRECT_PHYSICAL,
|
||||||
|
portbindings.VNIC_MACVTAP,
|
||||||
|
portbindings.VNIC_VHOST_VDPA,
|
||||||
|
]
|
||||||
|
|
|
@ -20,7 +20,6 @@ import operator
|
||||||
import signal
|
import signal
|
||||||
import threading
|
import threading
|
||||||
import types
|
import types
|
||||||
import uuid
|
|
||||||
|
|
||||||
from neutron_lib.api.definitions import portbindings
|
from neutron_lib.api.definitions import portbindings
|
||||||
from neutron_lib.api.definitions import provider_net
|
from neutron_lib.api.definitions import provider_net
|
||||||
|
@ -95,8 +94,7 @@ class OVNMechanismDriver(api.MechanismDriver):
|
||||||
update network/port case, all data validation must be done within
|
update network/port case, all data validation must be done within
|
||||||
methods that are part of the database transaction.
|
methods that are part of the database transaction.
|
||||||
"""
|
"""
|
||||||
resource_provider_uuid5_namespace = uuid.UUID(
|
resource_provider_uuid5_namespace = ovn_const.OVN_RP_UUID
|
||||||
'5533233b-800c-11eb-b1f4-000056b2f5b8')
|
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
"""Perform driver initialization.
|
"""Perform driver initialization.
|
||||||
|
@ -190,12 +188,7 @@ class OVNMechanismDriver(api.MechanismDriver):
|
||||||
in vlan_transparency_network_types)
|
in vlan_transparency_network_types)
|
||||||
|
|
||||||
def _setup_vif_port_bindings(self):
|
def _setup_vif_port_bindings(self):
|
||||||
self.supported_vnic_types = [portbindings.VNIC_NORMAL,
|
self.supported_vnic_types = ovn_const.OVN_SUPPORTED_VNIC_TYPES
|
||||||
portbindings.VNIC_DIRECT,
|
|
||||||
portbindings.VNIC_DIRECT_PHYSICAL,
|
|
||||||
portbindings.VNIC_MACVTAP,
|
|
||||||
portbindings.VNIC_VHOST_VDPA,
|
|
||||||
]
|
|
||||||
self.vif_details = {
|
self.vif_details = {
|
||||||
portbindings.VIF_TYPE_OVS: {
|
portbindings.VIF_TYPE_OVS: {
|
||||||
portbindings.CAP_PORT_FILTER: self.sg_enabled
|
portbindings.CAP_PORT_FILTER: self.sg_enabled
|
||||||
|
|
|
@ -0,0 +1,269 @@
|
||||||
|
# Copyright 2021 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.
|
||||||
|
|
||||||
|
import itertools
|
||||||
|
|
||||||
|
from keystoneauth1 import exceptions as ks_exc
|
||||||
|
from neutron_lib import constants as n_const
|
||||||
|
from neutron_lib.placement import constants as placement_constants
|
||||||
|
from neutron_lib.placement import utils as placement_utils
|
||||||
|
from neutron_lib.plugins import constants as plugins_constants
|
||||||
|
from neutron_lib.plugins import directory
|
||||||
|
from neutron_lib.utils import helpers
|
||||||
|
from oslo_log import log as logging
|
||||||
|
from ovsdbapp.backend.ovs_idl import event as row_event
|
||||||
|
|
||||||
|
from neutron.agent.common import placement_report
|
||||||
|
from neutron.common.ovn import constants as ovn_const
|
||||||
|
from neutron.common.ovn import utils as ovn_utils
|
||||||
|
from neutron.common import utils as common_utils
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_ovn_cms_options(chassis):
|
||||||
|
cms_options = ovn_utils.get_ovn_cms_options(chassis)
|
||||||
|
return {n_const.RP_BANDWIDTHS: _parse_bandwidths(cms_options),
|
||||||
|
n_const.RP_INVENTORY_DEFAULTS: _parse_inventory_defaults(
|
||||||
|
cms_options),
|
||||||
|
ovn_const.RP_HYPERVISORS: _parse_hypervisors(cms_options)}
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_bridge_mappings(chassis):
|
||||||
|
bridge_mappings = chassis.external_ids.get('ovn-bridge-mappings', '')
|
||||||
|
bridge_mappings = helpers.parse_mappings(bridge_mappings.split(','),
|
||||||
|
unique_values=False)
|
||||||
|
return {k: [v] for k, v in bridge_mappings.items()}
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_placement_option(option_name, cms_options):
|
||||||
|
for cms_option in (cms_option for cms_option in cms_options if
|
||||||
|
option_name in cms_option):
|
||||||
|
try:
|
||||||
|
return cms_option.split('=')[1]
|
||||||
|
except IndexError:
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_bandwidths(cms_options):
|
||||||
|
bw_values = _parse_placement_option(n_const.RP_BANDWIDTHS, cms_options)
|
||||||
|
if not bw_values:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
return placement_utils.parse_rp_bandwidths(bw_values.split(';'))
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_inventory_defaults(cms_options):
|
||||||
|
inv_defaults = _parse_placement_option(n_const.RP_INVENTORY_DEFAULTS,
|
||||||
|
cms_options)
|
||||||
|
if not inv_defaults:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
inventory = {}
|
||||||
|
for inv_default in inv_defaults.split(';'):
|
||||||
|
for key in placement_constants.INVENTORY_OPTIONS:
|
||||||
|
if key in inv_default:
|
||||||
|
inventory[key] = inv_default.split(':')[1]
|
||||||
|
return placement_utils.parse_rp_inventory_defaults(inventory)
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_hypervisors(cms_options):
|
||||||
|
hyperv = _parse_placement_option(ovn_const.RP_HYPERVISORS, cms_options)
|
||||||
|
if not hyperv:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
return helpers.parse_mappings(hyperv.split(';'), unique_values=False)
|
||||||
|
|
||||||
|
|
||||||
|
def _send_deferred_batch(state):
|
||||||
|
if not state:
|
||||||
|
return
|
||||||
|
|
||||||
|
deferred_batch = state.deferred_sync()
|
||||||
|
for deferred in deferred_batch:
|
||||||
|
try:
|
||||||
|
LOG.debug('Placement client: %s', str(deferred))
|
||||||
|
deferred.execute()
|
||||||
|
except Exception:
|
||||||
|
LOG.exception('Placement client call failed: %s', str(deferred))
|
||||||
|
|
||||||
|
|
||||||
|
def _dict_chassis_config(state):
|
||||||
|
if state:
|
||||||
|
return {n_const.RP_BANDWIDTHS: state._rp_bandwidths,
|
||||||
|
n_const.RP_INVENTORY_DEFAULTS: state._rp_inventory_defaults,
|
||||||
|
ovn_const.RP_HYPERVISORS: state._hypervisor_rps}
|
||||||
|
|
||||||
|
|
||||||
|
class ChassisBandwidthConfigEvent(row_event.RowEvent):
|
||||||
|
"""Chassis create update event to track the bandwidth config changes."""
|
||||||
|
|
||||||
|
def __init__(self, placement_extension):
|
||||||
|
self._placement_extension = placement_extension
|
||||||
|
# NOTE(ralonsoh): BW resource provider information is stored in
|
||||||
|
# "Chassis", not "Chassis_Private".
|
||||||
|
table = 'Chassis'
|
||||||
|
events = (self.ROW_CREATE, self.ROW_UPDATE)
|
||||||
|
super().__init__(events, table, None)
|
||||||
|
self.event_name = 'ChassisBandwidthConfigEvent'
|
||||||
|
|
||||||
|
def run(self, event, row, old):
|
||||||
|
name2uuid = self._placement_extension.name2uuid()
|
||||||
|
state = self._placement_extension.build_placement_state(row, name2uuid)
|
||||||
|
_send_deferred_batch(state)
|
||||||
|
self._placement_extension.add_chassis_config(
|
||||||
|
row.name, _dict_chassis_config(state))
|
||||||
|
ch_config = self._placement_extension.get_chassis_config(row.name)
|
||||||
|
LOG.debug('OVN chassis %(chassis)s Placement configuration modified: '
|
||||||
|
'%(config)s', {'chassis': row.name, 'config': ch_config})
|
||||||
|
|
||||||
|
|
||||||
|
@common_utils.SingletonDecorator
|
||||||
|
class OVNClientPlacementExtension(object):
|
||||||
|
"""OVN client Placement API extension"""
|
||||||
|
|
||||||
|
def __init__(self, driver):
|
||||||
|
LOG.info('Starting OVNClientPlacementExtension')
|
||||||
|
super().__init__()
|
||||||
|
self._config_event = None
|
||||||
|
self._reset(driver)
|
||||||
|
|
||||||
|
def _reset(self, driver):
|
||||||
|
"""Reset the interval members values
|
||||||
|
This class is a singleton. Once initialized, any other new instance
|
||||||
|
will return the same object reference with the same member values.
|
||||||
|
This method is used to reset all of them as when the class is initially
|
||||||
|
instantiated if needed.
|
||||||
|
"""
|
||||||
|
self._driver = driver
|
||||||
|
self._placement_plugin = None
|
||||||
|
self._plugin = None
|
||||||
|
self.uuid_ns = ovn_const.OVN_RP_UUID
|
||||||
|
self.supported_vnic_types = ovn_const.OVN_SUPPORTED_VNIC_TYPES
|
||||||
|
self._enabled = bool(self.placement_plugin)
|
||||||
|
self._chassis = {} # Initial config read could take some time.
|
||||||
|
if not self._enabled:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self._config_event:
|
||||||
|
self._config_event = ChassisBandwidthConfigEvent(self)
|
||||||
|
try:
|
||||||
|
self._driver._sb_idl.idl.notify_handler.watch_events(
|
||||||
|
[self._config_event])
|
||||||
|
except AttributeError:
|
||||||
|
self._enabled = False
|
||||||
|
|
||||||
|
if not self._enabled:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._chassis = self._read_initial_chassis_config()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def placement_plugin(self):
|
||||||
|
if self._placement_plugin is None:
|
||||||
|
self._placement_plugin = directory.get_plugin(
|
||||||
|
plugins_constants.PLACEMENT_REPORT)
|
||||||
|
return self._placement_plugin
|
||||||
|
|
||||||
|
@property
|
||||||
|
def plugin(self):
|
||||||
|
if self._plugin is None:
|
||||||
|
self._plugin = self._driver._plugin
|
||||||
|
return self._plugin
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ovn_mech_driver(self):
|
||||||
|
if self._ovn_mech_driver is None:
|
||||||
|
self._ovn_mech_driver = (
|
||||||
|
self.plugin.mechanism_manager.mech_drivers['ovn'].obj)
|
||||||
|
return self._ovn_mech_driver
|
||||||
|
|
||||||
|
@property
|
||||||
|
def chassis(self):
|
||||||
|
return self._chassis
|
||||||
|
|
||||||
|
def _read_initial_chassis_config(self):
|
||||||
|
chassis = {}
|
||||||
|
name2uuid = self.name2uuid()
|
||||||
|
for ch in self._driver._sb_idl.chassis_list().execute(
|
||||||
|
check_error=True):
|
||||||
|
state = self.build_placement_state(ch, name2uuid)
|
||||||
|
_send_deferred_batch(state)
|
||||||
|
config = _dict_chassis_config(state)
|
||||||
|
if config:
|
||||||
|
chassis[ch.name] = config
|
||||||
|
|
||||||
|
msg = '\n'.join(['Chassis %s: %s' % (name, config)
|
||||||
|
for (name, config) in chassis.items()])
|
||||||
|
LOG.debug('OVN chassis Placement initial configuration:\n%s', msg)
|
||||||
|
return chassis
|
||||||
|
|
||||||
|
def name2uuid(self, name=None):
|
||||||
|
try:
|
||||||
|
rps = self.placement_plugin._placement_client.\
|
||||||
|
list_resource_providers(name=name)['resource_providers']
|
||||||
|
except (ks_exc.HttpError, ks_exc.ClientException):
|
||||||
|
LOG.warning('Error connecting to Placement API.')
|
||||||
|
return {}
|
||||||
|
|
||||||
|
_name2uuid = {rp['name']: rp['uuid'] for rp in rps}
|
||||||
|
LOG.info('Placement information about resource providers '
|
||||||
|
'(name:uuid):%s ', _name2uuid)
|
||||||
|
return _name2uuid
|
||||||
|
|
||||||
|
def build_placement_state(self, chassis, name2uuid):
|
||||||
|
bridge_mappings = _parse_bridge_mappings(chassis)
|
||||||
|
cms_options = _parse_ovn_cms_options(chassis)
|
||||||
|
LOG.debug('Building placement options for chassis %s: %s',
|
||||||
|
chassis.name, cms_options)
|
||||||
|
hypervisor_rps = {}
|
||||||
|
for device, hyperv in cms_options[ovn_const.RP_HYPERVISORS].items():
|
||||||
|
try:
|
||||||
|
hypervisor_rps[device] = {'name': hyperv,
|
||||||
|
'uuid': name2uuid[hyperv]}
|
||||||
|
except (KeyError, AttributeError):
|
||||||
|
continue
|
||||||
|
|
||||||
|
bridges = set(itertools.chain(*bridge_mappings.values()))
|
||||||
|
# Remove "cms_options[RP_BANDWIDTHS]" not present in "hypervisor_rps"
|
||||||
|
# and "bridge_mappings". If we don't have a way to match the RP bridge
|
||||||
|
# with a host ("hypervisor_rps") or a way to match the RP bridge with
|
||||||
|
# an external network ("bridge_mappings"), this value is irrelevant.
|
||||||
|
rp_bw = cms_options[n_const.RP_BANDWIDTHS]
|
||||||
|
if rp_bw:
|
||||||
|
cms_options[n_const.RP_BANDWIDTHS] = {
|
||||||
|
device: bw for device, bw in rp_bw.items() if
|
||||||
|
device in hypervisor_rps and device in bridges}
|
||||||
|
|
||||||
|
# NOTE(ralonsoh): OVN only reports min BW RPs; packet processing RPs
|
||||||
|
# will be added in a future implementation.
|
||||||
|
return placement_report.PlacementState(
|
||||||
|
rp_bandwidths=cms_options[n_const.RP_BANDWIDTHS],
|
||||||
|
rp_inventory_defaults=cms_options[n_const.RP_INVENTORY_DEFAULTS],
|
||||||
|
rp_pkt_processing={},
|
||||||
|
rp_pkt_processing_inventory_defaults=None,
|
||||||
|
driver_uuid_namespace=self.uuid_ns,
|
||||||
|
agent_type=ovn_const.OVN_CONTROLLER_AGENT,
|
||||||
|
hypervisor_rps=hypervisor_rps,
|
||||||
|
device_mappings=bridge_mappings,
|
||||||
|
supported_vnic_types=self.supported_vnic_types,
|
||||||
|
client=self.placement_plugin._placement_client)
|
||||||
|
|
||||||
|
def get_chassis_config(self, chassis_name):
|
||||||
|
return self._chassis.get(chassis_name)
|
||||||
|
|
||||||
|
def add_chassis_config(self, chassis_name, config):
|
||||||
|
if config:
|
||||||
|
self._chassis[chassis_name] = config
|
|
@ -44,6 +44,8 @@ from neutron.common import utils as common_utils
|
||||||
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
|
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
|
||||||
from neutron.db import ovn_revision_numbers_db as db_rev
|
from neutron.db import ovn_revision_numbers_db as db_rev
|
||||||
from neutron.db import segments_db
|
from neutron.db import segments_db
|
||||||
|
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.extensions \
|
||||||
|
import placement as placement_extension
|
||||||
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.extensions \
|
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.extensions \
|
||||||
import qos as qos_extension
|
import qos as qos_extension
|
||||||
from neutron.scheduler import l3_ovn_scheduler
|
from neutron.scheduler import l3_ovn_scheduler
|
||||||
|
@ -74,6 +76,8 @@ class OVNClient(object):
|
||||||
|
|
||||||
# TODO(ralonsoh): handle the OVN client extensions with an ext. manager
|
# TODO(ralonsoh): handle the OVN client extensions with an ext. manager
|
||||||
self._qos_driver = qos_extension.OVNClientQosExtension(self)
|
self._qos_driver = qos_extension.OVNClientQosExtension(self)
|
||||||
|
self._placement_extension = (
|
||||||
|
placement_extension.OVNClientPlacementExtension(self))
|
||||||
self._ovn_scheduler = l3_ovn_scheduler.get_scheduler()
|
self._ovn_scheduler = l3_ovn_scheduler.get_scheduler()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -42,6 +42,8 @@ from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
|
||||||
from neutron.db import models # noqa
|
from neutron.db import models # noqa
|
||||||
from neutron import manager
|
from neutron import manager
|
||||||
from neutron.plugins.ml2.drivers.ovn.agent import neutron_agent
|
from neutron.plugins.ml2.drivers.ovn.agent import neutron_agent
|
||||||
|
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.extensions import \
|
||||||
|
placement as ovn_client_placement
|
||||||
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import worker
|
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import worker
|
||||||
from neutron.plugins.ml2.drivers import type_geneve # noqa
|
from neutron.plugins.ml2.drivers import type_geneve # noqa
|
||||||
from neutron import service # noqa
|
from neutron import service # noqa
|
||||||
|
@ -167,7 +169,7 @@ class TestOVNFunctionalBase(test_plugin.Ml2PluginV2TestCase,
|
||||||
self.temp_dir = self.useFixture(fixtures.TempDir()).path
|
self.temp_dir = self.useFixture(fixtures.TempDir()).path
|
||||||
self._start_ovsdb_server()
|
self._start_ovsdb_server()
|
||||||
|
|
||||||
def setUp(self, maintenance_worker=False):
|
def setUp(self, maintenance_worker=False, service_plugins=None):
|
||||||
ml2_config.cfg.CONF.set_override('extension_drivers',
|
ml2_config.cfg.CONF.set_override('extension_drivers',
|
||||||
self._extension_drivers,
|
self._extension_drivers,
|
||||||
group='ml2')
|
group='ml2')
|
||||||
|
@ -187,6 +189,7 @@ class TestOVNFunctionalBase(test_plugin.Ml2PluginV2TestCase,
|
||||||
|
|
||||||
self.addCleanup(exts.PluginAwareExtensionManager.clear_instance)
|
self.addCleanup(exts.PluginAwareExtensionManager.clear_instance)
|
||||||
self.ovsdb_server_mgr = None
|
self.ovsdb_server_mgr = None
|
||||||
|
self._service_plugins = service_plugins
|
||||||
super(TestOVNFunctionalBase, self).setUp()
|
super(TestOVNFunctionalBase, self).setUp()
|
||||||
self.test_log_dir = os.path.join(DEFAULT_LOG_DIR, self.id())
|
self.test_log_dir = os.path.join(DEFAULT_LOG_DIR, self.id())
|
||||||
base.setup_test_logging(
|
base.setup_test_logging(
|
||||||
|
@ -220,10 +223,14 @@ class TestOVNFunctionalBase(test_plugin.Ml2PluginV2TestCase,
|
||||||
self._start_idls()
|
self._start_idls()
|
||||||
self._start_ovn_northd()
|
self._start_ovn_northd()
|
||||||
self.addCleanup(self._reset_agent_cache_singleton)
|
self.addCleanup(self._reset_agent_cache_singleton)
|
||||||
|
self.addCleanup(self._reset_ovn_client_placement_extension)
|
||||||
|
|
||||||
def _reset_agent_cache_singleton(self):
|
def _reset_agent_cache_singleton(self):
|
||||||
neutron_agent.AgentCache._instance = None
|
neutron_agent.AgentCache._instance = None
|
||||||
|
|
||||||
|
def _reset_ovn_client_placement_extension(self):
|
||||||
|
ovn_client_placement.OVNClientPlacementExtension._instance = None
|
||||||
|
|
||||||
def _get_install_share_path(self):
|
def _get_install_share_path(self):
|
||||||
lookup_paths = set()
|
lookup_paths = set()
|
||||||
for installation in ['local', '']:
|
for installation in ['local', '']:
|
||||||
|
@ -253,8 +260,10 @@ class TestOVNFunctionalBase(test_plugin.Ml2PluginV2TestCase,
|
||||||
|
|
||||||
def get_additional_service_plugins(self):
|
def get_additional_service_plugins(self):
|
||||||
p = super(TestOVNFunctionalBase, self).get_additional_service_plugins()
|
p = super(TestOVNFunctionalBase, self).get_additional_service_plugins()
|
||||||
p.update({'revision_plugin_name': 'revisions'})
|
p.update({'revision_plugin_name': 'revisions',
|
||||||
p.update({'segments': 'neutron.services.segments.plugin.Plugin'})
|
'segments': 'neutron.services.segments.plugin.Plugin'})
|
||||||
|
if self._service_plugins:
|
||||||
|
p.update(self._service_plugins)
|
||||||
return p
|
return p
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -0,0 +1,185 @@
|
||||||
|
# Copyright 2021 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_lib import constants as n_const
|
||||||
|
from neutron_lib.plugins import constants as plugins_constants
|
||||||
|
|
||||||
|
from neutron.common.ovn import constants as ovn_const
|
||||||
|
from neutron.common import utils as common_utils
|
||||||
|
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.extensions \
|
||||||
|
import placement as placement_extension
|
||||||
|
from neutron.tests.functional import base
|
||||||
|
|
||||||
|
|
||||||
|
class TestOVNClientQosExtension(base.TestOVNFunctionalBase):
|
||||||
|
|
||||||
|
EMPTY_CHASSIS = {n_const.RP_BANDWIDTHS: {},
|
||||||
|
n_const.RP_INVENTORY_DEFAULTS: {},
|
||||||
|
ovn_const.RP_HYPERVISORS: {}}
|
||||||
|
|
||||||
|
RP_BANDWIDTHS_1 = {'br-provider0': {'egress': 1000, 'ingress': 2000}}
|
||||||
|
RP_INVENTORY_DEFAULTS_1 = {'allocation_ratio': 1.0, 'min_unit': 2}
|
||||||
|
RP_HYPERVISORS_1 = {'br-provider0': {'name': 'host1', 'uuid': 'uuid1'}}
|
||||||
|
CHASSIS1 = {
|
||||||
|
'chassis1': {
|
||||||
|
n_const.RP_BANDWIDTHS: RP_BANDWIDTHS_1,
|
||||||
|
n_const.RP_INVENTORY_DEFAULTS: RP_INVENTORY_DEFAULTS_1,
|
||||||
|
ovn_const.RP_HYPERVISORS: RP_HYPERVISORS_1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RP_BANDWIDTHS_2 = {'br-provider0': {'egress': 3000, 'ingress': 4000}}
|
||||||
|
RP_INVENTORY_DEFAULTS_2 = {'allocation_ratio': 3.0, 'min_unit': 1}
|
||||||
|
RP_HYPERVISORS_2 = {'br-provider0': {'name': 'host2', 'uuid': 'uuid2'}}
|
||||||
|
CHASSIS2 = {
|
||||||
|
'chassis2': {
|
||||||
|
n_const.RP_BANDWIDTHS: RP_BANDWIDTHS_2,
|
||||||
|
n_const.RP_INVENTORY_DEFAULTS: RP_INVENTORY_DEFAULTS_2,
|
||||||
|
ovn_const.RP_HYPERVISORS: RP_HYPERVISORS_2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RP_BANDWIDTHS_3 = {'br-provider0': {'egress': 5000, 'ingress': 6000}}
|
||||||
|
RP_INVENTORY_DEFAULTS_3 = {'allocation_ratio': 1.1, 'min_unit': 1}
|
||||||
|
CHASSIS2_B = {
|
||||||
|
'chassis2': {
|
||||||
|
n_const.RP_BANDWIDTHS: RP_BANDWIDTHS_3,
|
||||||
|
n_const.RP_INVENTORY_DEFAULTS: RP_INVENTORY_DEFAULTS_3,
|
||||||
|
ovn_const.RP_HYPERVISORS: RP_HYPERVISORS_2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def setUp(self, maintenance_worker=False, service_plugins=None):
|
||||||
|
service_plugins = {plugins_constants.PLACEMENT_REPORT: 'placement'}
|
||||||
|
super().setUp(maintenance_worker=maintenance_worker,
|
||||||
|
service_plugins=service_plugins)
|
||||||
|
self.ovn_client = self.mech_driver._ovn_client
|
||||||
|
self.placement_ext = self.ovn_client._placement_extension
|
||||||
|
self.mock_name2uuid = mock.patch.object(
|
||||||
|
self.placement_ext, 'name2uuid').start()
|
||||||
|
self.mock_send_batch = mock.patch.object(
|
||||||
|
placement_extension, '_send_deferred_batch').start()
|
||||||
|
|
||||||
|
def _build_external_ids(self, bandwidths, inventory_defaults, hypervisors):
|
||||||
|
options = []
|
||||||
|
if bandwidths:
|
||||||
|
options.append(n_const.RP_BANDWIDTHS + '=' + bandwidths)
|
||||||
|
if inventory_defaults:
|
||||||
|
options.append(n_const.RP_INVENTORY_DEFAULTS + '=' +
|
||||||
|
inventory_defaults)
|
||||||
|
if hypervisors:
|
||||||
|
options.append(ovn_const.RP_HYPERVISORS + '=' + hypervisors)
|
||||||
|
return {'ovn-cms-options': ','.join(options)}
|
||||||
|
|
||||||
|
def _create_chassis(self, host, name, physical_nets=None, bandwidths=None,
|
||||||
|
inventory_defaults=None, hypervisors=None):
|
||||||
|
external_ids = self._build_external_ids(bandwidths, inventory_defaults,
|
||||||
|
hypervisors)
|
||||||
|
self.add_fake_chassis(host, physical_nets=physical_nets,
|
||||||
|
external_ids=external_ids, name=name)
|
||||||
|
|
||||||
|
def _update_chassis(self, name, bandwidths=None, inventory_defaults=None,
|
||||||
|
hypervisors=None):
|
||||||
|
external_ids = self._build_external_ids(bandwidths, inventory_defaults,
|
||||||
|
hypervisors)
|
||||||
|
self.sb_api.db_set(
|
||||||
|
'Chassis', name, ('external_ids', external_ids)
|
||||||
|
).execute(check_error=True)
|
||||||
|
|
||||||
|
def _check_placement_cache(self, expected_chassis):
|
||||||
|
current_chassis = None
|
||||||
|
|
||||||
|
def check_chassis():
|
||||||
|
nonlocal current_chassis
|
||||||
|
current_chassis = self.placement_ext.chassis
|
||||||
|
return current_chassis == expected_chassis
|
||||||
|
|
||||||
|
try:
|
||||||
|
common_utils.wait_until_true(check_chassis, timeout=5)
|
||||||
|
except common_utils.WaitTimeout:
|
||||||
|
self.fail('OVN client Placement extension cache does not have '
|
||||||
|
'the expected chassis information.\nExpected: %s.\n'
|
||||||
|
'Actual: %s' % (expected_chassis, current_chassis))
|
||||||
|
|
||||||
|
def test_read_initial_config_and_update(self):
|
||||||
|
self.mock_name2uuid.return_value = {'host1': 'uuid1',
|
||||||
|
'host2': 'uuid2'}
|
||||||
|
self._create_chassis(
|
||||||
|
'host1', 'chassis1', physical_nets=['phys1'],
|
||||||
|
bandwidths='br-provider0:1000:2000',
|
||||||
|
inventory_defaults='allocation_ratio:1.0;min_unit:2',
|
||||||
|
hypervisors='br-provider0:host1')
|
||||||
|
self._create_chassis(
|
||||||
|
'host2', 'chassis2', physical_nets=['phys2'],
|
||||||
|
bandwidths='br-provider0:3000:4000',
|
||||||
|
inventory_defaults='allocation_ratio:3.0;min_unit:1',
|
||||||
|
hypervisors='br-provider0:host2')
|
||||||
|
self._check_placement_cache({**self.CHASSIS1, **self.CHASSIS2})
|
||||||
|
|
||||||
|
self._update_chassis(
|
||||||
|
'chassis2',
|
||||||
|
bandwidths='br-provider0:5000:6000',
|
||||||
|
inventory_defaults='allocation_ratio:1.1;min_unit:1',
|
||||||
|
hypervisors='br-provider0:host2')
|
||||||
|
self._check_placement_cache({**self.CHASSIS1, **self.CHASSIS2_B})
|
||||||
|
|
||||||
|
def test_read_initial_empty_config_and_update(self):
|
||||||
|
self.mock_name2uuid.return_value = {'host1': 'uuid1',
|
||||||
|
'host2': 'uuid2'}
|
||||||
|
self._create_chassis('host1', 'chassis1', physical_nets=['phys1'])
|
||||||
|
self._create_chassis('host2', 'chassis2', physical_nets=['phys2'])
|
||||||
|
self._check_placement_cache({**{'chassis1': self.EMPTY_CHASSIS},
|
||||||
|
**{'chassis2': self.EMPTY_CHASSIS}})
|
||||||
|
|
||||||
|
self._update_chassis(
|
||||||
|
'chassis1',
|
||||||
|
bandwidths='br-provider0:1000:2000',
|
||||||
|
inventory_defaults='allocation_ratio:1.0;min_unit:2',
|
||||||
|
hypervisors='br-provider0:host1')
|
||||||
|
self._check_placement_cache({**self.CHASSIS1,
|
||||||
|
**{'chassis2': self.EMPTY_CHASSIS}})
|
||||||
|
|
||||||
|
self._update_chassis(
|
||||||
|
'chassis2',
|
||||||
|
bandwidths='br-provider0:3000:4000',
|
||||||
|
inventory_defaults='allocation_ratio:3.0;min_unit:1',
|
||||||
|
hypervisors='br-provider0:host2')
|
||||||
|
self._check_placement_cache({**self.CHASSIS1, **self.CHASSIS2})
|
||||||
|
|
||||||
|
def test_update_twice(self):
|
||||||
|
self.mock_name2uuid.return_value = {'host1': 'uuid1',
|
||||||
|
'host2': 'uuid2'}
|
||||||
|
self._create_chassis(
|
||||||
|
'host1', 'chassis1', physical_nets=['phys1'],
|
||||||
|
bandwidths='br-provider0:1000:2000',
|
||||||
|
inventory_defaults='allocation_ratio:1.0;min_unit:2',
|
||||||
|
hypervisors='br-provider0:host1')
|
||||||
|
self._create_chassis('host2', 'chassis2', physical_nets=['phys2'])
|
||||||
|
self._check_placement_cache({**self.CHASSIS1,
|
||||||
|
**{'chassis2': self.EMPTY_CHASSIS}})
|
||||||
|
|
||||||
|
self._update_chassis(
|
||||||
|
'chassis2',
|
||||||
|
bandwidths='br-provider0:3000:4000',
|
||||||
|
inventory_defaults='allocation_ratio:3.0;min_unit:1',
|
||||||
|
hypervisors='br-provider0:host2')
|
||||||
|
self._check_placement_cache({**self.CHASSIS1, **self.CHASSIS2})
|
||||||
|
|
||||||
|
self._update_chassis(
|
||||||
|
'chassis2',
|
||||||
|
bandwidths='br-provider0:5000:6000',
|
||||||
|
inventory_defaults='allocation_ratio:1.1;min_unit:1',
|
||||||
|
hypervisors='br-provider0:host2')
|
||||||
|
self._check_placement_cache({**self.CHASSIS1, **self.CHASSIS2_B})
|
|
@ -17,6 +17,7 @@ import copy
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from neutron_lib.api.definitions import l3
|
from neutron_lib.api.definitions import l3
|
||||||
|
from neutron_lib import constants as n_const
|
||||||
from neutron_lib.utils import net
|
from neutron_lib.utils import net
|
||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
|
|
||||||
|
@ -819,7 +820,9 @@ class FakeOVNRouter(object):
|
||||||
class FakeChassis(object):
|
class FakeChassis(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create(attrs=None, az_list=None, chassis_as_gw=False):
|
def create(attrs=None, az_list=None, chassis_as_gw=False,
|
||||||
|
bridge_mappings=None, rp_bandwidths=None,
|
||||||
|
rp_inventory_defaults=None, rp_hypervisors=None):
|
||||||
cms_opts = []
|
cms_opts = []
|
||||||
if az_list:
|
if az_list:
|
||||||
cms_opts.append("%s=%s" % (ovn_const.CMS_OPT_AVAILABILITY_ZONES,
|
cms_opts.append("%s=%s" % (ovn_const.CMS_OPT_AVAILABILITY_ZONES,
|
||||||
|
@ -827,10 +830,33 @@ class FakeChassis(object):
|
||||||
if chassis_as_gw:
|
if chassis_as_gw:
|
||||||
cms_opts.append(ovn_const.CMS_OPT_CHASSIS_AS_GW)
|
cms_opts.append(ovn_const.CMS_OPT_CHASSIS_AS_GW)
|
||||||
|
|
||||||
|
if rp_bandwidths:
|
||||||
|
cms_opts.append('%s=%s' % (n_const.RP_BANDWIDTHS,
|
||||||
|
';'.join(rp_bandwidths)))
|
||||||
|
elif rp_bandwidths == '': # Test wrongly defined parameter
|
||||||
|
cms_opts.append('%s=' % n_const.RP_BANDWIDTHS)
|
||||||
|
|
||||||
|
if rp_inventory_defaults:
|
||||||
|
inv_defaults = ';'.join('%s:%s' % (key, value) for key, value in
|
||||||
|
rp_inventory_defaults.items())
|
||||||
|
cms_opts.append('%s=%s' % (n_const.RP_INVENTORY_DEFAULTS,
|
||||||
|
inv_defaults))
|
||||||
|
elif rp_inventory_defaults == '': # Test wrongly defined parameter
|
||||||
|
cms_opts.append('%s=' % n_const.RP_INVENTORY_DEFAULTS)
|
||||||
|
|
||||||
|
if rp_hypervisors:
|
||||||
|
cms_opts.append('%s=%s' % (ovn_const.RP_HYPERVISORS,
|
||||||
|
';'.join(rp_hypervisors)))
|
||||||
|
elif rp_hypervisors == '': # Test wrongly defined parameter
|
||||||
|
cms_opts.append('%s=' % ovn_const.RP_HYPERVISORS)
|
||||||
|
|
||||||
external_ids = {}
|
external_ids = {}
|
||||||
if cms_opts:
|
if cms_opts:
|
||||||
external_ids[ovn_const.OVN_CMS_OPTIONS] = ','.join(cms_opts)
|
external_ids[ovn_const.OVN_CMS_OPTIONS] = ','.join(cms_opts)
|
||||||
|
|
||||||
|
if bridge_mappings:
|
||||||
|
external_ids['ovn-bridge-mappings'] = ','.join(bridge_mappings)
|
||||||
|
|
||||||
attrs = {
|
attrs = {
|
||||||
'encaps': [],
|
'encaps': [],
|
||||||
'external_ids': external_ids,
|
'external_ids': external_ids,
|
||||||
|
|
|
@ -0,0 +1,229 @@
|
||||||
|
# Copyright 2021 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_lib import constants as n_const
|
||||||
|
|
||||||
|
from neutron.common.ovn import constants as ovn_const
|
||||||
|
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.extensions \
|
||||||
|
import placement as p_extension
|
||||||
|
from neutron.tests.unit import fake_resources as fakes
|
||||||
|
from neutron.tests.unit.plugins.ml2 import test_plugin
|
||||||
|
|
||||||
|
|
||||||
|
class TestOVNClientPlacementExtension(test_plugin.Ml2PluginV2TestCase):
|
||||||
|
|
||||||
|
CORE_PLUGIN_CLASS = 'neutron.plugins.ml2.plugin.Ml2Plugin'
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestOVNClientPlacementExtension, self).setUp()
|
||||||
|
self.setup_coreplugin(self.CORE_PLUGIN_CLASS, load_plugins=True)
|
||||||
|
self.plugin_driver = mock.Mock()
|
||||||
|
self.placement_driver = p_extension.OVNClientPlacementExtension(
|
||||||
|
self.plugin_driver)
|
||||||
|
self.placement_client = mock.Mock(
|
||||||
|
update_trait=mock.Mock(__name__='update_trait'),
|
||||||
|
ensure_resource_provider=mock.Mock(__name__='ensure_rp'),
|
||||||
|
update_resource_provider_traits=mock.Mock(
|
||||||
|
__name__='update_rp_traits'),
|
||||||
|
update_resource_provider_inventories=mock.Mock(
|
||||||
|
__name__='update_rp_inventories'))
|
||||||
|
self.placement_plugin = mock.Mock(
|
||||||
|
_placement_client=self.placement_client)
|
||||||
|
self.placement_driver._placement_plugin = self.placement_plugin
|
||||||
|
self.placement_client.list_resource_providers.return_value = {
|
||||||
|
'resource_providers': [{'name': 'compute1', 'uuid': 'uuid1'},
|
||||||
|
{'name': 'compute2', 'uuid': 'uuid2'}]
|
||||||
|
}
|
||||||
|
|
||||||
|
def test__read_initial_chassis_config(self):
|
||||||
|
# Add two public networks, a RP per bridge and the correlation between
|
||||||
|
# the hypervisors and the bridges.
|
||||||
|
chassis = fakes.FakeChassis.create(
|
||||||
|
bridge_mappings=['public1:br-ext1', 'public2:br-ext2'],
|
||||||
|
rp_bandwidths=['br-ext1:1000:2000', 'br-ext2:3000:4000'],
|
||||||
|
rp_inventory_defaults={'allocation_ratio': 1.0, 'min_unit': 5},
|
||||||
|
rp_hypervisors=['br-ext1:compute1', 'br-ext2:compute2'])
|
||||||
|
self.plugin_driver._sb_idl.chassis_list.return_value.execute.\
|
||||||
|
return_value = [chassis]
|
||||||
|
init_conf = self.placement_driver._read_initial_chassis_config()
|
||||||
|
expected = {chassis.name: {
|
||||||
|
n_const.RP_BANDWIDTHS: {
|
||||||
|
'br-ext1': {'egress': 1000, 'ingress': 2000},
|
||||||
|
'br-ext2': {'egress': 3000, 'ingress': 4000}},
|
||||||
|
n_const.RP_INVENTORY_DEFAULTS: {
|
||||||
|
'allocation_ratio': 1.0, 'min_unit': 5},
|
||||||
|
ovn_const.RP_HYPERVISORS: {
|
||||||
|
'br-ext1': {'name': 'compute1', 'uuid': 'uuid1'},
|
||||||
|
'br-ext2': {'name': 'compute2', 'uuid': 'uuid2'}}
|
||||||
|
}}
|
||||||
|
self.assertEqual(expected, init_conf)
|
||||||
|
|
||||||
|
# Add an extra bridge mapping that is discarded because it is not in
|
||||||
|
# the hypervisors list (wrong configuration).
|
||||||
|
chassis = fakes.FakeChassis.create(
|
||||||
|
bridge_mappings=['public1:br-ext1', 'public2:br-ext2',
|
||||||
|
'public3:br-ext3'],
|
||||||
|
rp_bandwidths=['br-ext1:1000:2000', 'br-ext2:3000:4000'],
|
||||||
|
rp_inventory_defaults={'allocation_ratio': 1.0, 'min_unit': 5},
|
||||||
|
rp_hypervisors=['br-ext1:compute1', 'br-ext2:compute2'])
|
||||||
|
self.plugin_driver._sb_idl.chassis_list.return_value.execute.\
|
||||||
|
return_value = [chassis]
|
||||||
|
init_conf = self.placement_driver._read_initial_chassis_config()
|
||||||
|
expected = {chassis.name: {
|
||||||
|
n_const.RP_BANDWIDTHS: {
|
||||||
|
'br-ext1': {'egress': 1000, 'ingress': 2000},
|
||||||
|
'br-ext2': {'egress': 3000, 'ingress': 4000}},
|
||||||
|
n_const.RP_INVENTORY_DEFAULTS: {
|
||||||
|
'allocation_ratio': 1.0, 'min_unit': 5},
|
||||||
|
ovn_const.RP_HYPERVISORS: {
|
||||||
|
'br-ext1': {'name': 'compute1', 'uuid': 'uuid1'},
|
||||||
|
'br-ext2': {'name': 'compute2', 'uuid': 'uuid2'}}
|
||||||
|
}}
|
||||||
|
self.assertEqual(expected, init_conf)
|
||||||
|
|
||||||
|
# Add an unknown bridge, not present in the bridge mappings, that is
|
||||||
|
# discarded (wrong configuration).
|
||||||
|
chassis = fakes.FakeChassis.create(
|
||||||
|
bridge_mappings=['public1:br-ext1', 'public2:br-ext2'],
|
||||||
|
rp_bandwidths=['br-ext1:1000:2000', 'br-ext3:3000:4000'],
|
||||||
|
rp_inventory_defaults={'allocation_ratio': 1.0, 'min_unit': 5},
|
||||||
|
rp_hypervisors=['br-ext1:compute1', 'br-ext2:compute2'])
|
||||||
|
self.plugin_driver._sb_idl.chassis_list.return_value.execute.\
|
||||||
|
return_value = [chassis]
|
||||||
|
init_conf = self.placement_driver._read_initial_chassis_config()
|
||||||
|
expected = {chassis.name: {
|
||||||
|
n_const.RP_BANDWIDTHS: {
|
||||||
|
'br-ext1': {'egress': 1000, 'ingress': 2000}},
|
||||||
|
n_const.RP_INVENTORY_DEFAULTS: {
|
||||||
|
'allocation_ratio': 1.0, 'min_unit': 5},
|
||||||
|
ovn_const.RP_HYPERVISORS: {
|
||||||
|
'br-ext1': {'name': 'compute1', 'uuid': 'uuid1'},
|
||||||
|
'br-ext2': {'name': 'compute2', 'uuid': 'uuid2'}}
|
||||||
|
}}
|
||||||
|
self.assertEqual(expected, init_conf)
|
||||||
|
|
||||||
|
# Add an unknown hypervisor, that is not present in the Placement list
|
||||||
|
# of resource providers. This hypervisor is discarded (wrong
|
||||||
|
# configuration). Because "br-ext2" has no match with an existing
|
||||||
|
# hypervisor, is discarded too.
|
||||||
|
chassis = fakes.FakeChassis.create(
|
||||||
|
bridge_mappings=['public1:br-ext1', 'public2:br-ext2'],
|
||||||
|
rp_bandwidths=['br-ext1:1000:2000', 'br-ext2:3000:4000'],
|
||||||
|
rp_inventory_defaults={'allocation_ratio': 1.0, 'min_unit': 5},
|
||||||
|
rp_hypervisors=['br-ext1:compute1', 'br-ext2:compute3'])
|
||||||
|
self.plugin_driver._sb_idl.chassis_list.return_value.execute.\
|
||||||
|
return_value = [chassis]
|
||||||
|
init_conf = self.placement_driver._read_initial_chassis_config()
|
||||||
|
expected = {chassis.name: {
|
||||||
|
n_const.RP_BANDWIDTHS: {
|
||||||
|
'br-ext1': {'egress': 1000, 'ingress': 2000}},
|
||||||
|
n_const.RP_INVENTORY_DEFAULTS: {
|
||||||
|
'allocation_ratio': 1.0, 'min_unit': 5},
|
||||||
|
ovn_const.RP_HYPERVISORS: {
|
||||||
|
'br-ext1': {'name': 'compute1', 'uuid': 'uuid1'}}
|
||||||
|
}}
|
||||||
|
self.assertEqual(expected, init_conf)
|
||||||
|
|
||||||
|
# Missing bridge mapping for br-ext2, the RP for this bridge will be
|
||||||
|
# discarded.
|
||||||
|
chassis = fakes.FakeChassis.create(
|
||||||
|
bridge_mappings=['public1:br-ext1'],
|
||||||
|
rp_bandwidths=['br-ext1:1000:2000', 'br-ext2:3000:4000'],
|
||||||
|
rp_inventory_defaults={'allocation_ratio': 1.0, 'min_unit': 5},
|
||||||
|
rp_hypervisors=['br-ext1:compute1', 'br-ext2:compute2'])
|
||||||
|
self.plugin_driver._sb_idl.chassis_list.return_value.execute.\
|
||||||
|
return_value = [chassis]
|
||||||
|
init_conf = self.placement_driver._read_initial_chassis_config()
|
||||||
|
expected = {chassis.name: {
|
||||||
|
n_const.RP_BANDWIDTHS: {
|
||||||
|
'br-ext1': {'egress': 1000, 'ingress': 2000}},
|
||||||
|
n_const.RP_INVENTORY_DEFAULTS: {
|
||||||
|
'allocation_ratio': 1.0, 'min_unit': 5},
|
||||||
|
ovn_const.RP_HYPERVISORS: {
|
||||||
|
'br-ext1': {'name': 'compute1', 'uuid': 'uuid1'},
|
||||||
|
'br-ext2': {'name': 'compute2', 'uuid': 'uuid2'}}
|
||||||
|
}}
|
||||||
|
self.assertEqual(expected, init_conf)
|
||||||
|
|
||||||
|
# No bridge mappings, no RP BW inventories.
|
||||||
|
chassis = fakes.FakeChassis.create(
|
||||||
|
bridge_mappings=None,
|
||||||
|
rp_bandwidths=['br-ext1:1000:2000', 'br-ext2:3000:4000'],
|
||||||
|
rp_inventory_defaults={'allocation_ratio': 1.0, 'min_unit': 5},
|
||||||
|
rp_hypervisors=['br-ext1:compute1', 'br-ext2:compute2'])
|
||||||
|
self.plugin_driver._sb_idl.chassis_list.return_value.execute.\
|
||||||
|
return_value = [chassis]
|
||||||
|
init_conf = self.placement_driver._read_initial_chassis_config()
|
||||||
|
expected = {chassis.name: {
|
||||||
|
n_const.RP_BANDWIDTHS: {},
|
||||||
|
n_const.RP_INVENTORY_DEFAULTS: {
|
||||||
|
'allocation_ratio': 1.0, 'min_unit': 5},
|
||||||
|
ovn_const.RP_HYPERVISORS: {
|
||||||
|
'br-ext1': {'name': 'compute1', 'uuid': 'uuid1'},
|
||||||
|
'br-ext2': {'name': 'compute2', 'uuid': 'uuid2'}}
|
||||||
|
}}
|
||||||
|
self.assertEqual(expected, init_conf)
|
||||||
|
|
||||||
|
# No bridge mappings nor hypervisors, no RP BW inventories.
|
||||||
|
chassis = fakes.FakeChassis.create(
|
||||||
|
bridge_mappings=None,
|
||||||
|
rp_bandwidths=['br-ext1:1000:2000', 'br-ext2:3000:4000'],
|
||||||
|
rp_inventory_defaults={'allocation_ratio': 1.0, 'min_unit': 5},
|
||||||
|
rp_hypervisors=None)
|
||||||
|
self.plugin_driver._sb_idl.chassis_list.return_value.execute.\
|
||||||
|
return_value = [chassis]
|
||||||
|
init_conf = self.placement_driver._read_initial_chassis_config()
|
||||||
|
expected = {chassis.name: {
|
||||||
|
n_const.RP_BANDWIDTHS: {},
|
||||||
|
n_const.RP_INVENTORY_DEFAULTS: {
|
||||||
|
'allocation_ratio': 1.0, 'min_unit': 5},
|
||||||
|
ovn_const.RP_HYPERVISORS: {}
|
||||||
|
}}
|
||||||
|
self.assertEqual(expected, init_conf)
|
||||||
|
|
||||||
|
# If no RP BW information (any deployment not using it), OVN Placement
|
||||||
|
# extension won't break anything (sorry for LP#1936983, that was me).
|
||||||
|
chassis = fakes.FakeChassis.create(
|
||||||
|
bridge_mappings=['public1:br-ext1'],
|
||||||
|
rp_bandwidths=None,
|
||||||
|
rp_inventory_defaults=None,
|
||||||
|
rp_hypervisors=None)
|
||||||
|
self.plugin_driver._sb_idl.chassis_list.return_value.execute.\
|
||||||
|
return_value = [chassis]
|
||||||
|
init_conf = self.placement_driver._read_initial_chassis_config()
|
||||||
|
expected = {chassis.name: {
|
||||||
|
n_const.RP_BANDWIDTHS: {},
|
||||||
|
n_const.RP_INVENTORY_DEFAULTS: {},
|
||||||
|
ovn_const.RP_HYPERVISORS: {}
|
||||||
|
}}
|
||||||
|
self.assertEqual(expected, init_conf)
|
||||||
|
|
||||||
|
# Test wrongly defined parameters. E.g.:
|
||||||
|
# external_ids: {ovn-cms-options={resource_provider_bandwidths=, ...}}
|
||||||
|
chassis = fakes.FakeChassis.create(
|
||||||
|
bridge_mappings=['public1:br-ext1'],
|
||||||
|
rp_bandwidths='',
|
||||||
|
rp_inventory_defaults='',
|
||||||
|
rp_hypervisors='')
|
||||||
|
self.plugin_driver._sb_idl.chassis_list.return_value.execute.\
|
||||||
|
return_value = [chassis]
|
||||||
|
init_conf = self.placement_driver._read_initial_chassis_config()
|
||||||
|
expected = {chassis.name: {
|
||||||
|
n_const.RP_BANDWIDTHS: {},
|
||||||
|
n_const.RP_INVENTORY_DEFAULTS: {},
|
||||||
|
ovn_const.RP_HYPERVISORS: {}
|
||||||
|
}}
|
||||||
|
self.assertEqual(expected, init_conf)
|
Loading…
Reference in New Issue