Upgrade novaclient to 2.18.1

This commit is contained in:
Matthew Jones 2014-08-06 15:36:24 -04:00
parent 7cf1df23ab
commit 07bfe7c275
105 changed files with 4651 additions and 1093 deletions

View File

@ -45,7 +45,7 @@ pip==1.5.4 (pip/*, excluded bin/pip*)
prettytable==0.7.2 (prettytable.py)
pyrax==1.9.0 (pyrax/*)
python-dateutil==2.2 (dateutil/*)
python-novaclient==2.17.0 (novaclient/*, excluded bin/nova)
python-novaclient==2.18.1 (novaclient/*, excluded bin/nova)
python-swiftclient==2.0.3 (swiftclient/*, excluded bin/swift)
pytz==2014.4 (pytz/*)
rackspace-auth-openstack==1.3 (rackspace_auth_openstack/*)

View File

@ -20,7 +20,6 @@ import pkg_resources
import six
from novaclient import exceptions
from novaclient.openstack.common.gettextutils import _
from novaclient import utils
@ -40,7 +39,7 @@ def discover_auth_systems():
try:
auth_plugin = ep.load()
except (ImportError, pkg_resources.UnknownExtra, AttributeError) as e:
logger.debug(_("ERROR: Cannot load auth plugin %s") % ep.name)
logger.debug("ERROR: Cannot load auth plugin %s" % ep.name)
logger.debug(e, exc_info=1)
else:
_discovered_plugins[ep.name] = auth_plugin

View File

@ -20,11 +20,7 @@ Base utilities to build API operation managers and objects on top of.
"""
import abc
import contextlib
import hashlib
import inspect
import os
import threading
import six
@ -52,11 +48,18 @@ class Manager(utils.HookableMixin):
etc.) and provide CRUD operations for them.
"""
resource_class = None
cache_lock = threading.RLock()
def __init__(self, api):
self.api = api
def _write_object_to_completion_cache(self, obj):
if hasattr(self.api, 'write_object_to_completion_cache'):
self.api.write_object_to_completion_cache(obj)
def _clear_completion_cache_for_class(self, obj_class):
if hasattr(self.api, 'clear_completion_cache_for_class'):
self.api.clear_completion_cache_for_class(obj_class)
def _list(self, url, response_key, obj_class=None, body=None):
if body:
_resp, body = self.api.client.post(url, body=body)
@ -75,77 +78,22 @@ class Manager(utils.HookableMixin):
except KeyError:
pass
with self.completion_cache('human_id', obj_class, mode="w"):
with self.completion_cache('uuid', obj_class, mode="w"):
return [obj_class(self, res, loaded=True)
for res in data if res]
self._clear_completion_cache_for_class(obj_class)
@contextlib.contextmanager
def completion_cache(self, cache_type, obj_class, mode):
"""
The completion cache store items that can be used for bash
autocompletion, like UUIDs or human-friendly IDs.
objs = []
for res in data:
if res:
obj = obj_class(self, res, loaded=True)
self._write_object_to_completion_cache(obj)
objs.append(obj)
A resource listing will clear and repopulate the cache.
A resource create will append to the cache.
Delete is not handled because listings are assumed to be performed
often enough to keep the cache reasonably up-to-date.
"""
# NOTE(wryan): This lock protects read and write access to the
# completion caches
with self.cache_lock:
base_dir = utils.env('NOVACLIENT_UUID_CACHE_DIR',
default="~/.novaclient")
# NOTE(sirp): Keep separate UUID caches for each username +
# endpoint pair
username = utils.env('OS_USERNAME', 'NOVA_USERNAME')
url = utils.env('OS_URL', 'NOVA_URL')
uniqifier = hashlib.md5(username.encode('utf-8') +
url.encode('utf-8')).hexdigest()
cache_dir = os.path.expanduser(os.path.join(base_dir, uniqifier))
try:
os.makedirs(cache_dir, 0o755)
except OSError:
# NOTE(kiall): This is typically either permission denied while
# attempting to create the directory, or the
# directory already exists. Either way, don't
# fail.
pass
resource = obj_class.__name__.lower()
filename = "%s-%s-cache" % (resource, cache_type.replace('_', '-'))
path = os.path.join(cache_dir, filename)
cache_attr = "_%s_cache" % cache_type
try:
setattr(self, cache_attr, open(path, mode))
except IOError:
# NOTE(kiall): This is typically a permission denied while
# attempting to write the cache file.
pass
try:
yield
finally:
cache = getattr(self, cache_attr, None)
if cache:
cache.close()
delattr(self, cache_attr)
def write_to_completion_cache(self, cache_type, val):
cache = getattr(self, "_%s_cache" % cache_type, None)
if cache:
cache.write("%s\n" % val)
return objs
def _get(self, url, response_key):
_resp, body = self.api.client.get(url)
return self.resource_class(self, body[response_key], loaded=True)
obj = self.resource_class(self, body[response_key], loaded=True)
self._write_object_to_completion_cache(obj)
return obj
def _create(self, url, body, response_key, return_raw=False, **kwargs):
self.run_hooks('modify_body_for_create', body, **kwargs)
@ -153,9 +101,9 @@ class Manager(utils.HookableMixin):
if return_raw:
return body[response_key]
with self.completion_cache('human_id', self.resource_class, mode="a"):
with self.completion_cache('uuid', self.resource_class, mode="a"):
return self.resource_class(self, body[response_key])
obj = self.resource_class(self, body[response_key])
self._write_object_to_completion_cache(obj)
return obj
def _delete(self, url):
_resp, _body = self.api.client.delete(url)

View File

@ -20,7 +20,12 @@
OpenStack Client interface. Handles the REST calls and responses.
"""
import errno
import functools
import glob
import hashlib
import logging
import os
import time
import requests
@ -35,21 +40,177 @@ from six.moves.urllib import parse
from novaclient import exceptions
from novaclient.openstack.common.gettextutils import _
from novaclient.openstack.common import network_utils
from novaclient import service_catalog
from novaclient import utils
_ADAPTERS = {}
SENSITIVE_HEADERS = ('X-Auth-Token',)
def _adapter_pool(url):
class _ClientConnectionPool(object):
def __init__(self):
self._adapters = {}
def get(self, url):
"""
Store and reuse HTTP adapters per Service URL.
"""
if url not in self._adapters:
self._adapters[url] = adapters.HTTPAdapter()
return self._adapters[url]
class CompletionCache(object):
"""The completion cache is how we support tab-completion with novaclient.
The `Manager` writes object IDs and Human-IDs to the completion-cache on
object-show, object-list, and object-create calls.
The `nova.bash_completion` script then uses these files to provide the
actual tab-completion.
The cache directory layout is:
~/.novaclient/
<hash-of-endpoint-and-username>/
<resource>-id-cache
<resource>-human-id-cache
"""
Store and reuse HTTP adapters per Service URL.
"""
if url not in _ADAPTERS:
_ADAPTERS[url] = adapters.HTTPAdapter()
def __init__(self, username, auth_url, attributes=('id', 'human_id')):
self.directory = self._make_directory_name(username, auth_url)
self.attributes = attributes
return _ADAPTERS[url]
def _make_directory_name(self, username, auth_url):
"""Creates a unique directory name based on the auth_url and username
of the current user.
"""
uniqifier = hashlib.md5(username.encode('utf-8') +
auth_url.encode('utf-8')).hexdigest()
base_dir = utils.env('NOVACLIENT_UUID_CACHE_DIR',
default="~/.novaclient")
return os.path.expanduser(os.path.join(base_dir, uniqifier))
def _prepare_directory(self):
try:
os.makedirs(self.directory, 0o755)
except OSError:
# NOTE(kiall): This is typically either permission denied while
# attempting to create the directory, or the
# directory already exists. Either way, don't
# fail.
pass
def clear_class(self, obj_class):
self._prepare_directory()
resource = obj_class.__name__.lower()
resource_glob = os.path.join(self.directory, "%s-*-cache" % resource)
for filename in glob.iglob(resource_glob):
try:
os.unlink(filename)
except OSError as e:
if e.errno != errno.ENOENT:
raise
def _write_attribute(self, resource, attribute, value):
self._prepare_directory()
filename = "%s-%s-cache" % (resource, attribute.replace('_', '-'))
path = os.path.join(self.directory, filename)
with open(path, 'a') as f:
f.write("%s\n" % value)
def write_object(self, obj):
resource = obj.__class__.__name__.lower()
for attribute in self.attributes:
value = getattr(obj, attribute, None)
if value:
self._write_attribute(resource, attribute, value)
class SessionClient(object):
def __init__(self, session, auth, interface, service_type, region_name):
self.session = session
self.auth = auth
self.interface = interface
self.service_type = service_type
self.region_name = region_name
def request(self, url, method, **kwargs):
kwargs.setdefault('user_agent', 'python-novaclient')
kwargs.setdefault('auth', self.auth)
kwargs.setdefault('authenticated', False)
try:
kwargs['json'] = kwargs.pop('body')
except KeyError:
pass
headers = kwargs.setdefault('headers', {})
headers.setdefault('Accept', 'application/json')
endpoint_filter = kwargs.setdefault('endpoint_filter', {})
endpoint_filter.setdefault('interface', self.interface)
endpoint_filter.setdefault('service_type', self.service_type)
endpoint_filter.setdefault('region_name', self.region_name)
resp = self.session.request(url, method, raise_exc=False, **kwargs)
body = None
if resp.text:
try:
body = resp.json()
except ValueError:
pass
if resp.status_code >= 400:
raise exceptions.from_response(resp, body, url, method)
return resp, body
def _cs_request(self, url, method, **kwargs):
# this function is mostly redundant but makes compatibility easier
kwargs.setdefault('authenticated', True)
return self.request(url, method, **kwargs)
def get(self, url, **kwargs):
return self._cs_request(url, 'GET', **kwargs)
def post(self, url, **kwargs):
return self._cs_request(url, 'POST', **kwargs)
def put(self, url, **kwargs):
return self._cs_request(url, 'PUT', **kwargs)
def delete(self, url, **kwargs):
return self._cs_request(url, 'DELETE', **kwargs)
def _original_only(f):
"""Indicates and enforces that this function can only be used if we are
using the original HTTPClient object.
We use this to specify that if you use the newer Session HTTP client then
you are aware that the way you use your client has been updated and certain
functions are no longer allowed to be used.
"""
@functools.wraps(f)
def wrapper(self, *args, **kwargs):
if isinstance(self.client, SessionClient):
msg = ('This call is no longer available. The operation should '
'be performed on the session object instead.')
raise exceptions.InvalidUsage(msg)
return f(self, *args, **kwargs)
return wrapper
class HTTPClient(object):
@ -64,12 +225,17 @@ class HTTPClient(object):
os_cache=False, no_cache=True,
http_log_debug=False, auth_system='keystone',
auth_plugin=None, auth_token=None,
cacert=None, tenant_id=None):
cacert=None, tenant_id=None, user_id=None,
connection_pool=False):
self.user = user
self.user_id = user_id
self.password = password
self.projectid = projectid
self.tenant_id = tenant_id
self._connection_pool = (_ClientConnectionPool()
if connection_pool else None)
# This will be called by #_get_password if self.password is None.
# EG if a password can only be obtained by prompting the user, but a
# token is available, you don't want to prompt until the token has
@ -83,7 +249,7 @@ class HTTPClient(object):
auth_url = auth_plugin.get_auth_url()
if not auth_url:
raise exceptions.EndpointNotFound()
self.auth_url = auth_url.rstrip('/')
self.auth_url = auth_url.rstrip('/') if auth_url else auth_url
self.version = 'v1.1'
self.region_name = region_name
self.endpoint_type = endpoint_type
@ -91,7 +257,7 @@ class HTTPClient(object):
self.service_name = service_name
self.volume_service_name = volume_service_name
self.timings = timings
self.bypass_url = bypass_url
self.bypass_url = bypass_url.rstrip('/') if bypass_url else bypass_url
self.os_cache = os_cache or not no_cache
self.http_log_debug = http_log_debug
if timeout is not None:
@ -118,8 +284,8 @@ class HTTPClient(object):
self.auth_system = auth_system
self.auth_plugin = auth_plugin
self._session = None
self._current_url = None
self._http = None
self._logger = logging.getLogger(__name__)
if self.http_log_debug and not self._logger.handlers:
@ -152,6 +318,16 @@ class HTTPClient(object):
def reset_timings(self):
self.times = []
def safe_header(self, name, value):
if name in SENSITIVE_HEADERS:
# because in python3 byte string handling is ... ug
v = value.encode('utf-8')
h = hashlib.sha1(v)
d = h.hexdigest()
return name, "{SHA1}%s" % d
else:
return name, value
def http_log_req(self, method, url, kwargs):
if not self.http_log_debug:
return
@ -164,35 +340,52 @@ class HTTPClient(object):
string_parts.append(" '%s'" % url)
string_parts.append(' -X %s' % method)
for element in kwargs['headers']:
header = ' -H "%s: %s"' % (element, kwargs['headers'][element])
# because dict ordering changes from 2 to 3
keys = sorted(kwargs['headers'].keys())
for name in keys:
value = kwargs['headers'][name]
header = ' -H "%s: %s"' % self.safe_header(name, value)
string_parts.append(header)
if 'data' in kwargs:
string_parts.append(" -d '%s'" % (kwargs['data']))
self._logger.debug("\nREQ: %s\n" % "".join(string_parts))
self._logger.debug("REQ: %s" % "".join(string_parts))
def http_log_resp(self, resp):
if not self.http_log_debug:
return
self._logger.debug(_("RESP: [%(status)s] %(headers)s\nRESP BODY: "
"%(text)s\n"), {'status': resp.status_code,
'headers': resp.headers,
'text': resp.text})
self._logger.debug("RESP: [%(status)s] %(headers)s\nRESP BODY: "
"%(text)s\n", {'status': resp.status_code,
'headers': resp.headers,
'text': resp.text})
def http(self, url):
magic_tuple = parse.urlsplit(url)
scheme, netloc, path, query, frag = magic_tuple
service_url = '%s://%s' % (scheme, netloc)
if self._current_url != service_url:
# Invalidate Session object in case the url is somehow changed
if self._http:
self._http.close()
self._current_url = service_url
self._logger.debug("New session created for: (%s)" % service_url)
self._http = requests.Session()
self._http.mount(service_url, _adapter_pool(service_url))
return self._http
def open_session(self):
if not self._connection_pool:
self._session = requests.Session()
def close_session(self):
if self._session and not self._connection_pool:
self._session.close()
self._session = None
def _get_session(self, url):
if self._connection_pool:
magic_tuple = parse.urlsplit(url)
scheme, netloc, path, query, frag = magic_tuple
service_url = '%s://%s' % (scheme, netloc)
if self._current_url != service_url:
# Invalidate Session object in case the url is somehow changed
if self._session:
self._session.close()
self._current_url = service_url
self._logger.debug(
"New session created for: (%s)" % service_url)
self._session = requests.Session()
self._session.mount(service_url,
self._connection_pool.get(service_url))
return self._session
elif self._session:
return self._session
def request(self, url, method, **kwargs):
kwargs.setdefault('headers', kwargs.get('headers', {}))
@ -207,7 +400,13 @@ class HTTPClient(object):
kwargs['verify'] = self.verify_cert
self.http_log_req(method, url, kwargs)
resp = self.http(url).request(
request_func = requests.request
session = self._get_session(url)
if session:
request_func = session.request
resp = request_func(
method,
url,
**kwargs)
@ -347,14 +546,14 @@ class HTTPClient(object):
# GET ...:5001/v2.0/tokens/#####/endpoints
url = '/'.join([url, 'tokens', '%s?belongsTo=%s'
% (self.proxy_token, self.proxy_tenant_id)])
self._logger.debug(_("Using Endpoint URL: %s") % url)
self._logger.debug("Using Endpoint URL: %s" % url)
resp, body = self._time_request(
url, "GET", headers={'X-Auth-Token': self.auth_token})
return self._extract_service_catalog(url, resp, body,
extract_token=False)
def authenticate(self):
magic_tuple = parse.urlsplit(self.auth_url)
magic_tuple = network_utils.urlsplit(self.auth_url)
scheme, netloc, path, query, frag = magic_tuple
port = magic_tuple.port
if port is None:
@ -456,6 +655,10 @@ class HTTPClient(object):
if self.auth_token:
body = {"auth": {
"token": {"id": self.auth_token}}}
elif self.user_id:
body = {"auth": {
"passwordCredentials": {"userId": self.user_id,
"password": self._get_password()}}}
else:
body = {"auth": {
"passwordCredentials": {"username": self.user,
@ -484,6 +687,53 @@ class HTTPClient(object):
return self._extract_service_catalog(url, resp, respbody)
def _construct_http_client(username=None, password=None, project_id=None,
auth_url=None, insecure=False, timeout=None,
proxy_tenant_id=None, proxy_token=None,
region_name=None, endpoint_type='publicURL',
extensions=None, service_type='compute',
service_name=None, volume_service_name=None,
timings=False, bypass_url=None, os_cache=False,
no_cache=True, http_log_debug=False,
auth_system='keystone', auth_plugin=None,
auth_token=None, cacert=None, tenant_id=None,
user_id=None, connection_pool=False, session=None,
auth=None):
if session:
return SessionClient(session=session,
auth=auth,
interface=endpoint_type,
service_type=service_type,
region_name=region_name)
else:
# FIXME(jamielennox): username and password are now optional. Need
# to test that they were provided in this mode.
return HTTPClient(username,
password,
user_id=user_id,
projectid=project_id,
tenant_id=tenant_id,
auth_url=auth_url,
auth_token=auth_token,
insecure=insecure,
timeout=timeout,
auth_system=auth_system,
auth_plugin=auth_plugin,
proxy_token=proxy_token,
proxy_tenant_id=proxy_tenant_id,
region_name=region_name,
endpoint_type=endpoint_type,
service_type=service_type,
service_name=service_name,
volume_service_name=volume_service_name,
timings=timings,
bypass_url=bypass_url,
os_cache=os_cache,
http_log_debug=http_log_debug,
cacert=cacert,
connection_pool=connection_pool)
def get_client_class(version):
version_map = {
'1.1': 'novaclient.v1_1.client.Client',

View File

@ -77,10 +77,17 @@ class ConnectionRefused(Exception):
return "ConnectionRefused: %s" % repr(self.response)
class InstanceInErrorState(Exception):
"""Instance is in the error state."""
pass
class ClientException(Exception):
"""
The base exception class for all exceptions this library raises.
"""
message = 'Unknown Error'
def __init__(self, code, message=None, details=None, request_id=None,
url=None, method=None):
self.code = code
@ -192,6 +199,16 @@ _error_classes = [BadRequest, Unauthorized, Forbidden, NotFound,
_code_map = dict((c.http_status, c) for c in _error_classes)
class InvalidUsage(RuntimeError):
"""This function call is invalid in the way you are using this client.
Due to the transition to using keystoneclient some function calls are no
longer available. You should make a similar call to the session object
instead.
"""
pass
def from_response(response, body, url, method=None):
"""
Return an instance of an ClientException or subclass

View File

@ -1,2 +1,17 @@
#
# 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 six
six.add_move(six.MovedModule('mox', 'mox', 'mox3.mox'))

View File

@ -213,8 +213,8 @@ class BaseAuthPlugin(object):
:type service_type: string
:param endpoint_type: Type of endpoint.
Possible values: public or publicURL,
internal or internalURL,
admin or adminURL
internal or internalURL,
admin or adminURL
:type endpoint_type: string
:returns: tuple of token and endpoint strings
:raises: EndpointException

View File

@ -30,6 +30,7 @@ import six
from six.moves.urllib import parse
from novaclient.openstack.common.apiclient import exceptions
from novaclient.openstack.common.gettextutils import _
from novaclient.openstack.common import strutils
@ -74,8 +75,8 @@ class HookableMixin(object):
:param cls: class that registers hooks
:param hook_type: hook type, e.g., '__pre_parse_args__'
:param **args: args to be passed to every hook function
:param **kwargs: kwargs to be passed to every hook function
:param args: args to be passed to every hook function
:param kwargs: kwargs to be passed to every hook function
"""
hook_funcs = cls._hooks_map.get(hook_type) or []
for hook_func in hook_funcs:
@ -219,7 +220,10 @@ class ManagerWithFind(BaseManager):
matches = self.findall(**kwargs)
num_matches = len(matches)
if num_matches == 0:
msg = "No %s matching %s." % (self.resource_class.__name__, kwargs)
msg = _("No %(name)s matching %(args)s.") % {
'name': self.resource_class.__name__,
'args': kwargs
}
raise exceptions.NotFound(msg)
elif num_matches > 1:
raise exceptions.NoUniqueMatch()
@ -373,7 +377,10 @@ class CrudManager(BaseManager):
num = len(rl)
if num == 0:
msg = "No %s matching %s." % (self.resource_class.__name__, kwargs)
msg = _("No %(name)s matching %(args)s.") % {
'name': self.resource_class.__name__,
'args': kwargs
}
raise exceptions.NotFound(404, msg)
elif num > 1:
raise exceptions.NoUniqueMatch
@ -441,8 +448,10 @@ class Resource(object):
def human_id(self):
"""Human-readable ID which can be used for bash completion.
"""
if self.NAME_ATTR in self.__dict__ and self.HUMAN_ID:
return strutils.to_slug(getattr(self, self.NAME_ATTR))
if self.HUMAN_ID:
name = getattr(self, self.NAME_ATTR, None)
if name is not None:
return strutils.to_slug(name)
return None
def _add_details(self, info):

View File

@ -36,6 +36,7 @@ except ImportError:
import requests
from novaclient.openstack.common.apiclient import exceptions
from novaclient.openstack.common.gettextutils import _
from novaclient.openstack.common import importutils
@ -46,6 +47,7 @@ class HTTPClient(object):
"""This client handles sending HTTP requests to OpenStack servers.
Features:
- share authentication information between several clients to different
services (e.g., for compute and image clients);
- reissue authentication request for expired tokens;
@ -151,7 +153,7 @@ class HTTPClient(object):
:param method: method of HTTP request
:param url: URL of HTTP request
:param kwargs: any other parameter that can be passed to
' requests.Session.request (such as `headers`) or `json`
requests.Session.request (such as `headers`) or `json`
that will be encoded as JSON and used as `data` argument
"""
kwargs.setdefault("headers", kwargs.get("headers", {}))
@ -206,7 +208,7 @@ class HTTPClient(object):
:param method: method of HTTP request
:param url: URL of HTTP request
:param kwargs: any other parameter that can be passed to
' `HTTPClient.request`
`HTTPClient.request`
"""
filter_args = {
@ -228,7 +230,7 @@ class HTTPClient(object):
**filter_args)
if not (token and endpoint):
raise exceptions.AuthorizationFailure(
"Cannot find endpoint or token for request")
_("Cannot find endpoint or token for request"))
old_token_endpoint = (token, endpoint)
kwargs.setdefault("headers", {})["X-Auth-Token"] = token
@ -351,8 +353,12 @@ class BaseClient(object):
try:
client_path = version_map[str(version)]
except (KeyError, ValueError):
msg = "Invalid %s client version '%s'. must be one of: %s" % (
(api_name, version, ', '.join(version_map.keys())))
msg = _("Invalid %(api_name)s client version '%(version)s'. "
"Must be one of: %(version_map)s") % {
'api_name': api_name,
'version': version,
'version_map': ', '.join(version_map.keys())
}
raise exceptions.UnsupportedVersion(msg)
return importutils.import_class(client_path)

View File

@ -25,6 +25,8 @@ import sys
import six
from novaclient.openstack.common.gettextutils import _
class ClientException(Exception):
"""The base exception class for all exceptions this library raises.
@ -36,7 +38,7 @@ class MissingArgs(ClientException):
"""Supplied arguments are not sufficient for calling a function."""
def __init__(self, missing):
self.missing = missing
msg = "Missing argument(s): %s" % ", ".join(missing)
msg = _("Missing arguments: %s") % ", ".join(missing)
super(MissingArgs, self).__init__(msg)
@ -55,6 +57,11 @@ class CommandError(ClientException):
pass
class ResourceNotFound(ClientException):
"""Error in getting the resource."""
pass
class AuthorizationFailure(ClientException):
"""Cannot authorize API client."""
pass
@ -69,16 +76,16 @@ class AuthPluginOptionsMissing(AuthorizationFailure):
"""Auth plugin misses some options."""
def __init__(self, opt_names):
super(AuthPluginOptionsMissing, self).__init__(
"Authentication failed. Missing options: %s" %
_("Authentication failed. Missing options: %s") %
", ".join(opt_names))
self.opt_names = opt_names
class AuthSystemNotFound(AuthorizationFailure):
"""User has specified a AuthSystem that is not installed."""
"""User has specified an AuthSystem that is not installed."""
def __init__(self, auth_system):
super(AuthSystemNotFound, self).__init__(
"AuthSystemNotFound: %s" % repr(auth_system))
_("AuthSystemNotFound: %s") % repr(auth_system))
self.auth_system = auth_system
@ -101,7 +108,7 @@ class AmbiguousEndpoints(EndpointException):
"""Found more than one matching endpoint in Service Catalog."""
def __init__(self, endpoints=None):
super(AmbiguousEndpoints, self).__init__(
"AmbiguousEndpoints: %s" % repr(endpoints))
_("AmbiguousEndpoints: %s") % repr(endpoints))
self.endpoints = endpoints
@ -109,7 +116,7 @@ class HttpError(ClientException):
"""The base exception class for all HTTP exceptions.
"""
http_status = 0
message = "HTTP Error"
message = _("HTTP Error")
def __init__(self, message=None, details=None,
response=None, request_id=None,
@ -129,7 +136,7 @@ class HttpError(ClientException):
class HTTPRedirection(HttpError):
"""HTTP Redirection."""
message = "HTTP Redirection"
message = _("HTTP Redirection")
class HTTPClientError(HttpError):
@ -137,7 +144,7 @@ class HTTPClientError(HttpError):
Exception for cases in which the client seems to have erred.
"""
message = "HTTP Client Error"
message = _("HTTP Client Error")
class HttpServerError(HttpError):
@ -146,7 +153,7 @@ class HttpServerError(HttpError):
Exception for cases in which the server is aware that it has
erred or is incapable of performing the request.
"""
message = "HTTP Server Error"
message = _("HTTP Server Error")
class MultipleChoices(HTTPRedirection):
@ -156,7 +163,7 @@ class MultipleChoices(HTTPRedirection):
"""
http_status = 300
message = "Multiple Choices"
message = _("Multiple Choices")
class BadRequest(HTTPClientError):
@ -165,7 +172,7 @@ class BadRequest(HTTPClientError):
The request cannot be fulfilled due to bad syntax.
"""
http_status = 400
message = "Bad Request"
message = _("Bad Request")
class Unauthorized(HTTPClientError):
@ -175,7 +182,7 @@ class Unauthorized(HTTPClientError):
is required and has failed or has not yet been provided.
"""
http_status = 401
message = "Unauthorized"
message = _("Unauthorized")
class PaymentRequired(HTTPClientError):
@ -184,7 +191,7 @@ class PaymentRequired(HTTPClientError):
Reserved for future use.
"""
http_status = 402
message = "Payment Required"
message = _("Payment Required")
class Forbidden(HTTPClientError):
@ -194,7 +201,7 @@ class Forbidden(HTTPClientError):
to it.
"""
http_status = 403
message = "Forbidden"
message = _("Forbidden")
class NotFound(HTTPClientError):
@ -204,7 +211,7 @@ class NotFound(HTTPClientError):
in the future.
"""
http_status = 404
message = "Not Found"
message = _("Not Found")
class MethodNotAllowed(HTTPClientError):
@ -214,7 +221,7 @@ class MethodNotAllowed(HTTPClientError):
by that resource.
"""
http_status = 405
message = "Method Not Allowed"
message = _("Method Not Allowed")
class NotAcceptable(HTTPClientError):
@ -224,7 +231,7 @@ class NotAcceptable(HTTPClientError):
acceptable according to the Accept headers sent in the request.
"""
http_status = 406
message = "Not Acceptable"
message = _("Not Acceptable")
class ProxyAuthenticationRequired(HTTPClientError):
@ -233,7 +240,7 @@ class ProxyAuthenticationRequired(HTTPClientError):
The client must first authenticate itself with the proxy.
"""
http_status = 407
message = "Proxy Authentication Required"
message = _("Proxy Authentication Required")
class RequestTimeout(HTTPClientError):
@ -242,7 +249,7 @@ class RequestTimeout(HTTPClientError):
The server timed out waiting for the request.
"""
http_status = 408
message = "Request Timeout"
message = _("Request Timeout")
class Conflict(HTTPClientError):
@ -252,7 +259,7 @@ class Conflict(HTTPClientError):
in the request, such as an edit conflict.
"""
http_status = 409
message = "Conflict"
message = _("Conflict")
class Gone(HTTPClientError):
@ -262,7 +269,7 @@ class Gone(HTTPClientError):
not be available again.
"""
http_status = 410
message = "Gone"
message = _("Gone")
class LengthRequired(HTTPClientError):
@ -272,7 +279,7 @@ class LengthRequired(HTTPClientError):
required by the requested resource.
"""
http_status = 411
message = "Length Required"
message = _("Length Required")
class PreconditionFailed(HTTPClientError):
@ -282,7 +289,7 @@ class PreconditionFailed(HTTPClientError):
put on the request.
"""
http_status = 412
message = "Precondition Failed"
message = _("Precondition Failed")
class RequestEntityTooLarge(HTTPClientError):
@ -291,7 +298,7 @@ class RequestEntityTooLarge(HTTPClientError):
The request is larger than the server is willing or able to process.
"""
http_status = 413
message = "Request Entity Too Large"
message = _("Request Entity Too Large")
def __init__(self, *args, **kwargs):
try:
@ -308,7 +315,7 @@ class RequestUriTooLong(HTTPClientError):
The URI provided was too long for the server to process.
"""
http_status = 414
message = "Request-URI Too Long"
message = _("Request-URI Too Long")
class UnsupportedMediaType(HTTPClientError):
@ -318,7 +325,7 @@ class UnsupportedMediaType(HTTPClientError):
not support.
"""
http_status = 415
message = "Unsupported Media Type"
message = _("Unsupported Media Type")
class RequestedRangeNotSatisfiable(HTTPClientError):
@ -328,7 +335,7 @@ class RequestedRangeNotSatisfiable(HTTPClientError):
supply that portion.
"""
http_status = 416
message = "Requested Range Not Satisfiable"
message = _("Requested Range Not Satisfiable")
class ExpectationFailed(HTTPClientError):
@ -337,7 +344,7 @@ class ExpectationFailed(HTTPClientError):
The server cannot meet the requirements of the Expect request-header field.
"""
http_status = 417
message = "Expectation Failed"
message = _("Expectation Failed")
class UnprocessableEntity(HTTPClientError):
@ -347,7 +354,7 @@ class UnprocessableEntity(HTTPClientError):
errors.
"""
http_status = 422
message = "Unprocessable Entity"
message = _("Unprocessable Entity")
class InternalServerError(HttpServerError):
@ -356,7 +363,7 @@ class InternalServerError(HttpServerError):
A generic error message, given when no more specific message is suitable.
"""
http_status = 500
message = "Internal Server Error"
message = _("Internal Server Error")
# NotImplemented is a python keyword.
@ -367,7 +374,7 @@ class HttpNotImplemented(HttpServerError):
the ability to fulfill the request.
"""
http_status = 501
message = "Not Implemented"
message = _("Not Implemented")
class BadGateway(HttpServerError):
@ -377,7 +384,7 @@ class BadGateway(HttpServerError):
response from the upstream server.
"""
http_status = 502
message = "Bad Gateway"
message = _("Bad Gateway")
class ServiceUnavailable(HttpServerError):
@ -386,7 +393,7 @@ class ServiceUnavailable(HttpServerError):
The server is currently unavailable.
"""
http_status = 503
message = "Service Unavailable"
message = _("Service Unavailable")
class GatewayTimeout(HttpServerError):
@ -396,7 +403,7 @@ class GatewayTimeout(HttpServerError):
response from the upstream server.
"""
http_status = 504
message = "Gateway Timeout"
message = _("Gateway Timeout")
class HttpVersionNotSupported(HttpServerError):
@ -405,7 +412,7 @@ class HttpVersionNotSupported(HttpServerError):
The server does not support the HTTP protocol version used in the request.
"""
http_status = 505
message = "HTTP Version Not Supported"
message = _("HTTP Version Not Supported")
# _code_map contains all the classes that have http_status attribute.
@ -423,12 +430,17 @@ def from_response(response, method, url):
:param method: HTTP method used for request
:param url: URL used for request
"""
req_id = response.headers.get("x-openstack-request-id")
#NOTE(hdd) true for older versions of nova and cinder
if not req_id:
req_id = response.headers.get("x-compute-request-id")
kwargs = {
"http_status": response.status_code,
"response": response,
"method": method,
"url": url,
"request_id": response.headers.get("x-compute-request-id"),
"request_id": req_id,
}
if "retry-after" in response.headers:
kwargs["retry_after"] = response.headers["retry-after"]

View File

@ -28,7 +28,6 @@ import gettext
import locale
from logging import handlers
import os
import re
from babel import localedata
import six
@ -248,47 +247,22 @@ class Message(six.text_type):
if other is None:
params = (other,)
elif isinstance(other, dict):
params = self._trim_dictionary_parameters(other)
# Merge the dictionaries
# Copy each item in case one does not support deep copy.
params = {}
if isinstance(self.params, dict):
for key, val in self.params.items():
params[key] = self._copy_param(val)
for key, val in other.items():
params[key] = self._copy_param(val)
else:
params = self._copy_param(other)
return params
def _trim_dictionary_parameters(self, dict_param):
"""Return a dict that only has matching entries in the msgid."""
# NOTE(luisg): Here we trim down the dictionary passed as parameters
# to avoid carrying a lot of unnecessary weight around in the message
# object, for example if someone passes in Message() % locals() but
# only some params are used, and additionally we prevent errors for
# non-deepcopyable objects by unicoding() them.
# Look for %(param) keys in msgid;
# Skip %% and deal with the case where % is first character on the line
keys = re.findall('(?:[^%]|^)?%\((\w*)\)[a-z]', self.msgid)
# If we don't find any %(param) keys but have a %s
if not keys and re.findall('(?:[^%]|^)%[a-z]', self.msgid):
# Apparently the full dictionary is the parameter
params = self._copy_param(dict_param)
else:
params = {}
# Save our existing parameters as defaults to protect
# ourselves from losing values if we are called through an
# (erroneous) chain that builds a valid Message with
# arguments, and then does something like "msg % kwds"
# where kwds is an empty dictionary.
src = {}
if isinstance(self.params, dict):
src.update(self.params)
src.update(dict_param)
for key in keys:
params[key] = self._copy_param(src[key])
return params
def _copy_param(self, param):
try:
return copy.deepcopy(param)
except TypeError:
except Exception:
# Fallback to casting to unicode this will handle the
# python code-like objects that can't be deep-copied
return six.text_type(param)

View File

@ -31,25 +31,29 @@ This module provides a few things:
'''
import codecs
import datetime
import functools
import inspect
import itertools
import json
try:
import xmlrpclib
except ImportError:
# NOTE(jaypipes): xmlrpclib was renamed to xmlrpc.client in Python3
# however the function and object call signatures
# remained the same. This whole try/except block should
# be removed and replaced with a call to six.moves once
# six 1.4.2 is released. See http://bit.ly/1bqrVzu
import xmlrpc.client as xmlrpclib
import sys
if sys.version_info < (2, 7):
# On Python <= 2.6, json module is not C boosted, so try to use
# simplejson module if available
try:
import simplejson as json
except ImportError:
import json
else:
import json
import six
import six.moves.xmlrpc_client as xmlrpclib
from novaclient.openstack.common import gettextutils
from novaclient.openstack.common import importutils
from novaclient.openstack.common import strutils
from novaclient.openstack.common import timeutils
netaddr = importutils.try_import("netaddr")
@ -164,12 +168,12 @@ def dumps(value, default=to_primitive, **kwargs):
return json.dumps(value, default=default, **kwargs)
def loads(s):
return json.loads(s)
def loads(s, encoding='utf-8'):
return json.loads(strutils.safe_decode(s, encoding))
def load(s):
return json.load(s)
def load(fp, encoding='utf-8'):
return json.load(codecs.getreader(encoding)(fp))
try:

View File

@ -0,0 +1,108 @@
# Copyright 2012 OpenStack Foundation.
# All Rights Reserved.
#
# 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.
"""
Network-related utilities and helper functions.
"""
# TODO(jd) Use six.moves once
# https://bitbucket.org/gutworth/six/pull-request/28
# is merged
try:
import urllib.parse
SplitResult = urllib.parse.SplitResult
except ImportError:
import urlparse
SplitResult = urlparse.SplitResult
from six.moves.urllib import parse
def parse_host_port(address, default_port=None):
"""Interpret a string as a host:port pair.
An IPv6 address MUST be escaped if accompanied by a port,
because otherwise ambiguity ensues: 2001:db8:85a3::8a2e:370:7334
means both [2001:db8:85a3::8a2e:370:7334] and
[2001:db8:85a3::8a2e:370]:7334.
>>> parse_host_port('server01:80')
('server01', 80)
>>> parse_host_port('server01')
('server01', None)
>>> parse_host_port('server01', default_port=1234)
('server01', 1234)
>>> parse_host_port('[::1]:80')
('::1', 80)
>>> parse_host_port('[::1]')
('::1', None)
>>> parse_host_port('[::1]', default_port=1234)
('::1', 1234)
>>> parse_host_port('2001:db8:85a3::8a2e:370:7334', default_port=1234)
('2001:db8:85a3::8a2e:370:7334', 1234)
"""
if address[0] == '[':
# Escaped ipv6
_host, _port = address[1:].split(']')
host = _host
if ':' in _port:
port = _port.split(':')[1]
else:
port = default_port
else:
if address.count(':') == 1:
host, port = address.split(':')
else:
# 0 means ipv4, >1 means ipv6.
# We prohibit unescaped ipv6 addresses with port.
host = address
port = default_port
return (host, None if port is None else int(port))
class ModifiedSplitResult(SplitResult):
"""Split results class for urlsplit."""
# NOTE(dims): The functions below are needed for Python 2.6.x.
# We can remove these when we drop support for 2.6.x.
@property
def hostname(self):
netloc = self.netloc.split('@', 1)[-1]
host, port = parse_host_port(netloc)
return host
@property
def port(self):
netloc = self.netloc.split('@', 1)[-1]
host, port = parse_host_port(netloc)
return port
def urlsplit(url, scheme='', allow_fragments=True):
"""Parse a URL using urlparse.urlsplit(), splitting query and fragments.
This function papers over Python issue9374 when needed.
The parameters are the same as urlparse.urlsplit.
"""
scheme, netloc, path, query, fragment = parse.urlsplit(
url, scheme, allow_fragments)
if allow_fragments and '#' in path:
path, fragment = path.split('#', 1)
if '?' in path:
path, query = path.split('?', 1)
return ModifiedSplitResult(scheme, netloc,
path, query, fragment)

View File

@ -98,7 +98,8 @@ def bool_from_string(subject, strict=False, default=False):
def safe_decode(text, incoming=None, errors='strict'):
"""Decodes incoming str using `incoming` if they're not already unicode.
"""Decodes incoming text/bytes string using `incoming` if they're not
already unicode.
:param incoming: Text's current encoding
:param errors: Errors handling policy. See here for valid
@ -107,7 +108,7 @@ def safe_decode(text, incoming=None, errors='strict'):
representation of it.
:raises TypeError: If text is not an instance of str
"""
if not isinstance(text, six.string_types):
if not isinstance(text, (six.string_types, six.binary_type)):
raise TypeError("%s can't be decoded" % type(text))
if isinstance(text, six.text_type):
@ -137,7 +138,7 @@ def safe_decode(text, incoming=None, errors='strict'):
def safe_encode(text, incoming=None,
encoding='utf-8', errors='strict'):
"""Encodes incoming str/unicode using `encoding`.
"""Encodes incoming text/bytes string using `encoding`.
If incoming is not specified, text is expected to be encoded with
current python's default encoding. (`sys.getdefaultencoding`)
@ -150,7 +151,7 @@ def safe_encode(text, incoming=None,
representation of it.
:raises TypeError: If text is not an instance of str
"""
if not isinstance(text, six.string_types):
if not isinstance(text, (six.string_types, six.binary_type)):
raise TypeError("%s can't be encoded" % type(text))
if not incoming:

View File

@ -285,6 +285,11 @@ class OpenStackComputeShell(object):
parser.add_argument('--os_username',
help=argparse.SUPPRESS)
parser.add_argument('--os-user-id',
metavar='<auth-user-id>',
default=utils.env('OS_USER_ID'),
help=_('Defaults to env[OS_USER_ID].'))
parser.add_argument('--os-password',
metavar='<auth-password>',
default=utils.env('OS_PASSWORD', 'NOVA_PASSWORD'),
@ -385,7 +390,9 @@ class OpenStackComputeShell(object):
parser.add_argument('--bypass-url',
metavar='<bypass-url>',
dest='bypass_url',
help="Use this API endpoint instead of the Service Catalog")
default=utils.env('NOVACLIENT_BYPASS_URL'),
help="Use this API endpoint instead of the Service Catalog. "
"Defaults to env[NOVACLIENT_BYPASS_URL]")
parser.add_argument('--bypass_url',
help=argparse.SUPPRESS)
@ -550,6 +557,7 @@ class OpenStackComputeShell(object):
return 0
os_username = args.os_username
os_user_id = args.os_user_id
os_password = None # Fetched and set later as needed
os_tenant_name = args.os_tenant_name
os_tenant_id = args.os_tenant_id
@ -606,9 +614,10 @@ class OpenStackComputeShell(object):
auth_plugin.parse_opts(args)
if not auth_plugin or not auth_plugin.opts:
if not os_username:
if not os_username and not os_user_id:
raise exc.CommandError(_("You must provide a username "
"via either --os-username or env[OS_USERNAME]"))
"or user id via --os-username, --os-user-id, "
"env[OS_USERNAME] or env[OS_USER_ID]"))
if not os_tenant_name and not os_tenant_id:
raise exc.CommandError(_("You must provide a tenant name "
@ -639,8 +648,11 @@ class OpenStackComputeShell(object):
raise exc.CommandError(_("You must provide an auth url "
"via either --os-auth-url or env[OS_AUTH_URL]"))
self.cs = client.Client(options.os_compute_api_version, os_username,
os_password, os_tenant_name, tenant_id=os_tenant_id,
completion_cache = client.CompletionCache(os_username, os_auth_url)
self.cs = client.Client(options.os_compute_api_version,
os_username, os_password, os_tenant_name,
tenant_id=os_tenant_id, user_id=os_user_id,
auth_url=os_auth_url, insecure=insecure,
region_name=os_region_name, endpoint_type=endpoint_type,
extensions=self.extensions, service_type=service_type,
@ -649,7 +661,8 @@ class OpenStackComputeShell(object):
volume_service_name=volume_service_name,
timings=args.timings, bypass_url=bypass_url,
os_cache=os_cache, http_log_debug=options.debug,
cacert=cacert, timeout=timeout)
cacert=cacert, timeout=timeout,
completion_cache=completion_cache)
# Now check for the password/token of which pieces of the
# identifying keyring key can come from the underlying client
@ -694,6 +707,12 @@ class OpenStackComputeShell(object):
# sometimes need to be able to look up images information
# via glance when connected to the nova api.
image_service_type = 'image'
# NOTE(hdd): the password is needed again because creating a new
# Client without specifying bypass_url will force authentication.
# We can't reuse self.cs's bypass_url, because that's the URL for
# the nova service; we need to get glance's URL for this Client
if not os_password:
os_password = helper.password
self.cs.image_cs = client.Client(
options.os_compute_api_version, os_username,
os_password, os_tenant_name, tenant_id=os_tenant_id,
@ -763,6 +782,11 @@ class OpenStackComputeShell(object):
# I'm picky about my shell help.
class OpenStackHelpFormatter(argparse.HelpFormatter):
def __init__(self, prog, indent_increment=2, max_help_position=32,
width=None):
super(OpenStackHelpFormatter, self).__init__(prog, indent_increment,
max_help_position, width)
def start_section(self, heading):
# Title-case the headings
heading = '%s%s' % (heading[0].upper(), heading[1:])
@ -771,11 +795,14 @@ class OpenStackHelpFormatter(argparse.HelpFormatter):
def main():
try:
OpenStackComputeShell().main(map(strutils.safe_decode, sys.argv[1:]))
argv = [strutils.safe_decode(a) for a in sys.argv[1:]]
OpenStackComputeShell().main(argv)
except Exception as e:
logger.debug(e, exc_info=1)
print("ERROR: %s" % strutils.safe_encode(six.text_type(e)),
details = {'name': strutils.safe_encode(e.__class__.__name__),
'msg': strutils.safe_encode(six.text_type(e))}
print("ERROR (%(name)s): %(msg)s" % details,
file=sys.stderr)
sys.exit(1)
except KeyboardInterrupt as e:

View File

@ -0,0 +1,57 @@
# 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 httpretty
from novaclient.openstack.common import jsonutils
from novaclient.tests.fixture_data import base
class Fixture(base.Fixture):
base_url = 'os-agents'
def setUp(self):
super(Fixture, self).setUp()
post_os_agents = {
'agent': {
'url': '/xxx/xxx/xxx',
'hypervisor': 'kvm',
'md5hash': 'add6bb58e139be103324d04d82d8f546',
'version': '7.0',
'architecture': 'x86',
'os': 'win',
'id': 1
}
}
httpretty.register_uri(httpretty.POST, self.url(),
body=jsonutils.dumps(post_os_agents),
content_type='application/json')
put_os_agents_1 = {
"agent": {
"url": "/yyy/yyyy/yyyy",
"version": "8.0",
"md5hash": "add6bb58e139be103324d04d82d8f546",
'id': 1
}
}
httpretty.register_uri(httpretty.PUT, self.url(1),
body=jsonutils.dumps(put_os_agents_1),
content_type='application/json')
httpretty.register_uri(httpretty.DELETE, self.url(1),
content_type='application/json',
status=202)

View File

@ -0,0 +1,52 @@
# 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 httpretty
from novaclient.openstack.common import jsonutils
from novaclient.tests.fixture_data import base
class Fixture(base.Fixture):
base_url = 'os-aggregates'
def setUp(self):
super(Fixture, self).setUp()
get_os_aggregates = {"aggregates": [
{'id': '1',
'name': 'test',
'availability_zone': 'nova1'},
{'id': '2',
'name': 'test2',
'availability_zone': 'nova1'},
]}
httpretty.register_uri(httpretty.GET, self.url(),
body=jsonutils.dumps(get_os_aggregates),
content_type='application/json')
r = jsonutils.dumps({'aggregate': get_os_aggregates['aggregates'][0]})
httpretty.register_uri(httpretty.POST, self.url(), body=r,
content_type='application/json')
for agg_id in (1, 2):
for method in (httpretty.GET, httpretty.PUT):
httpretty.register_uri(method, self.url(agg_id), body=r,
content_type='application/json')
httpretty.register_uri(httpretty.POST, self.url(agg_id, 'action'),
body=r, content_type='application/json')
httpretty.register_uri(httpretty.DELETE, self.url(1), status=202)

View File

@ -0,0 +1,99 @@
# 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 httpretty
from novaclient.openstack.common import jsonutils
from novaclient.tests.fixture_data import base
class V1(base.Fixture):
base_url = 'os-availability-zone'
zone_info_key = 'availabilityZoneInfo'
zone_name_key = 'zoneName'
zone_state_key = 'zoneState'
def setUp(self):
super(V1, self).setUp()
get_os_availability_zone = {
self.zone_info_key: [
{
self.zone_name_key: "zone-1",
self.zone_state_key: {"available": True},
"hosts": None
},
{
self.zone_name_key: "zone-2",
self.zone_state_key: {"available": False},
"hosts": None
}
]
}
httpretty.register_uri(httpretty.GET, self.url(),
body=jsonutils.dumps(get_os_availability_zone),
content_type='application/json')
get_os_zone_detail = {
self.zone_info_key: [
{
self.zone_name_key: "zone-1",
self.zone_state_key: {"available": True},
"hosts": {
"fake_host-1": {
"nova-compute": {
"active": True,
"available": True,
"updated_at": '2012-12-26 14:45:25'
}
}
}
},
{
self.zone_name_key: "internal",
self.zone_state_key: {"available": True},
"hosts": {
"fake_host-1": {
"nova-sched": {
"active": True,
"available": True,
"updated_at": '2012-12-26 14:45:25'
}
},
"fake_host-2": {
"nova-network": {
"active": True,
"available": False,
"updated_at": '2012-12-26 14:45:24'
}
}
}
},
{
self.zone_name_key: "zone-2",
self.zone_state_key: {"available": False},
"hosts": None
}
]
}
httpretty.register_uri(httpretty.GET, self.url('detail'),
body=jsonutils.dumps(get_os_zone_detail),
content_type='application/json')
class V3(V1):
zone_info_key = 'availability_zone_info'
zone_name_key = 'zone_name'
zone_state_key = 'zone_state'

View File

@ -0,0 +1,38 @@
# 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 fixtures
from six.moves.urllib import parse
COMPUTE_URL = 'http://compute.host'
class Fixture(fixtures.Fixture):
base_url = None
def __init__(self, compute_url=COMPUTE_URL):
super(Fixture, self).__init__()
self.compute_url = compute_url
def url(self, *args, **kwargs):
url_args = [self.compute_url]
if self.base_url:
url_args.append(self.base_url)
url = '/'.join(str(a).strip('/') for a in tuple(url_args) + args)
if kwargs:
url += '?%s' % parse.urlencode(kwargs, doseq=True)
return url

View File

@ -0,0 +1,58 @@
# 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 httpretty
from novaclient.openstack.common import jsonutils
from novaclient.tests.fixture_data import base
class Fixture(base.Fixture):
base_url = 'os-certificates'
def get_os_certificates_root(self, **kw):
return (
200,
{},
{'certificate': {'private_key': None, 'data': 'foo'}}
)
def post_os_certificates(self, **kw):
return (
200,
{},
{'certificate': {'private_key': 'foo', 'data': 'bar'}}
)
def setUp(self):
super(Fixture, self).setUp()
get_os_certificate = {
'certificate': {
'private_key': None,
'data': 'foo'
}
}
httpretty.register_uri(httpretty.GET, self.url('root'),
body=jsonutils.dumps(get_os_certificate),
content_type='application/json')
post_os_certificates = {
'certificate': {
'private_key': 'foo',
'data': 'bar'
}
}
httpretty.register_uri(httpretty.POST, self.url(),
body=jsonutils.dumps(post_os_certificates),
content_type='application/json')

View File

@ -0,0 +1,127 @@
# 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 fixtures
import httpretty
from keystoneclient.auth.identity import v2
from keystoneclient import session
from novaclient.openstack.common import jsonutils
from novaclient.v1_1 import client as v1_1client
from novaclient.v3 import client as v3client
IDENTITY_URL = 'http://identityserver:5000/v2.0'
COMPUTE_URL = 'http://compute.host'
class V1(fixtures.Fixture):
def __init__(self, compute_url=COMPUTE_URL, identity_url=IDENTITY_URL):
super(V1, self).__init__()
self.identity_url = identity_url
self.compute_url = compute_url
self.client = None
self.token = {
'access': {
"token": {
"id": "ab48a9efdfedb23ty3494",
"expires": "2010-11-01T03:32:15-05:00",
"tenant": {
"id": "345",
"name": "My Project"
}
},
"user": {
"id": "123",
"name": "jqsmith",
"roles": [
{
"id": "234",
"name": "compute:admin",
},
{
"id": "235",
"name": "object-store:admin",
"tenantId": "1",
}
],
"roles_links": [],
},
"serviceCatalog": [
{
"name": "Cloud Servers",
"type": "compute",
"endpoints": [
{
"publicURL": self.compute_url,
"internalURL": "https://compute1.host/v1/1",
},
],
"endpoints_links": [],
},
{
"name": "Cloud Servers",
"type": "computev3",
"endpoints": [
{
"publicURL": self.compute_url,
"internalURL": "https://compute1.host/v1/1",
},
],
"endpoints_links": [],
},
],
}
}
def setUp(self):
super(V1, self).setUp()
httpretty.enable()
self.addCleanup(httpretty.disable)
auth_url = '%s/tokens' % self.identity_url
httpretty.register_uri(httpretty.POST, auth_url,
body=jsonutils.dumps(self.token),
content_type='application/json')
self.client = self.new_client()
def new_client(self):
return v1_1client.Client(username='xx',
api_key='xx',
project_id='xx',
auth_url=self.identity_url)
class V3(V1):
def new_client(self):
return v3client.Client(username='xx',
password='xx',
project_id='xx',
auth_url=self.identity_url)
class SessionV1(V1):
def new_client(self):
self.session = session.Session()
self.session.auth = v2.Password(self.identity_url, 'xx', 'xx')
return v1_1client.Client(session=self.session)
class SessionV3(V1):
def new_client(self):
self.session = session.Session()
self.session.auth = v2.Password(self.identity_url, 'xx', 'xx')
return v3client.Client(session=self.session)

View File

@ -0,0 +1,40 @@
# 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 httpretty
from novaclient.openstack.common import jsonutils
from novaclient.tests.fixture_data import base
class Fixture(base.Fixture):
base_url = 'os-cloudpipe'
def setUp(self):
super(Fixture, self).setUp()
get_os_cloudpipe = {'cloudpipes': [{'project_id': 1}]}
httpretty.register_uri(httpretty.GET, self.url(),
body=jsonutils.dumps(get_os_cloudpipe),
content_type='application/json')
instance_id = '9d5824aa-20e6-4b9f-b967-76a699fc51fd'
post_os_cloudpipe = {'instance_id': instance_id}
httpretty.register_uri(httpretty.POST, self.url(),
body=jsonutils.dumps(post_os_cloudpipe),
content_type='application/json',
status=202)
httpretty.register_uri(httpretty.PUT, self.url('configure-project'),
content_type='application/json',
status=202)

View File

@ -0,0 +1,41 @@
# 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 httpretty
from novaclient.openstack.common import jsonutils
from novaclient.tests.fixture_data import base
class Fixture(base.Fixture):
base_url = 'os-fixed-ips'
def setUp(self):
super(Fixture, self).setUp()
get_os_fixed_ips = {
"fixed_ip": {
'cidr': '192.168.1.0/24',
'address': '192.168.1.1',
'hostname': 'foo',
'host': 'bar'
}
}
httpretty.register_uri(httpretty.GET, self.url('192.168.1.1'),
body=jsonutils.dumps(get_os_fixed_ips),
content_type='application/json')
httpretty.register_uri(httpretty.POST,
self.url('192.168.1.1', 'action'),
content_type='application/json',
status=202)

View File

@ -0,0 +1,216 @@
# 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 httpretty
from novaclient.openstack.common import jsonutils
from novaclient.tests import fakes
from novaclient.tests.fixture_data import base
class FloatingFixture(base.Fixture):
base_url = 'os-floating-ips'
def setUp(self):
super(FloatingFixture, self).setUp()
floating_ips = [{'id': 1, 'fixed_ip': '10.0.0.1', 'ip': '11.0.0.1'},
{'id': 2, 'fixed_ip': '10.0.0.2', 'ip': '11.0.0.2'}]
get_os_floating_ips = {'floating_ips': floating_ips}
httpretty.register_uri(httpretty.GET, self.url(),
body=jsonutils.dumps(get_os_floating_ips),
content_type='application/json')
for ip in floating_ips:
get_os_floating_ip = {'floating_ip': ip}
httpretty.register_uri(httpretty.GET, self.url(ip['id']),
body=jsonutils.dumps(get_os_floating_ip),
content_type='application/json')
httpretty.register_uri(httpretty.DELETE, self.url(ip['id']),
content_type='application/json',
status=204)
def post_os_floating_ips(request, url, headers):
body = jsonutils.loads(request.body.decode('utf-8'))
ip = floating_ips[0].copy()
ip['pool'] = body.get('pool')
ip = jsonutils.dumps({'floating_ip': ip})
return 200, headers, ip
httpretty.register_uri(httpretty.POST, self.url(),
body=post_os_floating_ips,
content_type='application/json')
class DNSFixture(base.Fixture):
base_url = 'os-floating-ip-dns'
def setUp(self):
super(DNSFixture, self).setUp()
get_os_floating_ip_dns = {
'domain_entries': [
{'domain': 'example.org'},
{'domain': 'example.com'}
]
}
httpretty.register_uri(httpretty.GET, self.url(),
body=jsonutils.dumps(get_os_floating_ip_dns),
content_type='application/json',
status=205)
get_dns_testdomain_entries_testname = {
'dns_entry': {
'ip': "10.10.10.10",
'name': 'testname',
'type': "A",
'domain': 'testdomain'
}
}
url = self.url('testdomain', 'entries', 'testname')
body = jsonutils.dumps(get_dns_testdomain_entries_testname)
httpretty.register_uri(httpretty.GET, url,
body=body,
content_type='application/json',
status=205)
httpretty.register_uri(httpretty.DELETE, self.url('testdomain'),
status=200)
url = self.url('testdomain', 'entries', 'testname')
httpretty.register_uri(httpretty.DELETE, url, status=200)
def put_dns_testdomain_entries_testname(request, url, headers):
body = jsonutils.loads(request.body.decode('utf-8'))
fakes.assert_has_keys(body['dns_entry'],
required=['ip', 'dns_type'])
return 205, headers, request.body
httpretty.register_uri(httpretty.PUT, url,
body=put_dns_testdomain_entries_testname,
content_type='application/json')
url = self.url('testdomain', 'entries')
httpretty.register_uri(httpretty.GET, url, status=404)
get_os_floating_ip_dns_testdomain_entries = {
'dns_entries': [
{
'dns_entry': {
'ip': '1.2.3.4',
'name': "host1",
'type': "A",
'domain': 'testdomain'
}
},
{
'dns_entry': {
'ip': '1.2.3.4',
'name': "host2",
'type': "A",
'domain': 'testdomain'
}
},
]
}
body = jsonutils.dumps(get_os_floating_ip_dns_testdomain_entries)
httpretty.register_uri(httpretty.GET, url + '?ip=1.2.3.4',
body=body,
status=205,
content_type='application/json')
def put_os_floating_ip_dns_testdomain(request, url, headers):
body = jsonutils.loads(request.body.decode('utf-8'))
if body['domain_entry']['scope'] == 'private':
fakes.assert_has_keys(body['domain_entry'],
required=['availability_zone', 'scope'])
elif body['domain_entry']['scope'] == 'public':
fakes.assert_has_keys(body['domain_entry'],
required=['project', 'scope'])
else:
fakes.assert_has_keys(body['domain_entry'],
required=['project', 'scope'])
headers['Content-Type'] = 'application/json'
return (205, headers, request.body)
httpretty.register_uri(httpretty.PUT, self.url('testdomain'),
body=put_os_floating_ip_dns_testdomain)
class BulkFixture(base.Fixture):
base_url = 'os-floating-ips-bulk'
def setUp(self):
super(BulkFixture, self).setUp()
get_os_floating_ips_bulk = {
'floating_ip_info': [
{'id': 1, 'fixed_ip': '10.0.0.1', 'ip': '11.0.0.1'},
{'id': 2, 'fixed_ip': '10.0.0.2', 'ip': '11.0.0.2'},
]
}
httpretty.register_uri(httpretty.GET, self.url(),
body=jsonutils.dumps(get_os_floating_ips_bulk),
content_type='application/json')
httpretty.register_uri(httpretty.GET, self.url('testHost'),
body=jsonutils.dumps(get_os_floating_ips_bulk),
content_type='application/json')
def put_os_floating_ips_bulk_delete(request, url, headers):
body = jsonutils.loads(request.body.decode('utf-8'))
ip_range = body.get('ip_range')
data = {'floating_ips_bulk_delete': ip_range}
return 200, headers, jsonutils.dumps(data)
httpretty.register_uri(httpretty.PUT, self.url('delete'),
body=put_os_floating_ips_bulk_delete,
content_type='application/json')
def post_os_floating_ips_bulk(request, url, headers):
body = jsonutils.loads(request.body.decode('utf-8'))
params = body.get('floating_ips_bulk_create')
pool = params.get('pool', 'defaultPool')
interface = params.get('interface', 'defaultInterface')
data = {
'floating_ips_bulk_create': {
'ip_range': '192.168.1.0/30',
'pool': pool,
'interface': interface
}
}
return 200, headers, jsonutils.dumps(data)
httpretty.register_uri(httpretty.POST, self.url(),
body=post_os_floating_ips_bulk,
content_type='application/json')
class PoolsFixture(base.Fixture):
base_url = 'os-floating-ip-pools'
def setUp(self):
super(PoolsFixture, self).setUp()
get_os_floating_ip_pools = {
'floating_ip_pools': [
{'name': 'foo'},
{'name': 'bar'}
]
}
httpretty.register_uri(httpretty.GET, self.url(),
body=jsonutils.dumps(get_os_floating_ip_pools),
content_type='application/json')

View File

@ -0,0 +1,49 @@
# 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 httpretty
from novaclient.openstack.common import jsonutils
from novaclient.tests.fixture_data import base
class Fixture(base.Fixture):
base_url = 'os-fping'
def setUp(self):
super(Fixture, self).setUp()
get_os_fping_1 = {
'server': {
"id": "1",
"project_id": "fake-project",
"alive": True,
}
}
httpretty.register_uri(httpretty.GET, self.url(1),
body=jsonutils.dumps(get_os_fping_1),
content_type='application/json')
get_os_fping = {
'servers': [
get_os_fping_1['server'],
{
"id": "2",
"project_id": "fake-project",
"alive": True,
},
]
}
httpretty.register_uri(httpretty.GET, self.url(),
body=jsonutils.dumps(get_os_fping),
content_type='application/json')

View File

@ -0,0 +1,165 @@
# 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 httpretty
from six.moves.urllib import parse
from novaclient.openstack.common import jsonutils
from novaclient.tests.fixture_data import base
class BaseFixture(base.Fixture):
base_url = 'os-hosts'
def setUp(self):
super(BaseFixture, self).setUp()
get_os_hosts_host = {
'host': [
{'resource': {'project': '(total)', 'host': 'dummy',
'cpu': 16, 'memory_mb': 32234, 'disk_gb': 128}},
{'resource': {'project': '(used_now)', 'host': 'dummy',
'cpu': 1, 'memory_mb': 2075, 'disk_gb': 45}},
{'resource': {'project': '(used_max)', 'host': 'dummy',
'cpu': 1, 'memory_mb': 2048, 'disk_gb': 30}},
{'resource': {'project': 'admin', 'host': 'dummy',
'cpu': 1, 'memory_mb': 2048, 'disk_gb': 30}}
]
}
httpretty.register_uri(httpretty.GET, self.url('host'),
body=jsonutils.dumps(get_os_hosts_host),
content_type='application/json')
def get_os_hosts(request, url, headers):
host, query = parse.splitquery(url)
zone = 'nova1'
if query:
qs = parse.parse_qs(query)
try:
zone = qs['zone'][0]
except Exception:
pass
data = {
'hosts': [
{
'host': 'host1',
'service': 'nova-compute',
'zone': zone
},
{
'host': 'host1',
'service': 'nova-cert',
'zone': zone
}
]
}
return 200, headers, jsonutils.dumps(data)
httpretty.register_uri(httpretty.GET, self.url(),
body=get_os_hosts,
content_type='application/json')
get_os_hosts_sample_host = {
'host': [
{'resource': {'host': 'sample_host'}}
],
}
httpretty.register_uri(httpretty.GET, self.url('sample_host'),
body=jsonutils.dumps(get_os_hosts_sample_host),
content_type='application/json')
httpretty.register_uri(httpretty.PUT, self.url('sample_host', 1),
body=jsonutils.dumps(self.put_host_1()),
content_type='application/json')
httpretty.register_uri(httpretty.PUT, self.url('sample_host', 2),
body=jsonutils.dumps(self.put_host_2()),
content_type='application/json')
httpretty.register_uri(httpretty.PUT, self.url('sample_host', 3),
body=jsonutils.dumps(self.put_host_3()),
content_type='application/json')
url = self.url('sample_host', 'reboot')
httpretty.register_uri(httpretty.GET, url,
body=jsonutils.dumps(self.get_host_reboot()),
content_type='application/json')
url = self.url('sample_host', 'startup')
httpretty.register_uri(httpretty.GET, url,
body=jsonutils.dumps(self.get_host_startup()),
content_type='application/json')
url = self.url('sample_host', 'shutdown')
httpretty.register_uri(httpretty.GET, url,
body=jsonutils.dumps(self.get_host_shutdown()),
content_type='application/json')
def put_os_hosts_sample_host(request, url, headers):
result = {'host': 'dummy'}
result.update(jsonutils.loads(request.body.decode('utf-8')))
return 200, headers, jsonutils.dumps(result)
httpretty.register_uri(httpretty.PUT, self.url('sample_host'),
body=put_os_hosts_sample_host,
content_type='application/json')
class V1(BaseFixture):
def put_host_1(self):
return {'host': 'sample-host_1',
'status': 'enabled'}
def put_host_2(self):
return {'host': 'sample-host_2',
'maintenance_mode': 'on_maintenance'}
def put_host_3(self):
return {'host': 'sample-host_3',
'status': 'enabled',
'maintenance_mode': 'on_maintenance'}
def get_host_reboot(self):
return {'host': 'sample_host',
'power_action': 'reboot'}
def get_host_startup(self):
return {'host': 'sample_host',
'power_action': 'startup'}
def get_host_shutdown(self):
return {'host': 'sample_host',
'power_action': 'shutdown'}
class V3(V1):
def put_host_1(self):
return {'host': super(V3, self).put_host_1()}
def put_host_2(self):
return {'host': super(V3, self).put_host_2()}
def put_host_3(self):
return {'host': super(V3, self).put_host_3()}
def get_host_reboot(self):
return {'host': super(V3, self).get_host_reboot()}
def get_host_startup(self):
return {'host': super(V3, self).get_host_startup()}
def get_host_shutdown(self):
return {'host': super(V3, self).get_host_shutdown()}

View File

@ -0,0 +1,210 @@
# 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 httpretty
from novaclient.openstack.common import jsonutils
from novaclient.tests.fixture_data import base
class V1(base.Fixture):
base_url = 'os-hypervisors'
def setUp(self):
super(V1, self).setUp()
get_os_hypervisors = {
'hypervisors': [
{'id': 1234, 'hypervisor_hostname': 'hyper1'},
{'id': 5678, 'hypervisor_hostname': 'hyper2'},
]
}
httpretty.register_uri(httpretty.GET, self.url(),
body=jsonutils.dumps(get_os_hypervisors),
content_type='application/json')
get_os_hypervisors_detail = {
'hypervisors': [
{
'id': 1234,
'service': {'id': 1, 'host': 'compute1'},
'vcpus': 4,
'memory_mb': 10 * 1024,
'local_gb': 250,
'vcpus_used': 2,
'memory_mb_used': 5 * 1024,
'local_gb_used': 125,
'hypervisor_type': 'xen',
'hypervisor_version': 3,
'hypervisor_hostname': 'hyper1',
'free_ram_mb': 5 * 1024,
'free_disk_gb': 125,
'current_workload': 2,
'running_vms': 2,
'cpu_info': 'cpu_info',
'disk_available_least': 100
},
{
'id': 2,
'service': {'id': 2, 'host': 'compute2'},
'vcpus': 4,
'memory_mb': 10 * 1024,
'local_gb': 250,
'vcpus_used': 2,
'memory_mb_used': 5 * 1024,
'local_gb_used': 125,
'hypervisor_type': 'xen',
'hypervisor_version': 3,
'hypervisor_hostname': 'hyper2',
'free_ram_mb': 5 * 1024,
'free_disk_gb': 125,
'current_workload': 2,
'running_vms': 2,
'cpu_info': 'cpu_info',
'disk_available_least': 100
}
]
}
httpretty.register_uri(httpretty.GET, self.url('detail'),
body=jsonutils.dumps(get_os_hypervisors_detail),
content_type='application/json')
get_os_hypervisors_stats = {
'hypervisor_statistics': {
'count': 2,
'vcpus': 8,
'memory_mb': 20 * 1024,
'local_gb': 500,
'vcpus_used': 4,
'memory_mb_used': 10 * 1024,
'local_gb_used': 250,
'free_ram_mb': 10 * 1024,
'free_disk_gb': 250,
'current_workload': 4,
'running_vms': 4,
'disk_available_least': 200,
}
}
httpretty.register_uri(httpretty.GET, self.url('statistics'),
body=jsonutils.dumps(get_os_hypervisors_stats),
content_type='application/json')
get_os_hypervisors_search = {
'hypervisors': [
{'id': 1234, 'hypervisor_hostname': 'hyper1'},
{'id': 5678, 'hypervisor_hostname': 'hyper2'}
]
}
httpretty.register_uri(httpretty.GET, self.url('hyper', 'search'),
body=jsonutils.dumps(get_os_hypervisors_search),
content_type='application/json')
get_hyper_server = {
'hypervisors': [
{
'id': 1234,
'hypervisor_hostname': 'hyper1',
'servers': [
{'name': 'inst1', 'uuid': 'uuid1'},
{'name': 'inst2', 'uuid': 'uuid2'}
]
},
{
'id': 5678,
'hypervisor_hostname': 'hyper2',
'servers': [
{'name': 'inst3', 'uuid': 'uuid3'},
{'name': 'inst4', 'uuid': 'uuid4'}
]
}
]
}
httpretty.register_uri(httpretty.GET, self.url('hyper', 'servers'),
body=jsonutils.dumps(get_hyper_server),
content_type='application/json')
get_os_hypervisors_1234 = {
'hypervisor': {
'id': 1234,
'service': {'id': 1, 'host': 'compute1'},
'vcpus': 4,
'memory_mb': 10 * 1024,
'local_gb': 250,
'vcpus_used': 2,
'memory_mb_used': 5 * 1024,
'local_gb_used': 125,
'hypervisor_type': 'xen',
'hypervisor_version': 3,
'hypervisor_hostname': 'hyper1',
'free_ram_mb': 5 * 1024,
'free_disk_gb': 125,
'current_workload': 2,
'running_vms': 2,
'cpu_info': 'cpu_info',
'disk_available_least': 100
}
}
httpretty.register_uri(httpretty.GET, self.url(1234),
body=jsonutils.dumps(get_os_hypervisors_1234),
content_type='application/json')
get_os_hypervisors_uptime = {
'hypervisor': {
'id': 1234,
'hypervisor_hostname': 'hyper1',
'uptime': 'fake uptime'
}
}
httpretty.register_uri(httpretty.GET, self.url(1234, 'uptime'),
body=jsonutils.dumps(get_os_hypervisors_uptime),
content_type='application/json')
class V3(V1):
def setUp(self):
super(V3, self).setUp()
get_os_hypervisors_search = {
'hypervisors': [
{'id': 1234, 'hypervisor_hostname': 'hyper1'},
{'id': 5678, 'hypervisor_hostname': 'hyper2'}
]
}
httpretty.register_uri(httpretty.GET,
self.url('search', query='hyper'),
body=jsonutils.dumps(get_os_hypervisors_search),
content_type='application/json')
get_1234_servers = {
'hypervisor': {
'id': 1234,
'hypervisor_hostname': 'hyper1',
'servers': [
{'name': 'inst1', 'id': 'uuid1'},
{'name': 'inst2', 'id': 'uuid2'}
]
},
}
httpretty.register_uri(httpretty.GET, self.url(1234, 'servers'),
body=jsonutils.dumps(get_1234_servers),
content_type='application/json')

View File

@ -0,0 +1,119 @@
# 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 httpretty
from novaclient.openstack.common import jsonutils
from novaclient.tests import fakes
from novaclient.tests.fixture_data import base
class V1(base.Fixture):
base_url = 'images'
def setUp(self):
super(V1, self).setUp()
get_images = {
'images': [
{'id': 1, 'name': 'CentOS 5.2'},
{'id': 2, 'name': 'My Server Backup'}
]
}
httpretty.register_uri(httpretty.GET, self.url(),
body=jsonutils.dumps(get_images),
content_type='application/json')
image_1 = {
'id': 1,
'name': 'CentOS 5.2',
"updated": "2010-10-10T12:00:00Z",
"created": "2010-08-10T12:00:00Z",
"status": "ACTIVE",
"metadata": {
"test_key": "test_value",
},
"links": {},
}
image_2 = {
"id": 2,
"name": "My Server Backup",
"serverId": 1234,
"updated": "2010-10-10T12:00:00Z",
"created": "2010-08-10T12:00:00Z",
"status": "SAVING",
"progress": 80,
"links": {},
}
get_images_detail = {'images': [image_1, image_2]}
httpretty.register_uri(httpretty.GET, self.url('detail'),
body=jsonutils.dumps(get_images_detail),
content_type='application/json')
get_images_1 = {'image': image_1}
httpretty.register_uri(httpretty.GET, self.url(1),
body=jsonutils.dumps(get_images_1),
content_type='application/json')
get_images_2 = {'image': image_2}
httpretty.register_uri(httpretty.GET, self.url(2),
body=jsonutils.dumps(get_images_2),
content_type='application/json')
httpretty.register_uri(httpretty.GET, self.url(456),
body=jsonutils.dumps(get_images_2),
content_type='application/json')
def post_images(request, url, headers):
body = jsonutils.loads(request.body.decode('utf-8'))
assert list(body) == ['image']
fakes.assert_has_keys(body['image'], required=['serverId', 'name'])
return 202, headers, jsonutils.dumps(images_1)
httpretty.register_uri(httpretty.POST, self.url(),
body=post_images,
content_type='application/json')
def post_images_1_metadata(request, url, headers):
body = jsonutils.loads(request.body.decode('utf-8'))
assert list(body) == ['metadata']
fakes.assert_has_keys(body['metadata'], required=['test_key'])
data = jsonutils.dumps({'metadata': image_1['metadata']})
return 200, headers, data
httpretty.register_uri(httpretty.POST, self.url(1, 'metadata'),
body=post_images_1_metadata,
content_type='application/json')
for u in (1, 2, '1/metadata/test_key'):
httpretty.register_uri(httpretty.DELETE, self.url(u),
status=204)
httpretty.register_uri(httpretty.HEAD, self.url(1), status=200,
x_image_meta_id=1,
x_image_meta_name='CentOS 5.2',
x_image_meta_updated='2010-10-10T12:00:00Z',
x_image_meta_created='2010-10-10T12:00:00Z',
x_image_meta_status='ACTIVE',
x_image_meta_property_test_key='test_value')
class V3(V1):
base_url = 'v1/images'

View File

@ -0,0 +1,52 @@
# 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 httpretty
from novaclient.openstack.common import jsonutils
from novaclient.tests import fakes
from novaclient.tests.fixture_data import base
class V1(base.Fixture):
base_url = 'os-keypairs'
def setUp(self):
super(V1, self).setUp()
keypair = {'fingerprint': 'FAKE_KEYPAIR', 'name': 'test'}
httpretty.register_uri(httpretty.GET, self.url(),
body=jsonutils.dumps({'keypairs': [keypair]}),
content_type='application/json')
httpretty.register_uri(httpretty.GET, self.url('test'),
body=jsonutils.dumps({'keypair': keypair}),
content_type='application/json')
httpretty.register_uri(httpretty.DELETE, self.url('test'), status=202)
def post_os_keypairs(request, url, headers):
body = jsonutils.loads(request.body.decode('utf-8'))
assert list(body) == ['keypair']
fakes.assert_has_keys(body['keypair'], required=['name'])
return 202, headers, jsonutils.dumps({'keypair': keypair})
httpretty.register_uri(httpretty.POST, self.url(),
body=post_os_keypairs,
content_type='application/json')
class V3(V1):
base_url = 'keypairs'

View File

@ -0,0 +1,82 @@
# 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 httpretty
from novaclient.openstack.common import jsonutils
from novaclient.tests.fixture_data import base
class Fixture(base.Fixture):
base_url = 'limits'
def setUp(self):
super(Fixture, self).setUp()
get_limits = {
"limits": {
"rate": [
{
"uri": "*",
"regex": ".*",
"limit": [
{
"value": 10,
"verb": "POST",
"remaining": 2,
"unit": "MINUTE",
"next-available": "2011-12-15T22:42:45Z"
},
{
"value": 10,
"verb": "PUT",
"remaining": 2,
"unit": "MINUTE",
"next-available": "2011-12-15T22:42:45Z"
},
{
"value": 100,
"verb": "DELETE",
"remaining": 100,
"unit": "MINUTE",
"next-available": "2011-12-15T22:42:45Z"
}
]
},
{
"uri": "*/servers",
"regex": "^/servers",
"limit": [
{
"verb": "POST",
"value": 25,
"remaining": 24,
"unit": "DAY",
"next-available": "2011-12-15T22:42:45Z"
}
]
}
],
"absolute": {
"maxTotalRAMSize": 51200,
"maxServerMeta": 5,
"maxImageMeta": 5,
"maxPersonality": 5,
"maxPersonalitySize": 10240
},
},
}
httpretty.register_uri(httpretty.GET, self.url(),
body=jsonutils.dumps(get_limits),
content_type='application/json')

