From e649cea843b8453d268da8ea74ea180f513d5ea3 Mon Sep 17 00:00:00 2001
From: Doug Hellmann <doug@doughellmann.com>
Date: Wed, 27 May 2015 18:08:02 +0000
Subject: [PATCH] Do not check requirements when loading entry points

Update the calls to pkg_resources to avoid forcing a requirements check
when the plugins are being loaded.

There are 2 versions of the entry point API in different releases of
setuptools. In one version, the require keyword argument can be passed
to load(). In the other, separate methods resolve() and require() need
to be used. This change updates the mock and fake objects to support
either, since the fakes are subclasses of the EntryPoint class in
pkg_resources.

It would be better to replace the calls to pkg_resources with stevedore,
which provides a more stable API, abstracts away this difference, and
provides an API for creating test managers directly. That change would
have required more extensive updates to the test suite, though, and
since I'm not as familiar with this code base as others will be, I will
leave those changes for someone else.

Change-Id: I2a9aeb53ccad04c7fa687f25340306b84218f9ff
Partial-bug: #1457100
---
 novaclient/auth_plugin.py                  |  9 +++-
 novaclient/tests/unit/test_auth_plugins.py | 62 ++++++++++++++++------
 novaclient/utils.py                        |  9 +++-
 3 files changed, 62 insertions(+), 18 deletions(-)

diff --git a/novaclient/auth_plugin.py b/novaclient/auth_plugin.py
index da2c07b26..d729c4d5a 100644
--- a/novaclient/auth_plugin.py
+++ b/novaclient/auth_plugin.py
@@ -37,7 +37,14 @@ def discover_auth_systems():
     ep_name = 'openstack.client.auth_plugin'
     for ep in pkg_resources.iter_entry_points(ep_name):
         try:
-            auth_plugin = ep.load()
+            # FIXME(dhellmann): It would be better to use stevedore
+            # here, since it abstracts this difference in behavior
+            # between versions of setuptools, but this seemed like a
+            # more expedient fix.
+            if hasattr(ep, 'resolve') and hasattr(ep, 'require'):
+                auth_plugin = ep.resolve()
+            else:
+                auth_plugin = ep.load(require=False)
         except (ImportError, pkg_resources.UnknownExtra, AttributeError) as e:
             logger.debug("ERROR: Cannot load auth plugin %s" % ep.name)
             logger.debug(e, exc_info=1)
diff --git a/novaclient/tests/unit/test_auth_plugins.py b/novaclient/tests/unit/test_auth_plugins.py
index 675102ca3..9dc02b02a 100644
--- a/novaclient/tests/unit/test_auth_plugins.py
+++ b/novaclient/tests/unit/test_auth_plugins.py
@@ -58,7 +58,10 @@ def requested_headers(cs):
 class DeprecatedAuthPluginTest(utils.TestCase):
     def test_auth_system_success(self):
         class MockEntrypoint(pkg_resources.EntryPoint):
-            def load(self):
+            def load(self, require=False):
+                return self.authenticate
+
+            def resolve(self):
                 return self.authenticate
 
             def authenticate(self, cls, auth_url):
@@ -117,14 +120,20 @@ class DeprecatedAuthPluginTest(utils.TestCase):
 
     def test_auth_system_defining_auth_url(self):
         class MockAuthUrlEntrypoint(pkg_resources.EntryPoint):
-            def load(self):
+            def load(self, require=False):
+                return self.auth_url
+
+            def resolve(self):
                 return self.auth_url
 
             def auth_url(self):
                 return "http://faked/v2.0"
 
         class MockAuthenticateEntrypoint(pkg_resources.EntryPoint):
-            def load(self):
+            def load(self, require=False):
+                return self.authenticate
+
+            def resolve(self):
                 return self.authenticate
 
             def authenticate(self, cls, auth_url):
@@ -160,7 +169,10 @@ class DeprecatedAuthPluginTest(utils.TestCase):
     @mock.patch.object(pkg_resources, "iter_entry_points")
     def test_client_raises_exc_without_auth_url(self, mock_iter_entry_points):
         class MockAuthUrlEntrypoint(pkg_resources.EntryPoint):
-            def load(self):
+            def load(self, require=False):
+                return self.auth_url
+
+            def resolve(self):
                 return self.auth_url
 
             def auth_url(self):
@@ -184,14 +196,17 @@ class AuthPluginTest(utils.TestCase):
     def test_auth_system_success(self, mock_iter_entry_points, mock_request):
         """Test that we can authenticate using the auth system."""
         class MockEntrypoint(pkg_resources.EntryPoint):
-            def load(self):
+            def load(self, require=False):
+                return FakePlugin
+
+            def resolve(self):
                 return FakePlugin
 
         class FakePlugin(auth_plugin.BaseAuthPlugin):
             def authenticate(self, cls, auth_url):
                 cls._authenticate(auth_url, {"fake": "me"})
 
