From c3b99a64fc117a3dc8f6d0ff92272183753fcf3e Mon Sep 17 00:00:00 2001 From: Slawek Kaplonski Date: Tue, 20 Jul 2021 21:25:54 +0000 Subject: [PATCH] Revert "[OVN][Placement] Add a SB Chassis event to track changes in BW config" This reverts commit df5cb28737453e2dfee17e177160fa6d7a59b7e0. The reverted commit triggers the failure of tempest-slow-py3 job. tempest-slow-py3 is equal to non-voting neutron-ovn-tempest-slow job in the neutron CI. It is a non-voting job so the error was not detected before merging it. To recover the tempest job in OpenStack wide, this commit reverts it. See http://lists.openstack.org/pipermail/openstack-discuss/2021-July/023764.html Closes-Bug: #1936983 Change-Id: Id8cdd9c69e4fef2d9c335447b498958add8b7816 --- doc/source/admin/config-qos-min-bw.rst | 22 -- neutron/common/ovn/constants.py | 5 - .../mech_driver/ovsdb/extensions/placement.py | 250 ------------------ .../ovn/mech_driver/ovsdb/ovn_client.py | 4 - .../ovn/mech_driver/ovsdb/ovsdb_monitor.py | 5 - neutron/tests/functional/base.py | 15 +- .../ovsdb/extensions/test_placement.py | 175 ------------ 7 files changed, 3 insertions(+), 473 deletions(-) delete mode 100644 neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/placement.py delete mode 100644 neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/test_placement.py diff --git a/doc/source/admin/config-qos-min-bw.rst b/doc/source/admin/config-qos-min-bw.rst index 17dd2249297..c84815a3d5c 100644 --- a/doc/source/admin/config-qos-min-bw.rst +++ b/doc/source/admin/config-qos-min-bw.rst @@ -262,28 +262,6 @@ neutron-openvswitch-agent. However look out for: resource_provider_bandwidths = ens5:40000000:40000000,ens6:40000000:40000000,... #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 ----------------------------------- diff --git a/neutron/common/ovn/constants.py b/neutron/common/ovn/constants.py index ffc8c9b56fe..f4fa5b74c79 100644 --- a/neutron/common/ovn/constants.py +++ b/neutron/common/ovn/constants.py @@ -297,8 +297,3 @@ CMS_OPT_AVAILABILITY_ZONES = 'availability-zones' # OVN vlan transparency option VLAN_PASSTHRU = 'vlan-passthru' - -# OVN Placement API; used for minimum bandwidth scheduling allocation. -RP_BANDWIDTHS = 'resource_provider_bandwidths' -RP_INVENTORY_DEFAULTS = 'resource_provider_inventory_defaults' -RP_HYPERVISORS = 'resource_provider_hypervisors' diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/placement.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/placement.py deleted file mode 100644 index 4e9443c536c..00000000000 --- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/placement.py +++ /dev/null @@ -1,250 +0,0 @@ -# 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 keystoneauth1 import exceptions as ks_exc -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 {ovn_const.RP_BANDWIDTHS: _parse_bandwidths(cms_options), - ovn_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_bandwidths(cms_options): - for cms_option in cms_options: - if ovn_const.RP_BANDWIDTHS in cms_option: - bw_values = cms_option.split('=')[1] - break - else: - return - - if bw_values: - return placement_utils.parse_rp_bandwidths(bw_values.split(';')) - - -def _parse_inventory_defaults(cms_options): - for cms_option in cms_options: - if ovn_const.RP_INVENTORY_DEFAULTS in cms_option: - inv_defaults = cms_option.split('=')[1] - break - else: - return - - 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): - for cms_option in cms_options: - if ovn_const.RP_HYPERVISORS in cms_option: - hypervisors = cms_option.split('=')[1] - break - else: - return - - if hypervisors: - return helpers.parse_mappings(hypervisors.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 {ovn_const.RP_BANDWIDTHS: state._rp_bandwidths, - ovn_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._driver = driver - self._placement_plugin = None - self._plugin = None - self._ovn_mech_driver = None - self._enabled = bool(self.placement_plugin) - self._chassis = {} # Initial config read could take some time. - if not self._enabled: - return - - try: - self._driver._sb_idl.idl.notify_handler.watch_events( - [ChassisBandwidthConfigEvent(self)]) - except AttributeError: - self._enabled = False - - if not self._enabled: - return - - self.uuid_ns = self.ovn_mech_driver.resource_provider_uuid5_namespace - self.supported_vnic_types = self.ovn_mech_driver.supported_vnic_types - 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 {} - - return {rp['name']: rp['uuid'] for rp in rps} - - 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 = {} - try: - for device, hyperv in cms_options[ - ovn_const.RP_HYPERVISORS].items(): - hypervisor_rps[device] = {'name': hyperv, - 'uuid': name2uuid[hyperv]} - except KeyError: - LOG.warning('Error updating BW information from chassis ' - '%(chassis)s, CMS options: %(cms_options)s', - {'chassis': chassis.name, 'cms_options': cms_options}) - return - - return placement_report.PlacementState( - rp_bandwidths=cms_options[ovn_const.RP_BANDWIDTHS], - rp_inventory_defaults=cms_options[ - ovn_const.RP_INVENTORY_DEFAULTS], - 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): - try: - return self._chassis[chassis_name] - except KeyError: - return - - def add_chassis_config(self, chassis_name, config): - if config: - self._chassis[chassis_name] = config diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py index 145919dc283..8efa3a75f31 100644 --- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py +++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py @@ -42,8 +42,6 @@ from neutron.common.ovn import utils 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 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 \ import qos as qos_extension from neutron.scheduler import l3_ovn_scheduler @@ -74,8 +72,6 @@ class OVNClient(object): # TODO(ralonsoh): handle the OVN client extensions with an ext. manager self._qos_driver = qos_extension.OVNClientQosExtension(self) - self._placement_extension = ( - placement_extension.OVNClientPlacementExtension(self)) self._ovn_scheduler = l3_ovn_scheduler.get_scheduler() @property diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovsdb_monitor.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovsdb_monitor.py index 1a73edb43b9..b6b0bda5075 100644 --- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovsdb_monitor.py +++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovsdb_monitor.py @@ -500,11 +500,6 @@ class BaseOvnIdl(Ml2OvnIdlBase): class BaseOvnSbIdl(Ml2OvnIdlBase): - - def __init__(self, remote, schema): - self.notify_handler = row_event.RowEventHandler() - super().__init__(remote, schema) - @classmethod def from_server(cls, connection_string, helper): helper.register_table('Chassis') diff --git a/neutron/tests/functional/base.py b/neutron/tests/functional/base.py index 6a4b5e164c3..1b4d674ceb1 100644 --- a/neutron/tests/functional/base.py +++ b/neutron/tests/functional/base.py @@ -42,8 +42,6 @@ from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf from neutron.db import models # noqa from neutron import manager 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 import type_geneve # noqa from neutron import service # noqa @@ -169,7 +167,7 @@ class TestOVNFunctionalBase(test_plugin.Ml2PluginV2TestCase, self.temp_dir = self.useFixture(fixtures.TempDir()).path self._start_ovsdb_server() - def setUp(self, maintenance_worker=False, service_plugins=None): + def setUp(self, maintenance_worker=False): ml2_config.cfg.CONF.set_override('extension_drivers', self._extension_drivers, group='ml2') @@ -186,7 +184,6 @@ class TestOVNFunctionalBase(test_plugin.Ml2PluginV2TestCase, self.addCleanup(exts.PluginAwareExtensionManager.clear_instance) self.ovsdb_server_mgr = None - self._service_plugins = service_plugins super(TestOVNFunctionalBase, self).setUp() self.test_log_dir = os.path.join(DEFAULT_LOG_DIR, self.id()) base.setup_test_logging( @@ -220,14 +217,10 @@ class TestOVNFunctionalBase(test_plugin.Ml2PluginV2TestCase, self._start_idls() self._start_ovn_northd() self.addCleanup(self._reset_agent_cache_singleton) - self.addCleanup(self._reset_ovn_client_placement_extension) def _reset_agent_cache_singleton(self): neutron_agent.AgentCache._instance = None - def _reset_ovn_client_placement_extension(self): - ovn_client_placement.OVNClientPlacementExtension._instance = None - def _get_install_share_path(self): lookup_paths = set() for installation in ['local', '']: @@ -257,10 +250,8 @@ class TestOVNFunctionalBase(test_plugin.Ml2PluginV2TestCase, def get_additional_service_plugins(self): p = super(TestOVNFunctionalBase, self).get_additional_service_plugins() - p.update({'revision_plugin_name': 'revisions', - 'segments': 'neutron.services.segments.plugin.Plugin'}) - if self._service_plugins: - p.update(self._service_plugins) + p.update({'revision_plugin_name': 'revisions'}) + p.update({'segments': 'neutron.services.segments.plugin.Plugin'}) return p @property diff --git a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/test_placement.py b/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/test_placement.py deleted file mode 100644 index ce83340d63a..00000000000 --- a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/test_placement.py +++ /dev/null @@ -1,175 +0,0 @@ -# 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.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): - - 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': { - ovn_const.RP_BANDWIDTHS: RP_BANDWIDTHS_1, - ovn_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': { - ovn_const.RP_BANDWIDTHS: RP_BANDWIDTHS_2, - ovn_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': { - ovn_const.RP_BANDWIDTHS: RP_BANDWIDTHS_2, - ovn_const.RP_INVENTORY_DEFAULTS: RP_INVENTORY_DEFAULTS_2, - 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(ovn_const.RP_BANDWIDTHS + '=' + bandwidths) - if inventory_defaults: - options.append(ovn_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') - 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({}) - - 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}) - - 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}) - - 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') - self._check_placement_cache({**self.CHASSIS1, **self.CHASSIS2_B})