View File

@ -0,0 +1,62 @@
# 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 httpretty
from novaclient.openstack.common import jsonutils
from novaclient.tests.fixture_data import base
class Fixture(base.Fixture):
base_url = 'os-networks'
def setUp(self):
super(Fixture, self).setUp()
get_os_networks = {
'networks': [
{
"label": "1",
"cidr": "10.0.0.0/24",
'project_id': '4ffc664c198e435e9853f2538fbcd7a7',
'id': '1'
}
]
}
httpretty.register_uri(httpretty.GET, self.url(),
body=jsonutils.dumps(get_os_networks),
content_type='application/json')
def post_os_networks(request, url, headers):
body = jsonutils.loads(request.body.decode('utf-8'))
data = jsonutils.dumps({'network': body})
return 202, headers, data
httpretty.register_uri(httpretty.POST, self.url(),
body=post_os_networks,
content_type='application/json')
get_os_networks_1 = {'network': {"label": "1", "cidr": "10.0.0.0/24"}}
httpretty.register_uri(httpretty.GET, self.url(1),
body=jsonutils.dumps(get_os_networks_1),
content_type='application/json')
httpretty.register_uri(httpretty.DELETE,
self.url('networkdelete'),
stauts=202)
for u in ('add', 'networkdisassociate/action', 'networktest/action',
'1/action', '2/action'):
httpretty.register_uri(httpretty.POST, self.url(u), stauts=202)