-        mock_iter_entry_points.side_effect = lambda _t: [
+        mock_iter_entry_points.side_effect = lambda _t, name=None: [
             MockEntrypoint("fake", "fake", ["FakePlugin"])]
 
         mock_request.side_effect = mock_http_request()
@@ -227,10 +242,13 @@ class AuthPluginTest(utils.TestCase):
                 return parser
 
         class MockEntrypoint(pkg_resources.EntryPoint):
-            def load(self):
+            def load(self, require=False):
                 return FakePlugin
 
-        mock_iter_entry_points.side_effect = lambda _t: [
+            def resolve(self):
+                return FakePlugin
+
+        mock_iter_entry_points.side_effect = lambda _t, name=None: [
             MockEntrypoint("fake", "fake", ["FakePlugin"])]
 
         parser = argparse.ArgumentParser()
@@ -244,7 +262,10 @@ class AuthPluginTest(utils.TestCase):
     def test_parse_auth_system_options(self, mock_iter_entry_points):
         """Test that we can parse the auth system options."""
         class MockEntrypoint(pkg_resources.EntryPoint):
-            def load(self):
+            def load(self, require=False):
+                return FakePlugin
+
+            def resolve(self):
                 return FakePlugin
 
         class FakePlugin(auth_plugin.BaseAuthPlugin):
@@ -254,7 +275,7 @@ class AuthPluginTest(utils.TestCase):
             def parse_opts(self, args):
                 return self.opts
 
-        mock_iter_entry_points.side_effect = lambda _t: [
+        mock_iter_entry_points.side_effect = lambda _t, name=None: [
             MockEntrypoint("fake", "fake", ["FakePlugin"])]
 
         auth_plugin.discover_auth_systems()
@@ -267,14 +288,17 @@ class AuthPluginTest(utils.TestCase):
     def test_auth_system_defining_url(self, mock_iter_entry_points):
         """Test the auth_system defining an url."""
         class MockEntrypoint(pkg_resources.EntryPoint):
-            def load(self):
+            def load(self, require=False):
+                return FakePlugin
+
+            def resolve(self):
                 return FakePlugin
 
         class FakePlugin(auth_plugin.BaseAuthPlugin):
             def get_auth_url(self):
                 return "http://faked/v2.0"
 
-        mock_iter_entry_points.side_effect = lambda _t: [
+        mock_iter_entry_points.side_effect = lambda _t, name=None: [
             MockEntrypoint("fake", "fake", ["FakePlugin"])]
 
         auth_plugin.discover_auth_systems()
@@ -289,13 +313,16 @@ class AuthPluginTest(utils.TestCase):
     def test_exception_if_no_authenticate(self, mock_iter_entry_points):
         """Test that no authenticate raises a proper exception."""
         class MockEntrypoint(pkg_resources.EntryPoint):
-            def load(self):
+            def load(self, require=False):
+                return FakePlugin
+
+            def resolve(self):
                 return FakePlugin
 
         class FakePlugin(auth_plugin.BaseAuthPlugin):
             pass
 
-        mock_iter_entry_points.side_effect = lambda _t: [
+        mock_iter_entry_points.side_effect = lambda _t, name=None: [
             MockEntrypoint("fake", "fake", ["FakePlugin"])]
 
         auth_plugin.discover_auth_systems()
@@ -310,13 +337,16 @@ class AuthPluginTest(utils.TestCase):
     def test_exception_if_no_url(self, mock_iter_entry_points):
         """Test that no auth_url at all raises exception."""
         class MockEntrypoint(pkg_resources.EntryPoint):
-            def load(self):
+            def load(self, require=False):
+                return FakePlugin
+
+            def resolve(self):
                 return FakePlugin
 
         class FakePlugin(auth_plugin.BaseAuthPlugin):
             pass
 
-        mock_iter_entry_points.side_effect = lambda _t: [
+        mock_iter_entry_points.side_effect = lambda _t, name=None: [
             MockEntrypoint("fake", "fake", ["FakePlugin"])]
 
         auth_plugin.discover_auth_systems()
diff --git a/novaclient/utils.py b/novaclient/utils.py
index 50310bfdb..1f4df7a57 100644
--- a/novaclient/utils.py
+++ b/novaclient/utils.py
@@ -319,7 +319,14 @@ def _load_entry_point(ep_name, name=None):
     """Try to load the entry point ep_name that matches name."""
     for ep in pkg_resources.iter_entry_points(ep_name, name=name):
         try:
-            return ep.load()
+            # FIXME(dhellmann): It would be better to use stevedore
+            # here, since it abstracts this difference in behavior
+            # between versions of setuptools, but this seemed like a
+            # more expedient fix.
+            if hasattr(ep, 'resolve') and hasattr(ep, 'require'):
+                return ep.resolve()
+            else:
+                return ep.load(require=False)
         except (ImportError, pkg_resources.UnknownExtra, AttributeError):
             continue