Fail if required extensions are missing

If the required extensions are missing, we currently log an error
that is going to be practically ignored. That said, the unfulfilled
requirement will most definitely going to lead to other failures,
so we might as well fail fast.

This patch also cleans up some <barf>dns-integration nonsense</barf>
within the ML2 framework: the extension must not be declared statically
as it's being loaded by the extension manager, and this fixes the lousy
unit tests we have to live with. As for the db base plugin, some cleanup
is still overdue, but it will have to be taken care of in a follow-up
patch.

Closes-bug: #1538623

Change-Id: Id50eeb52c5d209170042b48821a29af3421c2f5c
This commit is contained in:
armando-migliaccio 2016-01-27 08:23:48 -08:00
parent 5ccfbb5b25
commit b14c06b5ed
8 changed files with 33 additions and 7 deletions

View File

@ -74,6 +74,11 @@ Document common pitfalls as well as good practices done during plugin developmen
* When adding behavior to the L2 and L3 db base classes, do not assume that * When adding behavior to the L2 and L3 db base classes, do not assume that
there is an agent on the other side of the message broker that interacts there is an agent on the other side of the message broker that interacts
with the server. Plugins may not rely on `agents <https://review.openstack.org/#/c/174020/>`_ at all. with the server. Plugins may not rely on `agents <https://review.openstack.org/#/c/174020/>`_ at all.
* Be mindful of required capabilities when you develop plugin extensions. The
`Extension description <https://github.com/openstack/neutron/blob/master/neutron/api/extensions.py#L122>`_ provides the ability to specify the list of required capabilities
for the extension you are developing. By declaring this list, the server will
not start up if the requirements are not met, thus avoiding leading the system
to experience undetermined behavior at runtime.
Database interaction Database interaction
~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~

View File

@ -440,10 +440,11 @@ class ExtensionManager(object):
# Exit loop as no progress was made # Exit loop as no progress was made
break break
if exts_to_process: if exts_to_process:
# NOTE(salv-orlando): Consider whether this error should be fatal
LOG.error(_LE("It was impossible to process the following " LOG.error(_LE("It was impossible to process the following "
"extensions: %s because of missing requirements."), "extensions: %s because of missing requirements."),
','.join(exts_to_process.keys())) ','.join(exts_to_process.keys()))
raise exceptions.ExtensionsNotFound(
extensions=list(exts_to_process.keys()))
# Extending extensions' attributes map. # Extending extensions' attributes map.
for ext in processed_exts.values(): for ext in processed_exts.values():

View File

@ -1172,7 +1172,8 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
status=p.get('status', constants.PORT_STATUS_ACTIVE), status=p.get('status', constants.PORT_STATUS_ACTIVE),
device_id=p['device_id'], device_id=p['device_id'],
device_owner=p['device_owner']) device_owner=p['device_owner'])
if 'dns_name' in p: if ('dns-integration' in self.supported_extension_aliases and
'dns_name' in p):
request_dns_name = self._get_request_dns_name(p) request_dns_name = self._get_request_dns_name(p)
port_data['dns_name'] = request_dns_name port_data['dns_name'] = request_dns_name
@ -1190,13 +1191,15 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
ips = self.ipam.allocate_ips_for_port_and_store(context, port, ips = self.ipam.allocate_ips_for_port_and_store(context, port,
port_id) port_id)
if 'dns_name' in p: if ('dns-integration' in self.supported_extension_aliases and
'dns_name' in p):
dns_assignment = [] dns_assignment = []
if ips: if ips:
dns_assignment = self._get_dns_names_for_port( dns_assignment = self._get_dns_names_for_port(
context, ips, request_dns_name) context, ips, request_dns_name)
if 'dns_name' in p: if ('dns-integration' in self.supported_extension_aliases and
'dns_name' in p):
db_port['dns_assignment'] = dns_assignment db_port['dns_assignment'] = dns_assignment
return self._make_port_dict(db_port, process_extensions=False) return self._make_port_dict(db_port, process_extensions=False)

View File

@ -125,7 +125,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
"multi-provider", "allowed-address-pairs", "multi-provider", "allowed-address-pairs",
"extra_dhcp_opt", "subnet_allocation", "extra_dhcp_opt", "subnet_allocation",
"net-mtu", "vlan-transparent", "net-mtu", "vlan-transparent",
"address-scope", "dns-integration", "address-scope",
"availability_zone", "availability_zone",
"network_availability_zone"] "network_availability_zone"]

View File

@ -502,6 +502,13 @@ class RequestExtensionTest(base.BaseTestCase):
class ExtensionManagerTest(base.BaseTestCase): class ExtensionManagerTest(base.BaseTestCase):
def test_missing_required_extensions(self):
ext_mgr = extensions.ExtensionManager('')
attr_map = {}
ext_mgr.add_extension(ext_stubs.StubExtensionWithReqs('foo_alias'))
self.assertRaises(exceptions.ExtensionsNotFound,
ext_mgr.extend_resources, "2.0", attr_map)
def test_invalid_extensions_are_not_registered(self): def test_invalid_extensions_are_not_registered(self):
class InvalidExtension(object): class InvalidExtension(object):

View File

@ -1400,8 +1400,7 @@ class OvsAgentSchedulerTestCase(OvsAgentSchedulerTestCaseBase):
exc.HTTPNotFound.code) exc.HTTPNotFound.code)
class OvsDhcpAgentNotifierTestCase(test_l3.L3NatTestCaseMixin, class OvsDhcpAgentNotifierTestCase(test_agent.AgentDBTestMixIn,
test_agent.AgentDBTestMixIn,
AgentSchedulerTestMixIn, AgentSchedulerTestMixIn,
test_plugin.NeutronDbPluginV2TestCase): test_plugin.NeutronDbPluginV2TestCase):
plugin_str = 'neutron.plugins.ml2.plugin.Ml2Plugin' plugin_str = 'neutron.plugins.ml2.plugin.Ml2Plugin'

View File

@ -37,6 +37,12 @@ class StubExtension(extensions.ExtensionDescriptor):
return "" return ""
class StubExtensionWithReqs(StubExtension):
def get_required_extensions(self):
return ["foo"]
class StubPlugin(object): class StubPlugin(object):
def __init__(self, supported_extensions=None): def __init__(self, supported_extensions=None):

View File

@ -0,0 +1,5 @@
---
fixes:
- The server will fail to start if any of the declared required
extensions, as needed by core and service plugins, are not
properly configured.