View File

@ -0,0 +1,87 @@
# 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 httpretty
from novaclient.openstack.common import jsonutils
from novaclient.tests.fixture_data import base
class V1(base.Fixture):
base_url = 'os-quota-sets'
def setUp(self):
super(V1, self).setUp()
uuid = '97f4c221-bff4-4578-b030-0df4ef119353'
uuid2 = '97f4c221bff44578b0300df4ef119353'
test_json = jsonutils.dumps({'quota_set': self.test_quota('test')})
for u in ('test', 'tenant-id', 'tenant-id/defaults',
'%s/defaults' % uuid2):
httpretty.register_uri(httpretty.GET, self.url(u),
body=test_json,
content_type='application/json')
quota_json = jsonutils.dumps({'quota_set': self.test_quota(uuid)})
httpretty.register_uri(httpretty.PUT, self.url(uuid),
body=quota_json,
content_type='application/json')
httpretty.register_uri(httpretty.GET, self.url(uuid),
body=quota_json,
content_type='application/json')
quota_json2 = jsonutils.dumps({'quota_set': self.test_quota(uuid2)})
httpretty.register_uri(httpretty.PUT, self.url(uuid2),
body=quota_json2,
content_type='application/json')
httpretty.register_uri(httpretty.GET, self.url(uuid2),
body=quota_json2,
content_type='application/json')
for u in ('test', uuid2):
httpretty.register_uri(httpretty.DELETE, self.url(u), status=202)
def test_quota(self, tenant_id='test'):
return {
'tenant_id': tenant_id,
'metadata_items': [],
'injected_file_content_bytes': 1,
'injected_file_path_bytes': 1,
'ram': 1,
'floating_ips': 1,
'instances': 1,
'injected_files': 1,
'cores': 1,
'keypairs': 1,
'security_groups': 1,
'security_group_rules': 1
}
class V3(V1):
def setUp(self):
super(V3, self).setUp()
get_detail = {
'quota_set': {
'cores': {'reserved': 0, 'in_use': 0, 'limit': 10},
'instances': {'reserved': 0, 'in_use': 4, 'limit': 50},
'ram': {'reserved': 0, 'in_use': 1024, 'limit': 51200}
}
}
httpretty.register_uri(httpretty.GET, self.url('test', 'detail'),
body=jsonutils.dumps(get_detail),
content_type='application/json')

View File

@ -0,0 +1,57 @@
# 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 httpretty
from novaclient.openstack.common import jsonutils
from novaclient.tests import fakes
from novaclient.tests.fixture_data import base
class Fixture(base.Fixture):
base_url = 'os-security-group-rules'
def setUp(self):
super(Fixture, self).setUp()
rule = {
'id': 1,
'parent_group_id': 1,
'group_id': 2,
'ip_protocol': 'TCP',
'from_port': '22',
'to_port': 22,
'cidr': '10.0.0.0/8'
}
get_rules = {'security_group_rules': [rule]}
httpretty.register_uri(httpretty.GET, self.url(),
body=jsonutils.dumps(get_rules),
content_type='application/json')
for u in (1, 11, 12):
httpretty.register_uri(httpretty.DELETE, self.url(u), status=202)
def post_rules(request, url, headers):
body = jsonutils.loads(request.body.decode('utf-8'))
assert list(body) == ['security_group_rule']
fakes.assert_has_keys(body['security_group_rule'],
required=['parent_group_id'],
optional=['group_id', 'ip_protocol',
'from_port', 'to_port', 'cidr'])
return 202, headers, jsonutils.dumps({'security_group_rule': rule})
httpretty.register_uri(httpretty.POST, self.url(),
body=post_rules,
content_type='application/json')

View File

@ -0,0 +1,99 @@
# 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 httpretty
from novaclient.openstack.common import jsonutils
from novaclient.tests import fakes
from novaclient.tests.fixture_data import base
class Fixture(base.Fixture):
base_url = 'os-security-groups'
def setUp(self):
super(Fixture, self).setUp()
security_group_1 = {
"name": "test",
"description": "FAKE_SECURITY_GROUP",
"tenant_id": "4ffc664c198e435e9853f2538fbcd7a7",
"id": 1,
"rules": [
{
"id": 11,
"group": {},
"ip_protocol": "TCP",
"from_port": 22,
"to_port": 22,
"parent_group_id": 1,
"ip_range": {"cidr": "10.0.0.0/8"}
},
{
"id": 12,
"group": {
"tenant_id": "272bee4c1e624cd4a72a6b0ea55b4582",
"name": "test2"
},
"ip_protocol": "TCP",
"from_port": 222,
"to_port": 222,
"parent_group_id": 1,
"ip_range": {}
}
]
}
security_group_2 = {
"name": "test2",
"description": "FAKE_SECURITY_GROUP2",
"tenant_id": "272bee4c1e624cd4a72a6b0ea55b4582",
"id": 2,
"rules": []
}
get_groups = {'security_groups': [security_group_1, security_group_2]}
httpretty.register_uri(httpretty.GET, self.url(),
body=jsonutils.dumps(get_groups),
content_type='application/json')
get_group_1 = {'security_group': security_group_1}
httpretty.register_uri(httpretty.GET, self.url(1),
body=jsonutils.dumps(get_group_1),
content_type='application/json')
httpretty.register_uri(httpretty.DELETE, self.url(1), status=202)
def post_os_security_groups(request, url, headers):
body = jsonutils.loads(request.body.decode('utf-8'))
assert list(body) == ['security_group']
fakes.assert_has_keys(body['security_group'],
required=['name', 'description'])
r = jsonutils.dumps({'security_group': security_group_1})
return 202, headers, r
httpretty.register_uri(httpretty.POST, self.url(),
body=post_os_security_groups,
content_type='application/json')
def put_os_security_groups_1(request, url, headers):
body = jsonutils.loads(request.body.decode('utf-8'))
assert list(body) == ['security_group']
fakes.assert_has_keys(body['security_group'],
required=['name', 'description'])
return 205, headers, request.body
httpretty.register_uri(httpretty.PUT, self.url(1),
body=put_os_security_groups_1,
content_type='application/json')

View File

@ -0,0 +1,75 @@
# 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 httpretty
from novaclient.openstack.common import jsonutils
from novaclient.tests.fixture_data import base
class Fixture(base.Fixture):
base_url = 'os-server-groups'
def setUp(self):
super(Fixture, self).setUp()
server_groups = [
{
"members": [],
"metadata": {},
"id": "2cbd51f4-fafe-4cdb-801b-cf913a6f288b",
"policies": [],
"name": "ig1"
},
{
"members": [],
"metadata": {},
"id": "4473bb03-4370-4bfb-80d3-dc8cffc47d94",
"policies": ["anti-affinity"],
"name": "ig2"
},
{
"members": [],
"metadata": {"key": "value"},
"id": "31ab9bdb-55e1-4ac3-b094-97eeb1b65cc4",
"policies": [], "name": "ig3"
},
{
"members": ["2dccb4a1-02b9-482a-aa23-5799490d6f5d"],
"metadata": {},
"id": "4890bb03-7070-45fb-8453-d34556c87d94",
"policies": ["anti-affinity"],
"name": "ig2"
}
]
get_server_groups = {'server_groups': server_groups}
httpretty.register_uri(httpretty.GET, self.url(),
body=jsonutils.dumps(get_server_groups),
content_type='application/json')
server = server_groups[0]
server_json = jsonutils.dumps({'server_group': server})
def _register(method, *args):
httpretty.register_uri(method, self.url(*args), body=server_json)
_register(httpretty.POST)
_register(httpretty.POST, server['id'])
_register(httpretty.GET, server['id'])
_register(httpretty.PUT, server['id'])
_register(httpretty.POST, server['id'], '/action')
httpretty.register_uri(httpretty.DELETE, self.url(server['id']),
status=202)

View File

@ -92,11 +92,11 @@ class DeprecatedAuthPluginTest(utils.TestCase):
@mock.patch.object(pkg_resources, "iter_entry_points",
mock_iter_entry_points)
@mock.patch.object(requests.Session, "request", mock_request)
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
plugin = auth_plugin.DeprecatedAuthPlugin("fake")
cs = client.Client("username", "password", "project_id",
"auth_url/v2.0", auth_system="fake",
utils.AUTH_URL_V2, auth_system="fake",
auth_plugin=plugin)
cs.client.authenticate()
@ -121,12 +121,12 @@ class DeprecatedAuthPluginTest(utils.TestCase):
@mock.patch.object(pkg_resources, "iter_entry_points",
mock_iter_entry_points)
@mock.patch.object(requests.Session, "request", mock_request)
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
auth_plugin.discover_auth_systems()
plugin = auth_plugin.DeprecatedAuthPlugin("notexists")
cs = client.Client("username", "password", "project_id",
"auth_url/v2.0", auth_system="notexists",
utils.AUTH_URL_V2, auth_system="notexists",
auth_plugin=plugin)
self.assertRaises(exceptions.AuthSystemNotFound,
cs.client.authenticate)
@ -164,7 +164,7 @@ class DeprecatedAuthPluginTest(utils.TestCase):
@mock.patch.object(pkg_resources, "iter_entry_points",
mock_iter_entry_points)
@mock.patch.object(requests.Session, "request", mock_request)
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
plugin = auth_plugin.DeprecatedAuthPlugin("fakewithauthurl")
cs = client.Client("username", "password", "project_id",
@ -197,7 +197,7 @@ class DeprecatedAuthPluginTest(utils.TestCase):
class AuthPluginTest(utils.TestCase):
@mock.patch.object(requests.Session, "request")
@mock.patch.object(requests, "request")
@mock.patch.object(pkg_resources, "iter_entry_points")
def test_auth_system_success(self, mock_iter_entry_points, mock_request):
"""Test that we can authenticate using the auth system."""
@ -217,7 +217,7 @@ class AuthPluginTest(utils.TestCase):
auth_plugin.discover_auth_systems()
plugin = auth_plugin.load_plugin("fake")
cs = client.Client("username", "password", "project_id",
"auth_url/v2.0", auth_system="fake",
utils.AUTH_URL_V2, auth_system="fake",
auth_plugin=plugin)
cs.client.authenticate()

View File

@ -14,9 +14,12 @@
# under the License.
import logging
import mock
import requests
import fixtures
import novaclient.client
import novaclient.extension
import novaclient.tests.fakes as fakes
@ -27,6 +30,16 @@ import novaclient.v3.client
import json
class ClientConnectionPoolTest(utils.TestCase):
@mock.patch("novaclient.client.adapters.HTTPAdapter")
def test_get(self, mock_http_adapter):
mock_http_adapter.side_effect = lambda: mock.Mock()
pool = novaclient.client._ClientConnectionPool()
self.assertEqual(pool.get("abc"), pool.get("abc"))
self.assertNotEqual(pool.get("abc"), pool.get("def"))
class ClientTest(utils.TestCase):
def test_client_with_timeout(self):
@ -43,9 +56,9 @@ class ClientTest(utils.TestCase):
'x-server-management-url': 'blah.com',
'x-auth-token': 'blah',
}
with mock.patch('requests.Session.request', mock_request):
with mock.patch('requests.request', mock_request):
instance.authenticate()
requests.Session.request.assert_called_with(mock.ANY, mock.ANY,
requests.request.assert_called_with(mock.ANY, mock.ANY,
timeout=2,
headers=mock.ANY,
verify=mock.ANY)
@ -61,7 +74,7 @@ class ClientTest(utils.TestCase):
instance.version = 'v2.0'
mock_request = mock.Mock()
mock_request.side_effect = novaclient.exceptions.Unauthorized(401)
with mock.patch('requests.Session.request', mock_request):
with mock.patch('requests.request', mock_request):
try:
instance.get('/servers/detail')
except Exception:
@ -197,6 +210,26 @@ class ClientTest(utils.TestCase):
cs.authenticate()
self.assertTrue(mock_authenticate.called)
@mock.patch('novaclient.client.HTTPClient')
def test_contextmanager_v1_1(self, mock_http_client):
fake_client = mock.Mock()
mock_http_client.return_value = fake_client
with novaclient.v1_1.client.Client("user", "password", "project_id",
auth_url="foo/v2"):
pass
self.assertTrue(fake_client.open_session.called)
self.assertTrue(fake_client.close_session.called)
@mock.patch('novaclient.client.HTTPClient')
def test_contextmanager_v3(self, mock_http_client):
fake_client = mock.Mock()
mock_http_client.return_value = fake_client
with novaclient.v3.client.Client("user", "password", "project_id",
auth_url="foo/v2"):
pass
self.assertTrue(fake_client.open_session.called)
self.assertTrue(fake_client.close_session.called)
def test_get_password_simple(self):
cs = novaclient.client.HTTPClient("user", "password", "", "")
cs.password_func = mock.Mock()
@ -216,3 +249,110 @@ class ClientTest(utils.TestCase):
cs.password_func = mock.Mock()
self.assertEqual(cs._get_password(), "password")
self.assertFalse(cs.password_func.called)
def test_auth_url_rstrip_slash(self):
cs = novaclient.client.HTTPClient("user", "password", "project_id",
auth_url="foo/v2/")
self.assertEqual(cs.auth_url, "foo/v2")
def test_token_and_bypass_url(self):
cs = novaclient.client.HTTPClient(None, None, None,
auth_token="12345",
bypass_url="compute/v100/")
self.assertIsNone(cs.auth_url)
self.assertEqual(cs.auth_token, "12345")
self.assertEqual(cs.bypass_url, "compute/v100")
self.assertEqual(cs.management_url, "compute/v100")
@mock.patch("novaclient.client.requests.Session")
def test_session(self, mock_session):
fake_session = mock.Mock()
mock_session.return_value = fake_session
cs = novaclient.client.HTTPClient("user", None, "", "")
cs.open_session()
self.assertEqual(cs._session, fake_session)
cs.close_session()
self.assertIsNone(cs._session)
def test_session_connection_pool(self):
cs = novaclient.client.HTTPClient("user", None, "",
"", connection_pool=True)
cs.open_session()
self.assertIsNone(cs._session)
cs.close_session()
self.assertIsNone(cs._session)
def test_get_session(self):
cs = novaclient.client.HTTPClient("user", None, "", "")
self.assertIsNone(cs._get_session("http://nooooooooo.com"))
@mock.patch("novaclient.client.requests.Session")
def test_get_session_open_session(self, mock_session):
fake_session = mock.Mock()
mock_session.return_value = fake_session
cs = novaclient.client.HTTPClient("user", None, "", "")
cs.open_session()
self.assertEqual(fake_session, cs._get_session("http://example.com"))
@mock.patch("novaclient.client.requests.Session")
@mock.patch("novaclient.client._ClientConnectionPool")
def test_get_session_connection_pool(self, mock_pool, mock_session):
service_url = "http://example.com"
pool = mock.MagicMock()
pool.get.return_value = "http_adapter"
mock_pool.return_value = pool
cs = novaclient.client.HTTPClient("user", None, "",
"", connection_pool=True)
cs._current_url = "http://another.com"
session = cs._get_session(service_url)
self.assertEqual(session, mock_session.return_value)
pool.get.assert_called_once_with(service_url)
mock_session().mount.assert_called_once_with(service_url,
'http_adapter')
def test_init_without_connection_pool(self):
cs = novaclient.client.HTTPClient("user", None, "", "")
self.assertIsNone(cs._connection_pool)
@mock.patch("novaclient.client._ClientConnectionPool")
def test_init_with_proper_connection_pool(self, mock_pool):
fake_pool = mock.Mock()
mock_pool.return_value = fake_pool
cs = novaclient.client.HTTPClient("user", None, "",
connection_pool=True)
self.assertEqual(cs._connection_pool, fake_pool)
def test_log_req(self):
self.logger = self.useFixture(
fixtures.FakeLogger(
format="%(message)s",
level=logging.DEBUG,
nuke_handlers=True
)
)
cs = novaclient.client.HTTPClient("user", None, "",
connection_pool=True)
cs.http_log_debug = True
cs.http_log_req('GET', '/foo', {'headers': {}})
cs.http_log_req('GET', '/foo', {'headers':
{'X-Auth-Token': 'totally_bogus'}
})
cs.http_log_req('GET', '/foo', {'headers':
{'X-Foo': 'bar',
'X-Auth-Token': 'totally_bogus'}
})
output = self.logger.output.split('\n')
self.assertIn("REQ: curl -i '/foo' -X GET", output)
self.assertIn(
"REQ: curl -i '/foo' -X GET -H "
'"X-Auth-Token: {SHA1}b42162b6ffdbd7c3c37b7c95b7ba9f51dda0236d"',
output)
self.assertIn(
"REQ: curl -i '/foo' -X GET -H "
'"X-Auth-Token: {SHA1}b42162b6ffdbd7c3c37b7c95b7ba9f51dda0236d"'
' -H "X-Foo: bar"',
output)

View File

@ -13,6 +13,7 @@
import mock
import requests
import six
from novaclient import client
from novaclient import exceptions
@ -37,10 +38,17 @@ bad_req_response = utils.TestResponse({
})
bad_req_mock_request = mock.Mock(return_value=(bad_req_response))
unknown_error_response = utils.TestResponse({
"status_code": 503,
"text": '',
})
unknown_error_mock_request = mock.Mock(return_value=unknown_error_response)
def get_client():
cl = client.HTTPClient("username", "password",
"project_id", "auth_test")
"project_id",
utils.AUTH_URL_V2)
return cl
@ -56,7 +64,7 @@ class ClientTest(utils.TestCase):
def test_get(self):
cl = get_authed_client()
@mock.patch.object(requests.Session, "request", mock_request)
@mock.patch.object(requests, "request", mock_request)
@mock.patch('time.time', mock.Mock(return_value=1234))
def test_get_call():
resp, body = cl.get("/hi")
@ -78,7 +86,7 @@ class ClientTest(utils.TestCase):
def test_post(self):
cl = get_authed_client()
@mock.patch.object(requests.Session, "request", mock_request)
@mock.patch.object(requests, "request", mock_request)
def test_post_call():
cl.post("/hi", body=[1, 2, 3])
headers = {
@ -110,7 +118,7 @@ class ClientTest(utils.TestCase):
def test_connection_refused(self):
cl = get_client()
@mock.patch.object(requests.Session, "request", refused_mock_request)
@mock.patch.object(requests, "request", refused_mock_request)
def test_refused_call():
self.assertRaises(exceptions.ConnectionRefused, cl.get, "/hi")
@ -119,7 +127,7 @@ class ClientTest(utils.TestCase):
def test_bad_request(self):
cl = get_client()
@mock.patch.object(requests.Session, "request", bad_req_mock_request)
@mock.patch.object(requests, "request", bad_req_mock_request)
def test_refused_call():
self.assertRaises(exceptions.BadRequest, cl.get, "/hi")
@ -133,3 +141,16 @@ class ClientTest(utils.TestCase):
cl2 = client.HTTPClient("username", "password", "project_id",
"auth_test", http_log_debug=True)
self.assertEqual(len(cl2._logger.handlers), 1)
@mock.patch.object(requests, 'request', unknown_error_mock_request)
def test_unknown_server_error(self):
cl = get_client()
# This would be cleaner with the context manager version of
# assertRaises or assertRaisesRegexp, but both only appeared in
# Python 2.7 and testtools doesn't match that implementation yet
try:
cl.get('/hi')
except exceptions.ClientException as exc:
self.assertIn('Unknown Error', six.text_type(exc))
else:
self.fail('Expected exceptions.ClientException')

View File

@ -32,7 +32,7 @@ FAKE_ENV = {'OS_USERNAME': 'username',
'OS_TENANT_NAME': 'tenant_name',
'OS_AUTH_URL': 'http://no.where'}
FAKE_ENV2 = {'OS_USERNAME': 'username',
FAKE_ENV2 = {'OS_USER_ID': 'user_id',
'OS_PASSWORD': 'password',
'OS_TENANT_ID': 'tenant_id',
'OS_AUTH_URL': 'http://no.where'}
@ -133,25 +133,38 @@ class ShellTest(utils.TestCase):
matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE))
def test_no_username(self):
required = ('You must provide a username'
' via either --os-username or env[OS_USERNAME]',)
required = ('You must provide a username or user id'
' via --os-username, --os-user-id,'
' env[OS_USERNAME] or env[OS_USER_ID]')
self.make_env(exclude='OS_USERNAME')
try:
self.shell('list')
except exceptions.CommandError as message:
self.assertEqual(required, message.args)
self.assertEqual(required, message.args[0])
else:
self.fail('CommandError not raised')
def test_no_user_id(self):
required = ('You must provide a username or user id'
' via --os-username, --os-user-id,'
' env[OS_USERNAME] or env[OS_USER_ID]')
self.make_env(exclude='OS_USER_ID', fake_env=FAKE_ENV2)
try:
self.shell('list')
except exceptions.CommandError as message:
self.assertEqual(required, message.args[0])
else:
self.fail('CommandError not raised')
def test_no_tenant_name(self):
required = ('You must provide a tenant name or tenant id'
' via --os-tenant-name, --os-tenant-id,'
' env[OS_TENANT_NAME] or env[OS_TENANT_ID]',)
' env[OS_TENANT_NAME] or env[OS_TENANT_ID]')
self.make_env(exclude='OS_TENANT_NAME')
try:
self.shell('list')
except exceptions.CommandError as message:
self.assertEqual(required, message.args)
self.assertEqual(required, message.args[0])
else:
self.fail('CommandError not raised')
@ -244,3 +257,17 @@ class ShellTest(utils.TestCase):
@mock.patch('novaclient.client.Client')
def test_v_unknown_service_type(self, mock_client):
self._test_service_type('unknown', 'compute', mock_client)
@mock.patch('sys.argv', ['nova'])
@mock.patch('sys.stdout', six.StringIO())
@mock.patch('sys.stderr', six.StringIO())
def test_main_noargs(self):
# Ensure that main works with no command-line arguments
try:
novaclient.shell.main()
except SystemExit:
self.fail('Unexpected SystemExit')
# We expect the normal usage as a result
self.assertIn('Command-line interface to the OpenStack Nova API',
sys.stdout.getvalue())

