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)