summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.rst2
-rw-r--r--doc/source/api.rst37
-rw-r--r--novaclient/client.py30
-rw-r--r--novaclient/tests/unit/v2/test_shell.py8
-rw-r--r--novaclient/tests/unit/v2/test_versions.py8
-rw-r--r--novaclient/v2/client.py55
-rw-r--r--novaclient/v2/contrib/host_evacuate_live.py10
-rw-r--r--novaclient/v2/versions.py22
8 files changed, 113 insertions, 59 deletions
diff --git a/README.rst b/README.rst
index 00a361de..9725cd52 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 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."""