View File

@ -301,3 +301,23 @@ class ValidationsTestCase(test_utils.TestCase):
self.fail("Invalid key passed validation: %s" % key)
except exceptions.CommandError as ce:
self.assertTrue(key in str(ce))
class ResourceManagerExtraKwargsHookTestCase(test_utils.TestCase):
def test_get_resource_manager_extra_kwargs_hook_test(self):
do_foo = mock.MagicMock()
def hook1(args):
return {'kwarg1': 'v_hook1'}
def hook2(args):
return {'kwarg1': 'v_hook2'}
do_foo.resource_manager_kwargs_hooks = [hook1, hook2]
args = {}
exc = self.assertRaises(exceptions.NoUniqueMatch,
utils.get_resource_manager_extra_kwargs,
do_foo,
args)
except_error = ("Hook 'hook2' is attempting to redefine "
"attributes")
self.assertIn(except_error, six.text_type(exc))

View File

@ -14,9 +14,18 @@
import os
import fixtures
import httpretty
import requests
import six
import testscenarios
import testtools
from novaclient.openstack.common import jsonutils
AUTH_URL = "http://localhost:5002/auth_url"
AUTH_URL_V1 = "http://localhost:5002/auth_url/v1.0"
AUTH_URL_V2 = "http://localhost:5002/auth_url/v2.0"
class TestCase(testtools.TestCase):
TEST_REQUEST_BASE = {
@ -35,6 +44,40 @@ class TestCase(testtools.TestCase):
self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
class FixturedTestCase(testscenarios.TestWithScenarios, TestCase):
client_fixture_class = None
data_fixture_class = None
def setUp(self):
super(FixturedTestCase, self).setUp()
httpretty.reset()
self.data_fixture = None
self.client_fixture = None
self.cs = None
if self.client_fixture_class:
self.client_fixture = self.useFixture(self.client_fixture_class())
self.cs = self.client_fixture.client
if self.data_fixture_class:
self.data_fixture = self.useFixture(self.data_fixture_class())
def assert_called(self, method, path, body=None):
self.assertEqual(httpretty.last_request().method, method)
self.assertEqual(httpretty.last_request().path, path)
if body:
req_data = httpretty.last_request().body
if isinstance(req_data, six.binary_type):
req_data = req_data.decode('utf-8')
if not isinstance(body, six.string_types):
# json load if the input body to match against is not a string
req_data = jsonutils.loads(req_data)
self.assertEqual(req_data, body)
class TestResponse(requests.Response):
"""
Class used to wrap requests.Response and provide some

View File

@ -32,10 +32,10 @@ cs = fakes.FakeClient(extensions=extensions)
class AssistedVolumeSnapshotsTestCase(utils.TestCase):
def test_create_snap(self):
res = cs.assisted_volume_snapshots.create('1', {})
cs.assisted_volume_snapshots.create('1', {})
cs.assert_called('POST', '/os-assisted-volume-snapshots')
def test_delete_snap(self):
res = cs.assisted_volume_snapshots.delete('x', {})
cs.assisted_volume_snapshots.delete('x', {})
cs.assert_called('DELETE',
'/os-assisted-volume-snapshots/x?delete_info={}')

View File

@ -369,7 +369,10 @@ class FakeHTTPClient(base_client.HTTPClient):
if 'personality' in body['server']:
for pfile in body['server']['personality']:
fakes.assert_has_keys(pfile, required=['path', 'contents'])
return (202, {}, self.get_servers_1234()[2])
if body['server']['name'] == 'some-bad-server':
return (202, {}, self.get_servers_1235()[2])
else:
return (202, {}, self.get_servers_1234()[2])
def post_os_volumes_boot(self, body, **kw):
assert set(body.keys()) <= set(['server', 'os:scheduler_hints'])
@ -392,6 +395,13 @@ class FakeHTTPClient(base_client.HTTPClient):
r = {'server': self.get_servers_detail()[2]['servers'][0]}
return (200, {}, r)
def get_servers_1235(self, **kw):
r = {'server': self.get_servers_detail()[2]['servers'][0]}
r['server']['id'] = 1235
r['server']['status'] = 'error'
r['server']['fault'] = {'message': 'something went wrong!'}
return (200, {}, r)
def get_servers_5678(self, **kw):
r = {'server': self.get_servers_detail()[2]['servers'][1]}
return (200, {}, r)
@ -405,6 +415,12 @@ class FakeHTTPClient(base_client.HTTPClient):
fakes.assert_has_keys(body['server'], optional=['name', 'adminPass'])
return (204, {}, body)
def delete_os_server_groups_12345(self, **kw):
return (202, {}, None)
def delete_os_server_groups_56789(self, **kw):
return (202, {}, None)
def delete_servers_1234(self, **kw):
return (202, {}, None)
@ -525,11 +541,11 @@ class FakeHTTPClient(base_client.HTTPClient):
assert list(body[action]) == ['type']
assert body[action]['type'] in ['HARD', 'SOFT']
elif action == 'rebuild':
keys = list(body[action])
if 'adminPass' in keys:
keys.remove('adminPass')
assert 'imageRef' in keys
body = body[action]
adminPass = body.get('adminPass', 'randompassword')
assert 'imageRef' in body
_body = self.get_servers_1234()[2]
_body['server']['adminPass'] = adminPass
elif action == 'resize':
keys = body[action].keys()
assert 'flavorRef' in keys
@ -625,6 +641,9 @@ class FakeHTTPClient(base_client.HTTPClient):
raise AssertionError("Unexpected server action: %s" % action)
return (resp, _headers, _body)
def post_servers_5678_action(self, body, **kw):
return self.post_servers_1234_action(body, **kw)
#
# Cloudpipe
#
@ -734,8 +753,11 @@ class FakeHTTPClient(base_client.HTTPClient):
def get_flavors_512_MB_Server(self, **kw):
raise exceptions.NotFound('404')
def get_flavors_128_MB_Server(self, **kw):
raise exceptions.NotFound('404')
def get_flavors_aa1(self, **kw):
# Aplhanumeric flavor id are allowed.
# Alphanumeric flavor id are allowed.
return (
200,
{},
@ -1003,12 +1025,24 @@ class FakeHTTPClient(base_client.HTTPClient):
# Keypairs
#
def get_os_keypairs_test(self, *kw):
return (200, {}, {'keypair': self.get_os_keypairs()[2]['keypairs'][0]})
return (200, {}, {'keypair':
self.get_os_keypairs()[2]['keypairs'][0]['keypair']})
def get_os_keypairs(self, *kw):
return (200, {}, {"keypairs": [
{'fingerprint': 'FAKE_KEYPAIR', 'name': 'test'}
]})
{"keypair": {
"public_key": "FAKE_SSH_RSA",
"private_key": "FAKE_PRIVATE_KEY",
"user_id":
"81e373b596d6466e99c4896826abaa46",
"name": "test",
"deleted": False,
"created_at": "2014-04-19T02:16:44.000000",
"updated_at": "2014-04-19T10:12:3.000000",
"figerprint": "FAKE_KEYPAIR",
"deleted_at": None,
"id": 4}
}]})
def delete_os_keypairs_test(self, **kw):
return (202, {}, None)
@ -1017,7 +1051,7 @@ class FakeHTTPClient(base_client.HTTPClient):
assert list(body) == ['keypair']
fakes.assert_has_keys(body['keypair'],
required=['name'])
r = {'keypair': self.get_os_keypairs()[2]['keypairs'][0]}
r = {'keypair': self.get_os_keypairs()[2]['keypairs'][0]['keypair']}
return (202, {}, r)
#
@ -1486,6 +1520,9 @@ class FakeHTTPClient(base_client.HTTPClient):
'status': 'disabled',
'disabled_reason': body['disabled_reason']}})
def delete_os_services_1(self, **kw):
return (204, {}, None)
#
# Fixed IPs
#
@ -1996,3 +2033,48 @@ class FakeHTTPClient(base_client.HTTPClient):
return (200, {}, {'events': [
{'name': 'network-changed',
'server_uuid': '1234'}]})
#
# Server Groups
#
def get_os_server_groups(self, *kw):
return (200, {},
{"server_groups": [
{"members": [], "metadata": {},
"id": "2cbd51f4-fafe-4cdb-801b-cf913a6f288b",
"policies": [], "name": "ig1"},
{"members": [], "metadata": {},
"id": "4473bb03-4370-4bfb-80d3-dc8cffc47d94",
"policies": ["anti-affinity"], "name": "ig2"},
{"members": [], "metadata": {"key": "value"},
"id": "31ab9bdb-55e1-4ac3-b094-97eeb1b65cc4",
"policies": [], "name": "ig3"},
{"members": ["2dccb4a1-02b9-482a-aa23-5799490d6f5d"],
"metadata": {},
"id": "4890bb03-7070-45fb-8453-d34556c87d94",
"policies": ["anti-affinity"], "name": "ig2"}]})
def _return_server_group(self):
r = {'server_group':
self.get_os_server_groups()[2]['server_groups'][0]}
return (200, {}, r)
def post_os_server_groups(self, body, **kw):
return self._return_server_group()
def get_os_server_groups_2cbd51f4_fafe_4cdb_801b_cf913a6f288b(self,
**kw):
return self._return_server_group()
def put_os_server_groups_2cbd51f4_fafe_4cdb_801b_cf913a6f288b(self,
**kw):
return self._return_server_group()
def post_os_server_groups_2cbd51f4_fafe_4cdb_801b_cf913a6f288b_action(
self, body, **kw):
return self._return_server_group()
def delete_os_server_groups_2cbd51f4_fafe_4cdb_801b_cf913a6f288b(
self, **kw):
return (202, {}, None)

View File

@ -13,35 +13,64 @@
# License for the specific language governing permissions and limitations
# under the License.
import httpretty
from novaclient.openstack.common import jsonutils
from novaclient.tests.fixture_data import agents as data
from novaclient.tests.fixture_data import client
from novaclient.tests import utils
from novaclient.tests.v1_1 import fakes
from novaclient.v1_1 import agents
class AgentsTest(utils.TestCase):
def setUp(self):
super(AgentsTest, self).setUp()
self.cs = self._get_fake_client()
self.agent_type = self._get_agent_type()
class AgentsTest(utils.FixturedTestCase):
def _get_fake_client(self):
return fakes.FakeClient()
data_fixture_class = data.Fixture
def _get_agent_type(self):
return agents.Agent
scenarios = [('original', {'client_fixture_class': client.V1}),
('session', {'client_fixture_class': client.SessionV1})]
def stub_hypervisors(self, hypervisor='kvm'):
get_os_agents = {'agents':
[
{
'hypervisor': hypervisor,
'os': 'win',
'architecture': 'x86',
'version': '7.0',
'url': 'xxx://xxxx/xxx/xxx',
'md5hash': 'add6bb58e139be103324d04d82d8f545',
'id': 1
},
{
'hypervisor': hypervisor,
'os': 'linux',
'architecture': 'x86',
'version': '16.0',
'url': 'xxx://xxxx/xxx/xxx1',
'md5hash': 'add6bb58e139be103324d04d82d8f546',
'id': 2
},
]
}
httpretty.register_uri(httpretty.GET, self.data_fixture.url(),
body=jsonutils.dumps(get_os_agents),
content_type='application/json')
def test_list_agents(self):
self.stub_hypervisors()
ags = self.cs.agents.list()
self.cs.assert_called('GET', '/os-agents')
self.assert_called('GET', '/os-agents')
for a in ags:
self.assertIsInstance(a, self.agent_type)
self.assertIsInstance(a, agents.Agent)
self.assertEqual(a.hypervisor, 'kvm')
def test_list_agents_with_hypervisor(self):
self.stub_hypervisors('xen')
ags = self.cs.agents.list('xen')
self.cs.assert_called('GET', '/os-agents?hypervisor=xen')
self.assert_called('GET', '/os-agents?hypervisor=xen')
for a in ags:
self.assertIsInstance(a, self.agent_type)
self.assertIsInstance(a, agents.Agent)
self.assertEqual(a.hypervisor, 'xen')
def test_agents_create(self):
@ -56,12 +85,12 @@ class AgentsTest(utils.TestCase):
'version': '7.0',
'architecture': 'x86',
'os': 'win'}}
self.cs.assert_called('POST', '/os-agents', body)
self.assert_called('POST', '/os-agents', body)
self.assertEqual(1, ag._info.copy()['id'])
def test_agents_delete(self):
self.cs.agents.delete('1')
self.cs.assert_called('DELETE', '/os-agents/1')
self.assert_called('DELETE', '/os-agents/1')
def _build_example_update_body(self):
return {"para": {
@ -74,5 +103,5 @@ class AgentsTest(utils.TestCase):
'/yyy/yyyy/yyyy',
'add6bb58e139be103324d04d82d8f546')
body = self._build_example_update_body()
self.cs.assert_called('PUT', '/os-agents/1', body)
self.assert_called('PUT', '/os-agents/1', body)
self.assertEqual(1, ag.id)

View File

@ -13,51 +13,47 @@
# License for the specific language governing permissions and limitations
# under the License.
from novaclient.tests.fixture_data import aggregates as data
from novaclient.tests.fixture_data import client
from novaclient.tests import utils
from novaclient.tests.v1_1 import fakes
from novaclient.v1_1 import aggregates
class AggregatesTest(utils.TestCase):
def setUp(self):
super(AggregatesTest, self).setUp()
self.cs = self._get_fake_client()
self.aggregate_type = self._get_aggregate_type()
class AggregatesTest(utils.FixturedTestCase):
def _get_fake_client(self):
return fakes.FakeClient()
data_fixture_class = data.Fixture
def _get_aggregate_type(self):
return aggregates.Aggregate
scenarios = [('original', {'client_fixture_class': client.V1}),
('session', {'client_fixture_class': client.SessionV1})]
def test_list_aggregates(self):
result = self.cs.aggregates.list()
self.cs.assert_called('GET', '/os-aggregates')
self.assert_called('GET', '/os-aggregates')
for aggregate in result:
self.assertIsInstance(aggregate, aggregates.Aggregate)
def test_create_aggregate(self):
body = {"aggregate": {"name": "test", "availability_zone": "nova1"}}
aggregate = self.cs.aggregates.create("test", "nova1")
self.cs.assert_called('POST', '/os-aggregates', body)
self.assert_called('POST', '/os-aggregates', body)
self.assertIsInstance(aggregate, aggregates.Aggregate)
def test_get(self):
aggregate = self.cs.aggregates.get("1")
self.cs.assert_called('GET', '/os-aggregates/1')
self.assert_called('GET', '/os-aggregates/1')
self.assertIsInstance(aggregate, aggregates.Aggregate)
aggregate2 = self.cs.aggregates.get(aggregate)
self.cs.assert_called('GET', '/os-aggregates/1')
self.assert_called('GET', '/os-aggregates/1')
self.assertIsInstance(aggregate2, aggregates.Aggregate)
def test_get_details(self):
aggregate = self.cs.aggregates.get_details("1")
self.cs.assert_called('GET', '/os-aggregates/1')
self.assert_called('GET', '/os-aggregates/1')
self.assertIsInstance(aggregate, aggregates.Aggregate)
aggregate2 = self.cs.aggregates.get_details(aggregate)
self.cs.assert_called('GET', '/os-aggregates/1')
self.assert_called('GET', '/os-aggregates/1')
self.assertIsInstance(aggregate2, aggregates.Aggregate)
def test_update(self):
@ -66,11 +62,11 @@ class AggregatesTest(utils.TestCase):
body = {"aggregate": values}
result1 = aggregate.update(values)
self.cs.assert_called('PUT', '/os-aggregates/1', body)
self.assert_called('PUT', '/os-aggregates/1', body)
self.assertIsInstance(result1, aggregates.Aggregate)
result2 = self.cs.aggregates.update(2, values)
self.cs.assert_called('PUT', '/os-aggregates/2', body)
self.assert_called('PUT', '/os-aggregates/2', body)
self.assertIsInstance(result2, aggregates.Aggregate)
def test_update_with_availability_zone(self):
@ -79,7 +75,7 @@ class AggregatesTest(utils.TestCase):
body = {"aggregate": values}
result3 = self.cs.aggregates.update(aggregate, values)
self.cs.assert_called('PUT', '/os-aggregates/1', body)
self.assert_called('PUT', '/os-aggregates/1', body)
self.assertIsInstance(result3, aggregates.Aggregate)
def test_add_host(self):
@ -88,15 +84,15 @@ class AggregatesTest(utils.TestCase):
body = {"add_host": {"host": "host1"}}
result1 = aggregate.add_host(host)
self.cs.assert_called('POST', '/os-aggregates/1/action', body)
self.assert_called('POST', '/os-aggregates/1/action', body)
self.assertIsInstance(result1, aggregates.Aggregate)
result2 = self.cs.aggregates.add_host("2", host)
self.cs.assert_called('POST', '/os-aggregates/2/action', body)
self.assert_called('POST', '/os-aggregates/2/action', body)
self.assertIsInstance(result2, aggregates.Aggregate)
result3 = self.cs.aggregates.add_host(aggregate, host)
self.cs.assert_called('POST', '/os-aggregates/1/action', body)
self.assert_called('POST', '/os-aggregates/1/action', body)
self.assertIsInstance(result3, aggregates.Aggregate)
def test_remove_host(self):
@ -105,15 +101,15 @@ class AggregatesTest(utils.TestCase):
body = {"remove_host": {"host": "host1"}}
result1 = aggregate.remove_host(host)
self.cs.assert_called('POST', '/os-aggregates/1/action', body)
self.assert_called('POST', '/os-aggregates/1/action', body)
self.assertIsInstance(result1, aggregates.Aggregate)
result2 = self.cs.aggregates.remove_host("2", host)
self.cs.assert_called('POST', '/os-aggregates/2/action', body)
self.assert_called('POST', '/os-aggregates/2/action', body)
self.assertIsInstance(result2, aggregates.Aggregate)
result3 = self.cs.aggregates.remove_host(aggregate, host)
self.cs.assert_called('POST', '/os-aggregates/1/action', body)
self.assert_called('POST', '/os-aggregates/1/action', body)
self.assertIsInstance(result3, aggregates.Aggregate)
def test_set_metadata(self):
@ -122,24 +118,24 @@ class AggregatesTest(utils.TestCase):
body = {"set_metadata": {"metadata": metadata}}
result1 = aggregate.set_metadata(metadata)
self.cs.assert_called('POST', '/os-aggregates/1/action', body)
self.assert_called('POST', '/os-aggregates/1/action', body)
self.assertIsInstance(result1, aggregates.Aggregate)
result2 = self.cs.aggregates.set_metadata(2, metadata)
self.cs.assert_called('POST', '/os-aggregates/2/action', body)
self.assert_called('POST', '/os-aggregates/2/action', body)
self.assertIsInstance(result2, aggregates.Aggregate)
result3 = self.cs.aggregates.set_metadata(aggregate, metadata)
self.cs.assert_called('POST', '/os-aggregates/1/action', body)
self.assert_called('POST', '/os-aggregates/1/action', body)
self.assertIsInstance(result3, aggregates.Aggregate)
def test_delete_aggregate(self):
aggregate = self.cs.aggregates.list()[0]
aggregate.delete()
self.cs.assert_called('DELETE', '/os-aggregates/1')
self.assert_called('DELETE', '/os-aggregates/1')
self.cs.aggregates.delete('1')
self.cs.assert_called('DELETE', '/os-aggregates/1')
self.assert_called('DELETE', '/os-aggregates/1')
self.cs.aggregates.delete(aggregate)
self.cs.assert_called('DELETE', '/os-aggregates/1')
self.assert_called('DELETE', '/os-aggregates/1')

View File

@ -25,7 +25,7 @@ from novaclient.v1_1 import client
class AuthenticateAgainstKeystoneTests(utils.TestCase):
def test_authenticate_success(self):
cs = client.Client("username", "password", "project_id",
"auth_url/v2.0", service_type='compute')
utils.AUTH_URL_V2, service_type='compute')
resp = {
"access": {
"token": {
@ -57,7 +57,7 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
mock_request = mock.Mock(return_value=(auth_response))
@mock.patch.object(requests.Session, "request", mock_request)
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
cs.client.authenticate()
headers = {
@ -94,7 +94,7 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
def test_authenticate_failure(self):
cs = client.Client("username", "password", "project_id",
"auth_url/v2.0")
utils.AUTH_URL_V2)
resp = {"unauthorized": {"message": "Unauthorized", "code": "401"}}
auth_response = utils.TestResponse({
"status_code": 401,
@ -111,7 +111,7 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
def test_v1_auth_redirect(self):
cs = client.Client("username", "password", "project_id",
"auth_url/v1.0", service_type='compute')
utils.AUTH_URL_V1, service_type='compute')
dict_correct_response = {
"access": {
"token": {
@ -160,7 +160,7 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
mock_request = mock.Mock(side_effect=side_effect)
@mock.patch.object(requests.Session, "request", mock_request)
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
cs.client.authenticate()
headers = {
@ -199,7 +199,7 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
def test_v2_auth_redirect(self):
cs = client.Client("username", "password", "project_id",
"auth_url/v2.0", service_type='compute')
utils.AUTH_URL_V2, service_type='compute')
dict_correct_response = {
"access": {
"token": {
@ -248,7 +248,7 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
mock_request = mock.Mock(side_effect=side_effect)
@mock.patch.object(requests.Session, "request", mock_request)
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
cs.client.authenticate()
headers = {
@ -287,7 +287,7 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
def test_ambiguous_endpoints(self):
cs = client.Client("username", "password", "project_id",
"auth_url/v2.0", service_type='compute')
utils.AUTH_URL_V2, service_type='compute')
resp = {
"access": {
"token": {
@ -340,7 +340,7 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
def test_authenticate_with_token_success(self):
cs = client.Client("username", None, "project_id",
"auth_url/v2.0", service_type='compute')
utils.AUTH_URL_V2, service_type='compute')
cs.client.auth_token = "FAKE_ID"
resp = {
"access": {
@ -373,7 +373,7 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
mock_request = mock.Mock(return_value=(auth_response))
with mock.patch.object(requests.Session, "request", mock_request):
with mock.patch.object(requests, "request", mock_request):
cs.client.authenticate()
headers = {
'User-Agent': cs.client.USER_AGENT,
@ -405,7 +405,7 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
self.assertEqual(cs.client.auth_token, token_id)
def test_authenticate_with_token_failure(self):
cs = client.Client("username", None, "project_id", "auth_url/v2.0")
cs = client.Client("username", None, "project_id", utils.AUTH_URL_V2)
cs.client.auth_token = "FAKE_ID"
resp = {"unauthorized": {"message": "Unauthorized", "code": "401"}}
auth_response = utils.TestResponse({
@ -421,7 +421,8 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
class AuthenticationTests(utils.TestCase):
def test_authenticate_success(self):
cs = client.Client("username", "password", "project_id", "auth_url")
cs = client.Client("username", "password",
"project_id", utils.AUTH_URL)
management_url = 'https://localhost/v1.1/443470'
auth_response = utils.TestResponse({
'status_code': 204,
@ -432,7 +433,7 @@ class AuthenticationTests(utils.TestCase):
})
mock_request = mock.Mock(return_value=(auth_response))
@mock.patch.object(requests.Session, "request", mock_request)
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
cs.client.authenticate()
headers = {
@ -456,18 +457,20 @@ class AuthenticationTests(utils.TestCase):
test_auth_call()
def test_authenticate_failure(self):
cs = client.Client("username", "password", "project_id", "auth_url")
cs = client.Client("username", "password",
"project_id", utils.AUTH_URL)
auth_response = utils.TestResponse({'status_code': 401})
mock_request = mock.Mock(return_value=(auth_response))
@mock.patch.object(requests.Session, "request", mock_request)
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
self.assertRaises(exceptions.Unauthorized, cs.client.authenticate)
test_auth_call()
def test_auth_automatic(self):
cs = client.Client("username", "password", "project_id", "auth_url")
cs = client.Client("username", "password",
"project_id", utils.AUTH_URL)
http_client = cs.client
http_client.management_url = ''
mock_request = mock.Mock(return_value=(None, None))
@ -482,7 +485,8 @@ class AuthenticationTests(utils.TestCase):
test_auth_call()
def test_auth_manual(self):
cs = client.Client("username", "password", "project_id", "auth_url")
cs = client.Client("username", "password",
"project_id", utils.AUTH_URL)
@mock.patch.object(cs.client, 'authenticate')
def test_auth_call(m):

View File

@ -16,24 +16,26 @@
import six
from novaclient.tests.fixture_data import availability_zones as data
from novaclient.tests.fixture_data import client
from novaclient.tests import utils
from novaclient.tests.v1_1 import fakes
from novaclient.v1_1 import availability_zones
class AvailabilityZoneTest(utils.TestCase):
class AvailabilityZoneTest(utils.FixturedTestCase):
# NOTE(cyeoh): import shell here so the V3 version of
# this class can inherit off the v3 version of shell
from novaclient.v1_1 import shell # noqa
data_fixture_class = data.V1
scenarios = [('original', {'client_fixture_class': client.V1}),
('session', {'client_fixture_class': client.SessionV1})]
def setUp(self):
super(AvailabilityZoneTest, self).setUp()
self.cs = self._get_fake_client()
self.availability_zone_type = self._get_availability_zone_type()
def _get_fake_client(self):
return fakes.FakeClient()
def _get_availability_zone_type(self):
return availability_zones.AvailabilityZone
@ -43,7 +45,7 @@ class AvailabilityZoneTest(utils.TestCase):
def test_list_availability_zone(self):
zones = self.cs.availability_zones.list(detailed=False)
self.cs.assert_called('GET', '/os-availability-zone')
self.assert_called('GET', '/os-availability-zone')
for zone in zones:
self.assertIsInstance(zone, self.availability_zone_type)
@ -63,7 +65,7 @@ class AvailabilityZoneTest(utils.TestCase):
def test_detail_availability_zone(self):
zones = self.cs.availability_zones.list(detailed=True)
self.cs.assert_called('GET', '/os-availability-zone/detail')
self.assert_called('GET', '/os-availability-zone/detail')
for zone in zones:
self.assertIsInstance(zone, self.availability_zone_type)

View File

@ -11,29 +11,26 @@
# License for the specific language governing permissions and limitations
# under the License.
from novaclient.tests.fixture_data import certs as data
from novaclient.tests.fixture_data import client
from novaclient.tests import utils
from novaclient.tests.v1_1 import fakes
from novaclient.v1_1 import certs
class CertsTest(utils.TestCase):
def setUp(self):
super(CertsTest, self).setUp()
self.cs = self._get_fake_client()
self.cert_type = self._get_cert_type()
class CertsTest(utils.FixturedTestCase):
def _get_fake_client(self):
return fakes.FakeClient()
data_fixture_class = data.Fixture
cert_type = certs.Certificate
def _get_cert_type(self):
return certs.Certificate
scenarios = [('original', {'client_fixture_class': client.V1}),
('session', {'client_fixture_class': client.SessionV1})]
def test_create_cert(self):
cert = self.cs.certs.create()
self.cs.assert_called('POST', '/os-certificates')
self.assertIsInstance(cert, certs.Certificate)
self.assert_called('POST', '/os-certificates')
self.assertIsInstance(cert, self.cert_type)
def test_get_root_cert(self):
cert = self.cs.certs.get()
self.cs.assert_called('GET', '/os-certificates/root')
self.assertIsInstance(cert, certs.Certificate)
self.assert_called('GET', '/os-certificates/root')
self.assertIsInstance(cert, self.cert_type)

View File

@ -11,30 +11,35 @@
# License for the specific language governing permissions and limitations
# under the License.
import six
from novaclient.tests.fixture_data import client
from novaclient.tests.fixture_data import cloudpipe as data
from novaclient.tests import utils
from novaclient.tests.v1_1 import fakes
from novaclient.v1_1 import cloudpipe
cs = fakes.FakeClient()
class CloudpipeTest(utils.FixturedTestCase):
data_fixture_class = data.Fixture
class CloudpipeTest(utils.TestCase):
scenarios = [('original', {'client_fixture_class': client.V1}),
('session', {'client_fixture_class': client.SessionV1})]
def test_list_cloudpipes(self):
cp = cs.cloudpipe.list()
cs.assert_called('GET', '/os-cloudpipe')
cp = self.cs.cloudpipe.list()
self.assert_called('GET', '/os-cloudpipe')
[self.assertIsInstance(c, cloudpipe.Cloudpipe) for c in cp]
def test_create(self):
project = "test"
cp = cs.cloudpipe.create(project)
cp = self.cs.cloudpipe.create(project)
body = {'cloudpipe': {'project_id': project}}
cs.assert_called('POST', '/os-cloudpipe', body)
self.assertIsInstance(cp, str)
self.assert_called('POST', '/os-cloudpipe', body)
self.assertIsInstance(cp, six.string_types)
def test_update(self):
cs.cloudpipe.update("192.168.1.1", 2345)
self.cs.cloudpipe.update("192.168.1.1", 2345)
body = {'configure_project': {'vpn_ip': "192.168.1.1",
'vpn_port': 2345}}
cs.assert_called('PUT', '/os-cloudpipe/configure-project', body)
self.assert_called('PUT', '/os-cloudpipe/configure-project', body)

View File

@ -13,17 +13,21 @@
# License for the specific language governing permissions and limitations
# under the License.
from novaclient.tests.fixture_data import client
from novaclient.tests.fixture_data import fixedips as data
from novaclient.tests import utils
from novaclient.tests.v1_1 import fakes
cs = fakes.FakeClient()
class FixedIpsTest(utils.TestCase):
class FixedIpsTest(utils.FixturedTestCase):
data_fixture_class = data.Fixture
scenarios = [('original', {'client_fixture_class': client.V1}),
('session', {'client_fixture_class': client.SessionV1})]
def test_get_fixed_ip(self):
info = cs.fixed_ips.get(fixed_ip='192.168.1.1')
cs.assert_called('GET', '/os-fixed-ips/192.168.1.1')
info = self.cs.fixed_ips.get(fixed_ip='192.168.1.1')
self.assert_called('GET', '/os-fixed-ips/192.168.1.1')
self.assertEqual(info.cidr, '192.168.1.0/24')
self.assertEqual(info.address, '192.168.1.1')
self.assertEqual(info.hostname, 'foo')
@ -31,10 +35,10 @@ class FixedIpsTest(utils.TestCase):
def test_reserve_fixed_ip(self):
body = {"reserve": None}
res = cs.fixed_ips.reserve(fixed_ip='192.168.1.1')
cs.assert_called('POST', '/os-fixed-ips/192.168.1.1/action', body)
self.cs.fixed_ips.reserve(fixed_ip='192.168.1.1')
self.assert_called('POST', '/os-fixed-ips/192.168.1.1/action', body)
def test_unreserve_fixed_ip(self):
body = {"unreserve": None}
res = cs.fixed_ips.unreserve(fixed_ip='192.168.1.1')
cs.assert_called('POST', '/os-fixed-ips/192.168.1.1/action', body)
self.cs.fixed_ips.unreserve(fixed_ip='192.168.1.1')
self.assert_called('POST', '/os-fixed-ips/192.168.1.1/action', body)

View File

@ -56,3 +56,15 @@ class FlavorAccessTest(utils.TestCase):
cs.assert_called('POST', '/flavors/2/action', body)
[self.assertIsInstance(a, flavor_access.FlavorAccess) for a in r]
def test_repr_flavor_access(self):
flavor = cs.flavors.get(2)
tenant = 'proj3'
r = cs.flavor_access.add_tenant_access(flavor, tenant)
def get_expected(flavor_access):
return ("<FlavorAccess flavor id: %s, tenant id: %s>" %
(flavor_access.flavor_id, flavor_access.tenant_id))
for a in r:
self.assertEqual(get_expected(a), repr(a))

View File

@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import mock
from novaclient import exceptions
from novaclient.tests import utils
from novaclient.tests.v1_1 import fakes
@ -206,7 +208,12 @@ class FlavorsTest(utils.TestCase):
for key in invalid_keys:
self.assertRaises(exceptions.CommandError, f.set_keys, {key: 'v1'})
def test_unset_keys(self):
@mock.patch.object(flavors.FlavorManager, '_delete')
def test_unset_keys(self, mock_delete):
f = self.cs.flavors.get(1)
f.unset_keys(['k1'])
self.cs.assert_called('DELETE', '/flavors/1/os-extra_specs/k1')
keys = ['k1', 'k2']
f.unset_keys(keys)
mock_delete.assert_has_calls([
mock.call("/flavors/1/os-extra_specs/k1"),
mock.call("/flavors/1/os-extra_specs/k2")
])

View File

@ -11,20 +11,20 @@
# License for the specific language governing permissions and limitations
# under the License.
from novaclient.tests.fixture_data import client
from novaclient.tests.fixture_data import floatingips as data
from novaclient.tests import utils
from novaclient.tests.v1_1 import fakes
from novaclient.v1_1 import floating_ip_dns
cs = fakes.FakeClient()
class FloatingIPDNSDomainTest(utils.TestCase):
class FloatingIPDNSDomainTest(utils.FixturedTestCase):
testdomain = "testdomain"
client_fixture_class = client.V1
data_fixture_class = data.DNSFixture
def test_dns_domains(self):
domainlist = cs.dns_domains.domains()
domainlist = self.cs.dns_domains.domains()
self.assertEqual(len(domainlist), 2)
for entry in domainlist:
@ -34,30 +34,33 @@ class FloatingIPDNSDomainTest(utils.TestCase):
self.assertEqual(domainlist[1].domain, 'example.com')
def test_create_private_domain(self):
cs.dns_domains.create_private(self.testdomain, 'test_avzone')
cs.assert_called('PUT', '/os-floating-ip-dns/%s' %
self.testdomain)
self.cs.dns_domains.create_private(self.testdomain, 'test_avzone')
self.assert_called('PUT', '/os-floating-ip-dns/%s' %
self.testdomain)
def test_create_public_domain(self):
cs.dns_domains.create_public(self.testdomain, 'test_project')
cs.assert_called('PUT', '/os-floating-ip-dns/%s' %
self.testdomain)
self.cs.dns_domains.create_public(self.testdomain, 'test_project')
self.assert_called('PUT', '/os-floating-ip-dns/%s' %
self.testdomain)
def test_delete_domain(self):
cs.dns_domains.delete(self.testdomain)
cs.assert_called('DELETE', '/os-floating-ip-dns/%s' %
self.testdomain)
self.cs.dns_domains.delete(self.testdomain)
self.assert_called('DELETE', '/os-floating-ip-dns/%s' %
self.testdomain)
class FloatingIPDNSEntryTest(utils.TestCase):
class FloatingIPDNSEntryTest(utils.FixturedTestCase):
testname = "testname"
testip = "1.2.3.4"
testdomain = "testdomain"
testtype = "A"
client_fixture_class = client.V1
data_fixture_class = data.DNSFixture
def test_get_dns_entries_by_ip(self):
entries = cs.dns_entries.get_for_ip(self.testdomain, ip=self.testip)
entries = self.cs.dns_entries.get_for_ip(self.testdomain,
ip=self.testip)
self.assertEqual(len(entries), 2)
for entry in entries:
@ -68,21 +71,21 @@ class FloatingIPDNSEntryTest(utils.TestCase):
self.assertEqual(entries[1].dns_entry['ip'], self.testip)
def test_get_dns_entry_by_name(self):
entry = cs.dns_entries.get(self.testdomain,
self.testname)
entry = self.cs.dns_entries.get(self.testdomain,
self.testname)
self.assertIsInstance(entry, floating_ip_dns.FloatingIPDNSEntry)
self.assertEqual(entry.name, self.testname)
def test_create_entry(self):
cs.dns_entries.create(self.testdomain,
self.testname,
self.testip,
self.testtype)
self.cs.dns_entries.create(self.testdomain,
self.testname,
self.testip,
self.testtype)
cs.assert_called('PUT', '/os-floating-ip-dns/%s/entries/%s' %
(self.testdomain, self.testname))
self.assert_called('PUT', '/os-floating-ip-dns/%s/entries/%s' %
(self.testdomain, self.testname))
def test_delete_entry(self):
cs.dns_entries.delete(self.testdomain, self.testname)
cs.assert_called('DELETE', '/os-floating-ip-dns/%s/entries/%s' %
(self.testdomain, self.testname))
self.cs.dns_entries.delete(self.testdomain, self.testname)
self.assert_called('DELETE', '/os-floating-ip-dns/%s/entries/%s' %
(self.testdomain, self.testname))

View File

@ -14,18 +14,19 @@
# License for the specific language governing permissions and limitations
# under the License.
from novaclient.tests.fixture_data import client
from novaclient.tests.fixture_data import floatingips as data
from novaclient.tests import utils
from novaclient.tests.v1_1 import fakes
from novaclient.v1_1 import floating_ip_pools
cs = fakes.FakeClient()
class TestFloatingIPPools(utils.FixturedTestCase):
class TestFloatingIPPools(utils.TestCase):
client_fixture_class = client.V1
data_fixture_class = data.PoolsFixture
def test_list_floating_ips(self):
fl = cs.floating_ip_pools.list()
cs.assert_called('GET', '/os-floating-ip-pools')
fl = self.cs.floating_ip_pools.list()
self.assert_called('GET', '/os-floating-ip-pools')
[self.assertIsInstance(f, floating_ip_pools.FloatingIPPool)
for f in fl]

View File

@ -14,38 +14,46 @@
# License for the specific language governing permissions and limitations
# under the License.
from novaclient.tests.fixture_data import client
from novaclient.tests.fixture_data import floatingips as data
from novaclient.tests import utils
from novaclient.tests.v1_1 import fakes
from novaclient.v1_1 import floating_ips
cs = fakes.FakeClient()
class FloatingIPsTest(utils.FixturedTestCase):
class FloatingIPsTest(utils.TestCase):
client_fixture_class = client.V1
data_fixture_class = data.FloatingFixture
def test_list_floating_ips(self):
fl = cs.floating_ips.list()
cs.assert_called('GET', '/os-floating-ips')
[self.assertIsInstance(f, floating_ips.FloatingIP) for f in fl]
fips = self.cs.floating_ips.list()
self.assert_called('GET', '/os-floating-ips')
for fip in fips:
self.assertIsInstance(fip, floating_ips.FloatingIP)
def test_list_floating_ips_all_tenants(self):
fips = self.cs.floating_ips.list(all_tenants=True)
self.assert_called('GET', '/os-floating-ips?all_tenants=1')
for fip in fips:
self.assertIsInstance(fip, floating_ips.FloatingIP)
def test_delete_floating_ip(self):
fl = cs.floating_ips.list()[0]
fl = self.cs.floating_ips.list()[0]
fl.delete()
cs.assert_called('DELETE', '/os-floating-ips/1')
cs.floating_ips.delete(1)
cs.assert_called('DELETE', '/os-floating-ips/1')
cs.floating_ips.delete(fl)
cs.assert_called('DELETE', '/os-floating-ips/1')
self.assert_called('DELETE', '/os-floating-ips/1')
self.cs.floating_ips.delete(1)
self.assert_called('DELETE', '/os-floating-ips/1')
self.cs.floating_ips.delete(fl)
self.assert_called('DELETE', '/os-floating-ips/1')
def test_create_floating_ip(self):
fl = cs.floating_ips.create()
cs.assert_called('POST', '/os-floating-ips')
fl = self.cs.floating_ips.create()
self.assert_called('POST', '/os-floating-ips')
self.assertIsNone(fl.pool)
self.assertIsInstance(fl, floating_ips.FloatingIP)
def test_create_floating_ip_with_pool(self):
fl = cs.floating_ips.create('foo')
cs.assert_called('POST', '/os-floating-ips')
fl = self.cs.floating_ips.create('nova')
self.assert_called('POST', '/os-floating-ips')
self.assertEqual(fl.pool, 'nova')
self.assertIsInstance(fl, floating_ips.FloatingIP)

View File

@ -13,42 +13,43 @@
# License for the specific language governing permissions and limitations
# under the License.
from novaclient.tests.fixture_data import client
from novaclient.tests.fixture_data import floatingips as data
from novaclient.tests import utils
from novaclient.tests.v1_1 import fakes
from novaclient.v1_1 import floating_ips_bulk
cs = fakes.FakeClient()
class FloatingIPsBulkTest(utils.FixturedTestCase):
class FloatingIPsBulkTest(utils.TestCase):
client_fixture_class = client.V1
data_fixture_class = data.BulkFixture
def test_list_floating_ips_bulk(self):
fl = cs.floating_ips_bulk.list()
cs.assert_called('GET', '/os-floating-ips-bulk')
fl = self.cs.floating_ips_bulk.list()
self.assert_called('GET', '/os-floating-ips-bulk')
[self.assertIsInstance(f, floating_ips_bulk.FloatingIP)
for f in fl]
def test_list_floating_ips_bulk_host_filter(self):
fl = cs.floating_ips_bulk.list('testHost')
cs.assert_called('GET', '/os-floating-ips-bulk/testHost')
fl = self.cs.floating_ips_bulk.list('testHost')
self.assert_called('GET', '/os-floating-ips-bulk/testHost')
[self.assertIsInstance(f, floating_ips_bulk.FloatingIP)
for f in fl]
def test_create_floating_ips_bulk(self):
fl = cs.floating_ips_bulk.create('192.168.1.0/30')
fl = self.cs.floating_ips_bulk.create('192.168.1.0/30')
body = {'floating_ips_bulk_create': {'ip_range': '192.168.1.0/30'}}
cs.assert_called('POST', '/os-floating-ips-bulk', body)
self.assert_called('POST', '/os-floating-ips-bulk', body)
self.assertEqual(fl.ip_range,
body['floating_ips_bulk_create']['ip_range'])
def test_create_floating_ips_bulk_with_pool_and_host(self):
fl = cs.floating_ips_bulk.create('192.168.1.0/30', 'poolTest',
'interfaceTest')
fl = self.cs.floating_ips_bulk.create('192.168.1.0/30', 'poolTest',
'interfaceTest')
body = {'floating_ips_bulk_create':
{'ip_range': '192.168.1.0/30', 'pool': 'poolTest',
'interface': 'interfaceTest'}}
cs.assert_called('POST', '/os-floating-ips-bulk', body)
self.assert_called('POST', '/os-floating-ips-bulk', body)
self.assertEqual(fl.ip_range,
body['floating_ips_bulk_create']['ip_range'])
self.assertEqual(fl.pool,
@ -57,7 +58,7 @@ class FloatingIPsBulkTest(utils.TestCase):
body['floating_ips_bulk_create']['interface'])
def test_delete_floating_ips_bulk(self):
fl = cs.floating_ips_bulk.delete('192.168.1.0/30')
fl = self.cs.floating_ips_bulk.delete('192.168.1.0/30')
body = {'ip_range': '192.168.1.0/30'}
cs.assert_called('PUT', '/os-floating-ips-bulk/delete', body)
self.assert_called('PUT', '/os-floating-ips-bulk/delete', body)
self.assertEqual(fl.floating_ips_bulk_delete, body['ip_range'])

View File

@ -13,49 +13,50 @@
# License for the specific language governing permissions and limitations
# under the License.
from novaclient.tests.fixture_data import client
from novaclient.tests.fixture_data import fping as data
from novaclient.tests import utils
from novaclient.tests.v1_1 import fakes
from novaclient.v1_1 import fping
cs = fakes.FakeClient()
class FpingTest(utils.FixturedTestCase):
class FpingTest(utils.TestCase):
client_fixture_class = client.V1
data_fixture_class = data.Fixture
def test_fping_repr(self):
r = cs.fping.get(1)
r = self.cs.fping.get(1)
self.assertEqual(repr(r), "<Fping: 1>")
def test_list_fpings(self):
fl = cs.fping.list()
cs.assert_called('GET', '/os-fping')
fl = self.cs.fping.list()
self.assert_called('GET', '/os-fping')
for f in fl:
self.assertIsInstance(f, fping.Fping)
self.assertEqual(f.project_id, "fake-project")
self.assertEqual(f.alive, True)
def test_list_fpings_all_tenants(self):
fl = cs.fping.list(all_tenants=True)
fl = self.cs.fping.list(all_tenants=True)
for f in fl:
self.assertIsInstance(f, fping.Fping)
cs.assert_called('GET', '/os-fping?all_tenants=1')
self.assert_called('GET', '/os-fping?all_tenants=1')
def test_list_fpings_exclude(self):
fl = cs.fping.list(exclude=['1'])
fl = self.cs.fping.list(exclude=['1'])
for f in fl:
self.assertIsInstance(f, fping.Fping)
cs.assert_called('GET', '/os-fping?exclude=1')
self.assert_called('GET', '/os-fping?exclude=1')
def test_list_fpings_include(self):
fl = cs.fping.list(include=['1'])
fl = self.cs.fping.list(include=['1'])
for f in fl:
self.assertIsInstance(f, fping.Fping)
cs.assert_called('GET', '/os-fping?include=1')
self.assert_called('GET', '/os-fping?include=1')
def test_get_fping(self):
f = cs.fping.get(1)
cs.assert_called('GET', '/os-fping/1')
f = self.cs.fping.get(1)
self.assert_called('GET', '/os-fping/1')
self.assertIsInstance(f, fping.Fping)
self.assertEqual(f.project_id, "fake-project")
self.assertEqual(f.alive, True)

View File

@ -11,69 +11,70 @@
# License for the specific language governing permissions and limitations
# under the License.
from novaclient.tests.fixture_data import client
from novaclient.tests.fixture_data import hosts as data
from novaclient.tests import utils
from novaclient.tests.v1_1 import fakes
from novaclient.v1_1 import hosts
cs = fakes.FakeClient()
class HostsTest(utils.FixturedTestCase):
class HostsTest(utils.TestCase):
client_fixture_class = client.V1
data_fixture_class = data.V1
def test_describe_resource(self):
hs = cs.hosts.get('host')
cs.assert_called('GET', '/os-hosts/host')
hs = self.cs.hosts.get('host')
self.assert_called('GET', '/os-hosts/host')
[self.assertIsInstance(h, hosts.Host) for h in hs]
def test_list_host(self):
hs = cs.hosts.list()
cs.assert_called('GET', '/os-hosts')
hs = self.cs.hosts.list()
self.assert_called('GET', '/os-hosts')
[self.assertIsInstance(h, hosts.Host) for h in hs]
[self.assertEqual(h.zone, 'nova1') for h in hs]
def test_list_host_with_zone(self):
hs = cs.hosts.list('nova')
cs.assert_called('GET', '/os-hosts?zone=nova')
hs = self.cs.hosts.list('nova')
self.assert_called('GET', '/os-hosts?zone=nova')
[self.assertIsInstance(h, hosts.Host) for h in hs]
[self.assertEqual(h.zone, 'nova') for h in hs]
def test_update_enable(self):
host = cs.hosts.get('sample_host')[0]
host = self.cs.hosts.get('sample_host')[0]
values = {"status": "enabled"}
result = host.update(values)
cs.assert_called('PUT', '/os-hosts/sample_host', values)
self.assert_called('PUT', '/os-hosts/sample_host', values)
self.assertIsInstance(result, hosts.Host)
def test_update_maintenance(self):
host = cs.hosts.get('sample_host')[0]
host = self.cs.hosts.get('sample_host')[0]
values = {"maintenance_mode": "enable"}
result = host.update(values)
cs.assert_called('PUT', '/os-hosts/sample_host', values)
self.assert_called('PUT', '/os-hosts/sample_host', values)
self.assertIsInstance(result, hosts.Host)
def test_update_both(self):
host = cs.hosts.get('sample_host')[0]
host = self.cs.hosts.get('sample_host')[0]
values = {"status": "enabled",
"maintenance_mode": "enable"}
result = host.update(values)
cs.assert_called('PUT', '/os-hosts/sample_host', values)
self.assert_called('PUT', '/os-hosts/sample_host', values)
self.assertIsInstance(result, hosts.Host)
def test_host_startup(self):
host = cs.hosts.get('sample_host')[0]
result = host.startup()
cs.assert_called(
host = self.cs.hosts.get('sample_host')[0]
host.startup()
self.assert_called(
'GET', '/os-hosts/sample_host/startup')
def test_host_reboot(self):
host = cs.hosts.get('sample_host')[0]
result = host.reboot()
cs.assert_called(
host = self.cs.hosts.get('sample_host')[0]
host.reboot()
self.assert_called(
'GET', '/os-hosts/sample_host/reboot')
def test_host_shutdown(self):
host = cs.hosts.get('sample_host')[0]
result = host.shutdown()
cs.assert_called(
host = self.cs.hosts.get('sample_host')[0]
host.shutdown()
self.assert_called(
'GET', '/os-hosts/sample_host/shutdown')

View File

@ -13,17 +13,15 @@
# License for the specific language governing permissions and limitations
# under the License.
from novaclient.tests.fixture_data import client
from novaclient.tests.fixture_data import hypervisors as data
from novaclient.tests import utils
from novaclient.tests.v1_1 import fakes
class HypervisorsTest(utils.TestCase):
def setUp(self):
super(HypervisorsTest, self).setUp()
self.cs = self._get_fake_client()
class HypervisorsTest(utils.FixturedTestCase):
def _get_fake_client(self):
return fakes.FakeClient()
client_fixture_class = client.V1
data_fixture_class = data.V1
def compare_to_expected(self, expected, hyper):
for key, value in expected.items():
@ -36,7 +34,7 @@ class HypervisorsTest(utils.TestCase):
]
result = self.cs.hypervisors.list(False)
self.cs.assert_called('GET', '/os-hypervisors')
self.assert_called('GET', '/os-hypervisors')
for idx, hyper in enumerate(result):
self.compare_to_expected(expected[idx], hyper)
@ -79,7 +77,7 @@ class HypervisorsTest(utils.TestCase):
disk_available_least=100)]
result = self.cs.hypervisors.list()
self.cs.assert_called('GET', '/os-hypervisors/detail')
self.assert_called('GET', '/os-hypervisors/detail')
for idx, hyper in enumerate(result):
self.compare_to_expected(expected[idx], hyper)
@ -91,7 +89,7 @@ class HypervisorsTest(utils.TestCase):
]
result = self.cs.hypervisors.search('hyper')
self.cs.assert_called('GET', '/os-hypervisors/hyper/search')
self.assert_called('GET', '/os-hypervisors/hyper/search')
for idx, hyper in enumerate(result):
self.compare_to_expected(expected[idx], hyper)
@ -111,7 +109,7 @@ class HypervisorsTest(utils.TestCase):
]
result = self.cs.hypervisors.search('hyper', True)
self.cs.assert_called('GET', '/os-hypervisors/hyper/servers')
self.assert_called('GET', '/os-hypervisors/hyper/servers')
for idx, hyper in enumerate(result):
self.compare_to_expected(expected[idx], hyper)
@ -137,7 +135,7 @@ class HypervisorsTest(utils.TestCase):
disk_available_least=100)
result = self.cs.hypervisors.get(1234)
self.cs.assert_called('GET', '/os-hypervisors/1234')
self.assert_called('GET', '/os-hypervisors/1234')
self.compare_to_expected(expected, result)
@ -148,7 +146,7 @@ class HypervisorsTest(utils.TestCase):
uptime="fake uptime")
result = self.cs.hypervisors.uptime(1234)
self.cs.assert_called('GET', '/os-hypervisors/1234/uptime')
self.assert_called('GET', '/os-hypervisors/1234/uptime')
self.compare_to_expected(expected, result)
@ -169,6 +167,6 @@ class HypervisorsTest(utils.TestCase):
)
result = self.cs.hypervisors.statistics()
self.cs.assert_called('GET', '/os-hypervisors/statistics')
self.assert_called('GET', '/os-hypervisors/statistics')
self.compare_to_expected(expected, result)

View File

@ -11,56 +11,56 @@
# License for the specific language governing permissions and limitations
# under the License.
from novaclient.tests.fixture_data import client
from novaclient.tests.fixture_data import images as data
from novaclient.tests import utils
from novaclient.tests.v1_1 import fakes
from novaclient.v1_1 import images
cs = fakes.FakeClient()
class ImagesTest(utils.FixturedTestCase):
class ImagesTest(utils.TestCase):
client_fixture_class = client.V1
data_fixture_class = data.V1
def test_list_images(self):
il = cs.images.list()
cs.assert_called('GET', '/images/detail')
il = self.cs.images.list()
self.assert_called('GET', '/images/detail')
[self.assertIsInstance(i, images.Image) for i in il]
def test_list_images_undetailed(self):
il = cs.images.list(detailed=False)
cs.assert_called('GET', '/images')
il = self.cs.images.list(detailed=False)
self.assert_called('GET', '/images')
[self.assertIsInstance(i, images.Image) for i in il]
def test_list_images_with_limit(self):
il = cs.images.list(limit=4)
cs.assert_called('GET', '/images/detail?limit=4')
self.cs.images.list(limit=4)
self.assert_called('GET', '/images/detail?limit=4')
def test_get_image_details(self):
i = cs.images.get(1)
cs.assert_called('GET', '/images/1')
i = self.cs.images.get(1)
self.assert_called('GET', '/images/1')
self.assertIsInstance(i, images.Image)
self.assertEqual(i.id, 1)
self.assertEqual(i.name, 'CentOS 5.2')
def test_delete_image(self):
cs.images.delete(1)
cs.assert_called('DELETE', '/images/1')
self.cs.images.delete(1)
self.assert_called('DELETE', '/images/1')
def test_delete_meta(self):
cs.images.delete_meta(1, {'test_key': 'test_value'})
cs.assert_called('DELETE', '/images/1/metadata/test_key')
self.cs.images.delete_meta(1, {'test_key': 'test_value'})
self.assert_called('DELETE', '/images/1/metadata/test_key')
def test_set_meta(self):
cs.images.set_meta(1, {'test_key': 'test_value'})
cs.assert_called('POST', '/images/1/metadata',
self.cs.images.set_meta(1, {'test_key': 'test_value'})
self.assert_called('POST', '/images/1/metadata',
{"metadata": {'test_key': 'test_value'}})
def test_find(self):
i = cs.images.find(name="CentOS 5.2")
i = self.cs.images.find(name="CentOS 5.2")
self.assertEqual(i.id, 1)
cs.assert_called('GET', '/images', pos=-2)
cs.assert_called('GET', '/images/1', pos=-1)
self.assert_called('GET', '/images/1')
iml = cs.images.findall(status='SAVING')
iml = self.cs.images.findall(status='SAVING')
self.assertEqual(len(iml), 1)
self.assertEqual(iml[0].name, 'My Server Backup')

View File

@ -11,50 +11,54 @@
# License for the specific language governing permissions and limitations
# under the License.
from novaclient.tests.fixture_data import client
from novaclient.tests.fixture_data import keypairs as data
from novaclient.tests import utils
from novaclient.tests.v1_1 import fakes
from novaclient.v1_1 import keypairs
class KeypairsTest(utils.TestCase):
class KeypairsTest(utils.FixturedTestCase):
client_fixture_class = client.V1
data_fixture_class = data.V1
def setUp(self):
super(KeypairsTest, self).setUp()
self.cs = self._get_fake_client()
self.keypair_type = self._get_keypair_type()
self.keypair_prefix = keypairs.KeypairManager.keypair_prefix
def _get_fake_client(self):
return fakes.FakeClient()
self.keypair_prefix = self._get_keypair_prefix()
def _get_keypair_type(self):
return keypairs.Keypair
def _get_keypair_prefix(self):
return keypairs.KeypairManager.keypair_prefix
def test_get_keypair(self):
kp = self.cs.keypairs.get('test')
self.cs.assert_called('GET', '/%s/test' % self.keypair_prefix)
self.assert_called('GET', '/%s/test' % self.keypair_prefix)
self.assertIsInstance(kp, keypairs.Keypair)
self.assertEqual(kp.name, 'test')
def test_list_keypairs(self):
kps = self.cs.keypairs.list()
self.cs.assert_called('GET', '/%s' % self.keypair_prefix)
self.assert_called('GET', '/%s' % self.keypair_prefix)
[self.assertIsInstance(kp, keypairs.Keypair) for kp in kps]
def test_delete_keypair(self):
kp = self.cs.keypairs.list()[0]
kp.delete()
self.cs.assert_called('DELETE', '/%s/test' % self.keypair_prefix)
self.assert_called('DELETE', '/%s/test' % self.keypair_prefix)
self.cs.keypairs.delete('test')
self.cs.assert_called('DELETE', '/%s/test' % self.keypair_prefix)
self.assert_called('DELETE', '/%s/test' % self.keypair_prefix)
self.cs.keypairs.delete(kp)
self.cs.assert_called('DELETE', '/%s/test' % self.keypair_prefix)
self.assert_called('DELETE', '/%s/test' % self.keypair_prefix)
def test_create_keypair(self):
kp = self.cs.keypairs.create("foo")
self.cs.assert_called('POST', '/%s' % self.keypair_prefix)
self.assert_called('POST', '/%s' % self.keypair_prefix)
self.assertIsInstance(kp, keypairs.Keypair)
def test_import_keypair(self):
kp = self.cs.keypairs.create("foo", "fake-public-key")
self.cs.assert_called('POST', '/%s' % self.keypair_prefix)
self.assert_called('POST', '/%s' % self.keypair_prefix)
self.assertIsInstance(kp, keypairs.Keypair)

View File

@ -11,28 +11,29 @@
# License for the specific language governing permissions and limitations
# under the License.
from novaclient.tests.fixture_data import client
from novaclient.tests.fixture_data import limits as data
from novaclient.tests import utils
from novaclient.tests.v1_1 import fakes
from novaclient.v1_1 import limits
cs = fakes.FakeClient()
class LimitsTest(utils.FixturedTestCase):
class LimitsTest(utils.TestCase):
client_fixture_class = client.V1
data_fixture_class = data.Fixture
def test_get_limits(self):
obj = cs.limits.get()
cs.assert_called('GET', '/limits')
obj = self.cs.limits.get()
self.assert_called('GET', '/limits')
self.assertIsInstance(obj, limits.Limits)
def test_get_limits_for_a_tenant(self):
obj = cs.limits.get(tenant_id=1234)
cs.assert_called('GET', '/limits?tenant_id=1234')
obj = self.cs.limits.get(tenant_id=1234)
self.assert_called('GET', '/limits?tenant_id=1234')
self.assertIsInstance(obj, limits.Limits)
def test_absolute_limits(self):
obj = cs.limits.get()
obj = self.cs.limits.get()
expected = (
limits.AbsoluteLimit("maxTotalRAMSize", 51200),
@ -49,7 +50,7 @@ class LimitsTest(utils.TestCase):
self.assertTrue(limit in expected)
def test_absolute_limits_reserved(self):
obj = cs.limits.get(reserved=True)
obj = self.cs.limits.get(reserved=True)
expected = (
limits.AbsoluteLimit("maxTotalRAMSize", 51200),
@ -59,7 +60,7 @@ class LimitsTest(utils.TestCase):
limits.AbsoluteLimit("maxPersonalitySize", 10240),
)
cs.assert_called('GET', '/limits?reserved=1')
self.assert_called('GET', '/limits?reserved=1')
abs_limits = list(obj.absolute)
self.assertEqual(len(abs_limits), len(expected))
@ -67,7 +68,7 @@ class LimitsTest(utils.TestCase):
self.assertTrue(limit in expected)
def test_rate_limits(self):
obj = cs.limits.get()
obj = self.cs.limits.get()
expected = (
limits.RateLimit('POST', '*', '.*', 10, 2, 'MINUTE',

View File

@ -11,34 +11,35 @@
# License for the specific language governing permissions and limitations
# under the License.
from novaclient.tests.fixture_data import client
from novaclient.tests.fixture_data import networks as data
from novaclient.tests import utils
from novaclient.tests.v1_1 import fakes
from novaclient.v1_1 import networks
cs = fakes.FakeClient()
class NetworksTest(utils.FixturedTestCase):
class NetworksTest(utils.TestCase):
client_fixture_class = client.V1
data_fixture_class = data.Fixture
def test_list_networks(self):
fl = cs.networks.list()
cs.assert_called('GET', '/os-networks')
fl = self.cs.networks.list()
self.assert_called('GET', '/os-networks')
[self.assertIsInstance(f, networks.Network) for f in fl]
def test_get_network(self):
f = cs.networks.get(1)
cs.assert_called('GET', '/os-networks/1')
f = self.cs.networks.get(1)
self.assert_called('GET', '/os-networks/1')
self.assertIsInstance(f, networks.Network)
def test_delete(self):
cs.networks.delete('networkdelete')
cs.assert_called('DELETE', '/os-networks/networkdelete')
self.cs.networks.delete('networkdelete')
self.assert_called('DELETE', '/os-networks/networkdelete')
def test_create(self):
f = cs.networks.create(label='foo')
cs.assert_called('POST', '/os-networks',
{'network': {'label': 'foo'}})
f = self.cs.networks.create(label='foo')
self.assert_called('POST', '/os-networks',
{'network': {'label': 'foo'}})
self.assertIsInstance(f, networks.Network)
def test_create_allparams(self):
@ -56,43 +57,44 @@ class NetworksTest(utils.TestCase):
'multi_host': 'T',
'priority': '1',
'project_id': '1',
'vlan': 5,
'vlan_start': 1,
'vpn_start': 1
}
f = cs.networks.create(**params)
cs.assert_called('POST', '/os-networks', {'network': params})
f = self.cs.networks.create(**params)
self.assert_called('POST', '/os-networks', {'network': params})
self.assertIsInstance(f, networks.Network)
def test_associate_project(self):
cs.networks.associate_project('networktest')
cs.assert_called('POST', '/os-networks/add',
{'id': 'networktest'})
self.cs.networks.associate_project('networktest')
self.assert_called('POST', '/os-networks/add',
{'id': 'networktest'})
def test_associate_host(self):
cs.networks.associate_host('networktest', 'testHost')
cs.assert_called('POST', '/os-networks/networktest/action',
{'associate_host': 'testHost'})
self.cs.networks.associate_host('networktest', 'testHost')
self.assert_called('POST', '/os-networks/networktest/action',
{'associate_host': 'testHost'})
def test_disassociate(self):
cs.networks.disassociate('networkdisassociate')
cs.assert_called('POST',
'/os-networks/networkdisassociate/action',
{'disassociate': None})
self.cs.networks.disassociate('networkdisassociate')
self.assert_called('POST',
'/os-networks/networkdisassociate/action',
{'disassociate': None})
def test_disassociate_host_only(self):
cs.networks.disassociate('networkdisassociate', True, False)
cs.assert_called('POST',
'/os-networks/networkdisassociate/action',
{'disassociate_host': None})
self.cs.networks.disassociate('networkdisassociate', True, False)
self.assert_called('POST',
'/os-networks/networkdisassociate/action',
{'disassociate_host': None})
def test_disassociate_project(self):
cs.networks.disassociate('networkdisassociate', False, True)
cs.assert_called('POST',
'/os-networks/networkdisassociate/action',
{'disassociate_project': None})
self.cs.networks.disassociate('networkdisassociate', False, True)
self.assert_called('POST',
'/os-networks/networkdisassociate/action',
{'disassociate_project': None})
def test_add(self):
cs.networks.add('networkadd')
cs.assert_called('POST', '/os-networks/add',
{'id': 'networkadd'})
self.cs.networks.add('networkadd')
self.assert_called('POST', '/os-networks/add',
{'id': 'networkadd'})

View File

@ -13,39 +13,37 @@
# License for the specific language governing permissions and limitations
# under the License.
from novaclient.tests.fixture_data import client
from novaclient.tests.fixture_data import quotas as data
from novaclient.tests import utils
from novaclient.tests.v1_1 import fakes
class QuotaSetsTest(utils.TestCase):
def setUp(self):
super(QuotaSetsTest, self).setUp()
self.cs = self._get_fake_client()
class QuotaSetsTest(utils.FixturedTestCase):
def _get_fake_client(self):
return fakes.FakeClient()
client_fixture_class = client.V1
data_fixture_class = data.V1
def test_tenant_quotas_get(self):
tenant_id = 'test'
self.cs.quotas.get(tenant_id)
self.cs.assert_called('GET', '/os-quota-sets/%s' % tenant_id)
self.assert_called('GET', '/os-quota-sets/%s' % tenant_id)
def test_user_quotas_get(self):
tenant_id = 'test'
user_id = 'fake_user'
self.cs.quotas.get(tenant_id, user_id=user_id)
url = '/os-quota-sets/%s?user_id=%s' % (tenant_id, user_id)
self.cs.assert_called('GET', url)
self.assert_called('GET', url)
def test_tenant_quotas_defaults(self):
tenant_id = '97f4c221bff44578b0300df4ef119353'
self.cs.quotas.defaults(tenant_id)
self.cs.assert_called('GET', '/os-quota-sets/%s/defaults' % tenant_id)
self.assert_called('GET', '/os-quota-sets/%s/defaults' % tenant_id)
def test_force_update_quota(self):
q = self.cs.quotas.get('97f4c221bff44578b0300df4ef119353')
q.update(cores=2, force=True)
self.cs.assert_called(
self.assert_called(
'PUT', '/os-quota-sets/97f4c221bff44578b0300df4ef119353',
{'quota_set': {'force': True,
'cores': 2,
@ -54,11 +52,11 @@ class QuotaSetsTest(utils.TestCase):
def test_quotas_delete(self):
tenant_id = 'test'
self.cs.quotas.delete(tenant_id)
self.cs.assert_called('DELETE', '/os-quota-sets/%s' % tenant_id)
self.assert_called('DELETE', '/os-quota-sets/%s' % tenant_id)
def test_user_quotas_delete(self):
tenant_id = 'test'
user_id = 'fake_user'
self.cs.quotas.delete(tenant_id, user_id=user_id)
url = '/os-quota-sets/%s?user_id=%s' % (tenant_id, user_id)
self.cs.assert_called('DELETE', url)
self.assert_called('DELETE', url)

View File

@ -12,21 +12,24 @@
# under the License.
from novaclient import exceptions
from novaclient.tests.fixture_data import client
from novaclient.tests.fixture_data import security_group_rules as data
from novaclient.tests import utils
from novaclient.tests.v1_1 import fakes
from novaclient.v1_1 import security_group_rules
cs = fakes.FakeClient()
class SecurityGroupRulesTest(utils.FixturedTestCase):
client_fixture_class = client.V1
data_fixture_class = data.Fixture
class SecurityGroupRulesTest(utils.TestCase):
def test_delete_security_group_rule(self):
cs.security_group_rules.delete(1)
cs.assert_called('DELETE', '/os-security-group-rules/1')
self.cs.security_group_rules.delete(1)
self.assert_called('DELETE', '/os-security-group-rules/1')
def test_create_security_group_rule(self):
sg = cs.security_group_rules.create(1, "tcp", 1, 65535, "10.0.0.0/16")
sg = self.cs.security_group_rules.create(1, "tcp", 1, 65535,
"10.0.0.0/16")
body = {
"security_group_rule": {
@ -39,12 +42,12 @@ class SecurityGroupRulesTest(utils.TestCase):
}
}
cs.assert_called('POST', '/os-security-group-rules', body)
self.assert_called('POST', '/os-security-group-rules', body)
self.assertTrue(isinstance(sg, security_group_rules.SecurityGroupRule))
def test_create_security_group_group_rule(self):
sg = cs.security_group_rules.create(1, "tcp", 1, 65535, "10.0.0.0/16",
101)
sg = self.cs.security_group_rules.create(1, "tcp", 1, 65535,
"10.0.0.0/16", 101)
body = {
"security_group_rule": {
@ -57,25 +60,27 @@ class SecurityGroupRulesTest(utils.TestCase):
}
}
cs.assert_called('POST', '/os-security-group-rules', body)
self.assert_called('POST', '/os-security-group-rules', body)
self.assertTrue(isinstance(sg, security_group_rules.SecurityGroupRule))
def test_invalid_parameters_create(self):
self.assertRaises(exceptions.CommandError,
cs.security_group_rules.create,
self.cs.security_group_rules.create,
1, "invalid_ip_protocol", 1, 65535, "10.0.0.0/16", 101)
self.assertRaises(exceptions.CommandError,
cs.security_group_rules.create,
self.cs.security_group_rules.create,
1, "tcp", "invalid_from_port", 65535, "10.0.0.0/16", 101)
self.assertRaises(exceptions.CommandError,
cs.security_group_rules.create,
self.cs.security_group_rules.create,
1, "tcp", 1, "invalid_to_port", "10.0.0.0/16", 101)
def test_security_group_rule_str(self):
sg = cs.security_group_rules.create(1, "tcp", 1, 65535, "10.0.0.0/16")
sg = self.cs.security_group_rules.create(1, "tcp", 1, 65535,
"10.0.0.0/16")
self.assertEqual('1', str(sg))
def test_security_group_rule_del(self):
sg = cs.security_group_rules.create(1, "tcp", 1, 65535, "10.0.0.0/16")
sg = self.cs.security_group_rules.create(1, "tcp", 1, 65535,
"10.0.0.0/16")
sg.delete()
cs.assert_called('DELETE', '/os-security-group-rules/1')
self.assert_called('DELETE', '/os-security-group-rules/1')

View File

@ -11,18 +11,20 @@
# License for the specific language governing permissions and limitations
# under the License.
from novaclient.tests.fixture_data import client
from novaclient.tests.fixture_data import security_groups as data
from novaclient.tests import utils
from novaclient.tests.v1_1 import fakes
from novaclient.v1_1 import security_groups
cs = fakes.FakeClient()
class SecurityGroupsTest(utils.FixturedTestCase):
client_fixture_class = client.V1
data_fixture_class = data.Fixture
class SecurityGroupsTest(utils.TestCase):
def _do_test_list_security_groups(self, search_opts, path):
sgs = cs.security_groups.list(search_opts=search_opts)
cs.assert_called('GET', path)
sgs = self.cs.security_groups.list(search_opts=search_opts)
self.assert_called('GET', path)
for sg in sgs:
self.assertIsInstance(sg, security_groups.SecurityGroup)
@ -39,34 +41,34 @@ class SecurityGroupsTest(utils.TestCase):
{'all_tenants': 0}, '/os-security-groups')
def test_get_security_groups(self):
sg = cs.security_groups.get(1)
cs.assert_called('GET', '/os-security-groups/1')
sg = self.cs.security_groups.get(1)
self.assert_called('GET', '/os-security-groups/1')
self.assertIsInstance(sg, security_groups.SecurityGroup)
self.assertEqual('1', str(sg))
def test_delete_security_group(self):
sg = cs.security_groups.list()[0]
sg = self.cs.security_groups.list()[0]
sg.delete()
cs.assert_called('DELETE', '/os-security-groups/1')
cs.security_groups.delete(1)
cs.assert_called('DELETE', '/os-security-groups/1')
cs.security_groups.delete(sg)
cs.assert_called('DELETE', '/os-security-groups/1')
self.assert_called('DELETE', '/os-security-groups/1')
self.cs.security_groups.delete(1)
self.assert_called('DELETE', '/os-security-groups/1')
self.cs.security_groups.delete(sg)
self.assert_called('DELETE', '/os-security-groups/1')
def test_create_security_group(self):
sg = cs.security_groups.create("foo", "foo barr")
cs.assert_called('POST', '/os-security-groups')
sg = self.cs.security_groups.create("foo", "foo barr")
self.assert_called('POST', '/os-security-groups')
self.assertIsInstance(sg, security_groups.SecurityGroup)
def test_update_security_group(self):
sg = cs.security_groups.list()[0]
secgroup = cs.security_groups.update(sg, "update", "update")
cs.assert_called('PUT', '/os-security-groups/1')
sg = self.cs.security_groups.list()[0]
secgroup = self.cs.security_groups.update(sg, "update", "update")
self.assert_called('PUT', '/os-security-groups/1')
self.assertIsInstance(secgroup, security_groups.SecurityGroup)
def test_refresh_security_group(self):
sg = cs.security_groups.get(1)
sg2 = cs.security_groups.get(1)
sg = self.cs.security_groups.get(1)
sg2 = self.cs.security_groups.get(1)
self.assertEqual(sg.name, sg2.name)
sg2.name = "should be test"
self.assertNotEqual(sg.name, sg2.name)

View File

@ -0,0 +1,53 @@
# Copyright (c) 2014 VMware, Inc.
# All Rights Reserved.
#
# 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.
from novaclient.tests.fixture_data import client
from novaclient.tests.fixture_data import server_groups as data
from novaclient.tests import utils
from novaclient.v1_1 import server_groups
class ServerGroupsTest(utils.FixturedTestCase):
client_fixture_class = client.V1
data_fixture_class = data.Fixture
def test_list_server_groups(self):
result = self.cs.server_groups.list()
self.assert_called('GET', '/os-server-groups')
for server_group in result:
self.assertTrue(isinstance(server_group,
server_groups.ServerGroup))
def test_create_server_group(self):
kwargs = {'name': 'ig1',
'policies': ['anti-affinity']}
server_group = self.cs.server_groups.create(**kwargs)
body = {'server_group': kwargs}
self.assert_called('POST', '/os-server-groups', body)
self.assertTrue(isinstance(server_group,
server_groups.ServerGroup))
def test_get_server_group(self):
id = '2cbd51f4-fafe-4cdb-801b-cf913a6f288b'
server_group = self.cs.server_groups.get(id)
self.assert_called('GET', '/os-server-groups/%s' % id)
self.assertTrue(isinstance(server_group,
server_groups.ServerGroup))
def test_delete_server_group(self):
id = '2cbd51f4-fafe-4cdb-801b-cf913a6f288b'
self.cs.server_groups.delete(id)
self.assert_called('DELETE', '/os-server-groups/%s' % id)

View File

@ -104,6 +104,28 @@ class ServersTest(utils.TestCase):
test_create_server_from_volume()
def test_create_server_boot_with_nics_ipv6(self):
old_boot = cs.servers._boot
nics = [{'net-id': '11111111-1111-1111-1111-111111111111',
'v6-fixed-ip': '2001:db9:0:1::10'}]
def wrapped_boot(url, key, *boot_args, **boot_kwargs):
self.assertEqual(boot_kwargs['nics'], nics)
return old_boot(url, key, *boot_args, **boot_kwargs)
with mock.patch.object(cs.servers, '_boot', wrapped_boot):
s = cs.servers.create(
name="My server",
image=1,
flavor=1,
meta={'foo': 'bar'},
userdata="hello moto",
key_name="fakekey",
nics=nics
)
cs.assert_called('POST', '/servers')
self.assertIsInstance(s, servers.Server)
def test_create_server_userdata_file_object(self):
s = cs.servers.create(
name="My server",
@ -200,17 +222,17 @@ class ServersTest(utils.TestCase):
cs.assert_called('DELETE', '/servers/1234')
def test_delete_server_meta(self):
s = cs.servers.delete_meta(1234, ['test_key'])
cs.servers.delete_meta(1234, ['test_key'])
cs.assert_called('DELETE', '/servers/1234/metadata/test_key')
def test_set_server_meta(self):
s = cs.servers.set_meta(1234, {'test_key': 'test_value'})
reval = cs.assert_called('POST', '/servers/1234/metadata',
cs.servers.set_meta(1234, {'test_key': 'test_value'})
cs.assert_called('POST', '/servers/1234/metadata',
{'metadata': {'test_key': 'test_value'}})
def test_set_server_meta_item(self):
s = cs.servers.set_meta_item(1234, 'test_key', 'test_value')
reval = cs.assert_called('PUT', '/servers/1234/metadata/test_key',
cs.servers.set_meta_item(1234, 'test_key', 'test_value')
cs.assert_called('PUT', '/servers/1234/metadata/test_key',
{'meta': {'test_key': 'test_value'}})
def test_find(self):
@ -582,6 +604,32 @@ class ServersTest(utils.TestCase):
s.interface_list()
cs.assert_called('GET', '/servers/1234/os-interface')
def test_interface_list_result_string_representable(self):
"""Test for bugs.launchpad.net/python-novaclient/+bug/1280453."""
# According to https://github.com/openstack/nova/blob/master/
# nova/api/openstack/compute/contrib/attach_interfaces.py#L33,
# the attach_interface extension get method will return a json
# object partly like this:
interface_list = [{
'net_id': 'd7745cf5-63f9-4883-b0ae-983f061e4f23',
'port_id': 'f35079da-36d5-4513-8ec1-0298d703f70e',
'mac_addr': 'fa:16:3e:4c:37:c8',
'port_state': 'ACTIVE',
'fixed_ips': [{
'subnet_id': 'f1ad93ad-2967-46ba-b403-e8cbbe65f7fa',
'ip_address': '10.2.0.96'
}]
}]
# If server is not string representable, it will raise an exception,
# because attribute named 'name' cannot be found.
# Parameter 'loaded' must be True or it will try to get attribute
# 'id' then fails (lazy load detail), this is exactly same as
# novaclient.base.Manager._list()
s = servers.Server(servers.ServerManager, interface_list[0],
loaded=True)
# Trigger the __repr__ magic method
self.assertEqual('<Server: unknown-name>', '%r' % s)
def test_interface_attach(self):
s = cs.servers.get(1234)
s.interface_attach(None, None, None)

View File

@ -78,6 +78,10 @@ class ServicesTest(utils.TestCase):
self.assertIsInstance(service, self._get_service_type())
self.assertEqual(service.status, 'enabled')
def test_services_delete(self):
self.cs.services.delete('1')
self.cs.assert_called('DELETE', '/os-services/1')
def test_services_disable(self):
service = self.cs.services.disable('host1', 'nova-cert')
values = self._update_body("host1", "nova-cert")

View File

@ -23,6 +23,7 @@ import os
import fixtures
import mock
import six
from six.moves import builtins
import novaclient.client
from novaclient import exceptions
@ -128,20 +129,6 @@ class ShellTest(utils.TestCase):
}},
)
def test_boot_multiple(self):
self.run_command('boot --flavor 1 --image 1'
' --num-instances 3 some-server')
self.assert_called_anytime(
'POST', '/servers',
{'server': {
'flavorRef': '1',
'name': 'some-server',
'imageRef': '1',
'min_count': 1,
'max_count': 3,
}},
)
def test_boot_image_with(self):
self.run_command("boot --flavor 1"
" --image-with test_key=test_value some-server")
@ -172,8 +159,8 @@ class ShellTest(utils.TestCase):
def test_boot_user_data(self):
testfile = os.path.join(os.path.dirname(__file__), 'testfile.txt')
data = open(testfile).read()
expected_file_data = base64.b64encode(data.encode('utf-8'))
data = open(testfile).read().encode('utf-8')
expected_file_data = base64.b64encode(data).decode('utf-8')
self.run_command(
'boot --flavor 1 --image 1 --user_data %s some-server' % testfile)
self.assert_called_anytime(
@ -497,7 +484,33 @@ class ShellTest(utils.TestCase):
},
)
def tets_boot_nics_no_value(self):
def test_boot_nics_ipv6(self):
cmd = ('boot --image 1 --flavor 1 '
'--nic net-id=a=c,v6-fixed-ip=2001:db9:0:1::10 some-server')
self.run_command(cmd)
self.assert_called_anytime(
'POST', '/servers',
{
'server': {
'flavorRef': '1',
'name': 'some-server',
'imageRef': '1',
'min_count': 1,
'max_count': 1,
'networks': [
{'uuid': 'a=c', 'fixed_ip': '2001:db9:0:1::10'},
],
},
},
)
def test_boot_nics_both_ipv4_and_ipv6(self):
cmd = ('boot --image 1 --flavor 1 '
'--nic net-id=a=c,v4-fixed-ip=10.0.0.1,'
'v6-fixed-ip=2001:db9:0:1::10 some-server')
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
def test_boot_nics_no_value(self):
cmd = ('boot --image 1 --flavor 1 '
'--nic net-id some-server')
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
@ -512,10 +525,15 @@ class ShellTest(utils.TestCase):
'--nic v4-fixed-ip=10.0.0.1 some-server')
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
def test_boot_nics_netid_and_portid(self):
cmd = ('boot --image 1 --flavor 1 '
'--nic port-id=some=port,net-id=some=net some-server')
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
def test_boot_files(self):
testfile = os.path.join(os.path.dirname(__file__), 'testfile.txt')
data = open(testfile).read()
expected_file_data = base64.b64encode(data.encode('utf-8'))
expected = base64.b64encode(data.encode('utf-8')).decode('utf-8')
cmd = ('boot some-server --flavor 1 --image 1'
' --file /tmp/foo=%s --file /tmp/bar=%s')
@ -530,8 +548,8 @@ class ShellTest(utils.TestCase):
'min_count': 1,
'max_count': 1,
'personality': [
{'path': '/tmp/bar', 'contents': expected_file_data},
{'path': '/tmp/foo', 'contents': expected_file_data},
{'path': '/tmp/bar', 'contents': expected},
{'path': '/tmp/foo', 'contents': expected},
]
}},
)
@ -558,11 +576,69 @@ class ShellTest(utils.TestCase):
})
def test_boot_invalid_num_instances(self):
cmd = 'boot --image 1 --flavor 1 --num-instances 1 server'
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
cmd = 'boot --image 1 --flavor 1 --num-instances 0 server'
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
def test_boot_num_instances_and_count(self):
cmd = 'boot --image 1 --flavor 1 --num-instances 3 --min-count 3 serv'
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
cmd = 'boot --image 1 --flavor 1 --num-instances 3 --max-count 3 serv'
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
def test_boot_min_max_count(self):
self.run_command('boot --image 1 --flavor 1 --max-count 3 server')
self.assert_called_anytime(
'POST', '/servers',
{
'server': {
'flavorRef': '1',
'name': 'server',
'imageRef': '1',
'min_count': 1,
'max_count': 3,
}
})
self.run_command('boot --image 1 --flavor 1 --min-count 3 server')
self.assert_called_anytime(
'POST', '/servers',
{
'server': {
'flavorRef': '1',
'name': 'server',
'imageRef': '1',
'min_count': 3,
'max_count': 3,
}
})
self.run_command('boot --image 1 --flavor 1 '
'--min-count 3 --max-count 3 server')
self.assert_called_anytime(
'POST', '/servers',
{
'server': {
'flavorRef': '1',
'name': 'server',
'imageRef': '1',
'min_count': 3,
'max_count': 3,
}
})
self.run_command('boot --image 1 --flavor 1 '
'--min-count 3 --max-count 5 server')
self.assert_called_anytime(
'POST', '/servers',
{
'server': {
'flavorRef': '1',
'name': 'server',
'imageRef': '1',
'min_count': 3,
'max_count': 5,
}
})
cmd = 'boot --image 1 --flavor 1 --min-count 3 --max-count 1 serv'
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
@mock.patch('novaclient.v1_1.shell._poll_for_status')
def test_boot_with_poll(self, poll_method):
self.run_command('boot --flavor 1 --image 1 some-server --poll')
@ -581,6 +657,31 @@ class ShellTest(utils.TestCase):
[mock.call(self.shell.cs.servers.get, 1234, 'building',
['active'])])
def test_boot_with_poll_to_check_VM_state_error(self):
self.assertRaises(exceptions.InstanceInErrorState, self.run_command,
'boot --flavor 1 --image 1 some-bad-server --poll')
def test_boot_named_flavor(self):
self.run_command(["boot", "--image", "1",
"--flavor", "512 MB Server",
"--max-count", "3", "server"])
self.assert_called('GET', '/images/1', pos=0)
self.assert_called('GET', '/flavors/512 MB Server', pos=1)
self.assert_called('GET', '/flavors?is_public=None', pos=2)
self.assert_called('GET', '/flavors?is_public=None', pos=3)
self.assert_called('GET', '/flavors/2', pos=4)
self.assert_called(
'POST', '/servers',
{
'server': {
'flavorRef': '2',
'name': 'server',
'imageRef': '1',
'min_count': 1,
'max_count': 3,
}
}, pos=5)
def test_flavor_list(self):
self.run_command('flavor-list')
self.assert_called_anytime('GET', '/flavors/detail')
@ -602,6 +703,22 @@ class ShellTest(utils.TestCase):
self.run_command('flavor-show aa1')
self.assert_called_anytime('GET', '/flavors/aa1')
def test_flavor_show_by_name(self):
self.run_command(['flavor-show', '128 MB Server'])
self.assert_called('GET', '/flavors/128 MB Server', pos=0)
self.assert_called('GET', '/flavors?is_public=None', pos=1)
self.assert_called('GET', '/flavors?is_public=None', pos=2)
self.assert_called('GET', '/flavors/aa1', pos=3)
self.assert_called('GET', '/flavors/aa1/os-extra_specs', pos=4)
def test_flavor_show_by_name_priv(self):
self.run_command(['flavor-show', '512 MB Server'])
self.assert_called('GET', '/flavors/512 MB Server', pos=0)
self.assert_called('GET', '/flavors?is_public=None', pos=1)
self.assert_called('GET', '/flavors?is_public=None', pos=2)
self.assert_called('GET', '/flavors/2', pos=3)
self.assert_called('GET', '/flavors/2/os-extra_specs', pos=4)
def test_flavor_key_set(self):
self.run_command('flavor-key 1 set k1=v1')
self.assert_called('POST', '/flavors/1/os-extra_specs',
@ -735,43 +852,37 @@ class ShellTest(utils.TestCase):
{'reboot': {'type': 'HARD'}})
def test_rebuild(self):
self.run_command('rebuild sample-server 1')
self.assert_called('GET', '/servers', pos=-8)
self.assert_called('GET', '/servers/1234', pos=-7)
self.assert_called('GET', '/images/1', pos=-6)
output = self.run_command('rebuild sample-server 1')
self.assert_called('GET', '/servers', pos=-6)
self.assert_called('GET', '/servers/1234', pos=-5)
self.assert_called('GET', '/images/1', pos=-4)
self.assert_called('POST', '/servers/1234/action',
{'rebuild': {'imageRef': 1}}, pos=-5)
{'rebuild': {'imageRef': 1}}, pos=-3)
self.assert_called('GET', '/flavors/1', pos=-2)
self.assert_called('GET', '/images/2')
self.assertIn('adminPass', output)
self.run_command('rebuild sample-server 1 --rebuild-password asdf')
self.assert_called('GET', '/servers', pos=-8)
self.assert_called('GET', '/servers/1234', pos=-7)
self.assert_called('GET', '/images/1', pos=-6)
def test_rebuild_password(self):
output = self.run_command('rebuild sample-server 1'
' --rebuild-password asdf')
self.assert_called('GET', '/servers', pos=-6)
self.assert_called('GET', '/servers/1234', pos=-5)
self.assert_called('GET', '/images/1', pos=-4)
self.assert_called('POST', '/servers/1234/action',
{'rebuild': {'imageRef': 1, 'adminPass': 'asdf'}},
pos=-5)
pos=-3)
self.assert_called('GET', '/flavors/1', pos=-2)
self.assert_called('GET', '/images/2')
self.assertIn('adminPass', output)
def test_rebuild_preserve_ephemeral(self):
self.run_command('rebuild sample-server 1 --preserve-ephemeral')
self.assert_called('GET', '/servers', pos=-8)
self.assert_called('GET', '/servers/1234', pos=-7)
self.assert_called('GET', '/images/1', pos=-6)
self.assert_called('GET', '/servers', pos=-6)
self.assert_called('GET', '/servers/1234', pos=-5)
self.assert_called('GET', '/images/1', pos=-4)
self.assert_called('POST', '/servers/1234/action',
{'rebuild': {'imageRef': 1,
'preserve_ephemeral': True}}, pos=-5)
self.assert_called('GET', '/flavors/1', pos=-2)
self.assert_called('GET', '/images/2')
self.run_command('rebuild sample-server 1 --rebuild-password asdf')
self.assert_called('GET', '/servers', pos=-8)
self.assert_called('GET', '/servers/1234', pos=-7)
self.assert_called('GET', '/images/1', pos=-6)
self.assert_called('POST', '/servers/1234/action',
{'rebuild': {'imageRef': 1, 'adminPass': 'asdf'}},
pos=-5)
'preserve_ephemeral': True}}, pos=-3)
self.assert_called('GET', '/flavors/1', pos=-2)
self.assert_called('GET', '/images/2')
@ -1039,6 +1150,10 @@ class ShellTest(utils.TestCase):
self.run_command('floating-ip-list')
self.assert_called('GET', '/os-floating-ips')
def test_floating_ip_list_all_tenants(self):
self.run_command('floating-ip-list --all-tenants')
self.assert_called('GET', '/os-floating-ips?all_tenants=1')
def test_floating_ip_create(self):
self.run_command('floating-ip-create')
self.assert_called('GET', '/os-floating-ips/1')
@ -1251,6 +1366,20 @@ class ShellTest(utils.TestCase):
self.assert_called('POST', '/servers/1234/action',
{'os-resetState': {'state': 'active'}})
def test_reset_state_multiple(self):
self.run_command('reset-state sample-server sample-server2')
self.assert_called('POST', '/servers/1234/action',
{'os-resetState': {'state': 'error'}}, pos=-4)
self.assert_called('POST', '/servers/5678/action',
{'os-resetState': {'state': 'error'}}, pos=-1)
def test_reset_state_active_multiple(self):
self.run_command('reset-state --active sample-server sample-server2')
self.assert_called('POST', '/servers/1234/action',
{'os-resetState': {'state': 'active'}}, pos=-4)
self.assert_called('POST', '/servers/5678/action',
{'os-resetState': {'state': 'active'}}, pos=-1)
def test_reset_network(self):
self.run_command('reset-network sample-server')
self.assert_called('POST', '/servers/1234/action',
@ -1288,6 +1417,10 @@ class ShellTest(utils.TestCase):
'disabled_reason': 'no_reason'}
self.assert_called('PUT', '/os-services/disable-log-reason', body)
def test_services_delete(self):
self.run_command('service-delete 1')
self.assert_called('DELETE', '/os-services/1')
def test_fixed_ips_get(self):
self.run_command('fixed-ip-get 192.168.1.1')
self.assert_called('GET', '/os-fixed-ips/192.168.1.1')
@ -1518,11 +1651,22 @@ class ShellTest(utils.TestCase):
self.assert_called('GET', '/os-quota-class-sets/test')
def test_quota_class_update(self):
self.run_command('quota-class-update 97f4c221bff44578b0300df4ef119353'
' --instances=5')
self.assert_called('PUT',
'/os-quota-class-sets/97f4c221bff44578b0300'
'df4ef119353')
# The list of args we can update.
args = (
'--instances', '--cores', '--ram', '--floating-ips', '--fixed-ips',
'--metadata-items', '--injected-files',
'--injected-file-content-bytes', '--injected-file-path-bytes',
'--key-pairs', '--security-groups', '--security-group-rules'
)
for arg in args:
self.run_command('quota-class-update '
'97f4c221bff44578b0300df4ef119353 '
'%s=5' % arg)
request_param = arg[2:].replace('-', '_')
body = {'quota_class_set': {request_param: 5}}
self.assert_called(
'PUT', '/os-quota-class-sets/97f4c221bff44578b0300df4ef119353',
body)
def test_network_list(self):
self.run_command('network-list')
@ -1613,7 +1757,14 @@ class ShellTest(utils.TestCase):
self.run_command('network-create --fixed-range-v4 192.168.0.0/24'
' --vlan=200 new_network')
body = {'network': {'cidr': '192.168.0.0/24', 'label': 'new_network',
'vlan_start': '200'}}
'vlan': '200'}}
self.assert_called('POST', '/os-networks', body)
def test_network_create_vlan_start(self):
self.run_command('network-create --fixed-range-v4 192.168.0.0/24'
' --vlan-start=100 new_network')
body = {'network': {'cidr': '192.168.0.0/24', 'label': 'new_network',
'vlan_start': '100'}}
self.assert_called('POST', '/os-networks', body)
def test_add_fixed_ip(self):
@ -1918,6 +2069,45 @@ class ShellTest(utils.TestCase):
mock_system.assert_called_with("ssh -6 -p22 "
"root@2607:f0d0:1002::4 -1")
def test_keypair_add(self):
self.run_command('keypair-add test')
self.assert_called('POST', '/os-keypairs',
{'keypair':
{'name': 'test'}})
@mock.patch.object(builtins, 'open',
mock.mock_open(read_data='FAKE_PUBLIC_KEY'))
def test_keypair_import(self):
self.run_command('keypair-add --pub-key test.pub test')
self.assert_called('POST', '/os-keypairs',
{'keypair':
{'public_key': 'FAKE_PUBLIC_KEY',
'name': 'test'}})
def test_keypair_list(self):
self.run_command('keypair-list')
self.assert_called('GET', '/os-keypairs')
def test_keypair_show(self):
self.run_command('keypair-show test')
self.assert_called('GET', '/os-keypairs/test')
def test_keypair_delete(self):
self.run_command('keypair-delete test')
self.assert_called('DELETE', '/os-keypairs/test')
def test_create_server_group(self):
self.run_command('server-group-create wjsg affinity')
self.assert_called('POST', '/os-server-groups',
{'server_group':
{'name': 'wjsg',
'policies': ['affinity']}})
def test_delete_multi_server_groups(self):
self.run_command('server-group-delete 12345 56789')
self.assert_called('DELETE', '/os-server-groups/56789')
self.assert_called('DELETE', '/os-server-groups/12345', pos=-2)
class GetSecgroupTest(utils.TestCase):
def test_with_integer(self):
@ -1949,3 +2139,15 @@ class GetSecgroupTest(utils.TestCase):
novaclient.v1_1.shell._get_secgroup,
cs,
'abc')
def test_with_non_unique_name(self):
group_one = mock.MagicMock()
group_one.name = 'group_one'
cs = mock.Mock(**{
'security_groups.get.return_value': 'sec_group',
'security_groups.list.return_value': [group_one, group_one],
})
self.assertRaises(exceptions.NoUniqueMatch,
novaclient.v1_1.shell._get_secgroup,
cs,
'group_one')

View File

@ -120,6 +120,10 @@ class FakeHTTPClient(fakes_v1_1.FakeHTTPClient):
#
get_flavors_2_flavor_access = (
fakes_v1_1.FakeHTTPClient.get_flavors_2_os_flavor_access)
get_flavors_2_flavor_extra_specs = (
fakes_v1_1.FakeHTTPClient.get_flavors_2_os_extra_specs)
get_flavors_aa1_flavor_extra_specs = (
fakes_v1_1.FakeHTTPClient.get_flavors_aa1_os_extra_specs)
#
# Images
@ -170,7 +174,10 @@ class FakeHTTPClient(fakes_v1_1.FakeHTTPClient):
required=['name', 'image_ref', 'flavor_ref'],
optional=['metadata', 'personality',
'os-scheduler-hints:scheduler_hints'])
return (202, {}, self.get_servers_1234()[2])
if body['server']['name'] == 'some-bad-server':
return (202, {}, self.get_servers_1235()[2])
else:
return (202, {}, self.get_servers_1234()[2])
#
# Server Actions
@ -204,9 +211,10 @@ class FakeHTTPClient(fakes_v1_1.FakeHTTPClient):
'create_image': ['name', 'metadata'],
'migrate_live': ['host', 'block_migration', 'disk_over_commit'],
'create_backup': ['name', 'backup_type', 'rotation'],
'attach': ['volume_id', 'device'],
'detach': ['volume_id'],
'swap_volume_attachment': ['old_volume_id', 'new_volume_id']}
body_params_check_superset = {
'attach': ['volume_id', 'device']}
assert len(body.keys()) == 1
action = list(body)[0]
@ -224,6 +232,9 @@ class FakeHTTPClient(fakes_v1_1.FakeHTTPClient):
if action in body_params_check_exact:
assert set(body[action]) == set(body_params_check_exact[action])
if action in body_params_check_superset:
assert set(body[action]) >= set(body_params_check_superset[action])
if action == 'reboot':
assert body[action]['type'] in ['HARD', 'SOFT']
elif action == 'confirm_resize':
@ -234,7 +245,8 @@ class FakeHTTPClient(fakes_v1_1.FakeHTTPClient):
if action not in set.union(set(body_is_none_list),
set(body_params_check_exact.keys()),
set(body_param_check_exists.keys())):
set(body_param_check_exists.keys()),
set(body_params_check_superset.keys())):
raise AssertionError("Unexpected server action: %s" % action)
return (resp, _headers, _body)
@ -329,8 +341,8 @@ class FakeHTTPClient(fakes_v1_1.FakeHTTPClient):
{'id': 1234,
'hypervisor_hostname': 'hyper1',
'servers': [
{'name': 'inst1', 'uuid': 'uuid1'},
{'name': 'inst2', 'uuid': 'uuid2'}
{'name': 'inst1', 'id': 'uuid1'},
{'name': 'inst2', 'id': 'uuid2'}
]},
})
@ -341,3 +353,31 @@ class FakeHTTPClient(fakes_v1_1.FakeHTTPClient):
get_keypairs = fakes_v1_1.FakeHTTPClient.get_os_keypairs
delete_keypairs_test = fakes_v1_1.FakeHTTPClient.delete_os_keypairs_test
post_keypairs = fakes_v1_1.FakeHTTPClient.post_os_keypairs
#
# List all extensions
#
def get_extensions(self, **kw):
exts = [
{
"alias": "os-multinic",
"description": "Multiple network support",
"name": "Multinic",
"version": 1,
},
{
"alias": "os-extended-server-attributes",
"description": "Extended Server Attributes support.",
"name": "ExtendedServerAttributes",
"version": 1,
},
{
"alias": "os-extended-status",
"description": "Extended Status support",
"name": "ExtendedStatus",
"version": 1,
},
]
return (200, {}, {
"extensions": exts,
})

View File

@ -13,20 +13,17 @@
# License for the specific language governing permissions and limitations
# under the License.
from novaclient.tests.fixture_data import client
from novaclient.tests.v1_1 import test_agents
from novaclient.tests.v3 import fakes
from novaclient.v3 import agents
class AgentsTest(test_agents.AgentsTest):
scenarios = [('original', {'client_fixture_class': client.V3}),
('session', {'client_fixture_class': client.SessionV3})]
def _build_example_update_body(self):
return {"agent": {
"url": "/yyy/yyyy/yyyy",
"version": "8.0",
"md5hash": "add6bb58e139be103324d04d82d8f546"}}
def _get_fake_client(self):
return fakes.FakeClient()
def _get_agent_type(self):
return agents.Agent

View File

@ -12,19 +12,11 @@
# License for the specific language governing permissions and limitations
# under the License.
from novaclient.tests.fixture_data import client
from novaclient.tests.v1_1 import test_aggregates
from novaclient.tests.v3 import fakes
from novaclient.v3 import aggregates
class AggregatesTest(test_aggregates.AggregatesTest):
def setUp(self):
super(AggregatesTest, self).setUp()
self.cs = self._get_fake_client()
self.aggregate_type = self._get_aggregate_type()
def _get_fake_client(self):
return fakes.FakeClient()
def _get_aggregate_type(self):
return aggregates.Aggregate
scenarios = [('original', {'client_fixture_class': client.V3}),
('session', {'client_fixture_class': client.SessionV3})]

View File

@ -14,25 +14,23 @@
# License for the specific language governing permissions and limitations
# under the License.
from novaclient.tests.fixture_data import availability_zones as data
from novaclient.tests.fixture_data import client
from novaclient.tests.v1_1 import test_availability_zone
from novaclient.tests.v3 import fakes
from novaclient.v3 import availability_zones
class AvailabilityZoneTest(test_availability_zone.AvailabilityZoneTest):
from novaclient.v3 import shell # noqa
def setUp(self):
super(AvailabilityZoneTest, self).setUp()
self.cs = self._get_fake_client()
self.availability_zone_type = self._get_availability_zone_type()
data_fixture_class = data.V3
scenarios = [('original', {'client_fixture_class': client.V3}),
('session', {'client_fixture_class': client.SessionV3})]
def _assertZone(self, zone, name, status):
self.assertEqual(zone.zone_name, name)
self.assertEqual(zone.zone_state, status)
def _get_fake_client(self):
return fakes.FakeClient()
def _get_availability_zone_type(self):
return availability_zones.AvailabilityZone

View File

@ -11,19 +11,11 @@
# License for the specific language governing permissions and limitations
# under the License.
from novaclient.tests.v1_1 import fakes
from novaclient.tests.fixture_data import client
from novaclient.tests.v1_1 import test_certs
from novaclient.v3 import certs
class CertsTest(test_certs.CertsTest):
def setUp(self):
super(CertsTest, self).setUp()
self.cs = self._get_fake_client()
self.cert_type = self._get_cert_type()
def _get_fake_client(self):
return fakes.FakeClient()
def _get_cert_type(self):
return certs.Certificate
scenarios = [('original', {'client_fixture_class': client.V3}),
('session', {'client_fixture_class': client.SessionV3})]

View File

@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import mock
from novaclient.tests.v1_1 import test_flavors
from novaclient.tests.v3 import fakes
from novaclient.v3 import flavors
@ -57,10 +59,15 @@ class FlavorsTest(test_flavors.FlavorsTest):
self.cs.assert_called('POST', '/flavors/4/flavor-extra-specs',
{"extra_specs": {key: 'v4'}})
def test_unset_keys(self):
@mock.patch.object(flavors.FlavorManager, '_delete')
def test_unset_keys(self, mock_delete):
f = self.cs.flavors.get(1)
f.unset_keys(['k1'])
self.cs.assert_called('DELETE', '/flavors/1/flavor-extra-specs/k1')
keys = ['k1', 'k2']
f.unset_keys(keys)
mock_delete.assert_has_calls([
mock.call("/flavors/1/flavor-extra-specs/k1"),
mock.call("/flavors/1/flavor-extra-specs/k2")
])
def test_get_flavor_details_diablo(self):
# Don't need for V3 API to work against diablo

View File

@ -12,72 +12,73 @@
# License for the specific language governing permissions and limitations
# under the License.
from novaclient.tests.fixture_data import client
from novaclient.tests.fixture_data import hosts as data
from novaclient.tests import utils
from novaclient.tests.v3 import fakes
from novaclient.v3 import hosts
cs = fakes.FakeClient()
class HostsTest(utils.FixturedTestCase):
class HostsTest(utils.TestCase):
client_fixture_class = client.V3
data_fixture_class = data.V3
def test_describe_resource(self):
hs = cs.hosts.get('host')
cs.assert_called('GET', '/os-hosts/host')
hs = self.cs.hosts.get('host')
self.assert_called('GET', '/os-hosts/host')
for h in hs:
self.assertIsInstance(h, hosts.Host)
def test_list_host(self):
hs = cs.hosts.list()
cs.assert_called('GET', '/os-hosts')
hs = self.cs.hosts.list()
self.assert_called('GET', '/os-hosts')
for h in hs:
self.assertIsInstance(h, hosts.Host)
self.assertEqual(h.zone, 'nova1')
def test_list_host_with_zone(self):
hs = cs.hosts.list('nova')
cs.assert_called('GET', '/os-hosts?zone=nova')
hs = self.cs.hosts.list('nova')
self.assert_called('GET', '/os-hosts?zone=nova')
for h in hs:
self.assertIsInstance(h, hosts.Host)
self.assertEqual(h.zone, 'nova')
def test_update_enable(self):
host = cs.hosts.get('sample_host')[0]
host = self.cs.hosts.get('sample_host')[0]
values = {"status": "enabled"}
result = host.update(values)
cs.assert_called('PUT', '/os-hosts/sample_host', {"host": values})
self.assert_called('PUT', '/os-hosts/sample_host', {"host": values})
self.assertIsInstance(result, hosts.Host)
def test_update_maintenance(self):
host = cs.hosts.get('sample_host')[0]
host = self.cs.hosts.get('sample_host')[0]
values = {"maintenance_mode": "enable"}
result = host.update(values)
cs.assert_called('PUT', '/os-hosts/sample_host', {"host": values})
self.assert_called('PUT', '/os-hosts/sample_host', {"host": values})
self.assertIsInstance(result, hosts.Host)
def test_update_both(self):
host = cs.hosts.get('sample_host')[0]
host = self.cs.hosts.get('sample_host')[0]
values = {"status": "enabled",
"maintenance_mode": "enable"}
result = host.update(values)
cs.assert_called('PUT', '/os-hosts/sample_host', {"host": values})
self.assert_called('PUT', '/os-hosts/sample_host', {"host": values})
self.assertIsInstance(result, hosts.Host)
def test_host_startup(self):
host = cs.hosts.get('sample_host')[0]
result = host.startup()
cs.assert_called(
host = self.cs.hosts.get('sample_host')[0]
host.startup()
self.assert_called(
'GET', '/os-hosts/sample_host/startup')
def test_host_reboot(self):
host = cs.hosts.get('sample_host')[0]
result = host.reboot()
cs.assert_called(
host = self.cs.hosts.get('sample_host')[0]
host.reboot()
self.assert_called(
'GET', '/os-hosts/sample_host/reboot')
def test_host_shutdown(self):
host = cs.hosts.get('sample_host')[0]
result = host.shutdown()
cs.assert_called(
host = self.cs.hosts.get('sample_host')[0]
host.shutdown()
self.assert_called(
'GET', '/os-hosts/sample_host/shutdown')

View File

@ -13,17 +13,15 @@
# License for the specific language governing permissions and limitations
# under the License.
from novaclient.tests.fixture_data import client
from novaclient.tests.fixture_data import hypervisors as data
from novaclient.tests.v1_1 import test_hypervisors
from novaclient.tests.v3 import fakes
class HypervisorsTest(test_hypervisors.HypervisorsTest):
def setUp(self):
super(HypervisorsTest, self).setUp()
self.cs = self._get_fake_client()
def _get_fake_client(self):
return fakes.FakeClient()
client_fixture_class = client.V3
data_fixture_class = data.V3
def test_hypervisor_search(self):
expected = [
@ -32,7 +30,7 @@ class HypervisorsTest(test_hypervisors.HypervisorsTest):
]
result = self.cs.hypervisors.search('hyper')
self.cs.assert_called('GET', '/os-hypervisors/search?query=hyper')
self.assert_called('GET', '/os-hypervisors/search?query=hyper')
for idx, hyper in enumerate(result):
self.compare_to_expected(expected[idx], hyper)
@ -41,10 +39,10 @@ class HypervisorsTest(test_hypervisors.HypervisorsTest):
expected = dict(id=1234,
hypervisor_hostname='hyper1',
servers=[
dict(name='inst1', uuid='uuid1'),
dict(name='inst2', uuid='uuid2')])
dict(name='inst1', id='uuid1'),
dict(name='inst2', id='uuid2')])
result = self.cs.hypervisors.servers('1234')
self.cs.assert_called('GET', '/os-hypervisors/1234/servers')
self.assert_called('GET', '/os-hypervisors/1234/servers')
self.compare_to_expected(expected, result)

View File

@ -13,45 +13,45 @@
# under the License.
from novaclient.tests.fixture_data import client
from novaclient.tests.fixture_data import images as data
from novaclient.tests import utils
from novaclient.tests.v3 import fakes
from novaclient.v3 import images
cs = fakes.FakeClient()
class ImagesTest(utils.FixturedTestCase):
class ImagesTest(utils.TestCase):
client_fixture_class = client.V3
data_fixture_class = data.V3
def test_list_images(self):
il = cs.images.list()
cs.assert_called('GET', '/v1/images/detail')
il = self.cs.images.list()
self.assert_called('GET', '/v1/images/detail')
for i in il:
self.assertIsInstance(i, images.Image)
def test_list_images_undetailed(self):
il = cs.images.list(detailed=False)
cs.assert_called('GET', '/v1/images')
il = self.cs.images.list(detailed=False)
self.assert_called('GET', '/v1/images')
for i in il:
self.assertIsInstance(i, images.Image)
def test_list_images_with_limit(self):
il = cs.images.list(limit=4)
cs.assert_called('GET', '/v1/images/detail?limit=4')
self.cs.images.list(limit=4)
self.assert_called('GET', '/v1/images/detail?limit=4')
def test_get_image_details(self):
i = cs.images.get(1)
cs.assert_called('HEAD', '/v1/images/1')
i = self.cs.images.get(1)
self.assert_called('HEAD', '/v1/images/1')
self.assertIsInstance(i, images.Image)
self.assertEqual(i.id, '1')
self.assertEqual(i.name, 'CentOS 5.2')
def test_find(self):
i = cs.images.find(name="CentOS 5.2")
i = self.cs.images.find(name="CentOS 5.2")
self.assertEqual(i.id, '1')
cs.assert_called('GET', '/v1/images', pos=-2)
cs.assert_called('HEAD', '/v1/images/1', pos=-1)
self.assert_called('HEAD', '/v1/images/1')
iml = cs.images.findall(status='SAVING')
iml = self.cs.images.findall(status='SAVING')
self.assertEqual(len(iml), 1)
self.assertEqual(iml[0].name, 'My Server Backup')

View File

@ -12,20 +12,19 @@
# License for the specific language governing permissions and limitations
# under the License.
from novaclient.tests.fixture_data import client
from novaclient.tests.fixture_data import keypairs as data
from novaclient.tests.v1_1 import test_keypairs
from novaclient.tests.v3 import fakes
from novaclient.v3 import keypairs
class KeypairsTest(test_keypairs.KeypairsTest):
def setUp(self):
super(KeypairsTest, self).setUp()
self.cs = self._get_fake_client()
self.keypair_type = self._get_keypair_type()
self.keypair_prefix = keypairs.KeypairManager.keypair_prefix
def _get_fake_client(self):
return fakes.FakeClient()
client_fixture_class = client.V3
data_fixture_class = data.V3
def _get_keypair_type(self):
return keypairs.Keypair
def _get_keypair_prefix(self):
return keypairs.KeypairManager.keypair_prefix

View File

@ -0,0 +1,33 @@
# Copyright 2014 NEC Corporation. All rights reserved.
#
# 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.
from novaclient import extension
from novaclient.tests import utils
from novaclient.tests.v3 import fakes
from novaclient.v3 import list_extensions
extensions = [
extension.Extension("list_extensions", list_extensions),
]
cs = fakes.FakeClient(extensions=extensions)
class ListExtensionsTests(utils.TestCase):
def test_list_extensions(self):
all_exts = cs.list_extensions.show_all()
cs.assert_called('GET', '/extensions')
self.assertTrue(len(all_exts) > 0)
for r in all_exts:
self.assertTrue(len(r.summary) > 0)

View File

@ -12,22 +12,20 @@
# License for the specific language governing permissions and limitations
# under the License.
from novaclient.tests.fixture_data import client
from novaclient.tests.fixture_data import quotas as data
from novaclient.tests.v1_1 import test_quotas
from novaclient.tests.v3 import fakes
class QuotaSetsTest(test_quotas.QuotaSetsTest):
def setUp(self):
super(QuotaSetsTest, self).setUp()
self.cs = self._get_fake_client()
def _get_fake_client(self):
return fakes.FakeClient()
client_fixture_class = client.V3
data_fixture_class = data.V3
def test_force_update_quota(self):
q = self.cs.quotas.get('97f4c221bff44578b0300df4ef119353')
q.update(cores=2, force=True)
self.cs.assert_called(
self.assert_called(
'PUT', '/os-quota-sets/97f4c221bff44578b0300df4ef119353',
{'quota_set': {'force': True,
'cores': 2}})
@ -35,11 +33,11 @@ class QuotaSetsTest(test_quotas.QuotaSetsTest):
def test_tenant_quotas_get_detail(self):
tenant_id = 'test'
self.cs.quotas.get(tenant_id, detail=True)
self.cs.assert_called('GET', '/os-quota-sets/%s/detail' % tenant_id)
self.assert_called('GET', '/os-quota-sets/%s/detail' % tenant_id)
def test_user_quotas_get_detail(self):
tenant_id = 'test'
user_id = 'fake_user'
self.cs.quotas.get(tenant_id, user_id=user_id, detail=True)
url = '/os-quota-sets/%s/detail?user_id=%s' % (tenant_id, user_id)
self.cs.assert_called('GET', url)
self.assert_called('GET', url)

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import mock
import six
from novaclient import exceptions
@ -74,6 +75,50 @@ class ServersTest(utils.TestCase):
cs.assert_called('POST', '/servers')
self.assertIsInstance(s, servers.Server)
def test_create_server_boot_with_nics_ipv4(self):
old_boot = cs.servers._boot
nics = [{'net-id': '11111111-1111-1111-1111-111111111111',
'v4-fixed-ip': '10.10.0.7'}]
def wrapped_boot(url, key, *boot_args, **boot_kwargs):
self.assertEqual(boot_kwargs['nics'], nics)
return old_boot(url, key, *boot_args, **boot_kwargs)
with mock.patch.object(cs.servers, '_boot', wrapped_boot):
s = cs.servers.create(
name="My server",
image=1,
flavor=1,
meta={'foo': 'bar'},
userdata="hello moto",
key_name="fakekey",
nics=nics
)
cs.assert_called('POST', '/servers')
self.assertIsInstance(s, servers.Server)
def test_create_server_boot_with_nics_ipv6(self):
old_boot = cs.servers._boot
nics = [{'net-id': '11111111-1111-1111-1111-111111111111',
'v6-fixed-ip': '2001:db9:0:1::10'}]
def wrapped_boot(url, key, *boot_args, **boot_kwargs):
self.assertEqual(boot_kwargs['nics'], nics)
return old_boot(url, key, *boot_args, **boot_kwargs)
with mock.patch.object(cs.servers, '_boot', wrapped_boot):
s = cs.servers.create(
name="My server",
image=1,
flavor=1,
meta={'foo': 'bar'},
userdata="hello moto",
key_name="fakekey",
nics=nics
)
cs.assert_called('POST', '/servers')
self.assertIsInstance(s, servers.Server)
def test_create_server_userdata_file_object(self):
s = cs.servers.create(
name="My server",
@ -121,6 +166,26 @@ class ServersTest(utils.TestCase):
cs.assert_called('POST', '/servers')
self.assertIsInstance(s, servers.Server)
def test_create_server_return_reservation_id(self):
s = cs.servers.create(
name="My server",
image=1,
flavor=1,
reservation_id=True
)
expected_body = {
'server': {
'name': 'My server',
'image_ref': '1',
'flavor_ref': '1',
'os-multiple-create:min_count': 1,
'os-multiple-create:max_count': 1,
'os-multiple-create:return_reservation_id': True,
}
}
cs.assert_called('POST', '/servers', expected_body)
self.assertIsInstance(s, servers.Server)
def test_update_server(self):
s = cs.servers.get(1234)
@ -147,12 +212,12 @@ class ServersTest(utils.TestCase):
cs.assert_called('DELETE', '/servers/1234')
def test_delete_server_meta(self):
s = cs.servers.delete_meta(1234, ['test_key'])
cs.servers.delete_meta(1234, ['test_key'])
cs.assert_called('DELETE', '/servers/1234/metadata/test_key')
def test_set_server_meta(self):
s = cs.servers.set_meta(1234, {'test_key': 'test_value'})
reval = cs.assert_called('POST', '/servers/1234/metadata',
cs.servers.set_meta(1234, {'test_key': 'test_value'})
cs.assert_called('POST', '/servers/1234/metadata',
{'metadata': {'test_key': 'test_value'}})
def test_find(self):
@ -299,7 +364,8 @@ class ServersTest(utils.TestCase):
cs.servers.get_console_output(s)
self.assertEqual(cs.servers.get_console_output(s), success)
cs.assert_called('POST', '/servers/1234/action')
cs.assert_called('POST', '/servers/1234/action',
{'get_console_output': {'length': -1}})
def test_get_console_output_with_length(self):
success = 'foo'
@ -307,11 +373,13 @@ class ServersTest(utils.TestCase):
s = cs.servers.get(1234)
s.get_console_output(length=50)
self.assertEqual(s.get_console_output(length=50), success)
cs.assert_called('POST', '/servers/1234/action')
cs.assert_called('POST', '/servers/1234/action',
{'get_console_output': {'length': 50}})
cs.servers.get_console_output(s, length=50)
self.assertEqual(cs.servers.get_console_output(s, length=50), success)
cs.assert_called('POST', '/servers/1234/action')
cs.assert_called('POST', '/servers/1234/action',
{'get_console_output': {'length': 50}})
def test_get_password(self):
s = cs.servers.get(1234)

View File

@ -186,20 +186,6 @@ class ShellTest(utils.TestCase):
}},
)
def test_boot_multiple(self):
self.run_command('boot --flavor 1 --image 1'
' --num-instances 3 some-server')
self.assert_called_anytime(
'POST', '/servers',
{'server': {
'flavor_ref': '1',
'name': 'some-server',
'image_ref': '1',
'os-multiple-create:min_count': 1,
'os-multiple-create:max_count': 3,
}},
)
def test_boot_image_with(self):
self.run_command("boot --flavor 1"
" --image-with test_key=test_value some-server")
@ -240,6 +226,7 @@ class ShellTest(utils.TestCase):
mock_open.assert_called_once_with(testfile)
user_data = base64.b64encode(file_text.encode('utf-8')).decode('utf-8')
self.assert_called_anytime(
'POST', '/servers',
{'server': {
@ -248,9 +235,7 @@ class ShellTest(utils.TestCase):
'image_ref': '1',
'os-multiple-create:min_count': 1,
'os-multiple-create:max_count': 1,
'os-user-data:user_data': base64.b64encode(
file_text.encode('utf-8'))
}},
'os-user-data:user_data': user_data}},
)
def test_boot_avzone(self):
@ -428,7 +413,33 @@ class ShellTest(utils.TestCase):
},
)
def tets_boot_nics_no_value(self):
def test_boot_nics_ipv6(self):
cmd = ('boot --image 1 --flavor 1 '
'--nic net-id=a=c,v6-fixed-ip=2001:db9:0:1::10 some-server')
self.run_command(cmd)
self.assert_called_anytime(
'POST', '/servers',
{
'server': {
'flavor_ref': '1',
'name': 'some-server',
'image_ref': '1',
'os-multiple-create:min_count': 1,
'os-multiple-create:max_count': 1,
'networks': [
{'uuid': 'a=c', 'fixed_ip': '2001:db9:0:1::10'},
],
},
},
)
def test_boot_nics_both_ipv4_and_ipv6(self):
cmd = ('boot --image 1 --flavor 1 '
'--nic net-id=a=c,v4-fixed-ip=10.0.0.1,'
'v6-fixed-ip=2001:db9:0:1::10 some-server')
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
def test_boot_nics_no_value(self):
cmd = ('boot --image 1 --flavor 1 '
'--nic net-id some-server')
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
@ -443,6 +454,11 @@ class ShellTest(utils.TestCase):
'--nic v4-fixed-ip=10.0.0.1 some-server')
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
def test_boot_nics_netid_and_portid(self):
cmd = ('boot --image 1 --flavor 1 '
'--nic port-id=some=port,net-id=some=net some-server')
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
def test_boot_num_instances(self):
self.run_command('boot --image 1 --flavor 1 --num-instances 3 server')
self.assert_called_anytime(
@ -458,11 +474,69 @@ class ShellTest(utils.TestCase):
})
def test_boot_invalid_num_instances(self):
cmd = 'boot --image 1 --flavor 1 --num-instances 1 server'
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
cmd = 'boot --image 1 --flavor 1 --num-instances 0 server'
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
def test_boot_num_instances_and_count(self):
cmd = 'boot --image 1 --flavor 1 --num-instances 3 --min-count 3 serv'
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
cmd = 'boot --image 1 --flavor 1 --num-instances 3 --max-count 3 serv'
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
def test_boot_min_max_count(self):
self.run_command('boot --image 1 --flavor 1 --max-count 3 server')
self.assert_called_anytime(
'POST', '/servers',
{
'server': {
'flavor_ref': '1',
'name': 'server',
'image_ref': '1',
'os-multiple-create:min_count': 1,
'os-multiple-create:max_count': 3,
}
})
self.run_command('boot --image 1 --flavor 1 --min-count 3 server')
self.assert_called_anytime(
'POST', '/servers',
{
'server': {
'flavor_ref': '1',
'name': 'server',
'image_ref': '1',
'os-multiple-create:min_count': 3,
'os-multiple-create:max_count': 3,
}
})
self.run_command('boot --image 1 --flavor 1 '
'--min-count 3 --max-count 3 server')
self.assert_called_anytime(
'POST', '/servers',
{
'server': {
'flavor_ref': '1',
'name': 'server',
'image_ref': '1',
'os-multiple-create:min_count': 3,
'os-multiple-create:max_count': 3,
}
})
self.run_command('boot --image 1 --flavor 1 '
'--min-count 3 --max-count 5 server')
self.assert_called_anytime(
'POST', '/servers',
{
'server': {
'flavor_ref': '1',
'name': 'server',
'image_ref': '1',
'os-multiple-create:min_count': 3,
'os-multiple-create:max_count': 5,
}
})
cmd = 'boot --image 1 --flavor 1 --min-count 3 --max-count 1 serv'
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
@mock.patch('novaclient.v3.shell._poll_for_status')
def test_boot_with_poll(self, poll_method):
self.run_command('boot --flavor 1 --image 1 some-server --poll')
@ -480,3 +554,43 @@ class ShellTest(utils.TestCase):
poll_method.assert_has_calls(
[mock.call(self.shell.cs.servers.get, 1234, 'building',
['active'])])
def test_boot_with_poll_to_check_VM_state_error(self):
self.assertRaises(exceptions.InstanceInErrorState, self.run_command,
'boot --flavor 1 --image 1 some-bad-server --poll')
def test_boot_named_flavor(self):
self.run_command(["boot", "--image", "1",
"--flavor", "512 MB Server",
"--max-count", "3", "server"])
self.assert_called('GET', '/flavors/512 MB Server', pos=0)
self.assert_called('GET', '/flavors?is_public=None', pos=1)
self.assert_called('GET', '/flavors?is_public=None', pos=2)
self.assert_called('GET', '/flavors/2', pos=3)
self.assert_called(
'POST', '/servers',
{
'server': {
'flavor_ref': '2',
'name': 'server',
'image_ref': '1',
'os-multiple-create:min_count': 1,
'os-multiple-create:max_count': 3,
}
}, pos=4)
def test_flavor_show_by_name(self):
self.run_command(['flavor-show', '128 MB Server'])
self.assert_called('GET', '/flavors/128 MB Server', pos=0)
self.assert_called('GET', '/flavors?is_public=None', pos=1)
self.assert_called('GET', '/flavors?is_public=None', pos=2)
self.assert_called('GET', '/flavors/aa1', pos=3)
self.assert_called('GET', '/flavors/aa1/flavor-extra-specs', pos=4)
def test_flavor_show_by_name_priv(self):
self.run_command(['flavor-show', '512 MB Server'])
self.assert_called('GET', '/flavors/512 MB Server', pos=0)
self.assert_called('GET', '/flavors?is_public=None', pos=1)
self.assert_called('GET', '/flavors?is_public=None', pos=2)
self.assert_called('GET', '/flavors/2', pos=3)
self.assert_called('GET', '/flavors/2/flavor-extra-specs', pos=4)

View File

@ -26,16 +26,33 @@ class VolumesTest(utils.TestCase):
return fakes.FakeClient()
def test_attach_server_volume(self):
v = self.cs.volumes.attach_server_volume(
self.cs.volumes.attach_server_volume(
server=1234,
volume_id='15e59938-07d5-11e1-90e3-e3dffe0c5983',
device='/dev/vdb'
)
self.cs.assert_called('POST', '/servers/1234/action')
def test_attach_server_volume_disk_bus_device_type(self):
volume_id = '15e59938-07d5-11e1-90e3-e3dffe0c5983'
device = '/dev/vdb'
disk_bus = 'ide'
device_type = 'cdrom'
self.cs.volumes.attach_server_volume(server=1234,
volume_id=volume_id,
device=device,
disk_bus=disk_bus,
device_type=device_type)
body_params = {'volume_id': volume_id,
'device': device,
'disk_bus': disk_bus,
'device_type': device_type}
body = {'attach': body_params}
self.cs.assert_called('POST', '/servers/1234/action', body)
def test_update_server_volume(self):
vol_id = '15e59938-07d5-11e1-90e3-e3dffe0c5983'
v = self.cs.volumes.update_server_volume(
self.cs.volumes.update_server_volume(
server=1234,
old_volume_id='Work',
new_volume_id=vol_id

View File

@ -57,13 +57,14 @@ def get_resource_manager_extra_kwargs(f, args, allow_conflicts=False):
extra_kwargs = {}
for hook in hooks:
hook_kwargs = hook(args)
hook_name = hook.__name__
conflicting_keys = set(hook_kwargs.keys()) & set(extra_kwargs.keys())
if conflicting_keys and not allow_conflicts:
raise Exception(_("Hook '%(hook_name)s' is attempting to redefine"
" attributes '%(conflicting_keys)s'") %
{'hook_name': hook_name,
'conflicting_keys': conflicting_keys})
msg = (_("Hook '%(hook_name)s' is attempting to redefine "
"attributes '%(conflicting_keys)s'") %
{'hook_name': hook_name,
'conflicting_keys': conflicting_keys})
raise exceptions.NoUniqueMatch(msg)
extra_kwargs.update(hook_kwargs)
@ -203,7 +204,7 @@ def print_dict(d, dict_property="Property", dict_value="Value", wrap=0):
def find_resource(manager, name_or_id, **find_args):
"""Helper for the _find_* methods."""
# for str id which is not uuid (for Flavor search currently)
# for str id which is not uuid (for Flavor and Keypair search currently)
if getattr(manager, 'is_alphanum_id_allowed', False):
try:
return manager.get(name_or_id)
@ -351,7 +352,7 @@ def _load_entry_point(ep_name, name=None):
def is_integer_like(val):
"""Returns validation of a value as an integer."""
try:
value = int(val)
int(val)
return True
except (TypeError, ValueError, AttributeError):
return False

View File

@ -37,7 +37,7 @@ class CertificateManager(base.Manager):
def create(self):
"""
Create a x509 certificates for a user in tenant.
Create a x509 certificate for a user in tenant.
"""
return self._create('/os-certificates', {}, 'certificate')

View File

@ -37,6 +37,7 @@ from novaclient.v1_1 import quota_classes
from novaclient.v1_1 import quotas
from novaclient.v1_1 import security_group_rules
from novaclient.v1_1 import security_groups
from novaclient.v1_1 import server_groups
from novaclient.v1_1 import servers
from novaclient.v1_1 import services
from novaclient.v1_1 import usage
@ -61,24 +62,46 @@ class Client(object):
>>> client.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)
"""
# FIXME(jesse): project_id isn't required to authenticate
def __init__(self, username, api_key, project_id, auth_url=None,
insecure=False, timeout=None, proxy_tenant_id=None,
proxy_token=None, region_name=None,
endpoint_type='publicURL', extensions=None,
service_type='compute', service_name=None,
volume_service_name=None, timings=False,
bypass_url=None, os_cache=False, no_cache=True,
http_log_debug=False, auth_system='keystone',
auth_plugin=None, auth_token=None,
cacert=None, tenant_id=None):
def __init__(self, username=None, api_key=None, project_id=None,
auth_url=None, insecure=False, timeout=None,
proxy_tenant_id=None, proxy_token=None, region_name=None,
endpoint_type='publicURL', extensions=None,
service_type='compute', service_name=None,
volume_service_name=None, timings=False, bypass_url=None,
os_cache=False, no_cache=True, http_log_debug=False,
auth_system='keystone', auth_plugin=None, auth_token=None,
cacert=None, tenant_id=None, user_id=None,
connection_pool=False, session=None, auth=None,
completion_cache=None):
# FIXME(comstud): Rename the api_key argument above when we
# know it's not being used as keyword argument
# NOTE(cyeoh): In the novaclient context (unlike Nova) the
# project_id is not the same as the tenant_id. Here project_id
# is a name (what the Nova API often refers to as a project or
# tenant name) and tenant_id is a UUID (what the Nova API
# often refers to as a project_id or tenant_id).
password = api_key
self.projectid = project_id
self.tenant_id = tenant_id
self.user_id = user_id
self.flavors = flavors.FlavorManager(self)
self.flavor_access = flavor_access.FlavorAccessManager(self)
self.images = images.ImageManager(self)
@ -116,6 +139,7 @@ class Client(object):
self.os_cache = os_cache or not no_cache
self.availability_zones = \
availability_zones.AvailabilityZoneManager(self)
self.server_groups = server_groups.ServerGroupsManager(self)
# Add in any extensions...
if extensions:
@ -124,38 +148,66 @@ class Client(object):
setattr(self, extension.name,
extension.manager_class(self))
self.client = client.HTTPClient(username,
password,
projectid=project_id,
tenant_id=tenant_id,
auth_url=auth_url,
auth_token=auth_token,
insecure=insecure,
timeout=timeout,
auth_system=auth_system,
auth_plugin=auth_plugin,
proxy_token=proxy_token,
proxy_tenant_id=proxy_tenant_id,
region_name=region_name,
endpoint_type=endpoint_type,
service_type=service_type,
service_name=service_name,
volume_service_name=volume_service_name,
timings=timings,
bypass_url=bypass_url,
os_cache=self.os_cache,
http_log_debug=http_log_debug,
cacert=cacert)
self.client = client._construct_http_client(
username=username,
password=password,
user_id=user_id,
project_id=project_id,
tenant_id=tenant_id,
auth_url=auth_url,
auth_token=auth_token,
insecure=insecure,
timeout=timeout,
auth_system=auth_system,
auth_plugin=auth_plugin,
proxy_token=proxy_token,
proxy_tenant_id=proxy_tenant_id,
region_name=region_name,
endpoint_type=endpoint_type,
service_type=service_type,
service_name=service_name,
volume_service_name=volume_service_name,
timings=timings,
bypass_url=bypass_url,
os_cache=self.os_cache,
http_log_debug=http_log_debug,
cacert=cacert,
connection_pool=connection_pool,
session=session,
auth=auth)
self.completion_cache = completion_cache
def write_object_to_completion_cache(self, obj):
if self.completion_cache:
self.completion_cache.write_object(obj)
def clear_completion_cache_for_class(self, obj_class):
if self.completion_cache:
self.completion_cache.clear_class(obj_class)
@client._original_only
def __enter__(self):
self.client.open_session()
return self
@client._original_only
def __exit__(self, t, v, tb):
self.client.close_session()
@client._original_only
def set_management_url(self, url):
self.client.set_management_url(url)
@client._original_only
def get_timings(self):
return self.client.get_timings()
@client._original_only
def reset_timings(self):
self.client.reset_timings()
@client._original_only
def authenticate(self):
"""
Authenticate against the server.

View File

@ -194,7 +194,7 @@ def do_baremetal_node_create(cs, args):
@utils.arg('node',
metavar='<node>',
help='ID of the node to delete.')
help=_('ID of the node to delete.'))
def do_baremetal_node_delete(cs, args):
"""Remove a baremetal node and any associated interfaces."""
node = _find_baremetal_node(cs, args.node)
@ -219,10 +219,22 @@ def _translate_baremetal_node_keys(collection):
def _print_baremetal_nodes_list(nodes):
"""Print the list of baremetal nodes."""
def _parse_address(fields):
macs = []
for interface in fields.interfaces:
macs.append(interface['address'])
return ', '.join("%s" % i for i in macs)
formatters = {
'MAC Address': _parse_address
}
_translate_baremetal_node_keys(nodes)
utils.print_list(nodes, [
'ID',
'Host',
'Task State',
'CPUs',
'Memory_MB',
'Disk_GB',
@ -231,7 +243,7 @@ def _print_baremetal_nodes_list(nodes):
'PM Username',
'PM Password',
'Terminal Port',
])
], formatters=formatters)
def do_baremetal_node_list(cs, _args):
@ -263,7 +275,7 @@ def _print_baremetal_node_interfaces(interfaces):
@utils.arg('node',
metavar='<node>',
help="ID of node")
help=_("ID of node"))
def do_baremetal_node_show(cs, args):
"""Show information about a baremetal node."""
node = _find_baremetal_node(cs, args.node)
@ -298,7 +310,7 @@ def do_baremetal_interface_remove(cs, args):
cs.baremetal.remove_interface(args.node, args.address)
@utils.arg('node', metavar='<node>', help="ID of node")
@utils.arg('node', metavar='<node>', help=_("ID of node"))
def do_baremetal_interface_list(cs, args):
"""List network interfaces associated with a baremetal node."""
interfaces = cs.baremetal.list_interfaces(args.node)

View File

@ -21,7 +21,8 @@ from novaclient.openstack.common.gettextutils import _
class FlavorAccess(base.Resource):
def __repr__(self):
return "<FlavorAccess: %s>" % self.name
return ("<FlavorAccess flavor id: %s, tenant id: %s>" %
(self.flavor_id, self.tenant_id))
class FlavorAccessManager(base.ManagerWithFind):

View File

@ -83,9 +83,9 @@ class Flavor(base.Resource):
:param keys: A list of keys to be unset
"""
for k in keys:
return self.manager._delete(
"/flavors/%s/os-extra_specs/%s" % (
base.getid(self), k))
self.manager._delete(
"/flavors/%s/os-extra_specs/%s" % (
base.getid(self), k))
def delete(self):
"""

View File

@ -28,11 +28,14 @@ class FloatingIP(base.Resource):
class FloatingIPManager(base.ManagerWithFind):
resource_class = FloatingIP
def list(self):
def list(self, all_tenants=False):
"""
List floating ips for a tenant
List floating ips
"""
return self._list("/os-floating-ips", "floating_ips")
url = '/os-floating-ips'
if all_tenants:
url += '?all_tenants=1'
return self._list(url, "floating_ips")
def create(self, pool=None):
"""

View File

@ -53,6 +53,7 @@ class Keypair(base.Resource):
class KeypairManager(base.ManagerWithFind):
resource_class = Keypair
keypair_prefix = "os-keypairs"
is_alphanum_id_allowed = True
def get(self, keypair):
"""

View File

@ -86,6 +86,7 @@ class NetworkManager(base.ManagerWithFind):
:param multi_host: str
:param priority: str
:param project_id: str
:param vlan: int
:param vlan_start: int
:param vpn_start: int

View File

@ -0,0 +1,71 @@
# Copyright (c) 2014 VMware, Inc.
# All Rights Reserved.
#
# 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.
"""
Server group interface.
"""
from novaclient import base
class ServerGroup(base.Resource):
"""
A server group.
"""
NAME_ATTR = 'server_group_name'
def __repr__(self):
return '<ServerGroup: %s>' % self.id
def delete(self):
self.manager.delete(self)
class ServerGroupsManager(base.ManagerWithFind):
"""
Manage :class:`ServerGroup` resources.
"""
resource_class = ServerGroup
def list(self):
"""Get a list of all server groups.
:rtype: list of :class:`ServerGroup`.
"""
return self._list('/os-server-groups', 'server_groups')
def get(self, id):
"""Get a specific server group.
:param id: The ID of the :class:`ServerGroup` to get.
:rtype: :class:`ServerGroup`
"""
return self._get('/os-server-groups/%s' % id,
'server_group')
def delete(self, id):
"""Delete a specific server group.
:param id: The ID of the :class:`ServerGroup` to delete.
"""
self._delete('/os-server-groups/%s' % id)
def create(self, **kwargs):
"""Create (allocate) a server group.
:rtype: list of :class:`ServerGroup`
"""
body = {'server_group': kwargs}
return self._create('/os-server-groups', body, 'server_group')

View File

@ -26,6 +26,7 @@ from six.moves.urllib import parse
from novaclient import base
from novaclient import crypto
from novaclient.openstack.common.gettextutils import _
from novaclient.openstack.common import strutils
from novaclient.v1_1.security_groups import SecurityGroup
@ -36,7 +37,7 @@ class Server(base.Resource):
HUMAN_ID = True
def __repr__(self):
return "<Server: %s>" % self.name
return '<Server: %s>' % getattr(self, 'name', 'unknown-name')
def delete(self):
"""
@ -434,8 +435,8 @@ class ServerManager(base.BootingManagerWithFind):
connected networks, fixed ips, etc.
:param scheduler_hints: (optional extension) arbitrary key-value pairs
specified by the client to help boot an instance.
:param config_drive: (optional extension) value for config drive
either boolean, or volume-id
:param config_drive: (optional extension) If True, enable config drive
on the server.
:param admin_pass: admin password for the server.
:param disk_config: (optional extension) control how the disk is
partitioned when the server is created.
@ -454,7 +455,8 @@ class ServerManager(base.BootingManagerWithFind):
else:
userdata = strutils.safe_encode(userdata)
body["server"]["user_data"] = base64.b64encode(userdata)
userdata_b64 = base64.b64encode(userdata).decode('utf-8')
body["server"]["user_data"] = userdata_b64
if meta:
body["server"]["metadata"] = meta
if reservation_id:
@ -490,9 +492,11 @@ class ServerManager(base.BootingManagerWithFind):
data = file_or_string.read()
else:
data = file_or_string
cont = base64.b64encode(data.encode('utf-8')).decode('utf-8')
personality.append({
'path': filepath,
'contents': base64.b64encode(data.encode('utf-8')),
'contents': cont,
})
if availability_zone:
@ -520,8 +524,15 @@ class ServerManager(base.BootingManagerWithFind):
# if value is empty string, do not send value in body
if nic_info.get('net-id'):
net_data['uuid'] = nic_info['net-id']
if nic_info.get('v4-fixed-ip'):
if (nic_info.get('v4-fixed-ip') and
nic_info.get('v6-fixed-ip')):
raise base.exceptions.CommandError(_(
"Only one of 'v4-fixed-ip' and 'v6-fixed-ip' may be"
" provided."))
elif nic_info.get('v4-fixed-ip'):
net_data['fixed_ip'] = nic_info['v4-fixed-ip']
elif nic_info.get('v6-fixed-ip'):
net_data['fixed_ip'] = nic_info['v6-fixed-ip']
if nic_info.get('port-id'):
net_data['port'] = nic_info['port-id']
all_net_data.append(net_data)

View File

@ -69,3 +69,7 @@ class ServiceManager(base.ManagerWithFind):
"""Disable the service with reason."""
body = self._update_body(host, binary, reason)
return self._update("/os-services/disable-log-reason", body, "service")
def delete(self, service_id):
"""Delete a service."""
return self._delete("/os-services/%s" % service_id)

View File

@ -129,19 +129,8 @@ def _parse_block_device_mapping_v2(args, image):
return bdm
def _boot(cs, args, reservation_id=None, min_count=None, max_count=None):
def _boot(cs, args):
"""Boot a new server."""
if min_count is None:
min_count = 1
if max_count is None:
max_count = min_count
if min_count > max_count:
raise exceptions.CommandError(_("min_instances should be <= "
"max_instances"))
if not min_count or not max_count:
raise exceptions.CommandError(_("min_instances nor max_instances "
"should be 0"))
if args.image:
image = _find_image(cs, args.image)
else:
@ -157,10 +146,31 @@ def _boot(cs, args, reservation_id=None, min_count=None, max_count=None):
if not args.flavor:
raise exceptions.CommandError(_("you need to specify a Flavor ID "))
if args.num_instances is not None:
if args.num_instances <= 1:
raise exceptions.CommandError(_("num_instances should be > 1"))
min_count = 1
max_count = 1
# Don't let user mix num_instances and max_count/min_count.
if (args.num_instances is not None and
args.min_count is None and args.max_count is None):
if args.num_instances < 1:
raise exceptions.CommandError(_("num_instances should be >= 1"))
max_count = args.num_instances
elif (args.num_instances is not None and
(args.min_count is not None or args.max_count is not None)):
raise exceptions.CommandError(_("Don't mix num-instances and "
"max/min-count"))
if args.min_count is not None:
if args.min_count < 1:
raise exceptions.CommandError(_("min_count should be >= 1"))
min_count = args.min_count
max_count = min_count
if args.max_count is not None:
if args.max_count < 1:
raise exceptions.CommandError(_("max_count should be >= 1"))
max_count = args.max_count
if (args.min_count is not None and args.max_count is not None and
args.min_count > args.max_count):
raise exceptions.CommandError(_(
"min_count should be <= max_count"))
flavor = _find_flavor(cs, args.flavor)
@ -235,9 +245,10 @@ def _boot(cs, args, reservation_id=None, min_count=None, max_count=None):
for nic_str in args.nics:
err_msg = (_("Invalid nic argument '%s'. Nic arguments must be of "
"the form --nic <net-id=net-uuid,v4-fixed-ip=ip-addr,"
"port-id=port-uuid>, with at minimum net-id or port-id "
"specified.") % nic_str)
nic_info = {"net-id": "", "v4-fixed-ip": "", "port-id": ""}
"v6-fixed-ip=ip-addr,port-id=port-uuid>, with at minimum "
"net-id or port-id (but not both) specified.") % nic_str)
nic_info = {"net-id": "", "v4-fixed-ip": "", "v6-fixed-ip": "",
"port-id": ""}
for kv_str in nic_str.split(","):
try:
@ -250,7 +261,7 @@ def _boot(cs, args, reservation_id=None, min_count=None, max_count=None):
else:
raise exceptions.CommandError(err_msg)
if not nic_info['net-id'] and not nic_info['port-id']:
if bool(nic_info['net-id']) == bool(nic_info['port-id']):
raise exceptions.CommandError(err_msg)
nics.append(nic_info)
@ -280,7 +291,6 @@ def _boot(cs, args, reservation_id=None, min_count=None, max_count=None):
meta=meta,
files=files,
key_name=key_name,
reservation_id=reservation_id,
min_count=min_count,
max_count=max_count,
userdata=userdata,
@ -321,7 +331,17 @@ def _boot(cs, args, reservation_id=None, min_count=None, max_count=None):
default=None,
type=int,
metavar='<number>',
help=_("boot multiple servers at a time (limited by quota)."))
help=argparse.SUPPRESS)
@utils.arg('--min-count',
default=None,
type=int,
metavar='<number>',
help=_("Boot at least <number> servers (limited by quota)."))
@utils.arg('--max-count',
default=None,
type=int,
metavar='<number>',
help=_("Boot up to <number> servers (limited by quota)."))
@utils.arg('--meta',
metavar="<key=value>",
action='append',
@ -374,16 +394,25 @@ def _boot(cs, args, reservation_id=None, min_count=None, max_count=None):
action='append',
default=[],
help=_("Block device mapping with the keys: "
"id=image_id, snapshot_id or volume_id, "
"id=UUID (image_id, snapshot_id or volume_id only if using source "
"image, snapshot or volume) "
"source=source type (image, snapshot, volume or blank), "
"dest=destination type of the block device (volume or local), "
"bus=device's bus, "
"device=name of the device (e.g. vda, xda, ...), "
"size=size of the block device in GB, "
"format=device will be formatted (e.g. swap, ext3, ntfs, ...), "
"bootindex=integer used for ordering the boot disks, "
"type=device type (e.g. disk, cdrom, ...) and "
"shutdown=shutdown behaviour (either preserve or remove)."))
"bus=device's bus (e.g. uml, lxc, virtio, ...; if omitted, "
"hypervisor driver chooses a suitable default, "
"honoured only if device type is supplied) "
"type=device type (e.g. disk, cdrom, ...; defaults to 'disk') "
"device=name of the device (e.g. vda, xda, ...; "
"if omitted, hypervisor driver chooses suitable device "
"depending on selected bus), "
"size=size of the block device in GB (if omitted, "
"hypervisor driver calculates size), "
"format=device will be formatted (e.g. swap, ntfs, ...; optional), "
"bootindex=integer used for ordering the boot disks "
"(for image backed instances it is equal to 0, "
"for others need to be specified) and "
"shutdown=shutdown behaviour (either preserve or remove, "
"for local destination set to remove)."))
@utils.arg('--swap',
metavar="<swap_size>",
default=None,
@ -402,17 +431,19 @@ def _boot(cs, args, reservation_id=None, min_count=None, max_count=None):
help=_("Send arbitrary key/value pairs to the scheduler for custom "
"use."))
@utils.arg('--nic',
metavar="<net-id=net-uuid,v4-fixed-ip=ip-addr,port-id=port-uuid>",
metavar="<net-id=net-uuid,v4-fixed-ip=ip-addr,v6-fixed-ip=ip-addr,"
"port-id=port-uuid>",
action='append',
dest='nics',
default=[],
help=_("Create a NIC on the server. "
"Specify option multiple times to create multiple NICs. "
"net-id: attach NIC to network with this UUID "
"(required if no port-id), "
"(either port-id or net-id must be provided), "
"v4-fixed-ip: IPv4 fixed address for NIC (optional), "
"v6-fixed-ip: IPv6 fixed address for NIC (optional), "
"port-id: attach NIC to port with this UUID "
"(required if no net-id)"))
"(either port-id or net-id must be provided)."))
@utils.arg('--config-drive',
metavar="<value>",
dest='config_drive',
@ -422,7 +453,7 @@ def _boot(cs, args, reservation_id=None, min_count=None, max_count=None):
dest='poll',
action="store_true",
default=False,
help=_('Blocks while server builds so progress can be reported.'))
help=_('Report the new server boot progress until it completes.'))
def do_boot(cs, args):
"""Boot a new server."""
boot_args, boot_kwargs = _boot(cs, args)
@ -494,7 +525,7 @@ def _poll_for_status(poll_fn, obj_id, action, final_ok_states,
elif status == "error":
if not silent:
print(_("\nError %s server") % action)
break
raise exceptions.InstanceInErrorState(obj.fault['message'])
if not silent:
print_progress(progress)
@ -555,7 +586,7 @@ def _print_flavor_list(flavors, show_extra_specs=False):
'Memory_MB',
'Disk',
'Ephemeral',
'Swap',
'Swap_MB',
'VCPUs',
'RXTX_Factor',
'Is_Public',
@ -711,7 +742,7 @@ def do_flavor_access_list(cs, args):
help=_('Tenant ID to add flavor access for.'))
def do_flavor_access_add(cs, args):
"""Add flavor access for the given tenant."""
flavor = _find_flavor_for_admin(cs, args.flavor)
flavor = _find_flavor(cs, args.flavor)
access_list = cs.flavor_access.add_tenant_access(flavor, args.tenant)
columns = ['Flavor_ID', 'Tenant_ID']
utils.print_list(access_list, columns)
@ -724,7 +755,7 @@ def do_flavor_access_add(cs, args):
help=_('Tenant ID to remove flavor access for.'))
def do_flavor_access_remove(cs, args):
"""Remove flavor access for the given tenant."""
flavor = _find_flavor_for_admin(cs, args.flavor)
flavor = _find_flavor(cs, args.flavor)
access_list = cs.flavor_access.remove_tenant_access(flavor, args.tenant)
columns = ['Flavor_ID', 'Tenant_ID']
utils.print_list(access_list, columns)
@ -813,7 +844,7 @@ def _filter_network_create_options(args):
valid_args = ['label', 'cidr', 'vlan_start', 'vpn_start', 'cidr_v6',
'gateway', 'gateway_v6', 'bridge', 'bridge_interface',
'multi_host', 'dns1', 'dns2', 'uuid', 'fixed_cidr',
'project_id', 'priority']
'project_id', 'priority', 'vlan']
kwargs = {}
for k, v in args.__dict__.items():
if k in valid_args and v is not None:
@ -833,9 +864,14 @@ def _filter_network_create_options(args):
dest="cidr_v6",
help=_('IPv6 subnet (ex: fe80::/64'))
@utils.arg('--vlan',
dest='vlan_start',
dest='vlan',
metavar='<vlan id>',
help=_("vlan id"))
help=_("vlan id to be assigned to project"))
@utils.arg('--vlan-start',
dest='vlan_start',
metavar='<vlan start>',
help=_('First vlan ID to be assigned to project. Subsequent vlan'
' IDs will be assigned incrementally'))
@utils.arg('--vpn',
dest='vpn_start',
metavar='<vpn start>',
@ -1020,13 +1056,12 @@ def do_image_delete(cs, args):
dest='ip',
metavar='<ip-regexp>',
default=None,
help=_('Search with regular expression match by IP address (Admin only).'))
help=_('Search with regular expression match by IP address.'))
@utils.arg('--ip6',
dest='ip6',
metavar='<ip6-regexp>',
default=None,
help=_('Search with regular expression match by IPv6 address '
'(Admin only).'))
help=_('Search with regular expression match by IPv6 address.'))
@utils.arg('--name',
dest='name',
metavar='<name-regexp>',
@ -1036,8 +1071,7 @@ def do_image_delete(cs, args):
dest='instance_name',
metavar='<name-regexp>',
default=None,
help=_('Search with regular expression match by server name '
'(Admin only).'))
help=_('Search with regular expression match by server name.'))
@utils.arg('--instance_name',
help=argparse.SUPPRESS)
@utils.arg('--status',
@ -1174,7 +1208,7 @@ def do_list(cs, args):
dest='poll',
action="store_true",
default=False,
help=_('Blocks while server is rebooting.'))
help=_('Poll until reboot is complete.'))
def do_reboot(cs, args):
"""Reboot a server."""
server = _find_server(cs, args.server)
@ -1198,7 +1232,7 @@ def do_reboot(cs, args):
dest='poll',
action="store_true",
default=False,
help=_('Blocks while server rebuilds so progress can be reported.'))
help=_('Report the server rebuild progress until it completes.'))
@utils.arg('--minimal',
dest='minimal',
action="store_true",
@ -1220,8 +1254,8 @@ def do_rebuild(cs, args):
kwargs = utils.get_resource_manager_extra_kwargs(do_rebuild, args)
kwargs['preserve_ephemeral'] = args.preserve_ephemeral
server.rebuild(image, _password, **kwargs)
_print_server(cs, args)
server = server.rebuild(image, _password, **kwargs)
_print_server(cs, args, server)
if args.poll:
_poll_for_status(cs.servers.get, server.id, 'rebuilding', ['active'])
@ -1241,7 +1275,7 @@ def do_rename(cs, args):
dest='poll',
action="store_true",
default=False,
help=_('Blocks while servers resizes so progress can be reported.'))
help=_('Report the server resize progress until it completes.'))
def do_resize(cs, args):
"""Resize a server."""
server = _find_server(cs, args.server)
@ -1270,7 +1304,7 @@ def do_resize_revert(cs, args):
dest='poll',
action="store_true",
default=False,
help=_('Blocks while server migrates so progress can be reported.'))
help=_('Report the server migration progress until it completes.'))
def do_migrate(cs, args):
"""Migrate a server. The new host will be selected by the scheduler."""
server = _find_server(cs, args.server)
@ -1331,13 +1365,15 @@ def do_resume(cs, args):
@utils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
def do_rescue(cs, args):
"""Rescue a server."""
"""Reboots a server into rescue mode, which starts the machine
from the initial image, attaching the current boot disk as secondary.
"""
utils.print_dict(_find_server(cs, args.server).rescue()[1])
@utils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
def do_unrescue(cs, args):
"""Unrescue a server."""
"""Restart the server from normal boot disk again."""
_find_server(cs, args.server).unrescue()
@ -1400,7 +1436,8 @@ def do_root_password(cs, args):
dest='poll',
action="store_true",
default=False,
help=_('Blocks while server snapshots so progress can be reported.'))
help=_('Report the snapshot progress and poll until image creation is '
'complete.'))
def do_image_create(cs, args):
"""Create a new image by taking a snapshot of a running server."""
server = _find_server(cs, args.server)
@ -1535,6 +1572,7 @@ def do_delete(cs, args):
for server in args.server:
try:
_find_server(cs, server).delete()
print(_("Request to delete server %s has been accepted.") % server)
except Exception as e:
failure_count += 1
print(e)
@ -1554,18 +1592,10 @@ def _find_image(cs, image):
return utils.find_resource(cs.images, image)
def _find_flavor_for_admin(cs, flavor):
"""Get a flavor for administrator by name, ID, or RAM size."""
try:
return utils.find_resource(cs.flavors, flavor, is_public=None)
except exceptions.NotFound:
return cs.flavors.find(ram=flavor)
def _find_flavor(cs, flavor):
"""Get a flavor by name, ID, or RAM size."""
try:
return utils.find_resource(cs.flavors, flavor)
return utils.find_resource(cs.flavors, flavor, is_public=None)
except exceptions.NotFound:
return cs.flavors.find(ram=flavor)
@ -1746,17 +1776,16 @@ def do_volume_attach(cs, args):
metavar='<server>',
help=_('Name or ID of server.'))
@utils.arg('attachment_id',
metavar='<volume>',
metavar='<attachment>',
help=_('Attachment ID of the volume.'))
@utils.arg('new_volume',
metavar='<volume>',
help=_('ID of the volume to attach.'))
def do_volume_update(cs, args):
"""Update volume attachment."""
volume = cs.volumes.update_server_volume(_find_server(cs, args.server).id,
args.attachment_id,
args.new_volume)
_print_volume(volume)
cs.volumes.update_server_volume(_find_server(cs, args.server).id,
args.attachment_id,
args.new_volume)
@utils.arg('server',
@ -1764,11 +1793,11 @@ def do_volume_update(cs, args):
help=_('Name or ID of server.'))
@utils.arg('attachment_id',
metavar='<volume>',
help=_('Attachment ID of the volume.'))
help=_('ID of the volume to detach.'))
def do_volume_detach(cs, args):
"""Detach a volume from a server."""
cs.volumes.delete_server_volume(_find_server(cs, args.server).id,
args.attachment_id)
args.attachment_id)
@utils.service_type('volume')
@ -1843,7 +1872,7 @@ def do_volume_type_list(cs, args):
@utils.arg('name',
metavar='<name>',
help=_("Name of the new flavor"))
help=_("Name of the new volume type"))
@utils.service_type('volume')
def do_volume_type_create(cs, args):
"""Create a new volume type."""
@ -1856,7 +1885,7 @@ def do_volume_type_create(cs, args):
help=_("Unique ID of the volume type to delete"))
@utils.service_type('volume')
def do_volume_type_delete(cs, args):
"""Delete a specific flavor"""
"""Delete a specific volume type."""
cs.volume_types.delete(args.id)
@ -1935,6 +1964,9 @@ def do_clear_password(cs, args):
def _print_floating_ip_list(floating_ips):
convert = [('instance_id', 'server_id')]
_translate_keys(floating_ips, convert)
utils.print_list(floating_ips, ['Ip', 'Server Id', 'Fixed Ip', 'Pool'])
@ -2041,9 +2073,13 @@ def do_floating_ip_delete(cs, args):
args.address)
def do_floating_ip_list(cs, _args):
"""List floating ips for this tenant."""
_print_floating_ip_list(cs.floating_ips.list())
@utils.arg('--all-tenants',
action='store_true',
default=False,
help=_('Display floatingips from all tenants (Admin only).'))
def do_floating_ip_list(cs, args):
"""List floating ips."""
_print_floating_ip_list(cs.floating_ips.list(args.all_tenants))
def do_floating_ip_pool_list(cs, _args):
@ -2435,7 +2471,7 @@ def do_keypair_add(cs, args):
@utils.arg('name', metavar='<name>', help=_('Keypair name to delete.'))
def do_keypair_delete(cs, args):
"""Delete keypair given by its name."""
name = args.name
name = _find_keypair(cs, args.name)
cs.keypairs.delete(name)
@ -2458,10 +2494,15 @@ def _print_keypair(keypair):
help=_("Name or ID of keypair"))
def do_keypair_show(cs, args):
"""Show details about the given keypair."""
keypair = cs.keypairs.get(args.keypair)
keypair = _find_keypair(cs, args.keypair)
_print_keypair(keypair)
def _find_keypair(cs, keypair):
"""Get a keypair by name or ID."""
return utils.find_resource(cs.keypairs, keypair)
@utils.arg('--tenant',
#nova db searches by project_id
dest='tenant',
@ -2739,7 +2780,8 @@ def do_aggregate_update(cs, args):
nargs='+',
action='append',
default=[],
help=_('Metadata to add/update to aggregate'))
help=_('Metadata to add/update to aggregate. '
'Specify only the key to delete a metadata item.'))
def do_aggregate_set_metadata(cs, args):
"""Update the metadata associated with the aggregate."""
aggregate = _find_aggregate(cs, args.aggregate)
@ -2828,14 +2870,27 @@ def do_live_migration(cs, args):
args.disk_over_commit)
@utils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
@utils.arg('server', metavar='<server>', nargs='+',
help=_('Name or ID of server(s).'))
@utils.arg('--active', action='store_const', dest='state',
default='error', const='active',
help=_('Request the server be reset to "active" state instead '
'of "error" state (the default).'))
def do_reset_state(cs, args):
"""Reset the state of a server."""
_find_server(cs, args.server).reset_state(args.state)
failure_flag = False
for server in args.server:
try:
_find_server(cs, server).reset_state(args.state)
except Exception as e:
failure_flag = True
msg = "Reset state for server %s failed: %s" % (server, e)
print(msg)
if failure_flag:
msg = "Unable to reset the state for the specified server(s)."
raise exceptions.CommandError(msg)
@utils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
@ -2856,6 +2911,12 @@ def do_service_list(cs, args):
# so as not to add the column when the extended ext is not enabled.
if result and hasattr(result[0], 'disabled_reason'):
columns.append("Disabled Reason")
# NOTE(gtt): After https://review.openstack.org/#/c/39998/ nova will
# show id in response.
if result and hasattr(result[0], 'id'):
columns.insert(0, "Id")
utils.print_list(result, columns)
@ -2883,6 +2944,12 @@ def do_service_disable(cs, args):
utils.print_list([result], ['Host', 'Binary', 'Status'])
@utils.arg('id', metavar='<id>', help=_('Id of service.'))
def do_service_delete(cs, args):
"""Delete the service."""
cs.services.delete(args.id)
@utils.arg('fixed_ip', metavar='<fixed_ip>', help=_('Fixed IP Address.'))
def do_fixed_ip_get(cs, args):
"""Retrieve info on a fixed ip."""
@ -3328,6 +3395,11 @@ def do_quota_class_show(cs, args):
@utils.arg('--floating_ips',
type=int,
help=argparse.SUPPRESS)
@utils.arg('--fixed-ips',
metavar='<fixed-ips>',
type=int,
default=None,
help=_('New value for the "fixed-ips" quota.'))
@utils.arg('--metadata-items',
metavar='<metadata-items>',
type=int,
@ -3512,3 +3584,73 @@ def do_availability_zone_list(cs, _args):
_translate_availability_zone_keys(result)
utils.print_list(result, ['Name', 'Status'],
sortby_index=None)
def _print_server_group_details(server_group):
columns = ['Id', 'Name', 'Policies', 'Members', 'Metadata']
utils.print_list(server_group, columns)
def do_server_group_list(cs, args):
"""Print a list of all server groups."""
server_groups = cs.server_groups.list()
_print_server_group_details(server_groups)
@utils.arg('name', metavar='<name>', help='Server group name.')
# NOTE(wingwj): The '--policy' way is still reserved here for preserving
# the backwards compatibility of CLI, even if a user won't get this usage
# in '--help' description. It will be deprecated after an suitable deprecation
# period(probably 2 coordinated releases or so).
#
# Moreover, we imagine that a given user will use only positional parameters or
# only the "--policy" option. So we don't need to properly handle
# the possibility that they might mix them here. That usage is unsupported.
# The related discussion can be found in
# https://review.openstack.org/#/c/96382/2/.
@utils.arg('policy',
metavar='<policy>',
default=argparse.SUPPRESS,
nargs='*',
help='Policies for the server groups '
'("affinity" or "anti-affinity")')
@utils.arg('--policy',
default=[],
action='append',
help=argparse.SUPPRESS)
def do_server_group_create(cs, args):
"""Create a new server group with the specified details."""
if not args.policy:
raise exceptions.CommandError(_("at least one policy must be "
"specified"))
kwargs = {'name': args.name,
'policies': args.policy}
server_group = cs.server_groups.create(**kwargs)
_print_server_group_details([server_group])
@utils.arg('id', metavar='<id>', nargs='+',
help="Unique ID(s) of the server group to delete")
def do_server_group_delete(cs, args):
"""Delete specific server group(s)."""
failure_count = 0
for sg in args.id:
try:
cs.server_groups.delete(sg)
print(_("Server group %s has been successfully deleted.") % sg)
except Exception as e:
failure_count += 1
print(_("Delete for server group %(sg)s failed: %(e)s") %
{'sg': sg, 'e': e})
if failure_count == len(args.id):
raise exceptions.CommandError(_("Unable to delete any of the "
"specified server groups."))
@utils.arg('id', metavar='<id>',
help="Unique ID of the server group to get")
def do_server_group_get(cs, args):
"""Get a specific server group."""
server_group = cs.server_groups.get(args.id)
_print_server_group_details([server_group])

View File

@ -24,7 +24,7 @@ from novaclient.v3 import hosts
from novaclient.v3 import hypervisors
from novaclient.v3 import images
from novaclient.v3 import keypairs
from novaclient.v3 import quota_classes
from novaclient.v3 import list_extensions
from novaclient.v3 import quotas
from novaclient.v3 import servers
from novaclient.v3 import services
@ -47,21 +47,42 @@ class Client(object):
>>> client.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)
"""
# FIXME(jesse): project_id isn't required to authenticate
def __init__(self, username, password, project_id, auth_url=None,
insecure=False, timeout=None, proxy_tenant_id=None,
proxy_token=None, region_name=None,
endpoint_type='publicURL', extensions=None,
service_type='computev3', service_name=None,
volume_service_name=None, timings=False,
bypass_url=None, os_cache=False, no_cache=True,
http_log_debug=False, auth_system='keystone',
auth_plugin=None, auth_token=None,
cacert=None, tenant_id=None):
def __init__(self, username=None, password=None, project_id=None,
auth_url=None, insecure=False, timeout=None,
proxy_tenant_id=None, proxy_token=None, region_name=None,
endpoint_type='publicURL', extensions=None,
service_type='computev3', service_name=None,
volume_service_name=None, timings=False, bypass_url=None,
os_cache=False, no_cache=True, http_log_debug=False,
auth_system='keystone', auth_plugin=None, auth_token=None,
cacert=None, tenant_id=None, user_id=None,
connection_pool=False, session=None, auth=None,
completion_cache=None):
# NOTE(cyeoh): In the novaclient context (unlike Nova) the
# project_id is not the same as the tenant_id. Here project_id
# is a name (what the Nova API often refers to as a project or
# tenant name) and tenant_id is a UUID (what the Nova API
# often refers to as a project_id or tenant_id).
self.projectid = project_id
self.tenant_id = tenant_id
self.user_id = user_id
self.os_cache = os_cache or not no_cache
#TODO(bnemec): Add back in v3 extensions
self.agents = agents.AgentsManager(self)
@ -69,6 +90,7 @@ class Client(object):
self.availability_zones = \
availability_zones.AvailabilityZoneManager(self)
self.certs = certs.CertificateManager(self)
self.list_extensions = list_extensions.ListExtManager(self)
self.hosts = hosts.HostManager(self)
self.flavors = flavors.FlavorManager(self)
self.flavor_access = flavor_access.FlavorAccessManager(self)
@ -76,7 +98,6 @@ class Client(object):
self.images = images.ImageManager(self)
self.keypairs = keypairs.KeypairManager(self)
self.quotas = quotas.QuotaSetManager(self)
self.quota_classes = quota_classes.QuotaClassSetManager(self)
self.servers = servers.ServerManager(self)
self.services = services.ServiceManager(self)
self.usage = usage.UsageManager(self)
@ -89,38 +110,66 @@ class Client(object):
setattr(self, extension.name,
extension.manager_class(self))
self.client = client.HTTPClient(username,
password,
projectid=project_id,
tenant_id=tenant_id,
auth_url=auth_url,
insecure=insecure,
timeout=timeout,
auth_system=auth_system,
auth_plugin=auth_plugin,
auth_token=auth_token,
proxy_token=proxy_token,
proxy_tenant_id=proxy_tenant_id,
region_name=region_name,
endpoint_type=endpoint_type,
service_type=service_type,
service_name=service_name,
volume_service_name=volume_service_name,
timings=timings,
bypass_url=bypass_url,
os_cache=os_cache,
http_log_debug=http_log_debug,
cacert=cacert)
self.client = client._construct_http_client(
username=username,
password=password,
user_id=user_id,
project_id=project_id,
tenant_id=tenant_id,
auth_url=auth_url,
auth_token=auth_token,
insecure=insecure,
timeout=timeout,
auth_system=auth_system,
auth_plugin=auth_plugin,
proxy_token=proxy_token,
proxy_tenant_id=proxy_tenant_id,
region_name=region_name,
endpoint_type=endpoint_type,
service_type=service_type,
service_name=service_name,
volume_service_name=volume_service_name,
timings=timings,
bypass_url=bypass_url,
os_cache=self.os_cache,
http_log_debug=http_log_debug,
cacert=cacert,
connection_pool=connection_pool,
session=session,
auth=auth)
self.completion_cache = completion_cache
def write_object_to_completion_cache(self, obj):
if self.completion_cache:
self.completion_cache.write_object(obj)
def clear_completion_cache_for_class(self, obj_class):
if self.completion_cache:
self.completion_cache.clear_class(obj_class)
@client._original_only
def __enter__(self):
self.client.open_session()
return self
@client._original_only
def __exit__(self, t, v, tb):
self.client.close_session()
@client._original_only
def set_management_url(self, url):
self.client.set_management_url(url)
@client._original_only
def get_timings(self):
return self.client.get_timings()
@client._original_only
def reset_timings(self):
self.client.reset_timings()
@client._original_only
def authenticate(self):
"""
Authenticate against the server.

Some files were not shown because too many files have changed in this diff Show More