Merge "Support multiple subordinate api plugins"

This commit is contained in:
Zuul 2024-07-04 09:58:46 +00:00 committed by Gerrit Code Review
commit a4ce151ccc
2 changed files with 140 additions and 9 deletions

View File

@ -913,7 +913,14 @@ class NeutronApiSDNContext(context.SubordinateConfigContext):
def __call__(self):
ctxt = super(NeutronApiSDNContext, self).__call__()
for rid in relation_ids('neutron-plugin-api-subordinate'):
rel_ids = relation_ids('neutron-plugin-api-subordinate')
# Return empty dict when there are no related units, this will flag the
# context as incomplete and will allow end user messaging of missing
# relations
if not rel_ids:
return {}
for rid in rel_ids:
for unit in related_units(rid):
rdata = relation_get(rid=rid, unit=unit)
plugin = rdata.get('neutron-plugin')
@ -923,18 +930,33 @@ class NeutronApiSDNContext(context.SubordinateConfigContext):
for key in self.defaults.keys():
remote_value = rdata.get(key)
ctxt_key = self.defaults[key]['templ_key']
current = ctxt.get(ctxt_key, [])
if current:
current = [x.strip() for x in current.split(',')]
if remote_value:
ctxt[ctxt_key] = remote_value
elif self.defaults[key]['value']:
values = [v.strip() for v in remote_value.split(',')]
for value in values:
if value not in current:
current.append(value)
ctxt[ctxt_key] = ','.join(current)
elif self.defaults[key]['value'] and not current:
ctxt[ctxt_key] = self.defaults[key]['value']
else:
# Do not set empty values
pass
return ctxt
# Return empty dict when there are no related units, this will flag the
# context as incomplete and will allow end user messaging of missing
# relations
return {}
# Sanity check to ensure that the ovn and openvswitch mechanism
# drivers are not both set.
mechanism_drivers = ctxt.get('mechanism_drivers', None)
if mechanism_drivers:
mechanism_drivers = mechanism_drivers.split(',')
if ('ovn' in mechanism_drivers and
'openvswitch' in mechanism_drivers):
mechanism_drivers.remove('openvswitch')
ctxt['mechanism_drivers'] = ','.join(mechanism_drivers)
return ctxt
class NeutronApiSDNConfigFileContext(context.OSContextGenerator):

View File

@ -20,7 +20,7 @@ from unittest.mock import MagicMock, patch
import neutron_api_context as context
import charmhelpers
from test_utils import CharmTestCase
from test_utils import CharmTestCase, TestRelation
TO_PATCH = [
'config',
@ -1343,6 +1343,115 @@ class NeutronApiSDNContextTest(CharmTestCase):
}
)
@patch('neutron_api_context.super')
@patch.object(context, 'relation_get')
@patch.object(context, 'related_units')
@patch.object(context, 'relation_ids')
def test_multiple_subordinates_no_relation_data(self, rel_ids, rel_units,
rel_get, _super):
"""Test multiple subordinates w/out data provide empty context."""
napisdn_instance = context.NeutronApiSDNContext()
# Mock the call to super as it invokes relation-get and other methods
# due to these methods being imported into the
# charmhelpers.contrib.openstack.context library which requires
# multiple mocks for the same function (which is confusing).
_super.return_value = MagicMock(return_value={})
rel_ids.return_value = []
rel_units.return_value = []
rel_1 = TestRelation()
rel_2 = TestRelation()
def _relation_get(attribute=None, unit=None, rid=None, app=None):
if rid == 'rel-1':
return rel_1
if rid == 'rel-2':
return rel_2
return None
rel_get.side_effect = _relation_get
ctxt = napisdn_instance()
self.assertEqual(ctxt, {})
rel_ids.return_value = ['rel-1', 'rel-2']
rel_units.return_value = ['subordinate-1', 'subordinate-2']
ctxt = napisdn_instance()
self.assertEqual(ctxt, {})
@patch('neutron_api_context.super')
@patch.object(context, 'relation_get')
@patch.object(context, 'related_units')
@patch.object(context, 'relation_ids')
def test_multiple_subordinates_ovn_ironic(self, _rel_ids, _rel_units,
_rel_get, _super):
"""Test multiple subordinates provide merged context."""
napisdn_instance = context.NeutronApiSDNContext()
# Mock the call to super as it invokes relation-get and other methods
# due to these methods being imported into the
# charmhelpers.contrib.openstack.context library which requires
# multiple mocks for the same function (which is confusing).
_super.return_value = MagicMock(return_value={})
_rel_ids.return_value = ['rel-1', 'rel-2']
_rel_units.return_value = ['subordinate-1', 'subordinate-2']
ovn_rel = TestRelation({
'mechanism-drivers': 'ovn',
'neutron-plugin': 'ovn',
'service-plugins': (
'metering,segments,'
'neutron_dynamic_routing.services.bgp.bgp_plugin.BgpPlugin,'
'ovn-router,trunk'
),
'subordinate_configuration': (
'{"neutron-api": {"/etc/neutron/plugins/ml2/ml2_conf.ini":'
'{"sections": {"ovn": "test"}}}}'
),
'tenant-network-types': 'geneve, gre, vlan, flat, local'
})
ironic_rel = TestRelation({
'mechanism-drivers': 'openvswitch,l2population,baremetal',
'neutron-plugin': 'ironic',
'subordinate_configuration': '{}'
})
def _relation_get(attribute=None, unit=None, rid=None, app=None):
if rid == 'rel-1':
return ovn_rel
if rid == 'rel-2':
return ironic_rel
return None
_rel_get.side_effect = _relation_get
ctxt = napisdn_instance()
# Verify that when the ovn mechanism driver is specified, the
# openvswitch one is not available.
mechanism_drivers = ctxt.get('mechanism_drivers', '').split(',')
self.assertTrue(mechanism_drivers.index('ovn') >= 0)
self.assertRaises(ValueError, mechanism_drivers.index, 'openvswitch')
self.assertEqual(ctxt, {
'core_plugin': 'neutron.plugins.ml2.plugin.Ml2Plugin',
'mechanism_drivers': 'ovn,l2population,baremetal',
'neutron_plugin': 'ironic', # last one wins
'neutron_plugin_config': '/etc/neutron/plugins/ml2/ml2_conf.ini',
'service_plugins': (
'metering,segments,'
'neutron_dynamic_routing.services.bgp.bgp_plugin.BgpPlugin,'
'ovn-router,trunk'
),
'tenant_network_types': 'geneve,gre,vlan,flat,local',
})
def test_empty(self):
self.ctxt_check(
{},