From 43a948e47843ee4b3fc5dfdd4080aea67c3bfdaf Mon Sep 17 00:00:00 2001 From: Steve Baker Date: Fri, 4 Jul 2014 08:03:34 +1200 Subject: [PATCH] Use stevedore to load client plugins This uses stevedore to load any client plugin extension defined in the heat.clients namespace in setup.cfg. As a transition from non-plugin to plugin clients, requesting a named client will first search for a client plugin, then fallback to calling the _() method. Clients plugin initialise is triggered from resources initialise. This change also adds the clients package for the constraints plugin manager. Custom constraints often make client API calls and are not necessarily associated with resources, so in many cases the client plugin will be a better place for constraints to live. Change-Id: I853767944caeca2939c9494ae992a0c4f6474078 --- heat/engine/clients/__init__.py | 37 +++++++++++++++++++++++++++++++ heat/engine/resources/__init__.py | 3 +++ heat/tests/test_clients.py | 33 +++++++++++++++++++++++++-- 3 files changed, 71 insertions(+), 2 deletions(-) diff --git a/heat/engine/clients/__init__.py b/heat/engine/clients/__init__.py index 801752c09c..158e389419 100644 --- a/heat/engine/clients/__init__.py +++ b/heat/engine/clients/__init__.py @@ -19,6 +19,7 @@ from neutronclient.v2_0 import client as neutronclient from novaclient import client as novaclient from novaclient import shell as novashell from oslo.config import cfg +from stevedore import extension from swiftclient import client as swiftclient from troveclient import client as troveclient import warnings @@ -45,18 +46,36 @@ class OpenStackClients(object): ''' Convenience class to create and cache client instances. ''' + def __init__(self, context): self.context = context self._clients = {} + self._client_plugins = {} + + def client_plugin(self, name): + global _mgr + if name in self._client_plugins: + return self._client_plugins[name] + if _mgr and name in _mgr.names(): + client_plugin = _mgr[name].plugin(self) + self._client_plugins[name] = client_plugin + return client_plugin def client(self, name): + client_plugin = self.client_plugin(name) + if client_plugin: + return client_plugin.client() + if name in self._clients: return self._clients[name] + # call the local method _() if a real client plugin + # doesn't exist method_name = '_%s' % name if callable(getattr(self, method_name, None)): client = getattr(self, method_name)() self._clients[name] = client return client + LOG.warn(_('Requested client "%s" not found') % name) @property def auth_token(self): @@ -326,3 +345,21 @@ class ClientBackend(object): Clients = ClientBackend + + +_mgr = None + + +def has_client(name): + return _mgr and name in _mgr + + +def initialise(): + global _mgr + if _mgr: + return + + _mgr = extension.ExtensionManager( + namespace='heat.clients', + invoke_on_load=False, + verify_requirements=True) diff --git a/heat/engine/resources/__init__.py b/heat/engine/resources/__init__.py index 6c7a535abe..5a9ad8977f 100644 --- a/heat/engine/resources/__init__.py +++ b/heat/engine/resources/__init__.py @@ -13,6 +13,7 @@ from stevedore import extension +from heat.engine import clients from heat.engine import environment from heat.engine import plugin_manager @@ -49,6 +50,8 @@ def initialise(): if _environment is not None: return + clients.initialise() + global_env = environment.Environment({}, user_env=False) _load_global_environment(global_env) _environment = global_env diff --git a/heat/tests/test_clients.py b/heat/tests/test_clients.py index 4c4beadf78..c3f6ace628 100644 --- a/heat/tests/test_clients.py +++ b/heat/tests/test_clients.py @@ -11,10 +11,10 @@ # License for the specific language governing permissions and limitations # under the License. +from heatclient import client as heatclient import mock from oslo.config import cfg - -from heatclient import client as heatclient +from testtools.testcase import skip from heat.engine import clients from heat.engine.clients import client_plugin @@ -172,3 +172,32 @@ class ClientPluginTest(HeatTestCase): con = mock.Mock() c = clients.Clients(con) self.assertRaises(TypeError, client_plugin.ClientPlugin, c) + + +class TestClientPluginsInitialise(HeatTestCase): + + @skip('skipped until keystone can read context auth_ref') + def test_create_all_clients(self): + con = mock.Mock() + con.auth_url = "http://auth.example.com:5000/v2.0" + con.tenant_id = "b363706f891f48019483f8bd6503c54b" + con.auth_token = "3bcc3d3a03f44e3d8377f9247b0ad155" + c = clients.Clients(con) + + for plugin_name in clients._mgr.names(): + self.assertTrue(clients.has_client(plugin_name)) + c.client(plugin_name) + + def test_create_all_client_plugins(self): + plugin_types = clients._mgr.names() + self.assertIsNotNone(plugin_types) + + con = mock.Mock() + c = clients.Clients(con) + + for plugin_name in plugin_types: + plugin = c.client_plugin(plugin_name) + self.assertIsNotNone(plugin) + self.assertEqual(c, plugin.clients) + self.assertEqual(con, plugin.context) + self.assertIsNone(plugin._client)