From a96e9d57c56e53f4e02701d2ae9f9194bb6e3d5b Mon Sep 17 00:00:00 2001 From: Andrey Kurilin Date: Wed, 9 Sep 2015 14:02:42 +0300 Subject: [PATCH] Restrict direct usage of novaclient.v2.client A lot of project uses incorrect import of versioned novaclient client obj (i.e. novaclient.v2.client.Client). It leads to unability to change interface of such inner classes. This patch updates docs to include warning note and add warning message to `novaclient.v2.client.Client` object. Change-Id: Ifeba391716d3d51d6a75a53cad405e1ec595e27b Related-Bug: #1493576 --- README.rst | 2 +- doc/source/api.rst | 37 +++++++++++++++++++++++++---- novaclient/client.py | 25 ++++++++++++++++++-- novaclient/v2/client.py | 52 +++++++++++------------------------------ 4 files changed, 69 insertions(+), 47 deletions(-) diff --git a/README.rst b/README.rst index 00a361de8..9725cd521 100644 --- a/README.rst +++ b/README.rst @@ -1,5 +1,5 @@ Python bindings to the OpenStack Nova API -================================================== +========================================= This is a client for the OpenStack Nova API. There's a Python API (the ``novaclient`` module), and a command-line script (``nova``). Each diff --git a/doc/source/api.rst b/doc/source/api.rst index 8bcb219b3..ded67ed60 100644 --- a/doc/source/api.rst +++ b/doc/source/api.rst @@ -1,5 +1,5 @@ The :mod:`novaclient` Python API -================================== +================================ .. module:: novaclient :synopsis: A client for the OpenStack Nova API. @@ -14,7 +14,10 @@ First create a client instance with your credentials:: >>> from novaclient import client >>> nova = client.Client(VERSION, USERNAME, PASSWORD, PROJECT_ID, AUTH_URL) -Here ``VERSION`` can be: ``1.1``, ``2``. +Here ``VERSION`` can be a string or ``novaclient.api_versions.APIVersion`` obj. +If you prefer string value, you can use ``1.1`` (deprecated now), ``2`` or +``2.X`` (where X is a microversion). + Alternatively, you can create a client instance using the keystoneclient session API:: @@ -23,9 +26,9 @@ session API:: >>> from keystoneclient import session >>> from novaclient import client >>> auth = v2.Password(auth_url=AUTH_URL, - username=USERNAME, - password=PASSWORD, - tenant_name=PROJECT_ID) + ... username=USERNAME, + ... password=PASSWORD, + ... tenant_name=PROJECT_ID) >>> sess = session.Session(auth=auth) >>> nova = client.Client(VERSION, session=sess) @@ -33,6 +36,23 @@ For more information on this keystoneclient API, see `Using Sessions`_. .. _Using Sessions: http://docs.openstack.org/developer/python-keystoneclient/using-sessions.html +It is also possible to use an instance as a context manager in which case +there will be a session kept alive for the duration of the with statement:: + + >>> from novaclient import client + >>> with client.Client(VERSION, USERNAME, PASSWORD, + ... PROJECT_ID, AUTH_URL) as nova: + ... nova.servers.list() + ... nova.flavors.list() + ... + +It is also possible to have a permanent (process-long) connection pool, +by passing a connection_pool=True:: + + >>> from novaclient import client + >>> nova = client.Client(VERSION, USERNAME, PASSWORD, PROJECT_ID, + ... AUTH_URL, connection_pool=True) + Then call methods on its managers:: >>> nova.servers.list() @@ -51,6 +71,13 @@ Then call methods on its managers:: >>> nova.servers.create("my-server", flavor=fl) +.. warning:: Direct initialization of ``novaclient.v2.client.Client`` object + can cause you to "shoot yourself in the foot". See launchpad bug-report + `1493576`_ for more details. + +.. _1493576: https://launchpad.net/bugs/1493576 + + Reference --------- diff --git a/novaclient/client.py b/novaclient/client.py index 159255540..f7ac8702a 100644 --- a/novaclient/client.py +++ b/novaclient/client.py @@ -785,6 +785,27 @@ def get_client_class(version): def Client(version, *args, **kwargs): - """Initialize client object based on given version.""" + """Initialize client object based on given version. + + HOW-TO: + The simplest way to create a client instance is initialization with your + credentials:: + + >>> from novaclient import client + >>> nova = client.Client(VERSION, USERNAME, PASSWORD, + ... PROJECT_ID, AUTH_URL) + + Here ``VERSION`` can be a string or + ``novaclient.api_versions.APIVersion`` obj. If you prefer string value, + you can use ``1.1`` (deprecated now), ``2`` or ``2.X`` + (where X is a microversion). + + + Alternatively, you can create a client instance using the keystoneclient + session API. See "The novaclient Python API" page at + python-novaclient's doc. + """ api_version, client_class = _get_client_class_and_version(version) - return client_class(api_version=api_version, *args, **kwargs) + kwargs.pop("direct_use", None) + return client_class(api_version=api_version, direct_use=False, + *args, **kwargs) diff --git a/novaclient/v2/client.py b/novaclient/v2/client.py index 1c195d5a1..d48c062f8 100644 --- a/novaclient/v2/client.py +++ b/novaclient/v2/client.py @@ -14,6 +14,7 @@ # under the License. from novaclient import client +from novaclient.i18n import _LW from novaclient.v2 import agents from novaclient.v2 import aggregates from novaclient.v2 import availability_zones @@ -53,44 +54,8 @@ class Client(object): """ Top-level object to access the OpenStack Compute API. - Create an instance with your creds:: - - >>> client = Client(USERNAME, PASSWORD, PROJECT_ID, AUTH_URL) - - Or, alternatively, you can create a client instance using the - keystoneclient.session API:: - - >>> from keystoneclient.auth.identity import v2 - >>> from keystoneclient import session - >>> from novaclient import client - >>> auth = v2.Password(auth_url=AUTH_URL, - username=USERNAME, - password=PASSWORD, - tenant_name=PROJECT_ID) - >>> sess = session.Session(auth=auth) - >>> nova = client.Client(VERSION, session=sess) - - Then call methods on its managers:: - - >>> nova.servers.list() - ... - >>> nova.flavors.list() - ... - - It is also possible to use an instance as a context manager in which - case there will be a session kept alive for the duration of the with - statement:: - - >>> with Client(USERNAME, PASSWORD, PROJECT_ID, AUTH_URL) as client: - ... client.servers.list() - ... client.flavors.list() - ... - - It is also possible to have a permanent (process-long) connection pool, - by passing a connection_pool=True:: - - >>> client = Client(USERNAME, PASSWORD, PROJECT_ID, - ... AUTH_URL, connection_pool=True) + .. warning:: All scripts and projects should not initialize this class + directly. It should be done via `novaclient.client.Client` interface. """ def __init__(self, username=None, api_key=None, project_id=None, @@ -103,7 +68,7 @@ class Client(object): auth_system='keystone', auth_plugin=None, auth_token=None, cacert=None, tenant_id=None, user_id=None, connection_pool=False, session=None, auth=None, - api_version=None, **kwargs): + api_version=None, direct_use=True, **kwargs): """ :param str username: Username :param str api_key: API Key @@ -136,6 +101,15 @@ class Client(object): :param api_version: Compute API version :type api_version: novaclient.api_versions.APIVersion """ + if direct_use: + import warnings + + warnings.warn( + _LW("'novaclient.v2.client.Client' is not designed to be " + "initialized directly. It is inner class of novaclient. " + "Please, use 'novaclient.client.Client' instead. " + "Related lp bug-report: 1493576")) + # FIXME(comstud): Rename the api_key argument above when we # know it's not being used as keyword argument