diff options
-rw-r--r-- | README.rst | 2 | ||||
-rw-r--r-- | doc/source/api.rst | 37 | ||||
-rw-r--r-- | novaclient/client.py | 30 | ||||
-rw-r--r-- | novaclient/tests/unit/v2/test_shell.py | 8 | ||||
-rw-r--r-- | novaclient/tests/unit/v2/test_versions.py | 8 | ||||
-rw-r--r-- | novaclient/v2/client.py | 55 | ||||
-rw-r--r-- | novaclient/v2/contrib/host_evacuate_live.py | 10 | ||||
-rw-r--r-- | novaclient/v2/versions.py | 22 |
8 files changed, 113 insertions, 59 deletions
@@ -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 8bcb219b..ded67ed6 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) <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 --------- diff --git a/novaclient/client.py b/novaclient/client.py index 15925554..399d98e2 100644 --- a/novaclient/client.py +++ b/novaclient/client.py @@ -30,6 +30,7 @@ import logging import os import pkgutil import re +import warnings from keystoneclient import adapter from keystoneclient import session @@ -48,7 +49,7 @@ from six.moves.urllib import parse from novaclient import api_versions from novaclient import exceptions from novaclient import extension as ext -from novaclient.i18n import _ +from novaclient.i18n import _, _LW from novaclient import service_catalog from novaclient import utils @@ -780,11 +781,34 @@ def _get_client_class_and_version(version): def get_client_class(version): """Returns Client class based on given version.""" + warnings.warn(_LW("'get_client_class' is deprecated. " + "Please use `novaclient.client.Client` instead.")) _api_version, client_class = _get_client_class_and_version(version) return client_class 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/tests/unit/v2/test_shell.py b/novaclient/tests/unit/v2/test_shell.py index 7d4c4115..ed68c383 100644 --- a/novaclient/tests/unit/v2/test_shell.py +++ b/novaclient/tests/unit/v2/test_shell.py @@ -1580,6 +1580,14 @@ class ShellTest(utils.TestCase): self.assert_called('POST', '/servers/uuid3/action', body, pos=3) self.assert_called('POST', '/servers/uuid4/action', body, pos=4) + def test_host_evacuate_list_with_max_servers(self): + self.run_command('host-evacuate-live --max-servers 1 hyper') + self.assert_called('GET', '/os-hypervisors/hyper/servers', pos=0) + body = {'os-migrateLive': {'host': None, + 'block_migration': False, + 'disk_over_commit': False}} + self.assert_called('POST', '/servers/uuid1/action', body, pos=1) + def test_reset_state(self): self.run_command('reset-state sample-server') self.assert_called('POST', '/servers/1234/action', diff --git a/novaclient/tests/unit/v2/test_versions.py b/novaclient/tests/unit/v2/test_versions.py index da348751..aa8e84a6 100644 --- a/novaclient/tests/unit/v2/test_versions.py +++ b/novaclient/tests/unit/v2/test_versions.py @@ -73,3 +73,11 @@ class VersionsTest(utils.TestCase): def test_get_current_with_rax_workaround(self, session, get): self.cs.callback = [] self.assertIsNone(self.cs.versions.get_current()) + + @mock.patch.object(versions.VersionManager, '_is_session_client', + return_value=False) + @mock.patch.object(versions.VersionManager, '_list', + side_effect=exc.Unauthorized("401 RAX")) + def test_get_current_with_rax_auth_plugin_workaround(self, session, _list): + self.cs.callback = [] + self.assertIsNone(self.cs.versions.get_current()) diff --git a/novaclient/v2/client.py b/novaclient/v2/client.py index 1c195d5a..97150614 100644 --- a/novaclient/v2/client.py +++ b/novaclient/v2/client.py @@ -13,7 +13,9 @@ # License for the specific language governing permissions and limitations # under the License. +from novaclient import api_versions 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 +55,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 +69,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 +102,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 @@ -155,7 +130,7 @@ class Client(object): self.limits = limits.LimitsManager(self) self.servers = servers.ServerManager(self) self.versions = versions.VersionManager(self) - self.api_version = api_version + self.api_version = api_version or api_versions.APIVersion("2.0") # extensions self.agents = agents.AgentsManager(self) diff --git a/novaclient/v2/contrib/host_evacuate_live.py b/novaclient/v2/contrib/host_evacuate_live.py index 276fb2f7..f91599bb 100644 --- a/novaclient/v2/contrib/host_evacuate_live.py +++ b/novaclient/v2/contrib/host_evacuate_live.py @@ -54,15 +54,25 @@ def _server_live_migrate(cs, server, args): action='store_true', default=False, help=_('Enable disk overcommit.')) +@cliutils.arg( + '--max-servers', + type=int, + dest='max_servers', + metavar='<max_servers>', + help='Maximum number of servers to live migrate simultaneously') def do_host_evacuate_live(cs, args): """Live migrate all instances of the specified host to other available hosts. """ hypervisors = cs.hypervisors.search(args.host, servers=True) response = [] + migrating = 0 for hyper in hypervisors: for server in getattr(hyper, 'servers', []): response.append(_server_live_migrate(cs, server, args)) + migrating = migrating + 1 + if args.max_servers is not None and migrating >= args.max_servers: + break utils.print_list(response, ["Server UUID", "Live Migration Accepted", "Error Message"]) diff --git a/novaclient/v2/versions.py b/novaclient/v2/versions.py index 17a20df9..796ea7df 100644 --- a/novaclient/v2/versions.py +++ b/novaclient/v2/versions.py @@ -37,7 +37,7 @@ class VersionManager(base.ManagerWithFind): def _is_session_client(self): return isinstance(self.api.client, client.SessionClient) - def get_current(self): + def _get_current(self): """Returns info about current version.""" if self._is_session_client(): url = self.api.client.get_endpoint().rsplit("/", 1)[0] @@ -46,15 +46,7 @@ class VersionManager(base.ManagerWithFind): # that's actually a 300 redirect to /v2/... because of how # paste works. So adding the end slash is really important. url = "%s/" % url - try: - return self._get(url, "version") - except exc.Unauthorized: - # NOTE(sdague): RAX's repose configuration blocks - # access to the versioned endpoint, which is - # definitely non-compliant behavior. However, there is - # no defcore test for this yet. Remove this code block - # once we land things in defcore. - return None + return self._get(url, "version") else: # NOTE(andreykurilin): HTTPClient doesn't have ability to send get # request without token in the url, so `self._get` doesn't work. @@ -65,6 +57,16 @@ class VersionManager(base.ManagerWithFind): if link["href"].rstrip('/') == url: return version + def get_current(self): + try: + return self._get_current() + except exc.Unauthorized: + # NOTE(sdague): RAX's repose configuration blocks access to the + # versioned endpoint, which is definitely non-compliant behavior. + # However, there is no defcore test for this yet. Remove this code + # block once we land things in defcore. + return None + def list(self): """List all versions.""" |