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
This commit is contained in:
Andrey Kurilin 2015-09-09 14:02:42 +03:00
parent e612205ab8
commit a96e9d57c5
4 changed files with 69 additions and 47 deletions

View File

@ -1,5 +1,5 @@
Python bindings to the OpenStack Nova API Python bindings to the OpenStack Nova API
================================================== =========================================
This is a client for the OpenStack Nova API. There's a Python API (the This is a client for the OpenStack Nova API. There's a Python API (the
``novaclient`` module), and a command-line script (``nova``). Each ``novaclient`` module), and a command-line script (``nova``). Each

View File

@ -1,5 +1,5 @@
The :mod:`novaclient` Python API The :mod:`novaclient` Python API
================================== ================================
.. module:: novaclient .. module:: novaclient
:synopsis: A client for the OpenStack Nova API. :synopsis: A client for the OpenStack Nova API.
@ -14,7 +14,10 @@ First create a client instance with your credentials::
>>> from novaclient import client >>> from novaclient import client
>>> nova = client.Client(VERSION, USERNAME, PASSWORD, PROJECT_ID, AUTH_URL) >>> 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 Alternatively, you can create a client instance using the keystoneclient
session API:: session API::
@ -23,9 +26,9 @@ session API::
>>> from keystoneclient import session >>> from keystoneclient import session
>>> from novaclient import client >>> from novaclient import client
>>> auth = v2.Password(auth_url=AUTH_URL, >>> auth = v2.Password(auth_url=AUTH_URL,
username=USERNAME, ... username=USERNAME,
password=PASSWORD, ... password=PASSWORD,
tenant_name=PROJECT_ID) ... tenant_name=PROJECT_ID)
>>> sess = session.Session(auth=auth) >>> sess = session.Session(auth=auth)
>>> nova = client.Client(VERSION, session=sess) >>> 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 .. _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:: Then call methods on its managers::
>>> nova.servers.list() >>> nova.servers.list()
@ -51,6 +71,13 @@ Then call methods on its managers::
>>> nova.servers.create("my-server", flavor=fl) >>> nova.servers.create("my-server", flavor=fl)
<Server: my-server> <Server: my-server>
.. 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 Reference
--------- ---------

View File

@ -785,6 +785,27 @@ def get_client_class(version):
def Client(version, *args, **kwargs): 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) 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)

View File

@ -14,6 +14,7 @@
# under the License. # under the License.
from novaclient import client from novaclient import client
from novaclient.i18n import _LW
from novaclient.v2 import agents from novaclient.v2 import agents
from novaclient.v2 import aggregates from novaclient.v2 import aggregates
from novaclient.v2 import availability_zones from novaclient.v2 import availability_zones
@ -53,44 +54,8 @@ class Client(object):
""" """
Top-level object to access the OpenStack Compute API. Top-level object to access the OpenStack Compute API.
Create an instance with your creds:: .. warning:: All scripts and projects should not initialize this class
directly. It should be done via `novaclient.client.Client` interface.
>>> 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)
""" """
def __init__(self, username=None, api_key=None, project_id=None, 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, auth_system='keystone', auth_plugin=None, auth_token=None,
cacert=None, tenant_id=None, user_id=None, cacert=None, tenant_id=None, user_id=None,
connection_pool=False, session=None, auth=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 username: Username
:param str api_key: API Key :param str api_key: API Key
@ -136,6 +101,15 @@ class Client(object):
:param api_version: Compute API version :param api_version: Compute API version
:type api_version: novaclient.api_versions.APIVersion :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 # FIXME(comstud): Rename the api_key argument above when we
# know it's not being used as keyword argument # know it's not being used as keyword argument