From 9035ee7a7ba73caa561f6da5c739f6cc566c4dd7 Mon Sep 17 00:00:00 2001 From: Sven Rosenzweig Date: Tue, 12 Nov 2024 16:13:42 +0100 Subject: [PATCH 1/3] Register DEFAULT group on agent start From upstream commit 4d3a2747 fixing bug (#1968606), registration of the group DEFAULT does not happen automatically anymore. If not loaded, agent fails with 'oslo_config.cfg.NoSuchOptError: no such option api_extensions_path in group [DEFAULT]' --- networking_aci/plugins/ml2/drivers/mech_aci/agent/entry_point.py | 1 + 1 file changed, 1 insertion(+) diff --git a/networking_aci/plugins/ml2/drivers/mech_aci/agent/entry_point.py b/networking_aci/plugins/ml2/drivers/mech_aci/agent/entry_point.py index 2607938..bcc2498 100644 --- a/networking_aci/plugins/ml2/drivers/mech_aci/agent/entry_point.py +++ b/networking_aci/plugins/ml2/drivers/mech_aci/agent/entry_point.py @@ -27,6 +27,7 @@ def register_options(): + common_config.register_common_config_options() config.register_agent_state_opts_helper(cfg.CONF) From 0586f852ef9436816e061dc095007e85579103af Mon Sep 17 00:00:00 2001 From: Sven Rosenzweig Date: Tue, 12 Nov 2024 16:26:14 +0100 Subject: [PATCH 2/3] [Caracal] Update github workflow & Fix unit tests Update python version to 3.10 and set the base branch in the github workflowto Caracal(2024.1-m3). Fix Unit Tests: With later versions of pecan, WebTest is not a dependency of it anymore, so in order to continue using this, an additional import in test-requirements is required. Remove autoconfig loading, which breaks stestr test execution. Cli options can be loaded only once (without clearing them), which causes our tests to fail as required default configuration cli options can not be registered, as an default configuration has already been loaded. As part of the secure RBAC community goal, the "enforce_new_defaults" and "enforce_scope" setting is True by default. Avoiding PolicyNotAuthorized erros during test execution certain actions simply need to be executed as admin. --- .github/workflows/run-tox.yml | 4 ++-- networking_aci/tests/base.py | 1 + .../drivers/mech_aci/agent/test_aci_agent.py | 2 -- .../plugins/ml2/drivers/mech_aci/test_driver.py | 17 +++++++++-------- test-requirements.txt | 1 + tox.ini | 6 +++--- 6 files changed, 16 insertions(+), 15 deletions(-) diff --git a/.github/workflows/run-tox.yml b/.github/workflows/run-tox.yml index 55a0b33..e412fcc 100644 --- a/.github/workflows/run-tox.yml +++ b/.github/workflows/run-tox.yml @@ -2,14 +2,14 @@ name: run-tox on: push: branches: - - stable/yoga-m3 + - stable/2024.1-m3 pull_request: jobs: build: runs-on: ubuntu-latest strategy: matrix: - python: [3.8] + python: ['3.10'] steps: - uses: actions/checkout@v2 - name: Setup python diff --git a/networking_aci/tests/base.py b/networking_aci/tests/base.py index abb9c73..57e9ace 100644 --- a/networking_aci/tests/base.py +++ b/networking_aci/tests/base.py @@ -13,6 +13,7 @@ def get_additional_service_plugins(self): return dict(service_plugins='tag') def setUp(self): + config.register_common_config_options() self._mechanism_drivers.append(constants.ACI_DRIVER_NAME) cfg.CONF.set_override('debug', True) config.setup_logging() diff --git a/networking_aci/tests/unit/plugins/ml2/drivers/mech_aci/agent/test_aci_agent.py b/networking_aci/tests/unit/plugins/ml2/drivers/mech_aci/agent/test_aci_agent.py index e70f8ae..b49b758 100644 --- a/networking_aci/tests/unit/plugins/ml2/drivers/mech_aci/agent/test_aci_agent.py +++ b/networking_aci/tests/unit/plugins/ml2/drivers/mech_aci/agent/test_aci_agent.py @@ -19,8 +19,6 @@ from networking_aci.tests.unit import utils -cfg.CONF.use_stderr = False -cfg.CONF(args=[]) class AciNeutronAgentTest(base.BaseTestCase): diff --git a/networking_aci/tests/unit/plugins/ml2/drivers/mech_aci/test_driver.py b/networking_aci/tests/unit/plugins/ml2/drivers/mech_aci/test_driver.py index 1ba0141..a89fd79 100644 --- a/networking_aci/tests/unit/plugins/ml2/drivers/mech_aci/test_driver.py +++ b/networking_aci/tests/unit/plugins/ml2/drivers/mech_aci/test_driver.py @@ -29,21 +29,22 @@ def setUp(self): def test_create_subnet_az_hint_matches(self): net_kwargs = {'arg_list': (extnet_api.EXTERNAL,), extnet_api.EXTERNAL: True} - with self.network(**net_kwargs) as network: + with self.network(**net_kwargs, as_admin=True) as network: with self.subnetpool(["1.1.0.0/16", "1.2.0.0/24"], name="foo", tenant_id="foo", admin=True) as snp: with self.subnet(network=network, cidr="1.1.1.0/24", gateway_ip="1.1.1.1", - subnetpool_id=snp['subnetpool']['id']) as subnet: + subnetpool_id=snp['subnetpool']['id'], as_admin=True) as subnet: self.assertIsNotNone(subnet) def test_create_subnet_network_az_snp_no_az_fails(self): net_kwargs = {'arg_list': (extnet_api.EXTERNAL,), extnet_api.EXTERNAL: True} - with self.network(availability_zone_hints=["qa-de-1a"], **net_kwargs) as network: + with self.network(availability_zone_hints=["qa-de-1a"], **net_kwargs, as_admin=True) as network: with self.subnetpool(["1.1.0.0/16", "1.2.0.0/24"], address_scope_id=self._address_scope['id'], name="foo", tenant_id="foo", admin=True) as snp: resp = self._create_subnet(self.fmt, cidr="1.1.1.0/24", gateway_ip="1.1.1.1", name="foo", net_id=network['network']['id'], tenant_id=network['network']['tenant_id'], - subnetpool_id=snp['subnetpool']['id']) + subnetpool_id=snp['subnetpool']['id'], + as_admin=True) self.assertEqual(400, resp.status_code) self.assertEqual("SubnetSubnetPoolAZAffinityError", resp.json['NeutronError']['type']) self.assertIsNotNone(re.search(f"network {network['network']['id']} has AZ hint qa-de-1a,.*" @@ -52,7 +53,7 @@ def test_create_subnet_network_az_snp_no_az_fails(self): def test_create_subnet_network_no_az_snp_az_fails(self): net_kwargs = {'arg_list': (extnet_api.EXTERNAL,), extnet_api.EXTERNAL: True} - with self.network(**net_kwargs) as network: + with self.network(**net_kwargs, as_admin=True) as network: with self.subnetpool(["1.1.0.0/16", "1.2.0.0/24"], address_scope_id=self._address_scope['id'], name="foo", tenant_id="foo", admin=True) as snp: ctx = context.get_admin_context() @@ -63,7 +64,7 @@ def test_create_subnet_network_no_az_snp_az_fails(self): resp = self._create_subnet(self.fmt, cidr="1.1.1.0/24", gateway_ip="1.1.1.1", name="foo", net_id=network['network']['id'], tenant_id=network['network']['tenant_id'], - subnetpool_id=snp['subnetpool']['id']) + subnetpool_id=snp['subnetpool']['id'], as_admin=True) self.assertEqual(400, resp.status_code) self.assertEqual("SubnetSubnetPoolAZAffinityError", resp.json['NeutronError']['type']) self.assertIsNotNone(re.search(f"network {network['network']['id']} has AZ hint None,.*" @@ -73,9 +74,9 @@ def test_create_subnet_network_no_az_snp_az_fails(self): def test_create_subnet_network_snp_az_hint_works_when_turned_off(self): cfg.CONF.set_override('subnet_subnetpool_az_check_enabled', False, group='ml2_aci') net_kwargs = {'arg_list': (extnet_api.EXTERNAL,), extnet_api.EXTERNAL: True} - with self.network(availability_zone_hints=["qa-de-1a"], **net_kwargs) as network: + with self.network(availability_zone_hints=["qa-de-1a"], **net_kwargs, as_admin=True) as network: with self.subnetpool(["1.1.0.0/16", "1.2.0.0/24"], address_scope_id=self._address_scope['id'], name="foo", tenant_id="foo", admin=True) as snp: with self.subnet(network=network, cidr="1.1.1.0/24", gateway_ip="1.1.1.1", - subnetpool_id=snp['subnetpool']['id']) as subnet: + subnetpool_id=snp['subnetpool']['id'], as_admin=True) as subnet: self.assertIsNotNone(subnet) diff --git a/test-requirements.txt b/test-requirements.txt index a9da30d..3acc94d 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -14,3 +14,4 @@ testtools>=1.4.0 testresources>=0.2.4 requests stestr>=1.0.0 # Apache-2.0 +WebTest>=2.0.27 # MIT diff --git a/tox.ini b/tox.ini index 3bbd45c..4af1f0d 100644 --- a/tox.ini +++ b/tox.ini @@ -1,16 +1,16 @@ [tox] -envlist = py38 +envlist = py310 minversion = 3.18.0 skipsdist = True requires = virtualenv >= 20 [testenv] usedevelop = True -install_command = pip install -c {env:UPPER_CONSTRAINTS_FILE:https://raw.githubusercontent.com/sapcc/requirements/stable/yoga-m3/upper-constraints.txt} -r requirements.txt -r test-requirements.txt -U {opts} {packages} +install_command = pip install -c {env:UPPER_CONSTRAINTS_FILE:https://raw.githubusercontent.com/sapcc/requirements/stable/2024.1-m3/upper-constraints.txt} -r requirements.txt -r test-requirements.txt -U {opts} {packages} setenv = VIRTUAL_ENV={envdir} PYTHONWARNINGS=default::DeprecationWarning deps = -r{toxinidir}/test-requirements.txt - -e{env:NEUTRON_SOURCE:git+https://github.com/sapcc/neutron.git@stable/yoga-m3#egg=neutron} + -e{env:NEUTRON_SOURCE:git+https://github.com/sapcc/neutron.git@stable/2024.1-m3#egg=neutron} whitelist_externals = sh commands = stestr run {posargs} download = True From 9d9f5b903c2edd7f388bd8805a85a41071711db0 Mon Sep 17 00:00:00 2001 From: Sven Rosenzweig Date: Wed, 20 Nov 2024 12:28:14 +0100 Subject: [PATCH 3/3] Use the new enginefacade from oslo_db As per blueprint [1], the existing use of oslo_db session handling (e.g., context.session.begin()) introduces potential issues. Notably, unit tests failed during the Caracal release, though no definitive deployment impact has been identified yet. To future-proof the code and align with recommended practices, we are migrating to the enginefacade pattern now. This involves replacing: with context.session.begin(): context.session.add(obj) with 'db_api.CONTEXT_WRITER.using(context)' [1] https://blueprints.launchpad.net/neutron/+spec/enginefacade-switch [2] Oslo db spec: http://specs.openstack.org/openstack/oslo-specs/specs/kilo/make-enginefacade-a-facade.html --- .../drivers/mech_aci/allocations_manager.py | 57 ++++++++++--------- .../plugins/ml2/drivers/mech_aci/common.py | 35 ++++++++---- .../ml2/drivers/mech_aci/test_db_plugin.py | 27 +++++++++ .../ml2/drivers/mech_aci/test_driver.py | 5 +- 4 files changed, 83 insertions(+), 41 deletions(-) create mode 100644 networking_aci/tests/unit/plugins/ml2/drivers/mech_aci/test_db_plugin.py diff --git a/networking_aci/plugins/ml2/drivers/mech_aci/allocations_manager.py b/networking_aci/plugins/ml2/drivers/mech_aci/allocations_manager.py index 5bb90e6..83f0a84 100644 --- a/networking_aci/plugins/ml2/drivers/mech_aci/allocations_manager.py +++ b/networking_aci/plugins/ml2/drivers/mech_aci/allocations_manager.py @@ -103,17 +103,18 @@ def _allocate_vlan_segment(self, network, host_id, level, host_config): segment_type = host_config.get('segment_type', 'vlan') segment_physnet = host_config.get('physical_network', None) - session = db_api.get_writer_session() + ctx = context.get_admin_context() segmentation_id = self._get_provider_attribute(network, "provider:segmentation_id") network_id = network["id"] - segment = session.query(ml2_models.NetworkSegment).filter_by(segmentation_id=segmentation_id, + with db_api.CONTEXT_READER.using(ctx): + segment = ctx.session.query(ml2_models.NetworkSegment).filter_by(segmentation_id=segmentation_id, physical_network=segment_physnet, network_type=segment_type, network_id=network_id, level=level).first() if not segment: - with session.begin(subtransactions=True): + with db_api.CONTEXT_WRITER.using(ctx): segment = ml2_models.NetworkSegment( id=uuidutils.generate_uuid(), network_id=network_id, @@ -123,7 +124,7 @@ def _allocate_vlan_segment(self, network, host_id, level, host_config): segment_index=level, is_dynamic=False ) - session.add(segment) + ctx.session.add(segment) return AllocationsModel(host=host_id, level=level, segment_type=segment_type, segmentation_id=segmentation_id, segment_id=segment.id, network_id=network_id) @@ -135,21 +136,21 @@ def _allocate_vxlan_segment(self, network, host_id, level, host_config): segment_physnet = host_config.get('physical_network', None) network_id = network['id'] - session = db_api.get_writer_session() - with db_api.exc_to_retry(sa.exc.IntegrityError), session.begin(subtransactions=True): + ctx = context.get_admin_context() + with db_api.exc_to_retry(sa.exc.IntegrityError), db_api.CONTEXT_WRITER.using(ctx): LOG.debug("Searching for available allocation for host id %(host_id)s " "segment_type %(segment_type)s network_id %(network_id)s segment_physnet %(segment_physnet)s", {"host_id": host_id, "segment_type": segment_type, "segment_physnet": segment_physnet, "network_id": network_id} ) - alloc = session.query(AllocationsModel).filter_by(host=host_id, level=level, segment_type=segment_type, + alloc = ctx.session.query(AllocationsModel).filter_by(host=host_id, level=level, segment_type=segment_type, network_id=network_id).first() if alloc and alloc.segment_id: return alloc # we regard a segment as unallocated if its segment_id is None - select = (session.query(AllocationsModel). + select = (ctx.session.query(AllocationsModel). filter_by(host=host_id, level=level, segment_type=segment_type, segment_id=None)) # Selected segment can be allocated before update by someone else, @@ -171,7 +172,7 @@ def _allocate_vxlan_segment(self, network, host_id, level, host_config): segment_index=level, is_dynamic=False ) - session.add(segment) + ctx.session.add(segment) raw_segment = { 'host': alloc.host, @@ -182,7 +183,7 @@ def _allocate_vxlan_segment(self, network, host_id, level, host_config): LOG.debug("%(type)s segment allocated from pool with %(segment)s ", {"type": alloc.segment_type, "segment": alloc.segmentation_id}) - count = (session.query(AllocationsModel). + count = (ctx.session.query(AllocationsModel). filter_by(segment_id=None, **raw_segment). update({"network_id": network_id, 'segment_id': segment.id})) @@ -207,10 +208,10 @@ def release_segment(self, network, host_config, level, segment): def _release_vlan_segment(self, network, host_config, level, segment): LOG.debug("Checking release for segment %(segment)s with top level VLAN segment", {"segment": segment}) - session = db_api.get_writer_session() - with session.begin(subtransactions=True): + ctx = context.get_admin_context() + with db_api.CONTEXT_WRITER.using(ctx): # Delete the network segment - query = (session.query(ml2_models.NetworkSegment). + query = (ctx.session.query(ml2_models.NetworkSegment). filter_by(id=segment['id'], network_id=network['id'], network_type=segment['network_type'], segmentation_id=segment['segmentation_id'], segment_index=level)) query.delete() @@ -222,9 +223,9 @@ def _release_vxlan_segment(self, network, host_config, level, segment): segmentation_id = segment['segmentation_id'] network_id = network['id'] - session = db_api.get_writer_session() - with session.begin(subtransactions=True): - select = (session.query(models.PortBindingLevel). + ctx = context.get_admin_context() + with db_api.CONTEXT_WRITER.using(ctx): + select = (ctx.session.query(models.PortBindingLevel). filter_by(segment_id=segment_id, level=level)) if select.count() > 0: @@ -234,7 +235,7 @@ def _release_vxlan_segment(self, network, host_config, level, segment): segmentation_ids = self._segmentation_ids(host_config) inside = segmentation_id in segmentation_ids - query = (session.query(AllocationsModel). + query = (ctx.session.query(AllocationsModel). filter_by(network_id=network_id, level=level, segment_type=segment_type, segment_id=segment_id)) if inside: @@ -243,7 +244,7 @@ def _release_vxlan_segment(self, network, host_config, level, segment): query.delete() # Delete the network segment - query = (session.query(ml2_models.NetworkSegment). + query = (ctx.session.query(ml2_models.NetworkSegment). filter_by(id=segment_id, network_id=network_id, network_type=segment_type, segmentation_id=segmentation_id, segment_index=level)) @@ -281,15 +282,15 @@ def allocate_baremetal_segment(self, context, network, hostgroup, level, segment _release_vxlan_segment(). """ is_access = segmentation_id is None - session = context.session + ctx = context.get_admin_context() segment_type = hostgroup.get('segment_type', 'vlan') segment_physnet = hostgroup.get('physical_network') network_id = network['id'] access_id_pool = common.get_set_from_ranges(hostgroup['baremetal_access_vlan_ranges']) - with db_api.exc_to_retry(sa.exc.IntegrityError), session.begin(subtransactions=True): + with db_api.exc_to_retry(sa.exc.IntegrityError), db_api.CONTEXT_WRITER.using(ctx): # 1. check if segment exists - existing_segments = (session.query(ml2_models.NetworkSegment) + existing_segments = (ctx.session.query(ml2_models.NetworkSegment) .filter_by(network_id=network_id, physical_network=segment_physnet, segment_index=level, network_type=segment_type) .all()) @@ -323,7 +324,7 @@ def allocate_baremetal_segment(self, context, network, hostgroup, level, segment segment_id=far_segment_id) else: # for trunk mode: check segmentation_id is not already in use in physnet - existing_segments = (session.query(ml2_models.NetworkSegment) + existing_segments = (ctx.session.query(ml2_models.NetworkSegment) .filter_by(segmentation_id=segmentation_id, physical_network=segment_physnet, segment_index=level, network_type=segment_type) .all()) @@ -334,7 +335,7 @@ def allocate_baremetal_segment(self, context, network, hostgroup, level, segment # 3. no segment exists, allocate one if is_access: # find a free vlan id from the pool - physnet_segments = (session.query(ml2_models.NetworkSegment) + physnet_segments = (ctx.session.query(ml2_models.NetworkSegment) .filter_by(physical_network=segment_physnet) .all()) used_ids = set(n.segmentation_id for n in physnet_segments) @@ -353,7 +354,7 @@ def allocate_baremetal_segment(self, context, network, hostgroup, level, segment segment_index=level, is_dynamic=False ) - session.add(segment) + ctx.session.add(segment) return segment @@ -443,16 +444,16 @@ def _sync_allocations(self): def _sync_hostgroup_modes(self): LOG.info("Preparing hostgroup modes sync") - session = db_api.get_writer_session() - with session.begin(subtransactions=True): + ctx = context.get_admin_context() + with db_api.CONTEXT_WRITER.using(ctx): # fetch all mode-hostgroups from db db_groups = [] - for db_entry in (session.query(HostgroupModeModel).with_for_update()): + for db_entry in (ctx.session.query(HostgroupModeModel).with_for_update()): db_groups.append(db_entry.hostgroup) for hg_name, hg in ACI_CONFIG.hostgroups.items(): if hg['direct_mode'] and hg_name not in db_groups: LOG.info("Adding %s to hostgroup db", hg_name) hgmm = HostgroupModeModel(hostgroup=hg_name) - session.add(hgmm) + ctx.session.add(hgmm) LOG.info("Hostgroup modes synced") diff --git a/networking_aci/plugins/ml2/drivers/mech_aci/common.py b/networking_aci/plugins/ml2/drivers/mech_aci/common.py index d67ad89..294a84d 100644 --- a/networking_aci/plugins/ml2/drivers/mech_aci/common.py +++ b/networking_aci/plugins/ml2/drivers/mech_aci/common.py @@ -29,6 +29,7 @@ from neutron.db.models import segment as segment_models from neutron.db.models import tag as tag_models from neutron.db import segments_db as ml2_db +from neutron_lib.db import api as db_api from neutron.plugins.ml2 import models as ml2_models import neutron.services.trunk.models as trunk_models from oslo_config import cfg @@ -49,14 +50,15 @@ class DBPlugin(db_base_plugin_v2.NeutronDbPluginV2, def __init__(self): pass + @db_api.CONTEXT_READER def get_ports_with_binding(self, context, network_id): - with context.session.begin(subtransactions=True): - query = context.session.query(models_v2.Port) - query1 = query.join(ml2_models.PortBinding) - bind_ports = query1.filter(models_v2.Port.network_id == network_id) + query = context.session.query(models_v2.Port) + query1 = query.join(ml2_models.PortBinding) + bind_ports = query1.filter(models_v2.Port.network_id == network_id) - return bind_ports + return bind_ports + @db_api.CONTEXT_READER def get_network_ids(self, context): result = [] query = context.session.query(models_v2.Network.id).order_by(models_v2.Network.id) @@ -87,6 +89,7 @@ def get_address_scope_name(self, context, subnet_pool_id): return scope.get('name') + @db_api.CONTEXT_READER def get_hostgroup_modes(self, context, hostgroup_names=None): hg_modes = {} query = context.session.query(HostgroupModeModel) @@ -100,15 +103,16 @@ def get_hostgroup_mode(self, context, hostgroup_name): hg_modes = self.get_hostgroup_modes(context, [hostgroup_name]) return hg_modes.get(hostgroup_name) + @db_api.CONTEXT_WRITER def set_hostgroup_mode(self, context, hostgroup_name, hostgroup_mode): - with context.session.begin(subtransactions=True): - query = context.session.query(HostgroupModeModel).filter(HostgroupModeModel.hostgroup == hostgroup_name) - hg = query.first() - if not hg: - return False - hg.mode = hostgroup_mode + query = context.session.query(HostgroupModeModel).filter(HostgroupModeModel.hostgroup == hostgroup_name) + hg = query.first() + if not hg: + return False + hg.mode = hostgroup_mode return True + @db_api.CONTEXT_READER def get_hosts_on_segment(self, context, segment_id, level=None): """Get all binding hosts (from host or binding_profile) present on a segment""" # get all ports bound to segment, extract their host @@ -125,6 +129,7 @@ def get_hosts_on_segment(self, context, segment_id, level=None): hosts.add(host) return hosts + @db_api.CONTEXT_READER def get_hosts_on_network(self, context, network_id, level=None, with_segment=False, transit_hostgroups=None): """Get all binding hosts (from host or binding_profile) present on a network""" fields = [ml2_models.PortBinding.host, ml2_models.PortBinding.profile] @@ -167,6 +172,7 @@ def get_hosts_on_network(self, context, network_id, level=None, with_segment=Fal return hosts + @db_api.CONTEXT_READER def get_hosts_on_physnet(self, context, physical_network, level=None, with_segment=False, with_segmentation=False): """Get all binding hosts (from host or binding_profile) present on a network @@ -201,6 +207,7 @@ def get_hosts_on_physnet(self, context, physical_network, level=None, with_segme hosts.add(host) return hosts + @db_api.CONTEXT_READER def get_segment_ids_by_physnet(self, context, physical_network, fuzzy_match=False): query = context.session.query(segment_models.NetworkSegment.id) if fuzzy_match: @@ -209,6 +216,7 @@ def get_segment_ids_by_physnet(self, context, physical_network, fuzzy_match=Fals query = query.filter(segment_models.NetworkSegment.physical_network == physical_network) return [seg.id for seg in query.all()] + @db_api.CONTEXT_READER def get_ports_on_network_by_physnet_prefix(self, context, network_id, physical_network_prefix): # get all ports for a network that are on a segment with a physnet prefix fields = [ @@ -233,6 +241,7 @@ def get_ports_on_network_by_physnet_prefix(self, context, network_id, physical_n return result + @db_api.CONTEXT_READER def get_bound_projects_by_physnet_prefix(self, context, physical_network_prefix): # get all projects that have a port bound to a segment with this prefix query = context.session.query(models_v2.Port.project_id) @@ -244,6 +253,7 @@ def get_bound_projects_by_physnet_prefix(self, context, physical_network_prefix) return [entry.project_id for entry in query.all()] + @db_api.CONTEXT_READER def get_trunk_vlan_usage_on_project(self, context, project_id, segmentation_id=None): # return vlan --> networks mapping for aci trunk ports inside a project query = context.session.query(models_v2.Port.network_id, trunk_models.SubPort.segmentation_id) @@ -262,6 +272,7 @@ def get_trunk_vlan_usage_on_project(self, context, project_id, segmentation_id=N return vlan_map + @db_api.CONTEXT_READER def get_az_aware_external_subnets(self, context): if not cfg.CONF.ml2_aci.handle_all_l3_gateways: return [] @@ -298,6 +309,7 @@ def get_az_aware_external_subnets(self, context): return subnets + @db_api.CONTEXT_READER def get_external_subnet_nullroute_mapping(self, context, level=1): if not cfg.CONF.ml2_aci.handle_all_l3_gateways: return {} @@ -417,6 +429,7 @@ def get_external_subnet_nullroute_mapping(self, context, level=1): return subnets + @db_api.CONTEXT_READER def get_subnetpool_details(self, context, subnetpool_ids): # get az from tags fields = [models_v2.SubnetPool.id, tag_models.Tag.tag] diff --git a/networking_aci/tests/unit/plugins/ml2/drivers/mech_aci/test_db_plugin.py b/networking_aci/tests/unit/plugins/ml2/drivers/mech_aci/test_db_plugin.py new file mode 100644 index 0000000..057fd2a --- /dev/null +++ b/networking_aci/tests/unit/plugins/ml2/drivers/mech_aci/test_db_plugin.py @@ -0,0 +1,27 @@ +from neutron.tests.unit.plugins.ml2 import test_plugin +from neutron_lib import context +from neutron_lib.db import api as db_api +from oslotest import base + +from networking_aci.db.models import HostgroupModeModel +from networking_aci.plugins.ml2.drivers.mech_aci.common import DBPlugin + + +class NetworkingDBPluginTests(test_plugin.Ml2PluginV2TestCase, base.BaseTestCase): + + def _create_hostgroup_mode(self, hostgroup, mode): + ctx = context.get_admin_context() + with db_api.CONTEXT_WRITER.using(ctx): + self._hg_mode = HostgroupModeModel(hostgroup=hostgroup, mode=mode) + ctx.session.add(self._hg_mode) + + def test_set_hostgroup_mode(self): + new_hg_mode = 'something-different' + + self._create_hostgroup_mode(hostgroup="hg-1", mode="infra") + plugin = DBPlugin() + ctx = context.get_admin_context() + plugin.set_hostgroup_mode(ctx, "hg-1", new_hg_mode) + + hg_mode = plugin.get_hostgroup_mode(ctx, "hg-1") + self.assertEqual(new_hg_mode, hg_mode, f"Hostgroup Mode should match {new_hg_mode} but got {hg_mode}") diff --git a/networking_aci/tests/unit/plugins/ml2/drivers/mech_aci/test_driver.py b/networking_aci/tests/unit/plugins/ml2/drivers/mech_aci/test_driver.py index a89fd79..fe2025d 100644 --- a/networking_aci/tests/unit/plugins/ml2/drivers/mech_aci/test_driver.py +++ b/networking_aci/tests/unit/plugins/ml2/drivers/mech_aci/test_driver.py @@ -4,6 +4,7 @@ from neutron.db.models import tag as tag_models from neutron.db import models_v2 from neutron_lib.api.definitions import external_net as extnet_api +from neutron_lib.db import api as db_api from neutron_lib import context from neutron.tests.common import helpers as neutron_test_helpers from neutron.tests.unit.plugins.ml2 import test_plugin @@ -23,7 +24,7 @@ def setUp(self): super().setUp() self._register_azs() ctx = context.get_admin_context() - with ctx.session.begin(subtransactions=True): + with db_api.CONTEXT_WRITER.using(ctx): self._address_scope = ascope_models.AddressScope(name="the-open-sea", ip_version=4) ctx.session.add(self._address_scope) @@ -57,7 +58,7 @@ def test_create_subnet_network_no_az_snp_az_fails(self): with self.subnetpool(["1.1.0.0/16", "1.2.0.0/24"], address_scope_id=self._address_scope['id'], name="foo", tenant_id="foo", admin=True) as snp: ctx = context.get_admin_context() - with ctx.session.begin(): + with db_api.CONTEXT_WRITER.using(ctx): snp_db = ctx.session.query(models_v2.SubnetPool).get(snp['subnetpool']['id']) ctx.session.add(tag_models.Tag(standard_attr_id=snp_db.standard_attr_id, tag="availability-zone::qa-de-1a"))