From 05055066c190a38c25877b9d590f71678660233a Mon Sep 17 00:00:00 2001 From: Alexander Saprykin Date: Tue, 26 Apr 2016 11:49:47 +0200 Subject: [PATCH] Remove APIClient singleton from fuelclient library Fuelclient connection configuration was hardcoded and it was impossible to create clients for multiple fuel servers. User should be able to use multiple connections. This patches introduces fuelclient.connect() function which returns conneciton object that can be used as a parameter to fuelclient.get_client() function. Closes-Bug: #1454347 Change-Id: I81f630a2bd8464e9cbeac0543c3a7125d97c1152 --- fuelclient/__init__.py | 16 +++++- fuelclient/client.py | 63 +++++++++++++-------- fuelclient/tests/unit/common/test_utils.py | 2 +- fuelclient/tests/unit/v2/lib/test_client.py | 54 ++++++++++++++++++ fuelclient/v1/base_v1.py | 14 +++++ fuelclient/v1/cluster_settings.py | 4 +- fuelclient/v1/deployment_history.py | 4 +- fuelclient/v1/deployment_info.py | 4 +- fuelclient/v1/environment.py | 4 +- fuelclient/v1/fuelversion.py | 4 +- fuelclient/v1/graph.py | 63 +++++++++------------ fuelclient/v1/network_configuration.py | 4 +- fuelclient/v1/network_group.py | 4 +- fuelclient/v1/node.py | 4 +- fuelclient/v1/openstack_config.py | 4 +- fuelclient/v1/plugins.py | 4 +- fuelclient/v1/task.py | 4 +- fuelclient/v1/vip.py | 4 +- 18 files changed, 171 insertions(+), 89 deletions(-) create mode 100644 fuelclient/tests/unit/v2/lib/test_client.py diff --git a/fuelclient/__init__.py b/fuelclient/__init__.py index 9f90572..2f8e6a2 100644 --- a/fuelclient/__init__.py +++ b/fuelclient/__init__.py @@ -26,7 +26,17 @@ except ImportError: __version__ = "" -def get_client(resource, version='v1'): +def connect(host, port, http_proxy=None, os_username=None, os_password=None, + os_tenant_name=None, debug=False): + """Creates API connection.""" + from fuelclient import client + + return client.Client( + host, port, http_proxy=http_proxy, os_username=os_username, + os_password=os_password, os_tenant_name=os_tenant_name, debug=debug) + + +def get_client(resource, version='v1', connection=None): """Gets an API client for a resource python-fuelclient provides access to Fuel's API @@ -40,6 +50,8 @@ def get_client(resource, version='v1'): :param version: Version of the Fuel's API :type version: str, Available: v1. Default: v1. + :param connection: API connection + :type connection: fuelclient.client.Client :return: Facade to the specified resource that wraps calls to the specified version of the API. @@ -65,7 +77,7 @@ def get_client(resource, version='v1'): } try: - return version_map[version][resource].get_client() + return version_map[version][resource].get_client(connection) except KeyError: msg = 'Cannot load API client for "{r}" in the API version "{v}".' raise ValueError(msg.format(r=resource, v=version)) diff --git a/fuelclient/client.py b/fuelclient/client.py index 84f9e65..f73cd4d 100644 --- a/fuelclient/client.py +++ b/fuelclient/client.py @@ -34,20 +34,40 @@ class Client(object): """This class handles API requests """ - def __init__(self): - conf = fuelclient_settings.get_settings() + def __init__(self, host, port, http_proxy=None, http_timeout=None, + os_username=None, os_password=None, + os_tenant_name=None, debug=False): + self.debug = debug - self.debug = False - self.root = "http://{server}:{port}".format(server=conf.SERVER_ADDRESS, - port=conf.SERVER_PORT) + self._http_proxy = http_proxy + self._http_timeout = http_timeout + self._os_username = os_username + self._os_password = os_password + self._os_tenant_name = os_tenant_name + + self.root = "http://{host}:{port}".format(host=host, port=port) self.keystone_base = urlparse.urljoin(self.root, "/keystone/v2.0") self.api_root = urlparse.urljoin(self.root, "/api/v1/") self.ostf_root = urlparse.urljoin(self.root, "/ostf/") + self._keystone_client = None self._auth_required = None self._session = None + @classmethod + def default_client(cls): + conf = fuelclient_settings.get_settings() + return cls( + host=conf.SERVER_ADDRESS, + port=conf.SERVER_PORT, + http_proxy=conf.HTTP_PROXY, + http_timeout=conf.HTTP_TIMEOUT, + os_username=conf.OS_USERNAME, + os_password=conf.OS_PASSWORD, + os_tenant_name=conf.OS_TENANT_NAME + ) + def _make_common_headers(self): """Returns a dict of HTTP headers common for all requests.""" @@ -57,23 +77,17 @@ class Client(object): def _make_proxies(self): """Provides HTTP proxy configuration for requests module.""" - - conf = fuelclient_settings.get_settings() - - if conf.HTTP_PROXY is None: + if self._http_proxy is None: return None - return {'http': conf.HTTP_PROXY, - 'https': conf.HTTP_PROXY} + return {'http': self._http_proxy, + 'https': self._http_proxy} def _make_session(self): """Initializes a HTTP session.""" - - conf = fuelclient_settings.get_settings() - session = requests.Session() session.headers.update(self._make_common_headers()) - session.timeout = conf.HTTP_TIMEOUT + session.timeout = self._http_timeout session.proxies = self._make_proxies() return session @@ -119,21 +133,17 @@ class Client(object): return self._keystone_client def update_own_password(self, new_pass): - conf = fuelclient_settings.get_settings() - if self.auth_token: - self.keystone_client.users.update_own_password(conf.OS_PASSWORD, - new_pass) + self.keystone_client.users.update_own_password( + self._os_password, new_pass) def initialize_keystone_client(self): - conf = fuelclient_settings.get_settings() - if self.auth_required: self._keystone_client = auth_client.Client( auth_url=self.keystone_base, - username=conf.OS_USERNAME, - password=conf.OS_PASSWORD, - tenant_name=conf.OS_TENANT_NAME) + username=self._os_username, + password=self._os_password, + tenant_name=self._os_tenant_name) self._keystone_client.session.auth = self._keystone_client self._keystone_client.authenticate() @@ -233,4 +243,7 @@ class Client(object): # This line is single point of instantiation for 'Client' class, # which intended to implement Singleton design pattern. -APIClient = Client() +APIClient = Client.default_client() +""" +.. deprecated:: Use fuelclient.client.Client instead +""" diff --git a/fuelclient/tests/unit/common/test_utils.py b/fuelclient/tests/unit/common/test_utils.py index 5cff867..015b5bb 100644 --- a/fuelclient/tests/unit/common/test_utils.py +++ b/fuelclient/tests/unit/common/test_utils.py @@ -239,4 +239,4 @@ class TestUtils(base.UnitTestCase): with self.assertRaisesRegexp(error.HTTPError, '403.*{}'.format(text)): - client.Client().post_request('address') + client.APIClient.post_request('address') diff --git a/fuelclient/tests/unit/v2/lib/test_client.py b/fuelclient/tests/unit/v2/lib/test_client.py new file mode 100644 index 0000000..8f11d66 --- /dev/null +++ b/fuelclient/tests/unit/v2/lib/test_client.py @@ -0,0 +1,54 @@ +# +# Copyright 2016 Mirantis, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import fuelclient +from fuelclient.objects import base +from fuelclient.tests.unit.v2.lib import test_api +from fuelclient.v1 import base_v1 + + +class FakeObject(base.BaseObject): + class_api_path = 'fake/objects/' + + +class FakeClient(base_v1.BaseV1Client): + + _entity_wrapper = FakeObject + + +class TestClient(test_api.BaseLibTest): + + def setUp(self): + super(TestClient, self).setUp() + + self.host = 'test.host.tld' + self.port = 8888 + + self.connection = fuelclient.connect(self.host, self.port) + + self.client = FakeClient(connection=self.connection) + + self.version = 'v1' + self.res_uri = '/api/{version}/fake/objects/'.format( + version=self.version) + + def test_custom_connection_used(self): + m_get = self.m_request.get(self.res_uri, json={}) + self.client.get_all() + + self.assertTrue(m_get.called) + self.assertEqual( + m_get.last_request.netloc, + '{host}:{port}'.format(host=self.host, port=self.port)) diff --git a/fuelclient/v1/base_v1.py b/fuelclient/v1/base_v1.py index 7705a86..2269941 100644 --- a/fuelclient/v1/base_v1.py +++ b/fuelclient/v1/base_v1.py @@ -16,6 +16,8 @@ import abc import six +from fuelclient import client + @six.add_metaclass(abc.ABCMeta) class BaseV1Client(object): @@ -24,6 +26,18 @@ class BaseV1Client(object): def _entity_wrapper(self): pass + def __init__(self, connection=None): + if connection is None: + connection = client.APIClient + self.connection = connection + + cls_wrapper = self.__class__._entity_wrapper + self._entity_wrapper = type( + cls_wrapper.__name__, + (cls_wrapper, ), + {'connection': self.connection} + ) + def get_all(self): result = self._entity_wrapper.get_all_data() diff --git a/fuelclient/v1/cluster_settings.py b/fuelclient/v1/cluster_settings.py index ffd7093..e5140de 100644 --- a/fuelclient/v1/cluster_settings.py +++ b/fuelclient/v1/cluster_settings.py @@ -27,5 +27,5 @@ class ClusterSettingsClient(base_v1.BaseV1Client): return task.cluster_settings() -def get_client(): - return ClusterSettingsClient() +def get_client(connection): + return ClusterSettingsClient(connection) diff --git a/fuelclient/v1/deployment_history.py b/fuelclient/v1/deployment_history.py index 43a0196..decd97a 100644 --- a/fuelclient/v1/deployment_history.py +++ b/fuelclient/v1/deployment_history.py @@ -27,5 +27,5 @@ class DeploymentHistoryClient(base_v1.BaseV1Client): statuses=statuses) -def get_client(): - return DeploymentHistoryClient() +def get_client(connection): + return DeploymentHistoryClient(connection) diff --git a/fuelclient/v1/deployment_info.py b/fuelclient/v1/deployment_info.py index 6580dbd..14555d1 100644 --- a/fuelclient/v1/deployment_info.py +++ b/fuelclient/v1/deployment_info.py @@ -27,5 +27,5 @@ class DeploymentInfoClient(base_v1.BaseV1Client): return task.deployment_info() -def get_client(): - return DeploymentInfoClient() +def get_client(connection): + return DeploymentInfoClient(connection) diff --git a/fuelclient/v1/environment.py b/fuelclient/v1/environment.py index 5f7ebea..58d9990 100644 --- a/fuelclient/v1/environment.py +++ b/fuelclient/v1/environment.py @@ -105,5 +105,5 @@ class EnvironmentClient(base_v1.BaseV1Client): env.delete_network_template_data() -def get_client(): - return EnvironmentClient() +def get_client(connection): + return EnvironmentClient(connection) diff --git a/fuelclient/v1/fuelversion.py b/fuelclient/v1/fuelversion.py index 87c2263..cfcd5b3 100644 --- a/fuelclient/v1/fuelversion.py +++ b/fuelclient/v1/fuelversion.py @@ -21,5 +21,5 @@ class FuelVersionClient(base_v1.BaseV1Client): _entity_wrapper = objects.FuelVersion -def get_client(): - return FuelVersionClient() +def get_client(connection): + return FuelVersionClient(connection) diff --git a/fuelclient/v1/graph.py b/fuelclient/v1/graph.py index fa203f0..c1b73b5 100644 --- a/fuelclient/v1/graph.py +++ b/fuelclient/v1/graph.py @@ -15,7 +15,6 @@ # under the License. from fuelclient.cli import error -from fuelclient.client import APIClient from fuelclient import objects from fuelclient.v1 import base_v1 @@ -40,33 +39,30 @@ class GraphClient(base_v1.BaseV1Client): cluster_release_tasks_api_path = "clusters/{env_id}/deployment_tasks" \ "/release/?graph_type={graph_type}" - @classmethod def update_graph_for_model( - cls, data, related_model, related_model_id, graph_type=None): - return APIClient.put_request( - cls.related_graph_api_path.format( + self, data, related_model, related_model_id, graph_type=None): + return self.connection.put_request( + self.related_graph_api_path.format( related_model=related_model, related_model_id=related_model_id, graph_type=graph_type or ""), data ) - @classmethod def create_graph_for_model( - cls, data, related_model, related_model_id, graph_type=None): - return APIClient.post_request( - cls.related_graph_api_path.format( + self, data, related_model, related_model_id, graph_type=None): + return self.connection.post_request( + self.related_graph_api_path.format( related_model=related_model, related_model_id=related_model_id, graph_type=graph_type or ""), data ) - @classmethod def get_graph_for_model( - cls, related_model, related_model_id, graph_type=None): - return APIClient.get_request( - cls.related_graph_api_path.format( + self, related_model, related_model_id, graph_type=None): + return self.connection.get_request( + self.related_graph_api_path.format( related_model=related_model, related_model_id=related_model_id, graph_type=graph_type or "")) @@ -83,8 +79,7 @@ class GraphClient(base_v1.BaseV1Client): self.create_graph_for_model( {'tasks': data}, related_model, related_id, graph_type) - @classmethod - def execute(cls, env_id, nodes, graph_type=None): + def execute(self, env_id, nodes, graph_type=None): put_args = [] if nodes: @@ -94,32 +89,28 @@ class GraphClient(base_v1.BaseV1Client): put_args.append(("graph_type=" + graph_type)) url = "".join([ - cls.cluster_deploy_api_path.format(env_id=env_id), + self.cluster_deploy_api_path.format(env_id=env_id), '?', '&'.join(put_args)]) - deploy_data = APIClient.put_request(url, {}) + deploy_data = self.connection.put_request(url, {}) return objects.DeployTask.init_with_data(deploy_data) - # download - @classmethod - def get_merged_cluster_tasks(cls, env_id, graph_type=None): - return APIClient.get_request( - cls.merged_cluster_tasks_api_path.format( + def get_merged_cluster_tasks(self, env_id, graph_type=None): + return self.connection.get_request( + self.merged_cluster_tasks_api_path.format( env_id=env_id, graph_type=graph_type or "")) - @classmethod - def get_merged_plugins_tasks(cls, env_id, graph_type=None): - return APIClient.get_request( - cls.merged_plugins_tasks_api_path.format( + def get_merged_plugins_tasks(self, env_id, graph_type=None): + return self.connection.get_request( + self.merged_plugins_tasks_api_path.format( env_id=env_id, graph_type=graph_type or "")) - @classmethod - def get_release_tasks_for_cluster(cls, env_id, graph_type=None): - return APIClient.get_request( - cls.merged_plugins_tasks_api_path.format( + def get_release_tasks_for_cluster(self, env_id, graph_type=None): + return self.connection.get_request( + self.merged_plugins_tasks_api_path.format( env_id=env_id, graph_type=graph_type or "")) @@ -143,15 +134,13 @@ class GraphClient(base_v1.BaseV1Client): } return tasks_levels[level]() - # list - @classmethod - def list(cls, env_id): + def list(self, env_id): # todo(ikutukov): extend lists to support all models - return APIClient.get_request( - cls.related_graphs_list_api_path.format( + return self.connection.get_request( + self.related_graphs_list_api_path.format( related_model='clusters', related_model_id=env_id)) -def get_client(): - return GraphClient() +def get_client(connection): + return GraphClient(connection) diff --git a/fuelclient/v1/network_configuration.py b/fuelclient/v1/network_configuration.py index 337bb71..d4790f0 100644 --- a/fuelclient/v1/network_configuration.py +++ b/fuelclient/v1/network_configuration.py @@ -27,5 +27,5 @@ class NetworkConfigurationClient(base_v1.BaseV1Client): return task.network_configuration() -def get_client(): - return NetworkConfigurationClient() +def get_client(connection): + return NetworkConfigurationClient(connection) diff --git a/fuelclient/v1/network_group.py b/fuelclient/v1/network_group.py index c3267bc..ff375b1 100644 --- a/fuelclient/v1/network_group.py +++ b/fuelclient/v1/network_group.py @@ -46,5 +46,5 @@ class NetworkGroupClient(base_v1.BaseV1Client): env_obj.delete() -def get_client(): - return NetworkGroupClient() +def get_client(connection): + return NetworkGroupClient(connection) diff --git a/fuelclient/v1/node.py b/fuelclient/v1/node.py index 3784d28..bebe706 100644 --- a/fuelclient/v1/node.py +++ b/fuelclient/v1/node.py @@ -213,5 +213,5 @@ class NodeClient(base_v1.BaseV1Client): return SplittedLabel(name, value, bool(separator)) -def get_client(): - return NodeClient() +def get_client(connection): + return NodeClient(connection) diff --git a/fuelclient/v1/openstack_config.py b/fuelclient/v1/openstack_config.py index 0bd589a..9bfb6d8 100644 --- a/fuelclient/v1/openstack_config.py +++ b/fuelclient/v1/openstack_config.py @@ -46,5 +46,5 @@ class OpenstackConfigClient(base_v1.BaseV1Client): node_role=node_role, is_active=is_active) -def get_client(): - return OpenstackConfigClient() +def get_client(connection): + return OpenstackConfigClient(connection) diff --git a/fuelclient/v1/plugins.py b/fuelclient/v1/plugins.py index d0a2796..930ce70 100644 --- a/fuelclient/v1/plugins.py +++ b/fuelclient/v1/plugins.py @@ -54,5 +54,5 @@ class PluginsClient(base_v1.BaseV1Client): self._entity_wrapper.sync(plugin_ids=ids) -def get_client(): - return PluginsClient() +def get_client(connection): + return PluginsClient(connection) diff --git a/fuelclient/v1/task.py b/fuelclient/v1/task.py index 76a3490..8096ce0 100644 --- a/fuelclient/v1/task.py +++ b/fuelclient/v1/task.py @@ -21,5 +21,5 @@ class TaskClient(base_v1.BaseV1Client): _entity_wrapper = objects.Task -def get_client(): - return TaskClient() +def get_client(connection): + return TaskClient(connection) diff --git a/fuelclient/v1/vip.py b/fuelclient/v1/vip.py index 3511740..c97efc9 100644 --- a/fuelclient/v1/vip.py +++ b/fuelclient/v1/vip.py @@ -50,5 +50,5 @@ class VipClient(base_v1.BaseV1Client): env.set_vips_data(vips_data) -def get_client(): - return VipClient() +def get_client(connection): + return VipClient(connection)