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
This commit is contained in:
Alexander Saprykin
2016-04-26 11:49:47 +02:00
parent 12de059d8a
commit 05055066c1
18 changed files with 171 additions and 89 deletions

View File

@@ -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))

View File

@@ -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
"""

View File

@@ -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')

View File

@@ -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))

View File

@@ -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()

View File

@@ -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)

View File

@@ -27,5 +27,5 @@ class DeploymentHistoryClient(base_v1.BaseV1Client):
statuses=statuses)
def get_client():
return DeploymentHistoryClient()
def get_client(connection):
return DeploymentHistoryClient(connection)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -46,5 +46,5 @@ class NetworkGroupClient(base_v1.BaseV1Client):
env_obj.delete()
def get_client():
return NetworkGroupClient()
def get_client(connection):
return NetworkGroupClient(connection)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)