Upgrading some novaclient specific vendored modules

This commit is contained in:
Matthew Jones 2015-04-13 11:35:24 -04:00
parent 3d8955b298
commit 768d9d65b9
157 changed files with 23886 additions and 601 deletions

View File

@ -42,8 +42,8 @@ netaddr==0.7.14 (netaddr/*)
os_client_config==0.6.0 (os_client_config/*)
ordereddict==1.1 (ordereddict.py, needed for Python 2.6 support)
os_diskconfig_python_novaclient_ext==0.1.2 (os_diskconfig_python_novaclient_ext/*)
os_networksv2_python_novaclient_ext==0.21 (os_networksv2_python_novaclient_ext.py)
os_virtual_interfacesv2_python_novaclient_ext==0.15 (os_virtual_interfacesv2_python_novaclient_ext.py)
os_networksv2_python_novaclient_ext==0.25 (os_networksv2_python_novaclient_ext.py)
os_virtual_interfacesv2_python_novaclient_ext==0.19 (os_virtual_interfacesv2_python_novaclient_ext.py)
oslo.i18n==1.5.0 (oslo_i18n/* oslo.i18n/* which goes in oslo/i18n... created empty __init__.py at oslo/)
oslo.config==1.9.3 (oslo_config/* oslo/config/* but not __init__.py from oslo/ used empty one instead)
oslo.serialization==1.4.0 (oslo_serialization/* oslo/serialization/* but not __init__.py from oslo/ used empty one instead)
@ -59,7 +59,7 @@ python-dateutil==2.4.0 (dateutil/*)
python-glanceclient==0.17.0 (glanceclient/*)
python-ironicclient==0.5.0 (ironicclient/*)
python-neutronclient==2.3.11 (neutronclient/*)
python-novaclient==2.18.1 (novaclient/*, excluded bin/nova)
python-novaclient==2.23.0 (novaclient/*, excluded bin/nova)
python-swiftclient==2.2.0 (swiftclient/*, excluded bin/swift)
python-troveclient==1.0.9 (troveclient/*)
pytz==2014.10 (pytz/*)

View File

@ -20,13 +20,17 @@ 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
from novaclient import exceptions
from novaclient.openstack.common.apiclient import base
from novaclient import utils
from novaclient.openstack.common import cliutils
Resource = base.Resource
@ -42,24 +46,17 @@ def getid(obj):
return obj
class Manager(utils.HookableMixin):
class Manager(base.HookableMixin):
"""
Managers interact with a particular type of API (servers, flavors, images,
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)
@ -78,22 +75,86 @@ class Manager(utils.HookableMixin):
except KeyError:
pass
self._clear_completion_cache_for_class(obj_class)
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]
objs = []
for res in data:
if res:
obj = obj_class(self, res, loaded=True)
self._write_object_to_completion_cache(obj)
objs.append(obj)
@contextlib.contextmanager
def alternate_service_type(self, service_type):
original_service_type = self.api.client.service_type
self.api.client.service_type = service_type
try:
yield
finally:
self.api.client.service_type = original_service_type
return objs
@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.
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 = cliutils.env('NOVACLIENT_UUID_CACHE_DIR',
default="~/.novaclient")
# NOTE(sirp): Keep separate UUID caches for each username +
# endpoint pair
username = cliutils.env('OS_USERNAME', 'NOVA_USERNAME')
url = cliutils.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)
def _get(self, url, response_key):
_resp, body = self.api.client.get(url)
obj = self.resource_class(self, body[response_key], loaded=True)
self._write_object_to_completion_cache(obj)
return obj
return self.resource_class(self, body[response_key], loaded=True)
def _create(self, url, body, response_key, return_raw=False, **kwargs):
self.run_hooks('modify_body_for_create', body, **kwargs)
@ -101,9 +162,9 @@ class Manager(utils.HookableMixin):
if return_raw:
return body[response_key]
obj = self.resource_class(self, body[response_key])
self._write_object_to_completion_cache(obj)
return obj
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])
def _delete(self, url):
_resp, _body = self.api.client.delete(url)
@ -131,9 +192,6 @@ class ManagerWithFind(Manager):
def find(self, **kwargs):
"""
Find a single item with attributes matching ``**kwargs``.
This isn't very efficient: it loads the entire list then filters on
the Python side.
"""
matches = self.findall(**kwargs)
num_matches = len(matches)
@ -148,9 +206,6 @@ class ManagerWithFind(Manager):
def findall(self, **kwargs):
"""
Find all items with attributes matching ``**kwargs``.
This isn't very efficient: it loads the entire list then filters on
the Python side.
"""
found = []
searches = kwargs.items()
@ -173,6 +228,24 @@ class ManagerWithFind(Manager):
del tmp_kwargs['is_public']
searches = tmp_kwargs.items()
if 'search_opts' in list_argspec.args:
# pass search_opts in to do server side based filtering.
# TODO(jogo) not all search_opts support regex, find way to
# identify when to use regex and when to use string matching.
# volumes does not support regex while servers does. So when
# doing findall on servers some client side filtering is still
# needed.
if "human_id" in kwargs:
list_kwargs['search_opts'] = {"name": kwargs["human_id"]}
elif "name" in kwargs:
list_kwargs['search_opts'] = {"name": kwargs["name"]}
elif "display_name" in kwargs:
list_kwargs['search_opts'] = {"name": kwargs["display_name"]}
if "all_tenants" in kwargs:
all_tenants = kwargs['all_tenants']
list_kwargs['search_opts']['all_tenants'] = all_tenants
searches = [(k, v) for k, v in searches if k != 'all_tenants']
listing = self.list(**list_kwargs)
for obj in listing:
@ -204,11 +277,15 @@ class BootingManagerWithFind(ManagerWithFind):
mapping_parts = mapping.split(':')
source_id = mapping_parts[0]
bdm_dict['uuid'] = source_id
bdm_dict['boot_index'] = 0
if len(mapping_parts) == 1:
bdm_dict['volume_id'] = source_id
bdm_dict['source_type'] = 'volume'
elif len(mapping_parts) > 1:
source_type = mapping_parts[1]
bdm_dict['source_type'] = source_type
if source_type.startswith('snap'):
bdm_dict['snapshot_id'] = source_id
else:

View File

@ -20,14 +20,17 @@
OpenStack Client interface. Handles the REST calls and responses.
"""
import errno
import copy
import functools
import glob
import hashlib
import logging
import os
import re
import socket
import time
from keystoneclient import adapter
from oslo.utils import importutils
from oslo.utils import netutils
import requests
from requests import adapters
@ -39,12 +42,19 @@ except ImportError:
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.i18n import _
from novaclient import service_catalog
from novaclient import utils
SENSITIVE_HEADERS = ('X-Auth-Token',)
class TCPKeepAliveAdapter(adapters.HTTPAdapter):
"""The custom adapter used to set TCP Keep-Alive on all connections."""
def init_poolmanager(self, *args, **kwargs):
if requests.__version__ >= '2.4.1':
kwargs.setdefault('socket_options', [
(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1),
(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1),
])
super(TCPKeepAliveAdapter, self).init_poolmanager(*args, **kwargs)
class _ClientConnectionPool(object):
@ -57,140 +67,41 @@ class _ClientConnectionPool(object):
Store and reuse HTTP adapters per Service URL.
"""
if url not in self._adapters:
self._adapters[url] = adapters.HTTPAdapter()
self._adapters[url] = TCPKeepAliveAdapter()
return self._adapters[url]
class CompletionCache(object):
"""The completion cache is how we support tab-completion with novaclient.
class SessionClient(adapter.LegacyJsonAdapter):
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
"""
def __init__(self, username, auth_url, attributes=('id', 'human_id')):
self.directory = self._make_directory_name(username, auth_url)
self.attributes = attributes
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 __init__(self, *args, **kwargs):
self.times = []
super(SessionClient, self).__init__(*args, **kwargs)
def request(self, url, method, **kwargs):
kwargs.setdefault('user_agent', 'python-novaclient')
kwargs.setdefault('auth', self.auth)
kwargs.setdefault('authenticated', False)
# NOTE(jamielennox): The standard call raises errors from
# keystoneclient, where we need to raise the novaclient errors.
raise_exc = kwargs.pop('raise_exc', True)
start_time = time.time()
resp, body = super(SessionClient, self).request(url,
method,
raise_exc=False,
**kwargs)
try:
kwargs['json'] = kwargs.pop('body')
except KeyError:
pass
end_time = time.time()
self.times.append(('%s %s' % (method, url),
start_time, end_time))
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:
if raise_exc and 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_timings(self):
return self.times
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 reset_timings(self):
self.times = []
def _original_only(f):
@ -234,7 +145,7 @@ class HTTPClient(object):
self.tenant_id = tenant_id
self._connection_pool = (_ClientConnectionPool()
if connection_pool else None)
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
@ -301,6 +212,11 @@ class HTTPClient(object):
# otherwise we will get all the requests logging messages
rql.setLevel(logging.WARNING)
# NOTE(melwitt): Service catalog is only set if bypass_url isn't
# used. Otherwise, we can cache using services_url.
self.service_catalog = None
self.services_url = {}
def use_token_cache(self, use_it):
self.os_cache = use_it
@ -318,21 +234,47 @@ 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 _redact(self, target, path, text=None):
"""Replace the value of a key in `target`.
The key can be at the top level by specifying a list with a single
key as the path. Nested dictionaries are also supported by passing a
list of keys to be navigated to find the one that should be replaced.
In this case the last one is the one that will be replaced.
:param dict target: the dictionary that may have a key to be redacted;
modified in place
:param list path: a list representing the nested structure in `target`
that should be redacted; modified in place
:param string text: optional text to use as a replacement for the
redacted key. if text is not specified, the
default text will be sha1 hash of the value being
redacted
"""
key = path.pop()
# move to the most nested dict
for p in path:
try:
target = target[p]
except KeyError:
return
if key in target:
if text:
target[key] = text
else:
# because in python3 byte string handling is ... ug
value = target[key].encode('utf-8')
sha1sum = hashlib.sha1(value)
target[key] = "{SHA1}%s" % sha1sum.hexdigest()
def http_log_req(self, method, url, kwargs):
if not self.http_log_debug:
return
string_parts = ['curl -i']
string_parts = ['curl -g -i']
if not kwargs.get('verify', True):
string_parts.append(' --insecure')
@ -340,24 +282,38 @@ class HTTPClient(object):
string_parts.append(" '%s'" % url)
string_parts.append(' -X %s' % method)
headers = copy.deepcopy(kwargs['headers'])
self._redact(headers, ['X-Auth-Token'])
# because dict ordering changes from 2 to 3
keys = sorted(kwargs['headers'].keys())
keys = sorted(headers.keys())
for name in keys:
value = kwargs['headers'][name]
header = ' -H "%s: %s"' % self.safe_header(name, value)
value = headers[name]
header = ' -H "%s: %s"' % (name, value)
string_parts.append(header)
if 'data' in kwargs:
string_parts.append(" -d '%s'" % (kwargs['data']))
data = json.loads(kwargs['data'])
self._redact(data, ['auth', 'passwordCredentials', 'password'])
string_parts.append(" -d '%s'" % json.dumps(data))
self._logger.debug("REQ: %s" % "".join(string_parts))
def http_log_resp(self, resp):
if not self.http_log_debug:
return
if resp.text and resp.status_code != 400:
try:
body = json.loads(resp.text)
self._redact(body, ['access', 'token', 'id'])
except ValueError:
body = None
else:
body = None
self._logger.debug("RESP: [%(status)s] %(headers)s\nRESP BODY: "
"%(text)s\n", {'status': resp.status_code,
'headers': resp.headers,
'text': resp.text})
'text': json.dumps(body)})
def open_session(self):
if not self._connection_pool:
@ -379,10 +335,10 @@ class HTTPClient(object):
self._session.close()
self._current_url = service_url
self._logger.debug(
"New session created for: (%s)" % service_url)
"New session created for: (%s)" % service_url)
self._session = requests.Session()
self._session.mount(service_url,
self._connection_pool.get(service_url))
self._connection_pool.get(service_url))
return self._session
elif self._session:
return self._session
@ -422,7 +378,7 @@ class HTTPClient(object):
# or 'actively refused' in the body, so that's what we'll do.
if resp.status_code == 400:
if ('Connection refused' in resp.text or
'actively refused' in resp.text):
'actively refused' in resp.text):
raise exceptions.ConnectionRefused(resp.text)
try:
body = json.loads(resp.text)
@ -446,6 +402,20 @@ class HTTPClient(object):
def _cs_request(self, url, method, **kwargs):
if not self.management_url:
self.authenticate()
if url is None:
# To get API version information, it is necessary to GET
# a nova endpoint directly without "v2/<tenant-id>".
magic_tuple = parse.urlsplit(self.management_url)
scheme, netloc, path, query, frag = magic_tuple
path = re.sub(r'v[1-9]/[a-z0-9]+$', '', path)
url = parse.urlunsplit((scheme, netloc, path, None, None))
else:
if self.service_catalog:
url = self.get_service_url(self.service_type) + url
else:
# NOTE(melwitt): The service catalog is not available
# when bypass_url is used.
url = self.management_url + url
# Perform the request once. If we get a 401 back then it
# might be because the auth token expired, so try to
@ -455,8 +425,7 @@ class HTTPClient(object):
if self.projectid:
kwargs['headers']['X-Auth-Project-Id'] = self.projectid
resp, body = self._time_request(self.management_url + url, method,
**kwargs)
resp, body = self._time_request(url, method, **kwargs)
return resp, body
except exceptions.Unauthorized as e:
try:
@ -467,8 +436,7 @@ class HTTPClient(object):
self.keyring_saved = False
self.authenticate()
kwargs['headers']['X-Auth-Token'] = self.auth_token
resp, body = self._time_request(self.management_url + url,
method, **kwargs)
resp, body = self._time_request(url, method, **kwargs)
return resp, body
except exceptions.Unauthorized:
raise e
@ -490,6 +458,19 @@ class HTTPClient(object):
def delete(self, url, **kwargs):
return self._cs_request(url, 'DELETE', **kwargs)
def get_service_url(self, service_type):
if service_type not in self.services_url:
url = self.service_catalog.url_for(
attr='region',
filter_value=self.region_name,
endpoint_type=self.endpoint_type,
service_type=service_type,
service_name=self.service_name,
volume_service_name=self.volume_service_name,)
url = url.rstrip('/')
self.services_url[service_type] = url
return self.services_url[service_type]
def _extract_service_catalog(self, url, resp, body, extract_token=True):
"""See what the auth service told us and process the response.
We may get redirected to another site, fail or actually get
@ -506,14 +487,7 @@ class HTTPClient(object):
self.auth_token = self.service_catalog.get_token()
self.tenant_id = self.service_catalog.get_tenant_id()
management_url = self.service_catalog.url_for(
attr='region',
filter_value=self.region_name,
endpoint_type=self.endpoint_type,
service_type=self.service_type,
service_name=self.service_name,
volume_service_name=self.volume_service_name,)
self.management_url = management_url.rstrip('/')
self.management_url = self.get_service_url(self.service_type)
return None
except exceptions.AmbiguousEndpoints:
print(_("Found more than one valid endpoint. Use a more "
@ -553,7 +527,11 @@ class HTTPClient(object):
extract_token=False)
def authenticate(self):
magic_tuple = network_utils.urlsplit(self.auth_url)
if not self.auth_url:
msg = _("Authentication requires 'auth_url', which should be "
"specified in '%s'") % self.__class__.__name__
raise exceptions.AuthorizationFailure(msg)
magic_tuple = netutils.urlsplit(self.auth_url)
scheme, netloc, path, query, frag = magic_tuple
port = magic_tuple.port
if port is None:
@ -698,13 +676,17 @@ def _construct_http_client(username=None, password=None, project_id=None,
auth_system='keystone', auth_plugin=None,
auth_token=None, cacert=None, tenant_id=None,
user_id=None, connection_pool=False, session=None,
auth=None):
auth=None, user_agent='python-novaclient',
interface=None, **kwargs):
if session:
return SessionClient(session=session,
auth=auth,
interface=endpoint_type,
interface=interface or endpoint_type,
service_type=service_type,
region_name=region_name)
region_name=region_name,
service_name=service_name,
user_agent=user_agent,
**kwargs)
else:
# FIXME(jamielennox): username and password are now optional. Need
# to test that they were provided in this mode.
@ -736,9 +718,9 @@ def _construct_http_client(username=None, password=None, project_id=None,
def get_client_class(version):
version_map = {
'1.1': 'novaclient.v1_1.client.Client',
'2': 'novaclient.v1_1.client.Client',
'3': 'novaclient.v3.client.Client',
'1.1': 'novaclient.v2.client.Client',
'2': 'novaclient.v2.client.Client',
'3': 'novaclient.v2.client.Client',
}
try:
client_path = version_map[str(version)]
@ -748,7 +730,7 @@ def get_client_class(version):
'keys': ', '.join(version_map.keys())}
raise exceptions.UnsupportedVersion(msg)
return utils.import_class(client_path)
return importutils.import_class(client_path)
def Client(version, *args, **kwargs):

View File

@ -22,7 +22,7 @@ class DecryptionFailure(Exception):
def decrypt_password(private_key, password):
"""Base64 decodes password and unecrypts it with private key.
"""Base64 decodes password and unencrypts it with private key.
Requires openssl binary available in the path.
"""

View File

@ -105,6 +105,19 @@ class ClientException(Exception):
return formatted_string
class RetryAfterException(ClientException):
"""
The base exception class for ClientExceptions that use Retry-After header.
"""
def __init__(self, *args, **kwargs):
try:
self.retry_after = int(kwargs.pop('retry_after'))
except (KeyError, ValueError):
self.retry_after = 0
super(RetryAfterException, self).__init__(*args, **kwargs)
class BadRequest(ClientException):
"""
HTTP 400 - Bad request: you sent some malformed data.
@ -154,23 +167,15 @@ class Conflict(ClientException):
message = "Conflict"
class OverLimit(ClientException):
class OverLimit(RetryAfterException):
"""
HTTP 413 - Over limit: you're over the API limits for this time period.
"""
http_status = 413
message = "Over limit"
def __init__(self, *args, **kwargs):
try:
self.retry_after = int(kwargs.pop('retry_after'))
except (KeyError, ValueError):
self.retry_after = 0
super(OverLimit, self).__init__(*args, **kwargs)
class RateLimit(OverLimit):
class RateLimit(RetryAfterException):
"""
HTTP 429 - Rate limit: you've sent too many requests for this time period.
"""
@ -220,6 +225,8 @@ def from_response(response, body, url, method=None):
if resp.status_code != 200:
raise exception_from_response(resp, rest.text)
"""
cls = _code_map.get(response.status_code, ClientException)
kwargs = {
'code': response.status_code,
'method': method,
@ -230,7 +237,8 @@ def from_response(response, body, url, method=None):
if response.headers:
kwargs['request_id'] = response.headers.get('x-compute-request-id')
if 'retry-after' in response.headers:
if (issubclass(cls, RetryAfterException) and
'retry-after' in response.headers):
kwargs['retry_after'] = response.headers.get('retry-after')
if body:
@ -245,5 +253,9 @@ def from_response(response, body, url, method=None):
kwargs['message'] = message
kwargs['details'] = details
cls = _code_map.get(response.status_code, ClientException)
return cls(**kwargs)
class ResourceNotFound(Exception):
"""Error in getting the resource."""
pass

View File

@ -14,10 +14,11 @@
# under the License.
from novaclient import base
from novaclient.openstack.common.apiclient import base as common_base
from novaclient import utils
class Extension(utils.HookableMixin):
class Extension(common_base.HookableMixin):
"""Extension descriptor."""
SUPPORTED_HOOKS = ('__pre_parse_args__', '__post_parse_args__')

View File

@ -0,0 +1,35 @@
# 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.
"""oslo.i18n integration module for novaclient.
See http://docs.openstack.org/developer/oslo.i18n/usage.html .
"""
from oslo import i18n
_translators = i18n.TranslatorFactory(domain='novaclient')
# The primary translation function using the well-known name "_"
_ = _translators.primary
# Translators for log levels.
#
# The abbreviated names are meant to reflect the usual use of a short
# name like '_'. The "L" is for "log" and the other letter comes from
# the level.
_LI = _translators.log_info
_LW = _translators.log_warning
_LE = _translators.log_error
_LC = _translators.log_critical

View File

@ -1,17 +0,0 @@
#
# 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

@ -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.
"""oslo.i18n integration module.
See http://docs.openstack.org/developer/oslo.i18n/usage.html
"""
import oslo.i18n
# NOTE(dhellmann): This reference to o-s-l-o will be replaced by the
# application name when this module is synced into the separate
# repository. It is OK to have more than one translation function
# using the same domain, since there will still only be one message
# catalog.
_translators = oslo.i18n.TranslatorFactory(domain='novaclient')
# The primary translation function using the well-known name "_"
_ = _translators.primary
# Translators for log levels.
#
# The abbreviated names are meant to reflect the usual use of a short
# name like '_'. The "L" is for "log" and the other letter comes from
# the level.
_LI = _translators.log_info
_LW = _translators.log_warning
_LE = _translators.log_error
_LC = _translators.log_critical

View File

@ -26,12 +26,12 @@ Base utilities to build API operation managers and objects on top of.
import abc
import copy
from oslo.utils import strutils
import six
from six.moves.urllib import parse
from novaclient.openstack.common._i18n import _
from novaclient.openstack.common.apiclient import exceptions
from novaclient.openstack.common.gettextutils import _
from novaclient.openstack.common import strutils
def getid(obj):
@ -99,12 +99,13 @@ class BaseManager(HookableMixin):
super(BaseManager, self).__init__()
self.client = client
def _list(self, url, response_key, obj_class=None, json=None):
def _list(self, url, response_key=None, obj_class=None, json=None):
"""List the collection.
:param url: a partial URL, e.g., '/servers'
:param response_key: the key to be looked up in response dictionary,
e.g., 'servers'
e.g., 'servers'. If response_key is None - all response body
will be used.
:param obj_class: class for constructing the returned objects
(self.resource_class will be used by default)
:param json: data that will be encoded as JSON and passed in POST
@ -118,7 +119,7 @@ class BaseManager(HookableMixin):
if obj_class is None:
obj_class = self.resource_class
data = body[response_key]
data = body[response_key] if response_key is not None else body
# NOTE(ja): keystone returns values as list as {'values': [ ... ]}
# unlike other services which just return the list...
try:
@ -128,15 +129,17 @@ class BaseManager(HookableMixin):
return [obj_class(self, res, loaded=True) for res in data if res]
def _get(self, url, response_key):
def _get(self, url, response_key=None):
"""Get an object from collection.
:param url: a partial URL, e.g., '/servers'
:param response_key: the key to be looked up in response dictionary,
e.g., 'server'
e.g., 'server'. If response_key is None - all response body
will be used.
"""
body = self.client.get(url).json()
return self.resource_class(self, body[response_key], loaded=True)
data = body[response_key] if response_key is not None else body
return self.resource_class(self, data, loaded=True)
def _head(self, url):
"""Retrieve request headers for an object.
@ -146,21 +149,23 @@ class BaseManager(HookableMixin):
resp = self.client.head(url)
return resp.status_code == 204
def _post(self, url, json, response_key, return_raw=False):
def _post(self, url, json, response_key=None, return_raw=False):
"""Create an object.
:param url: a partial URL, e.g., '/servers'
:param json: data that will be encoded as JSON and passed in POST
request (GET will be sent by default)
:param response_key: the key to be looked up in response dictionary,
e.g., 'servers'
e.g., 'server'. If response_key is None - all response body
will be used.
:param return_raw: flag to force returning raw JSON instead of
Python object of self.resource_class
"""
body = self.client.post(url, json=json).json()
data = body[response_key] if response_key is not None else body
if return_raw:
return body[response_key]
return self.resource_class(self, body[response_key])
return data
return self.resource_class(self, data)
def _put(self, url, json=None, response_key=None):
"""Update an object with PUT method.
@ -169,7 +174,8 @@ class BaseManager(HookableMixin):
:param json: data that will be encoded as JSON and passed in POST
request (GET will be sent by default)
:param response_key: the key to be looked up in response dictionary,
e.g., 'servers'
e.g., 'servers'. If response_key is None - all response body
will be used.
"""
resp = self.client.put(url, json=json)
# PUT requests may not return a body
@ -187,7 +193,8 @@ class BaseManager(HookableMixin):
:param json: data that will be encoded as JSON and passed in POST
request (GET will be sent by default)
:param response_key: the key to be looked up in response dictionary,
e.g., 'servers'
e.g., 'servers'. If response_key is None - all response body
will be used.
"""
body = self.client.patch(url, json=json).json()
if response_key is not None:
@ -465,7 +472,7 @@ class Resource(object):
def __getattr__(self, k):
if k not in self.__dict__:
#NOTE(bcwaldon): disallow lazy-loading if already loaded once
# NOTE(bcwaldon): disallow lazy-loading if already loaded once
if not self.is_loaded():
self.get()
return self.__getattr__(k)

View File

@ -33,11 +33,11 @@ try:
except ImportError:
import json
from oslo.utils import importutils
import requests
from novaclient.openstack.common._i18n import _
from novaclient.openstack.common.apiclient import exceptions
from novaclient.openstack.common.gettextutils import _
from novaclient.openstack.common import importutils
_logger = logging.getLogger(__name__)
@ -104,7 +104,7 @@ class HTTPClient(object):
return
string_parts = [
"curl -i",
"curl -g -i",
"-X '%s'" % method,
"'%s'" % url,
]
@ -156,7 +156,7 @@ class HTTPClient(object):
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", {}))
kwargs.setdefault("headers", {})
kwargs["headers"]["User-Agent"] = self.user_agent
if self.original_ip:
kwargs["headers"]["Forwarded"] = "for=%s;by=%s" % (
@ -247,6 +247,10 @@ class HTTPClient(object):
raise
self.cached_token = None
client.cached_endpoint = None
if self.auth_plugin.opts.get('token'):
self.auth_plugin.opts['token'] = None
if self.auth_plugin.opts.get('endpoint'):
self.auth_plugin.opts['endpoint'] = None
self.authenticate()
try:
token, endpoint = self.auth_plugin.token_and_endpoint(
@ -357,8 +361,7 @@ class BaseClient(object):
"Must be one of: %(version_map)s") % {
'api_name': api_name,
'version': version,
'version_map': ', '.join(version_map.keys())
}
'version_map': ', '.join(version_map.keys())}
raise exceptions.UnsupportedVersion(msg)
return importutils.import_class(client_path)

View File

@ -25,7 +25,7 @@ import sys
import six
from novaclient.openstack.common.gettextutils import _
from novaclient.openstack.common._i18n import _
class ClientException(Exception):
@ -34,14 +34,6 @@ class ClientException(Exception):
pass
class MissingArgs(ClientException):
"""Supplied arguments are not sufficient for calling a function."""
def __init__(self, missing):
self.missing = missing
msg = _("Missing arguments: %s") % ", ".join(missing)
super(MissingArgs, self).__init__(msg)
class ValidationError(ClientException):
"""Error in validation on API client side."""
pass
@ -57,11 +49,6 @@ class CommandError(ClientException):
pass
class ResourceNotFound(ClientException):
"""Error in getting the resource."""
pass
class AuthorizationFailure(ClientException):
"""Cannot authorize API client."""
pass
@ -432,7 +419,7 @@ def from_response(response, method, url):
"""
req_id = response.headers.get("x-openstack-request-id")
#NOTE(hdd) true for older versions of nova and cinder
# NOTE(hdd) true for older versions of nova and cinder
if not req_id:
req_id = response.headers.get("x-compute-request-id")
kwargs = {
@ -452,8 +439,8 @@ def from_response(response, method, url):
except ValueError:
pass
else:
if isinstance(body, dict):
error = list(body.values())[0]
if isinstance(body, dict) and isinstance(body.get("error"), dict):
error = body["error"]
kwargs["message"] = error.get("message")
kwargs["details"] = error.get("details")
elif content_type.startswith("text/"):

View File

@ -33,7 +33,9 @@ from six.moves.urllib import parse
from novaclient.openstack.common.apiclient import client
def assert_has_keys(dct, required=[], optional=[]):
def assert_has_keys(dct, required=None, optional=None):
required = required or []
optional = optional or []
for k in required:
try:
assert k in dct
@ -79,7 +81,7 @@ class FakeHTTPClient(client.HTTPClient):
def __init__(self, *args, **kwargs):
self.callstack = []
self.fixtures = kwargs.pop("fixtures", None) or {}
if not args and not "auth_plugin" in kwargs:
if not args and "auth_plugin" not in kwargs:
args = (None, )
super(FakeHTTPClient, self).__init__(*args, **kwargs)

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.
from oslo.utils import encodeutils
import six
from novaclient.openstack.common._i18n import _
from novaclient.openstack.common.apiclient import exceptions
from novaclient.openstack.common import uuidutils
def find_resource(manager, name_or_id, **find_args):
"""Look for resource in a given manager.
Used as a helper for the _find_* methods.
Example:
.. code-block:: python
def _find_hypervisor(cs, hypervisor):
#Get a hypervisor by name or ID.
return cliutils.find_resource(cs.hypervisors, hypervisor)
"""
# first try to get entity as integer id
try:
return manager.get(int(name_or_id))
except (TypeError, ValueError, exceptions.NotFound):
pass
# now try to get entity as uuid
try:
if six.PY2:
tmp_id = encodeutils.safe_encode(name_or_id)
else:
tmp_id = encodeutils.safe_decode(name_or_id)
if uuidutils.is_uuid_like(tmp_id):
return manager.get(tmp_id)
except (TypeError, ValueError, exceptions.NotFound):
pass
# for str id which is not uuid
if getattr(manager, 'is_alphanum_id_allowed', False):
try:
return manager.get(name_or_id)
except exceptions.NotFound:
pass
try:
try:
return manager.find(human_id=name_or_id, **find_args)
except exceptions.NotFound:
pass
# finally try to find entity by name
try:
resource = getattr(manager, 'resource_class', None)
name_attr = resource.NAME_ATTR if resource else 'name'
kwargs = {name_attr: name_or_id}
kwargs.update(find_args)
return manager.find(**kwargs)
except exceptions.NotFound:
msg = _("No %(name)s with a name or "
"ID of '%(name_or_id)s' exists.") % \
{
"name": manager.resource_class.__name__.lower(),
"name_or_id": name_or_id
}
raise exceptions.CommandError(msg)
except exceptions.NoUniqueMatch:
msg = _("Multiple %(name)s matches found for "
"'%(name_or_id)s', use an ID to be more specific.") % \
{
"name": manager.resource_class.__name__.lower(),
"name_or_id": name_or_id
}
raise exceptions.CommandError(msg)

View File

@ -24,14 +24,21 @@ import os
import sys
import textwrap
from oslo.utils import encodeutils
from oslo.utils import strutils
import prettytable
import six
from six import moves
from novaclient.openstack.common.apiclient import exceptions
from novaclient.openstack.common.gettextutils import _
from novaclient.openstack.common import strutils
from novaclient.openstack.common import uuidutils
from novaclient.openstack.common._i18n import _
class MissingArgs(Exception):
"""Supplied arguments are not sufficient for calling a function."""
def __init__(self, missing):
self.missing = missing
msg = _("Missing arguments: %s") % ", ".join(missing)
super(MissingArgs, self).__init__(msg)
def validate_args(fn, *args, **kwargs):
@ -56,7 +63,7 @@ def validate_args(fn, *args, **kwargs):
required_args = argspec.args[:len(argspec.args) - num_defaults]
def isbound(method):
return getattr(method, 'im_self', None) is not None
return getattr(method, '__self__', None) is not None
if isbound(fn):
required_args.pop(0)
@ -64,7 +71,7 @@ def validate_args(fn, *args, **kwargs):
missing = [arg for arg in required_args if arg not in kwargs]
missing = missing[len(args):]
if missing:
raise exceptions.MissingArgs(missing)
raise MissingArgs(missing)
def arg(*args, **kwargs):
@ -132,7 +139,7 @@ def isunauthenticated(func):
def print_list(objs, fields, formatters=None, sortby_index=0,
mixed_case_fields=None):
mixed_case_fields=None, field_labels=None):
"""Print a list or objects as a table, one row per object.
:param objs: iterable of :class:`Resource`
@ -141,14 +148,22 @@ def print_list(objs, fields, formatters=None, sortby_index=0,
:param sortby_index: index of the field for sorting table rows
:param mixed_case_fields: fields corresponding to object attributes that
have mixed case names (e.g., 'serverId')
:param field_labels: Labels to use in the heading of the table, default to
fields.
"""
formatters = formatters or {}
mixed_case_fields = mixed_case_fields or []
field_labels = field_labels or fields
if len(field_labels) != len(fields):
raise ValueError(_("Field labels list %(labels)s has different number "
"of elements than fields list %(fields)s"),
{'labels': field_labels, 'fields': fields})
if sortby_index is None:
kwargs = {}
else:
kwargs = {'sortby': fields[sortby_index]}
pt = prettytable.PrettyTable(fields, caching=False)
kwargs = {'sortby': field_labels[sortby_index]}
pt = prettytable.PrettyTable(field_labels)
pt.align = 'l'
for o in objs:
@ -165,7 +180,7 @@ def print_list(objs, fields, formatters=None, sortby_index=0,
row.append(data)
pt.add_row(row)
print(strutils.safe_encode(pt.get_string(**kwargs)))
print(encodeutils.safe_encode(pt.get_string(**kwargs)))
def print_dict(dct, dict_property="Property", wrap=0):
@ -175,7 +190,7 @@ def print_dict(dct, dict_property="Property", wrap=0):
:param dict_property: name of the first column
:param wrap: wrapping for the second column
"""
pt = prettytable.PrettyTable([dict_property, 'Value'], caching=False)
pt = prettytable.PrettyTable([dict_property, 'Value'])
pt.align = 'l'
for k, v in six.iteritems(dct):
# convert dict to str to check length
@ -193,7 +208,7 @@ def print_dict(dct, dict_property="Property", wrap=0):
col1 = ''
else:
pt.add_row([k, v])
print(strutils.safe_encode(pt.get_string()))
print(encodeutils.safe_encode(pt.get_string()))
def get_password(max_password_prompts=3):
@ -217,76 +232,16 @@ def get_password(max_password_prompts=3):
return pw
def find_resource(manager, name_or_id, **find_args):
"""Look for resource in a given manager.
Used as a helper for the _find_* methods.
Example:
def _find_hypervisor(cs, hypervisor):
#Get a hypervisor by name or ID.
return cliutils.find_resource(cs.hypervisors, hypervisor)
"""
# first try to get entity as integer id
try:
return manager.get(int(name_or_id))
except (TypeError, ValueError, exceptions.NotFound):
pass
# now try to get entity as uuid
try:
tmp_id = strutils.safe_encode(name_or_id)
if uuidutils.is_uuid_like(tmp_id):
return manager.get(tmp_id)
except (TypeError, ValueError, exceptions.NotFound):
pass
# for str id which is not uuid
if getattr(manager, 'is_alphanum_id_allowed', False):
try:
return manager.get(name_or_id)
except exceptions.NotFound:
pass
try:
try:
return manager.find(human_id=name_or_id, **find_args)
except exceptions.NotFound:
pass
# finally try to find entity by name
try:
resource = getattr(manager, 'resource_class', None)
name_attr = resource.NAME_ATTR if resource else 'name'
kwargs = {name_attr: name_or_id}
kwargs.update(find_args)
return manager.find(**kwargs)
except exceptions.NotFound:
msg = _("No %(name)s with a name or "
"ID of '%(name_or_id)s' exists.") % \
{
"name": manager.resource_class.__name__.lower(),
"name_or_id": name_or_id
}
raise exceptions.CommandError(msg)
except exceptions.NoUniqueMatch:
msg = _("Multiple %(name)s matches found for "
"'%(name_or_id)s', use an ID to be more specific.") % \
{
"name": manager.resource_class.__name__.lower(),
"name_or_id": name_or_id
}
raise exceptions.CommandError(msg)
def service_type(stype):
"""Adds 'service_type' attribute to decorated function.
Usage:
@service_type('volume')
def mymethod(f):
...
.. code-block:: python
@service_type('volume')
def mymethod(f):
...
"""
def inner(f):
f.service_type = stype

View File

@ -32,8 +32,8 @@ class ServiceCatalog(object):
return self.catalog['access']['token']['tenant']['id']
def url_for(self, attr=None, filter_value=None,
service_type=None, endpoint_type='publicURL',
service_name=None, volume_service_name=None):
service_type=None, endpoint_type='publicURL',
service_name=None, volume_service_name=None):
"""Fetch the public URL from the Compute service for
a particular endpoint attribute. If none given, return
the first. See tests for sample service catalog.
@ -72,11 +72,11 @@ class ServiceCatalog(object):
endpoints = service['endpoints']
for endpoint in endpoints:
# Ignore 1.0 compute endpoints
if service.get("type") == 'compute' and \
endpoint.get('versionId', '2') not in ('1.1', '2'):
if (service.get("type") == 'compute' and
endpoint.get('versionId', '2') not in ('1.1', '2')):
continue
if not filter_value or \
endpoint.get(attr).lower() == filter_value.lower():
if (not filter_value or
endpoint.get(attr).lower() == filter_value.lower()):
endpoint["serviceName"] = service.get("name")
matching_endpoints.append(endpoint)
@ -84,6 +84,6 @@ class ServiceCatalog(object):
raise novaclient.exceptions.EndpointNotFound()
elif len(matching_endpoints) > 1:
raise novaclient.exceptions.AmbiguousEndpoints(
endpoints=matching_endpoints)
endpoints=matching_endpoints)
else:
return matching_endpoints[0][endpoint_type]

View File

@ -28,7 +28,14 @@ import logging
import os
import pkgutil
import sys
import time
from keystoneclient.auth.identity.generic import password
from keystoneclient.auth.identity.generic import token
from keystoneclient.auth.identity import v3 as identity
from keystoneclient import session as ksession
from oslo.utils import encodeutils
from oslo.utils import strutils
import pkg_resources
import six
@ -45,14 +52,12 @@ import novaclient.auth_plugin
from novaclient import client
from novaclient import exceptions as exc
import novaclient.extension
from novaclient.i18n import _
from novaclient.openstack.common import cliutils
from novaclient.openstack.common.gettextutils import _
from novaclient.openstack.common import strutils
from novaclient import utils
from novaclient.v1_1 import shell as shell_v1_1
from novaclient.v3 import shell as shell_v3
from novaclient.v2 import shell as shell_v2
DEFAULT_OS_COMPUTE_API_VERSION = "1.1"
DEFAULT_OS_COMPUTE_API_VERSION = "2"
DEFAULT_NOVA_ENDPOINT_TYPE = 'publicURL'
# NOTE(cyeoh): Having the service type dependent on the API version
# is pretty ugly, but we have to do this because traditionally the
@ -134,7 +139,7 @@ class SecretsHelper(object):
if not HAS_KEYRING or not self.args.os_cache:
return
if (auth_token == self.auth_token and
management_url == self.management_url):
management_url == self.management_url):
# Nothing changed....
return
if not all([management_url, auth_token, tenant_id]):
@ -154,7 +159,7 @@ class SecretsHelper(object):
self._password = self.args.os_password
else:
verify_pass = strutils.bool_from_string(
utils.env("OS_VERIFY_PASSWORD", default=False), True)
cliutils.env("OS_VERIFY_PASSWORD", default=False), True)
self._password = self._prompt_password(verify_pass)
if not self._password:
raise exc.CommandError(
@ -219,17 +224,51 @@ class NovaClientArgumentParser(argparse.ArgumentParser):
exits.
"""
self.print_usage(sys.stderr)
#FIXME(lzyeval): if changes occur in argparse.ArgParser._check_value
# FIXME(lzyeval): if changes occur in argparse.ArgParser._check_value
choose_from = ' (choose from'
progparts = self.prog.partition(' ')
self.exit(2, _("error: %(errmsg)s\nTry '%(mainp)s help %(subp)s'"
" for more information.\n") %
{'errmsg': message.split(choose_from)[0],
'mainp': progparts[0],
'subp': progparts[2]})
" for more information.\n") %
{'errmsg': message.split(choose_from)[0],
'mainp': progparts[0],
'subp': progparts[2]})
def _get_option_tuples(self, option_string):
"""returns (action, option, value) candidates for an option prefix
Returns [first candidate] if all candidates refers to current and
deprecated forms of the same options: "nova boot ... --key KEY"
parsing succeed because --key could only match --key-name,
--key_name which are current/deprecated forms of the same option.
"""
option_tuples = (super(NovaClientArgumentParser, self)
._get_option_tuples(option_string))
if len(option_tuples) > 1:
normalizeds = [option.replace('_', '-')
for action, option, value in option_tuples]
if len(set(normalizeds)) == 1:
return option_tuples[:1]
return option_tuples
class OpenStackComputeShell(object):
times = []
def _append_global_identity_args(self, parser):
# Register the CLI arguments that have moved to the session object.
ksession.Session.register_cli_options(parser)
parser.set_defaults(insecure=cliutils.env('NOVACLIENT_INSECURE',
default=False))
identity.Password.register_argparse_arguments(parser)
parser.set_defaults(os_username=cliutils.env('OS_USERNAME',
'NOVA_USERNAME'))
parser.set_defaults(os_password=cliutils.env('OS_PASSWORD',
'NOVA_PASSWORD'))
parser.set_defaults(os_auth_url=cliutils.env('OS_AUTH_URL',
'NOVA_URL'))
def get_base_parser(self):
parser = NovaClientArgumentParser(
@ -242,7 +281,8 @@ class OpenStackComputeShell(object):
)
# Global arguments
parser.add_argument('-h', '--help',
parser.add_argument(
'-h', '--help',
action='store_true',
help=argparse.SUPPRESS,
)
@ -251,154 +291,151 @@ class OpenStackComputeShell(object):
action='version',
version=novaclient.__version__)
parser.add_argument('--debug',
parser.add_argument(
'--debug',
default=False,
action='store_true',
help=_("Print debugging output"))
parser.add_argument('--os-cache',
parser.add_argument(
'--os-cache',
default=strutils.bool_from_string(
utils.env('OS_CACHE', default=False), True),
cliutils.env('OS_CACHE', default=False), True),
action='store_true',
help=_("Use the auth token cache. Defaults to False if "
"env[OS_CACHE] is not set."))
parser.add_argument('--timings',
parser.add_argument(
'--timings',
default=False,
action='store_true',
help=_("Print call timing info"))
parser.add_argument('--timeout',
default=600,
metavar='<seconds>',
type=positive_non_zero_float,
help=_("Set HTTP call timeout (in seconds)"))
parser.add_argument(
'--os-auth-token',
default=cliutils.env('OS_AUTH_TOKEN'),
help='Defaults to env[OS_AUTH_TOKEN]')
parser.add_argument('--os-auth-token',
default=utils.env('OS_AUTH_TOKEN'),
help='Defaults to env[OS_AUTH_TOKEN]')
parser.add_argument('--os-username',
metavar='<auth-user-name>',
default=utils.env('OS_USERNAME', 'NOVA_USERNAME'),
help=_('Defaults to env[OS_USERNAME].'))
parser.add_argument('--os_username',
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'),
help=_('Defaults to env[OS_PASSWORD].'))
parser.add_argument('--os_password',
parser.add_argument(
'--os_password',
help=argparse.SUPPRESS)
parser.add_argument('--os-tenant-name',
parser.add_argument(
'--os-tenant-name',
metavar='<auth-tenant-name>',
default=utils.env('OS_TENANT_NAME', 'NOVA_PROJECT_ID'),
default=cliutils.env('OS_TENANT_NAME', 'NOVA_PROJECT_ID'),
help=_('Defaults to env[OS_TENANT_NAME].'))
parser.add_argument('--os_tenant_name',
parser.add_argument(
'--os_tenant_name',
help=argparse.SUPPRESS)
parser.add_argument('--os-tenant-id',
parser.add_argument(
'--os-tenant-id',
metavar='<auth-tenant-id>',
default=utils.env('OS_TENANT_ID'),
default=cliutils.env('OS_TENANT_ID'),
help=_('Defaults to env[OS_TENANT_ID].'))
parser.add_argument('--os-auth-url',
metavar='<auth-url>',
default=utils.env('OS_AUTH_URL', 'NOVA_URL'),
help=_('Defaults to env[OS_AUTH_URL].'))
parser.add_argument('--os_auth_url',
parser.add_argument(
'--os_auth_url',
help=argparse.SUPPRESS)
parser.add_argument('--os-region-name',
parser.add_argument(
'--os-region-name',
metavar='<region-name>',
default=utils.env('OS_REGION_NAME', 'NOVA_REGION_NAME'),
default=cliutils.env('OS_REGION_NAME', 'NOVA_REGION_NAME'),
help=_('Defaults to env[OS_REGION_NAME].'))
parser.add_argument('--os_region_name',
parser.add_argument(
'--os_region_name',
help=argparse.SUPPRESS)
parser.add_argument('--os-auth-system',
parser.add_argument(
'--os-auth-system',
metavar='<auth-system>',
default=utils.env('OS_AUTH_SYSTEM'),
default=cliutils.env('OS_AUTH_SYSTEM'),
help='Defaults to env[OS_AUTH_SYSTEM].')
parser.add_argument('--os_auth_system',
parser.add_argument(
'--os_auth_system',
help=argparse.SUPPRESS)
parser.add_argument('--service-type',
parser.add_argument(
'--service-type',
metavar='<service-type>',
help=_('Defaults to compute for most actions'))
parser.add_argument('--service_type',
parser.add_argument(
'--service_type',
help=argparse.SUPPRESS)
parser.add_argument('--service-name',
parser.add_argument(
'--service-name',
metavar='<service-name>',
default=utils.env('NOVA_SERVICE_NAME'),
default=cliutils.env('NOVA_SERVICE_NAME'),
help=_('Defaults to env[NOVA_SERVICE_NAME]'))
parser.add_argument('--service_name',
parser.add_argument(
'--service_name',
help=argparse.SUPPRESS)
parser.add_argument('--volume-service-name',
parser.add_argument(
'--volume-service-name',
metavar='<volume-service-name>',
default=utils.env('NOVA_VOLUME_SERVICE_NAME'),
default=cliutils.env('NOVA_VOLUME_SERVICE_NAME'),
help=_('Defaults to env[NOVA_VOLUME_SERVICE_NAME]'))
parser.add_argument('--volume_service_name',
parser.add_argument(
'--volume_service_name',
help=argparse.SUPPRESS)
parser.add_argument('--endpoint-type',
parser.add_argument(
'--os-endpoint-type',
metavar='<endpoint-type>',
default=utils.env('NOVA_ENDPOINT_TYPE',
default=DEFAULT_NOVA_ENDPOINT_TYPE),
help=_('Defaults to env[NOVA_ENDPOINT_TYPE] or ')
+ DEFAULT_NOVA_ENDPOINT_TYPE + '.')
dest='endpoint_type',
default=cliutils.env(
'NOVA_ENDPOINT_TYPE',
default=cliutils.env(
'OS_ENDPOINT_TYPE',
default=DEFAULT_NOVA_ENDPOINT_TYPE)),
help=_('Defaults to env[NOVA_ENDPOINT_TYPE], '
'env[OS_ENDPOINT_TYPE] or ') +
DEFAULT_NOVA_ENDPOINT_TYPE + '.')
parser.add_argument(
'--endpoint-type',
help=argparse.SUPPRESS)
# NOTE(dtroyer): We can't add --endpoint_type here due to argparse
# thinking usage-list --end is ambiguous; but it
# works fine with only --endpoint-type present
# Go figure. I'm leaving this here for doc purposes.
#parser.add_argument('--endpoint_type',
# help=argparse.SUPPRESS)
# parser.add_argument('--endpoint_type',
# help=argparse.SUPPRESS)
parser.add_argument('--os-compute-api-version',
parser.add_argument(
'--os-compute-api-version',
metavar='<compute-api-ver>',
default=utils.env('OS_COMPUTE_API_VERSION',
default=DEFAULT_OS_COMPUTE_API_VERSION),
default=cliutils.env('OS_COMPUTE_API_VERSION',
default=DEFAULT_OS_COMPUTE_API_VERSION),
help=_('Accepts 1.1 or 3, '
'defaults to env[OS_COMPUTE_API_VERSION].'))
parser.add_argument('--os_compute_api_version',
'defaults to env[OS_COMPUTE_API_VERSION].'))
parser.add_argument(
'--os_compute_api_version',
help=argparse.SUPPRESS)
parser.add_argument('--os-cacert',
metavar='<ca-certificate>',
default=utils.env('OS_CACERT', default=None),
help='Specify a CA bundle file to use in '
'verifying a TLS (https) server certificate. '
'Defaults to env[OS_CACERT]')
parser.add_argument('--insecure',
default=utils.env('NOVACLIENT_INSECURE', default=False),
action='store_true',
help=_("Explicitly allow novaclient to perform \"insecure\" "
"SSL (https) requests. The server's certificate will "
"not be verified against any certificate authorities. "
"This option should be used with caution."))
parser.add_argument('--bypass-url',
parser.add_argument(
'--bypass-url',
metavar='<bypass-url>',
dest='bypass_url',
default=utils.env('NOVACLIENT_BYPASS_URL'),
default=cliutils.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)
help=argparse.SUPPRESS)
# The auth-system-plugins might require some extra options
novaclient.auth_plugin.load_auth_system_opts(parser)
self._append_global_identity_args(parser)
return parser
def get_subcommand_parser(self, version):
@ -409,12 +446,12 @@ class OpenStackComputeShell(object):
try:
actions_module = {
'1.1': shell_v1_1,
'2': shell_v1_1,
'3': shell_v3,
'1.1': shell_v2,
'2': shell_v2,
'3': shell_v2,
}[version]
except KeyError:
actions_module = shell_v1_1
actions_module = shell_v2
self._find_actions(subparsers, actions_module)
self._find_actions(subparsers, self)
@ -454,6 +491,10 @@ class OpenStackComputeShell(object):
def _discover_via_contrib_path(self, version):
module_path = os.path.dirname(os.path.abspath(__file__))
version_str = "v%s" % version.replace('.', '_')
# NOTE(akurilin): v1.1, v2 and v3 have one implementation, so
# we should discover contrib modules in one place.
if version_str in ["v1_1", "v3"]:
version_str = "v2"
ext_path = os.path.join(module_path, version_str, 'contrib')
ext_glob = os.path.join(ext_path, "*.py")
@ -474,7 +515,8 @@ class OpenStackComputeShell(object):
yield name, module
def _add_bash_completion_subparser(self, subparsers):
subparser = subparsers.add_parser('bash_completion',
subparser = subparsers.add_parser(
'bash_completion',
add_help=False,
formatter_class=OpenStackHelpFormatter
)
@ -490,13 +532,14 @@ class OpenStackComputeShell(object):
action_help = desc.strip()
arguments = getattr(callback, 'arguments', [])
subparser = subparsers.add_parser(command,
subparser = subparsers.add_parser(
command,
help=action_help,
description=desc,
add_help=False,
formatter_class=OpenStackHelpFormatter
)
subparser.add_argument('-h', '--help',
formatter_class=OpenStackHelpFormatter)
subparser.add_argument(
'-h', '--help',
action='help',
help=argparse.SUPPRESS,
)
@ -515,6 +558,20 @@ class OpenStackComputeShell(object):
logging.basicConfig(level=logging.DEBUG,
format=streamformat)
def _get_keystone_auth(self, session, auth_url, **kwargs):
auth_token = kwargs.pop('auth_token', None)
if auth_token:
return token.Token(auth_url, auth_token, **kwargs)
else:
return password.Password(
auth_url,
username=kwargs.pop('username'),
user_id=kwargs.pop('user_id'),
password=kwargs.pop('password'),
user_domain_id=kwargs.pop('user_domain_id'),
user_domain_name=kwargs.pop('user_domain_name'),
**kwargs)
def main(self, argv):
# Parse args once to find version and debug settings
parser = self.get_base_parser()
@ -526,7 +583,7 @@ class OpenStackComputeShell(object):
# build available subcommands based on version
self.extensions = self._discover_extensions(
options.os_compute_api_version)
options.os_compute_api_version)
self._run_extension_hooks('__pre_parse_args__')
# NOTE(dtroyer): Hackery to handle --endpoint_type due to argparse
@ -538,7 +595,7 @@ class OpenStackComputeShell(object):
argv[spot] = '--endpoint-type'
subcommand_parser = self.get_subcommand_parser(
options.os_compute_api_version)
options.os_compute_api_version)
self.parser = subcommand_parser
if options.help or not argv:
@ -574,6 +631,9 @@ class OpenStackComputeShell(object):
cacert = args.os_cacert
timeout = args.timeout
keystone_session = None
keystone_auth = None
# We may have either, both or none of these.
# If we have both, we don't need USERNAME, PASSWORD etc.
# Fill in the blanks from the SecretsHelper if possible.
@ -591,6 +651,11 @@ class OpenStackComputeShell(object):
if not endpoint_type:
endpoint_type = DEFAULT_NOVA_ENDPOINT_TYPE
# This allow users to use endpoint_type as (internal, public or admin)
# just like other openstack clients (glance, cinder etc)
if endpoint_type in ['internal', 'public', 'admin']:
endpoint_type += 'URL'
if not service_type:
os_compute_api_version = (options.os_compute_api_version or
DEFAULT_OS_COMPUTE_API_VERSION)
@ -600,14 +665,21 @@ class OpenStackComputeShell(object):
except KeyError:
service_type = DEFAULT_NOVA_SERVICE_TYPE_MAP[
DEFAULT_OS_COMPUTE_API_VERSION]
service_type = utils.get_service_type(args.func) or service_type
service_type = cliutils.get_service_type(args.func) or service_type
# If we have an auth token but no management_url, we must auth anyway.
# Expired tokens are handled by client.py:_cs_request
must_auth = not (cliutils.isunauthenticated(args.func)
or (auth_token and management_url))
#FIXME(usrleon): Here should be restrict for project id same as
# Do not use Keystone session for cases with no session support. The
# presence of auth_plugin means os_auth_system is present and is not
# keystone.
use_session = True
if auth_plugin or bypass_url or os_cache or volume_service_name:
use_session = False
# FIXME(usrleon): Here should be restrict for project id same as
# for os_username or os_password but for compatibility it is not.
if must_auth:
if auth_plugin:
@ -615,54 +687,86 @@ class OpenStackComputeShell(object):
if not auth_plugin or not auth_plugin.opts:
if not os_username and not os_user_id:
raise exc.CommandError(_("You must provide a username "
"or user id via --os-username, --os-user-id, "
"env[OS_USERNAME] or env[OS_USER_ID]"))
raise exc.CommandError(
_("You must provide a 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 "
"or tenant id via --os-tenant-name, "
"--os-tenant-id, env[OS_TENANT_NAME] "
"or env[OS_TENANT_ID]"))
if not any([args.os_tenant_name, args.os_tenant_id,
args.os_project_id, args.os_project_name]):
raise exc.CommandError(_("You must provide a project name or"
" project id via --os-project-name,"
" --os-project-id, env[OS_PROJECT_ID]"
" or env[OS_PROJECT_NAME]. You may"
" use os-project and os-tenant"
" interchangeably."))
if not os_auth_url:
if os_auth_system and os_auth_system != 'keystone':
os_auth_url = auth_plugin.get_auth_url()
if not os_auth_url:
raise exc.CommandError(_("You must provide an auth url "
"via either --os-auth-url or env[OS_AUTH_URL] "
"or specify an auth_system which defines a "
"default url with --os-auth-system "
"or env[OS_AUTH_SYSTEM]"))
raise exc.CommandError(
_("You must provide an auth url "
"via either --os-auth-url or env[OS_AUTH_URL] "
"or specify an auth_system which defines a "
"default url with --os-auth-system "
"or env[OS_AUTH_SYSTEM]"))
project_id = args.os_project_id or args.os_tenant_id
project_name = args.os_project_name or args.os_tenant_name
if use_session:
# Not using Nova auth plugin, so use keystone
start_time = time.time()
keystone_session = ksession.Session.load_from_cli_options(args)
keystone_auth = self._get_keystone_auth(
keystone_session,
args.os_auth_url,
username=args.os_username,
user_id=args.os_user_id,
user_domain_id=args.os_user_domain_id,
user_domain_name=args.os_user_domain_name,
password=args.os_password,
auth_token=args.os_auth_token,
project_id=project_id,
project_name=project_name,
project_domain_id=args.os_project_domain_id,
project_domain_name=args.os_project_domain_name)
end_time = time.time()
self.times.append(
('%s %s' % ('auth_url', args.os_auth_url),
start_time, end_time))
if (options.os_compute_api_version and
options.os_compute_api_version != '1.0'):
if not os_tenant_name and not os_tenant_id:
raise exc.CommandError(_("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]"))
if not any([args.os_tenant_id, args.os_tenant_name,
args.os_project_id, args.os_project_name]):
raise exc.CommandError(_("You must provide a project name or"
" project id via --os-project-name,"
" --os-project-id, env[OS_PROJECT_ID]"
" or env[OS_PROJECT_NAME]. You may"
" use os-project and os-tenant"
" interchangeably."))
if not os_auth_url:
raise exc.CommandError(_("You must provide an auth url "
"via either --os-auth-url or env[OS_AUTH_URL]"))
raise exc.CommandError(
_("You must provide an auth url "
"via either --os-auth-url or env[OS_AUTH_URL]"))
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,
service_name=service_name, auth_system=os_auth_system,
auth_plugin=auth_plugin, auth_token=auth_token,
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,
completion_cache=completion_cache)
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,
service_name=service_name, auth_system=os_auth_system,
auth_plugin=auth_plugin, auth_token=auth_token,
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,
session=keystone_session, auth=keystone_auth)
# Now check for the password/token of which pieces of the
# identifying keyring key can come from the underlying client
@ -695,13 +799,17 @@ class OpenStackComputeShell(object):
# This does a couple of bits which are useful even if we've
# got the token + service URL already. It exits fast in that case.
if not cliutils.isunauthenticated(args.func):
self.cs.authenticate()
if not use_session:
# Only call authenticate() if Nova auth plugin is used.
# If keystone is used, authentication is handled as part
# of session.
self.cs.authenticate()
except exc.Unauthorized:
raise exc.CommandError(_("Invalid OpenStack Nova credentials."))
except exc.AuthorizationFailure:
raise exc.CommandError(_("Unable to authorize user"))
if os_compute_api_version == "3" and service_type != 'image':
if options.os_compute_api_version == "3" and service_type != 'image':
# NOTE(cyeoh): create an image based client because the
# images api is no longer proxied by the V3 API and we
# sometimes need to be able to look up images information
@ -724,12 +832,13 @@ 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,
session=keystone_session, auth=keystone_auth,
cacert=cacert, timeout=timeout)
args.func(self.cs, args)
if args.timings:
self._dump_timings(self.cs.get_timings())
self._dump_timings(self.times + self.cs.get_timings())
def _dump_timings(self, timings):
class Tyme(object):
@ -764,8 +873,11 @@ class OpenStackComputeShell(object):
commands.remove('bash_completion')
print(' '.join(commands | options))
@utils.arg('command', metavar='<subcommand>', nargs='?',
help='Display help for <subcommand>')
@cliutils.arg(
'command',
metavar='<subcommand>',
nargs='?',
help='Display help for <subcommand>')
def do_help(self, args):
"""
Display help about this program or one of its subcommands.
@ -785,7 +897,7 @@ 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)
max_help_position, width)
def start_section(self, heading):
# Title-case the headings
@ -795,19 +907,19 @@ class OpenStackHelpFormatter(argparse.HelpFormatter):
def main():
try:
argv = [strutils.safe_decode(a) for a in sys.argv[1:]]
argv = [encodeutils.safe_decode(a) for a in sys.argv[1:]]
OpenStackComputeShell().main(argv)
except Exception as e:
logger.debug(e, exc_info=1)
details = {'name': strutils.safe_encode(e.__class__.__name__),
'msg': strutils.safe_encode(six.text_type(e))}
details = {'name': encodeutils.safe_encode(e.__class__.__name__),
'msg': encodeutils.safe_encode(six.text_type(e))}
print("ERROR (%(name)s): %(msg)s" % details,
file=sys.stderr)
sys.exit(1)
except KeyboardInterrupt as e:
print("Shutting down novaclient", file=sys.stderr)
sys.exit(1)
except KeyboardInterrupt:
print("... terminating nova client", file=sys.stderr)
sys.exit(130)
if __name__ == "__main__":

View File

@ -0,0 +1,50 @@
=====================================
python-novaclient functional testing
=====================================
Idea
------
Over time we have noticed two issues with novaclient unit tests.
* Does not exercise the CLI
* We can get the expected server behavior wrong, and test the wrong thing.
We are using functional tests, run against a running cloud
(primarily devstack), to address these two cases.
Additionally these functional tests can be considered example uses
of python-novaclient.
These tests started out in tempest as read only nova CLI tests, to make sure
the CLI didn't simply stacktrace when being used (which happened on
multiple occasions).
Testing Theory
----------------
We are treating python-novaclient as legacy code, so we do not want to spend a
lot of effort adding in missing features. In the future the CLI will move to
python-openstackclient, and the python API will be based on the OpenStack
SDK project. But until that happens we still need better functional testing,
to prevent regressions etc.
Since python-novaclient has two uses, CLI and python API, we should have two
sets of functional tests. CLI and python API. The python API tests should
never use the CLI. But the CLI tests can use the python API where adding
native support to the CLI for the required functionality would involve a
non trivial amount of work.
Functional Test Guidelines
---------------------------
* Consume credentials via standard client environmental variables::
OS_USERNAME
OS_PASSWORD
OS_TENANT_NAME
OS_AUTH_URL
* Try not to require an additional configuration file

View File

@ -0,0 +1,44 @@
# 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 os
from tempest_lib.cli import base
class ClientTestBase(base.ClientTestBase):
"""
This is a first pass at a simple read only python-novaclient test. This
only exercises client commands that are read only.
This should test commands:
* as a regular user
* as a admin user
* with and without optional parameters
* initially just check return codes, and later test command outputs
"""
def _get_clients(self):
cli_dir = os.environ.get(
'OS_NOVACLIENT_EXEC_DIR',
os.path.join(os.path.abspath('.'), '.tox/functional/bin'))
return base.CLIClient(
username=os.environ.get('OS_USERNAME'),
password=os.environ.get('OS_PASSWORD'),
tenant_name=os.environ.get('OS_TENANT_NAME'),
uri=os.environ.get('OS_AUTH_URL'),
cli_dir=cli_dir)
def nova(self, *args, **kwargs):
return self.clients.nova(*args,
**kwargs)

View File

@ -0,0 +1,50 @@
#!/bin/bash -xe
# 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.
# This script is executed inside post_test_hook function in devstack gate.
function generate_testr_results {
if [ -f .testrepository/0 ]; then
sudo .tox/functional/bin/testr last --subunit > $WORKSPACE/testrepository.subunit
sudo mv $WORKSPACE/testrepository.subunit $BASE/logs/testrepository.subunit
sudo .tox/functional/bin/python /usr/local/jenkins/slave_scripts/subunit2html.py $BASE/logs/testrepository.subunit $BASE/logs/testr_results.html
sudo gzip -9 $BASE/logs/testrepository.subunit
sudo gzip -9 $BASE/logs/testr_results.html
sudo chown jenkins:jenkins $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz
sudo chmod a+r $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz
fi
}
export NOVACLIENT_DIR="$BASE/new/python-novaclient"
# Get admin credentials
cd $BASE/new/devstack
source openrc admin admin
# Go to the novaclient dir
cd $NOVACLIENT_DIR
sudo chown -R jenkins:stack $NOVACLIENT_DIR
# Run tests
echo "Running novaclient functional test suite"
set +e
# Preserve env for OS_ credentials
sudo -E -H -u jenkins tox -efunctional
EXIT_CODE=$?
set -e
# Collect and parse result
generate_testr_results
exit $EXIT_CODE

View File

@ -0,0 +1,155 @@
# 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 os
import time
import uuid
import novaclient.client
from novaclient.tests.functional import base
# TODO(sdague): content that probably should be in utils, also throw
# Exceptions when they fail.
def pick_flavor(flavors):
"""Given a flavor list pick a reasonable one."""
for flavor in flavors:
if flavor.name == 'm1.tiny':
return flavor
for flavor in flavors:
if flavor.name == 'm1.small':
return flavor
def pick_image(images):
for image in images:
if image.name.startswith('cirros') and image.name.endswith('-uec'):
return image
def volume_id_from_cli_create(output):
"""Scrape the volume id out of the 'volume create' command
The cli for Nova automatically routes requests to the volumes
service end point. However the nova api low level commands don't
redirect to the correct service endpoint, so for volumes commands
(even setup ones) we use the cli for magic routing.
This function lets us get the id out of the prettytable that's
dumped on the cli during create.
"""
for line in output.split("\n"):
fields = line.split()
if len(fields) > 4:
if fields[1] == "id":
return fields[3]
def volume_at_status(output, volume_id, status):
for line in output.split("\n"):
fields = line.split()
if len(fields) > 4:
if fields[1] == volume_id:
return fields[3] == status
raise Exception("Volume %s did not reach status '%s' in output: %s"
% (volume_id, status, output))
class TestInstanceCLI(base.ClientTestBase):
def setUp(self):
super(TestInstanceCLI, self).setUp()
# TODO(sdague): while we collect this information in
# tempest-lib, we do it in a way that's not available for top
# level tests. Long term this probably needs to be in the base
# class.
user = os.environ['OS_USERNAME']
passwd = os.environ['OS_PASSWORD']
tenant = os.environ['OS_TENANT_NAME']
auth_url = os.environ['OS_AUTH_URL']
# TODO(sdague): we made a lot of fun of the glanceclient team
# for version as int in first parameter. I guess we know where
# they copied it from.
self.client = novaclient.client.Client(
2, user, passwd, tenant,
auth_url=auth_url)
# pick some reasonable flavor / image combo
self.flavor = pick_flavor(self.client.flavors.list())
self.image = pick_image(self.client.images.list())
def test_attach_volume(self):
"""Test we can attach a volume via the cli.
This test was added after bug 1423695. That bug exposed
inconsistencies in how to talk to API services from the CLI
vs. API level. The volumes api calls that were designed to
populate the completion cache were incorrectly routed to the
Nova endpoint. Novaclient volumes support actually talks to
Cinder endpoint directly.
This would case volume-attach to return a bad error code,
however it does this *after* the attach command is correctly
dispatched. So the volume-attach still works, but the user is
presented a 404 error.
This test ensures we can do a through path test of: boot,
create volume, attach volume, detach volume, delete volume,
destroy.
"""
# TODO(sdague): better random name
name = str(uuid.uuid4())
# Boot via the cli, as we're primarily testing the cli in this test
self.nova('boot', params="--flavor %s --image %s %s --poll" %
(self.flavor.name, self.image.name, name))
# Be nice about cleaning up, however, use the API for this to avoid
# parsing text.
servers = self.client.servers.list(search_opts={"name": name})
# the name is a random uuid, there better only be one
self.assertEqual(1, len(servers), servers)
server = servers[0]
self.addCleanup(server.delete)
# create a volume for attachment. We use the CLI because it
# magic routes to cinder, however the low level API does not.
volume_id = volume_id_from_cli_create(
self.nova('volume-create', params="1"))
self.addCleanup(self.nova, 'volume-delete', params=volume_id)
# allow volume to become available
for x in xrange(60):
volumes = self.nova('volume-list')
if volume_at_status(volumes, volume_id, 'available'):
break
time.sleep(1)
else:
self.fail("Volume %s not available after 60s" % volume_id)
# attach the volume
self.nova('volume-attach', params="%s %s" % (name, volume_id))
# volume needs to transition to 'in-use' to be attached
for x in xrange(60):
volumes = self.nova('volume-list')
if volume_at_status(volumes, volume_id, 'in-use'):
break
time.sleep(1)
else:
self.fail("Volume %s not attached after 60s" % volume_id)
# clean up on success
self.nova('volume-detach', params="%s %s" % (name, volume_id))

View File

@ -0,0 +1,171 @@
# 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 tempest_lib import decorators
from tempest_lib import exceptions
from novaclient.tests.functional import base
class SimpleReadOnlyNovaClientTest(base.ClientTestBase):
"""
read only functional python-novaclient tests.
This only exercises client commands that are read only.
"""
def test_admin_fake_action(self):
self.assertRaises(exceptions.CommandFailed,
self.nova,
'this-does-nova-exist')
# NOTE(jogo): Commands in order listed in 'nova help'
def test_admin_absolute_limites(self):
self.nova('absolute-limits')
self.nova('absolute-limits', params='--reserved')
def test_admin_aggregate_list(self):
self.nova('aggregate-list')
def test_admin_availability_zone_list(self):
self.assertIn("internal", self.nova('availability-zone-list'))
def test_admin_cloudpipe_list(self):
self.nova('cloudpipe-list')
def test_admin_credentials(self):
self.nova('credentials')
# "Neutron does not provide this feature"
def test_admin_dns_domains(self):
self.nova('dns-domains')
@decorators.skip_because(bug="1157349")
def test_admin_dns_list(self):
self.nova('dns-list')
def test_admin_endpoints(self):
self.nova('endpoints')
def test_admin_flavor_acces_list(self):
self.assertRaises(exceptions.CommandFailed,
self.nova,
'flavor-access-list')
# Failed to get access list for public flavor type
self.assertRaises(exceptions.CommandFailed,
self.nova,
'flavor-access-list',
params='--flavor m1.tiny')
def test_admin_flavor_list(self):
self.assertIn("Memory_MB", self.nova('flavor-list'))
def test_admin_floating_ip_bulk_list(self):
self.nova('floating-ip-bulk-list')
def test_admin_floating_ip_list(self):
self.nova('floating-ip-list')
def test_admin_floating_ip_pool_list(self):
self.nova('floating-ip-pool-list')
def test_admin_host_list(self):
self.nova('host-list')
def test_admin_hypervisor_list(self):
self.nova('hypervisor-list')
def test_admin_image_list(self):
self.nova('image-list')
@decorators.skip_because(bug="1157349")
def test_admin_interface_list(self):
self.nova('interface-list')
def test_admin_keypair_list(self):
self.nova('keypair-list')
def test_admin_list(self):
self.nova('list')
self.nova('list', params='--all-tenants 1')
self.nova('list', params='--all-tenants 0')
self.assertRaises(exceptions.CommandFailed,
self.nova,
'list',
params='--all-tenants bad')
def test_admin_network_list(self):
self.nova('network-list')
def test_admin_rate_limits(self):
self.nova('rate-limits')
def test_admin_secgroup_list(self):
self.nova('secgroup-list')
@decorators.skip_because(bug="1157349")
def test_admin_secgroup_list_rules(self):
self.nova('secgroup-list-rules')
def test_admin_server_group_list(self):
self.nova('server-group-list')
def test_admin_servce_list(self):
self.nova('service-list')
def test_admin_usage(self):
self.nova('usage')
def test_admin_usage_list(self):
self.nova('usage-list')
def test_admin_volume_list(self):
self.nova('volume-list')
def test_admin_volume_snapshot_list(self):
self.nova('volume-snapshot-list')
def test_admin_volume_type_list(self):
self.nova('volume-type-list')
def test_admin_help(self):
self.nova('help')
def test_admin_list_extensions(self):
self.nova('list-extensions')
def test_admin_net_list(self):
self.nova('net-list')
def test_agent_list(self):
self.nova('agent-list')
self.nova('agent-list', flags='--debug')
def test_migration_list(self):
self.nova('migration-list')
self.nova('migration-list', flags='--debug')
# Optional arguments:
def test_admin_version(self):
self.nova('', flags='--version')
def test_admin_debug_list(self):
self.nova('list', flags='--debug')
def test_admin_timeout(self):
self.nova('list', flags='--timeout %d' % 10)
def test_admin_timing(self):
self.nova('list', flags='--timing')

View File

@ -0,0 +1,111 @@
# 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 os
import time
import uuid
import six.moves
from novaclient import client
from novaclient import exceptions
from novaclient.tests.functional import base
def wait_for_delete(test, name, thing, get_func):
thing.delete()
for x in six.moves.range(60):
try:
thing = get_func(thing.id)
except exceptions.NotFound:
break
time.sleep(1)
else:
test.fail('%s %s still not deleted after 60s' % (name, thing.id))
class TestVolumesAPI(base.ClientTestBase):
def setUp(self):
super(TestVolumesAPI, self).setUp()
user = os.environ['OS_USERNAME']
passwd = os.environ['OS_PASSWORD']
tenant = os.environ['OS_TENANT_NAME']
auth_url = os.environ['OS_AUTH_URL']
self.client = client.Client(2, user, passwd, tenant, auth_url=auth_url)
def test_volumes_snapshots_types_create_get_list_delete(self):
# Create a volume
volume = self.client.volumes.create(1)
# Make sure we can still list servers after using the volume endpoint
self.client.servers.list()
# This cleanup tests volume delete
self.addCleanup(volume.delete)
# Wait for the volume to become available
for x in six.moves.range(60):
volume = self.client.volumes.get(volume.id)
if volume.status == 'available':
break
elif volume.status == 'error':
self.fail('Volume %s is in error state' % volume.id)
time.sleep(1)
else:
self.fail('Volume %s not available after 60s' % volume.id)
# List all volumes
self.client.volumes.list()
# Create a volume snapshot
snapshot = self.client.volume_snapshots.create(volume.id)
# This cleanup tests volume snapshot delete. The volume
# can't be deleted until the dependent snapshot is gone
self.addCleanup(wait_for_delete, self, 'Snapshot', snapshot,
self.client.volume_snapshots.get)
# Wait for the snapshot to become available
for x in six.moves.range(60):
snapshot = self.client.volume_snapshots.get(snapshot.id)
if snapshot.status == 'available':
break
elif snapshot.status == 'error':
self.fail('Snapshot %s is in error state' % snapshot.id)
time.sleep(1)
else:
self.fail('Snapshot %s not available after 60s' % snapshot.id)
# List snapshots
self.client.volume_snapshots.list()
# List servers again to make sure things are still good
self.client.servers.list()
# Create a volume type
# TODO(melwitt): Use a better random name
name = str(uuid.uuid4())
volume_type = self.client.volume_types.create(name)
# This cleanup tests volume type delete
self.addCleanup(self.client.volume_types.delete, volume_type.id)
# Get the volume type
volume_type = self.client.volume_types.get(volume_type.id)
# List all volume types
self.client.volume_types.list()
# One more servers list
self.client.servers.list()

View File

@ -0,0 +1,92 @@
#
# 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.
"""
A fake server that "responds" to API methods with pre-canned responses.
All of these responses come from the spec, so if for some reason the spec's
wrong the tests might raise AssertionError. I've indicated in comments the
places where actual behavior differs from the spec.
"""
from novaclient import base
def assert_has_keys(dict, required=[], optional=[]):
keys = dict.keys()
for k in required:
try:
assert k in keys
except AssertionError:
extra_keys = set(keys).difference(set(required + optional))
raise AssertionError("found unexpected keys: %s" %
list(extra_keys))
class FakeClient(object):
def assert_called(self, method, url, body=None, pos=-1):
"""
Assert than an API method was just called.
"""
expected = (method, url)
called = self.client.callstack[pos][0:2]
assert self.client.callstack, \
"Expected %s %s but no calls were made." % expected
assert expected == called, \
'Expected %s %s; got %s %s' % (expected + called)
if body is not None:
if self.client.callstack[pos][2] != body:
raise AssertionError('%r != %r' %
(self.client.callstack[pos][2], body))
def assert_called_anytime(self, method, url, body=None):
"""
Assert than an API method was called anytime in the test.
"""
expected = (method, url)
assert self.client.callstack, \
"Expected %s %s but no calls were made." % expected
found = False
for entry in self.client.callstack:
if expected == entry[0:2]:
found = True
break
assert found, 'Expected %s; got %s' % (expected, self.client.callstack)
if body is not None:
try:
assert entry[2] == body
except AssertionError:
print(entry[2])
print("!=")
print(body)
raise
self.client.callstack = []
def clear_callstack(self):
self.client.callstack = []
def authenticate(self):
pass
# Fake class that will be used as an extension
class FakeManager(base.Manager):
pass

View File

@ -0,0 +1,54 @@
# 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.unit.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
}
}
self.requests.register_uri('POST', self.url(),
json=post_os_agents,
headers=self.json_headers)
put_os_agents_1 = {
"agent": {
"url": "/yyy/yyyy/yyyy",
"version": "8.0",
"md5hash": "add6bb58e139be103324d04d82d8f546",
'id': 1
}
}
self.requests.register_uri('PUT', self.url(1),
json=put_os_agents_1,
headers=self.json_headers)
self.requests.register_uri('DELETE', self.url(1),
headers=self.json_headers,
status_code=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.
from novaclient.tests.unit.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'},
]}
self.requests.register_uri('GET', self.url(),
json=get_os_aggregates,
headers=self.json_headers)
get_aggregates_1 = {'aggregate': get_os_aggregates['aggregates'][0]}
self.requests.register_uri('POST', self.url(),
json=get_aggregates_1,
headers=self.json_headers)
for agg_id in (1, 2):
for method in ('GET', 'PUT'):
self.requests.register_uri(method, self.url(agg_id),
json=get_aggregates_1,
headers=self.json_headers)
self.requests.register_uri('POST', self.url(agg_id, 'action'),
json=get_aggregates_1,
headers=self.json_headers)
self.requests.register_uri('DELETE', self.url(1), status_code=202)

View File

@ -0,0 +1,91 @@
# 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.unit.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
}
]
}
self.requests.register_uri('GET', self.url(),
json=get_os_availability_zone,
headers=self.json_headers)
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
}
]
}
self.requests.register_uri('GET', self.url('detail'),
json=get_os_zone_detail,
headers=self.json_headers)

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 fixtures
from six.moves.urllib import parse
COMPUTE_URL = 'http://compute.host'
class Fixture(fixtures.Fixture):
base_url = None
json_headers = {'Content-Type': 'application/json'}
def __init__(self, requests, compute_url=COMPUTE_URL):
super(Fixture, self).__init__()
self.requests = requests
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,55 @@
# 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.unit.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'
}
}
self.requests.register_uri('GET', self.url('root'),
json=get_os_certificate,
headers=self.json_headers)
post_os_certificates = {
'certificate': {
'private_key': 'foo',
'data': 'bar'
}
}
self.requests.register_uri('POST', self.url(),
json=post_os_certificates,
headers=self.json_headers)

View File

@ -0,0 +1,65 @@
# 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 keystoneclient.auth.identity import v2
from keystoneclient import fixture
from keystoneclient import session
from novaclient.v2 import client as v2client
IDENTITY_URL = 'http://identityserver:5000/v2.0'
COMPUTE_URL = 'http://compute.host'
class V1(fixtures.Fixture):
def __init__(self, requests,
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.requests = requests
self.token = fixture.V2Token()
self.token.set_scope()
s = self.token.add_service('compute')
s.add_endpoint(self.compute_url)
s = self.token.add_service('computev3')
s.add_endpoint(self.compute_url)
def setUp(self):
super(V1, self).setUp()
auth_url = '%s/tokens' % self.identity_url
headers = {'X-Content-Type': 'application/json'}
self.requests.register_uri('POST', auth_url,
json=self.token,
headers=headers)
self.client = self.new_client()
def new_client(self):
return v2client.Client(username='xx',
api_key='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 v2client.Client(session=self.session)

View File

@ -0,0 +1,37 @@
# 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.unit.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}]}
self.requests.register_uri('GET', self.url(),
json=get_os_cloudpipe,
headers=self.json_headers)
instance_id = '9d5824aa-20e6-4b9f-b967-76a699fc51fd'
post_os_cloudpipe = {'instance_id': instance_id}
self.requests.register_uri('POST', self.url(),
json=post_os_cloudpipe,
headers=self.json_headers,
status_code=202)
self.requests.register_uri('PUT', self.url('configure-project'),
headers=self.json_headers,
status_code=202)

View File

@ -0,0 +1,39 @@
# 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.unit.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'
}
}
self.requests.register_uri('GET', self.url('192.168.1.1'),
json=get_os_fixed_ips,
headers=self.json_headers)
self.requests.register_uri('POST',
self.url('192.168.1.1', 'action'),
headers=self.json_headers,
status_code=202)

View File

@ -0,0 +1,211 @@
# 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 oslo.serialization import jsonutils
from novaclient.tests.unit import fakes
from novaclient.tests.unit.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}
self.requests.register_uri('GET', self.url(),
json=get_os_floating_ips,
headers=self.json_headers)
for ip in floating_ips:
get_os_floating_ip = {'floating_ip': ip}
self.requests.register_uri('GET', self.url(ip['id']),
json=get_os_floating_ip,
headers=self.json_headers)
self.requests.register_uri('DELETE', self.url(ip['id']),
headers=self.json_headers,
status_code=204)
def post_os_floating_ips(request, context):
body = jsonutils.loads(request.body)
ip = floating_ips[0].copy()
ip['pool'] = body.get('pool')
return {'floating_ip': ip}
self.requests.register_uri('POST', self.url(),
json=post_os_floating_ips,
headers=self.json_headers)
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'}
]
}
self.requests.register_uri('GET', self.url(),
json=get_os_floating_ip_dns,
headers=self.json_headers,
status_code=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')
self.requests.register_uri('GET', url,
json=get_dns_testdomain_entries_testname,
headers=self.json_headers,
status_code=205)
self.requests.register_uri('DELETE', self.url('testdomain'))
url = self.url('testdomain', 'entries', 'testname')
self.requests.register_uri('DELETE', url)
def put_dns_testdomain_entries_testname(request, context):
body = jsonutils.loads(request.body)
fakes.assert_has_keys(body['dns_entry'],
required=['ip', 'dns_type'])
context.status_code = 205
return request.body
self.requests.register_uri('PUT', url,
text=put_dns_testdomain_entries_testname,
headers=self.json_headers)
url = self.url('testdomain', 'entries')
self.requests.register_uri('GET', url, status_code=404)
get_os_floating_ip_dns_testdomain = {
'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'
}
},
]
}
self.requests.register_uri('GET', url + '?ip=1.2.3.4',
json=get_os_floating_ip_dns_testdomain,
status_code=205,
headers=self.json_headers)
def put_os_floating_ip_dns_testdomain(request, context):
body = jsonutils.loads(request.body)
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'])
return request.body
self.requests.register_uri('PUT', self.url('testdomain'),
text=put_os_floating_ip_dns_testdomain,
status_code=205,
headers=self.json_headers)
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'},
]
}
self.requests.register_uri('GET', self.url(),
json=get_os_floating_ips_bulk,
headers=self.json_headers)
self.requests.register_uri('GET', self.url('testHost'),
json=get_os_floating_ips_bulk,
headers=self.json_headers)
def put_os_floating_ips_bulk_delete(request, context):
body = jsonutils.loads(request.body)
ip_range = body.get('ip_range')
return {'floating_ips_bulk_delete': ip_range}
self.requests.register_uri('PUT', self.url('delete'),
json=put_os_floating_ips_bulk_delete,
headers=self.json_headers)
def post_os_floating_ips_bulk(request, context):
body = jsonutils.loads(request.body)
params = body.get('floating_ips_bulk_create')
pool = params.get('pool', 'defaultPool')
interface = params.get('interface', 'defaultInterface')
return {
'floating_ips_bulk_create': {
'ip_range': '192.168.1.0/30',
'pool': pool,
'interface': interface
}
}
self.requests.register_uri('POST', self.url(),
json=post_os_floating_ips_bulk,
headers=self.json_headers)
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'}
]
}
self.requests.register_uri('GET', self.url(),
json=get_os_floating_ip_pools,
headers=self.json_headers)

View File

@ -0,0 +1,46 @@
# 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.unit.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,
}
}
self.requests.register_uri('GET', self.url(1),
json=get_os_fping_1,
headers=self.json_headers)
get_os_fping = {
'servers': [
get_os_fping_1['server'],
{
"id": "2",
"project_id": "fake-project",
"alive": True,
},
]
}
self.requests.register_uri('GET', self.url(),
json=get_os_fping,
headers=self.json_headers)

View File

@ -0,0 +1,149 @@
# 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 oslo.serialization import jsonutils
from six.moves.urllib import parse
from novaclient.tests.unit.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}}
]
}
headers = {'Content-Type': 'application/json'}
self.requests.register_uri('GET', self.url('host'),
json=get_os_hosts_host,
headers=headers)
def get_os_hosts(request, context):
host, query = parse.splitquery(request.url)
zone = 'nova1'
service = None
if query:
qs = parse.parse_qs(query)
try:
zone = qs['zone'][0]
except Exception:
pass
try:
service = qs['service'][0]
except Exception:
pass
return {
'hosts': [
{
'host': 'host1',
'service': service or 'nova-compute',
'zone': zone
},
{
'host': 'host1',
'service': service or 'nova-cert',
'zone': zone
}
]
}
self.requests.register_uri('GET', self.url(),
json=get_os_hosts,
headers=headers)
get_os_hosts_sample_host = {
'host': [
{'resource': {'host': 'sample_host'}}
],
}
self.requests.register_uri('GET', self.url('sample_host'),
json=get_os_hosts_sample_host,
headers=headers)
self.requests.register_uri('PUT', self.url('sample_host', 1),
json=self.put_host_1(),
headers=headers)
self.requests.register_uri('PUT', self.url('sample_host', 2),
json=self.put_host_2(),
headers=headers)
self.requests.register_uri('PUT', self.url('sample_host', 3),
json=self.put_host_3(),
headers=headers)
self.requests.register_uri('GET', self.url('sample_host', 'reboot'),
json=self.get_host_reboot(),
headers=headers)
self.requests.register_uri('GET', self.url('sample_host', 'startup'),
json=self.get_host_startup(),
headers=headers)
self.requests.register_uri('GET', self.url('sample_host', 'shutdown'),
json=self.get_host_shutdown(),
headers=headers)
def put_os_hosts_sample_host(request, context):
result = {'host': 'dummy'}
result.update(jsonutils.loads(request.body))
return result
self.requests.register_uri('PUT', self.url('sample_host'),
json=put_os_hosts_sample_host,
headers=headers)
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'}

View File

@ -0,0 +1,182 @@
# 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.unit.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'},
]
}
self.headers = {'Content-Type': 'application/json'}
self.requests.register_uri('GET', self.url(),
json=get_os_hypervisors,
headers=self.headers)
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
}
]
}
self.requests.register_uri('GET', self.url('detail'),
json=get_os_hypervisors_detail,
headers=self.headers)
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,
}
}
self.requests.register_uri('GET', self.url('statistics'),
json=get_os_hypervisors_stats,
headers=self.headers)
get_os_hypervisors_search = {
'hypervisors': [
{'id': 1234, 'hypervisor_hostname': 'hyper1'},
{'id': 5678, 'hypervisor_hostname': 'hyper2'}
]
}
self.requests.register_uri('GET', self.url('hyper', 'search'),
json=get_os_hypervisors_search,
headers=self.headers)
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'}
]
}
]
}
self.requests.register_uri('GET', self.url('hyper', 'servers'),
json=get_hyper_server,
headers=self.headers)
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
}
}
self.requests.register_uri('GET', self.url(1234),
json=get_os_hypervisors_1234,
headers=self.headers)
get_os_hypervisors_uptime = {
'hypervisor': {
'id': 1234,
'hypervisor_hostname': 'hyper1',
'uptime': 'fake uptime'
}
}
self.requests.register_uri('GET', self.url(1234, 'uptime'),
json=get_os_hypervisors_uptime,
headers=self.headers)

View File

@ -0,0 +1,86 @@
# 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 oslo.serialization import jsonutils
from novaclient.tests.unit import fakes
from novaclient.tests.unit.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'}
]
}
headers = {'Content-Type': 'application/json'}
self.requests.register_uri('GET', self.url(),
json=get_images,
headers=headers)
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": {},
}
self.requests.register_uri('GET', self.url('detail'),
json={'images': [image_1, image_2]},
headers=headers)
self.requests.register_uri('GET', self.url(1),
json={'image': image_1},
headers=headers)
def post_images_1_metadata(request, context):
body = jsonutils.loads(request.body)
assert list(body) == ['metadata']
fakes.assert_has_keys(body['metadata'], required=['test_key'])
return {'metadata': image_1['metadata']}
self.requests.register_uri('POST', self.url(1, 'metadata'),
json=post_images_1_metadata,
headers=headers)
for u in (1, '1/metadata/test_key'):
self.requests.register_uri('DELETE', self.url(u), status_code=204)
class V3(V1):
base_url = 'v1/images'

View File

@ -0,0 +1,47 @@
# 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 oslo.serialization import jsonutils
from novaclient.tests.unit import fakes
from novaclient.tests.unit.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'}
headers = {'Content-Type': 'application/json'}
self.requests.register_uri('GET', self.url(),
json={'keypairs': [keypair]},
headers=headers)
self.requests.register_uri('GET', self.url('test'),
json={'keypair': keypair},
headers=headers)
self.requests.register_uri('DELETE', self.url('test'), status_code=202)
def post_os_keypairs(request, context):
body = jsonutils.loads(request.body)
assert list(body) == ['keypair']
fakes.assert_has_keys(body['keypair'], required=['name'])
return {'keypair': keypair}
self.requests.register_uri('POST', self.url(),
json=post_os_keypairs,
headers=headers)

View File

@ -0,0 +1,80 @@
# 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.unit.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
},
},
}
headers = {'Content-Type': 'application/json'}
self.requests.register_uri('GET', self.url(),
json=get_limits,
headers=headers)

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.
from oslo.serialization import jsonutils
from novaclient.tests.unit.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'
}
]
}
headers = {'Content-Type': 'application/json'}
self.requests.register_uri('GET', self.url(),
json=get_os_networks,
headers=headers)
def post_os_networks(request, context):
body = jsonutils.loads(request.body)
return {'network': body}
self.requests.register_uri("POST", self.url(),
json=post_os_networks,
headers=headers)
get_os_networks_1 = {'network': {"label": "1", "cidr": "10.0.0.0/24"}}
self.requests.register_uri('GET', self.url(1),
json=get_os_networks_1,
headers=headers)
self.requests.register_uri('DELETE',
self.url('networkdelete'),
status_code=202)
for u in ('add', 'networkdisassociate/action', 'networktest/action',
'1/action', '2/action'):
self.requests.register_uri('POST', self.url(u), status_code=202)

View File

@ -0,0 +1,66 @@
# 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.unit.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 = {'quota_set': self.test_quota('test')}
self.headers = {'Content-Type': 'application/json'}
for u in ('test', 'tenant-id', 'tenant-id/defaults',
'%s/defaults' % uuid2):
self.requests.register_uri('GET', self.url(u),
json=test_json,
headers=self.headers)
self.requests.register_uri('PUT', self.url(uuid),
json={'quota_set': self.test_quota(uuid)},
headers=self.headers)
self.requests.register_uri('GET', self.url(uuid),
json={'quota_set': self.test_quota(uuid)},
headers=self.headers)
self.requests.register_uri('PUT', self.url(uuid2),
json={'quota_set': self.test_quota(uuid2)},
headers=self.headers)
self.requests.register_uri('GET', self.url(uuid2),
json={'quota_set': self.test_quota(uuid2)},
headers=self.headers)
for u in ('test', uuid2):
self.requests.register_uri('DELETE', self.url(u), status_code=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
}

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.
from oslo.serialization import jsonutils
from novaclient.tests.unit import fakes
from novaclient.tests.unit.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'
}
headers = {'Content-Type': 'application/json'}
self.requests.register_uri('GET', self.url(),
json={'security_group_rules': [rule]},
headers=headers)
for u in (1, 11, 12):
self.requests.register_uri('DELETE', self.url(u), status_code=202)
def post_rules(request, context):
body = jsonutils.loads(request.body)
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 {'security_group_rule': rule}
self.requests.register_uri('POST', self.url(),
json=post_rules,
headers=headers,
status_code=202)

View File

@ -0,0 +1,100 @@
# 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 oslo.serialization import jsonutils
from novaclient.tests.unit import fakes
from novaclient.tests.unit.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]}
headers = {'Content-Type': 'application/json'}
self.requests.register_uri('GET', self.url(),
json=get_groups,
headers=headers)
get_group_1 = {'security_group': security_group_1}
self.requests.register_uri('GET', self.url(1),
json=get_group_1,
headers=headers)
self.requests.register_uri('DELETE', self.url(1), status_code=202)
def post_os_security_groups(request, context):
body = jsonutils.loads(request.body)
assert list(body) == ['security_group']
fakes.assert_has_keys(body['security_group'],
required=['name', 'description'])
return {'security_group': security_group_1}
self.requests.register_uri('POST', self.url(),
json=post_os_security_groups,
headers=headers,
status_code=202)
def put_os_security_groups_1(request, context):
body = jsonutils.loads(request.body)
assert list(body) == ['security_group']
fakes.assert_has_keys(body['security_group'],
required=['name', 'description'])
return body
self.requests.register_uri('PUT', self.url(1),
json=put_os_security_groups_1,
headers=headers,
status_code=205)

View File

@ -0,0 +1,74 @@
# 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 oslo.serialization import jsonutils
from novaclient.tests.unit.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"
}
]
headers = {'Content-Type': 'application/json'}
self.requests.register_uri('GET', self.url(),
json={'server_groups': server_groups},
headers=headers)
server = server_groups[0]
server_j = jsonutils.dumps({'server_group': server})
def _register(method, *args):
self.requests.register_uri(method, self.url(*args), text=server_j)
_register('POST')
_register('POST', server['id'])
_register('GET', server['id'])
_register('PUT', server['id'])
_register('POST', server['id'], '/action')
self.requests.register_uri('DELETE', self.url(server['id']),
status_code=202)

View File

@ -0,0 +1,427 @@
# 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 oslo.serialization import jsonutils
from novaclient.tests.unit import fakes
from novaclient.tests.unit.fixture_data import base
from novaclient.tests.unit.v2 import fakes as v2_fakes
class Base(base.Fixture):
base_url = 'servers'
def setUp(self):
super(Base, self).setUp()
get_servers = {
"servers": [
{'id': 1234, 'name': 'sample-server'},
{'id': 5678, 'name': 'sample-server2'}
]
}
self.requests.register_uri('GET', self.url(),
json=get_servers,
headers=self.json_headers)
self.server_1234 = {
"id": 1234,
"name": "sample-server",
"image": {
"id": 2,
"name": "sample image",
},
"flavor": {
"id": 1,
"name": "256 MB Server",
},
"hostId": "e4d909c290d0fb1ca068ffaddf22cbd0",
"status": "BUILD",
"progress": 60,
"addresses": {
"public": [
{
"version": 4,
"addr": "1.2.3.4",
},
{
"version": 4,
"addr": "5.6.7.8",
}],
"private": [{
"version": 4,
"addr": "10.11.12.13",
}],
},
"metadata": {
"Server Label": "Web Head 1",
"Image Version": "2.1"
},
"OS-EXT-SRV-ATTR:host": "computenode1",
"security_groups": [{
'id': 1, 'name': 'securitygroup1',
'description': 'FAKE_SECURITY_GROUP',
'tenant_id': '4ffc664c198e435e9853f2538fbcd7a7'
}],
"OS-EXT-MOD:some_thing": "mod_some_thing_value",
}
self.server_5678 = {
"id": 5678,
"name": "sample-server2",
"image": {
"id": 2,
"name": "sample image",
},
"flavor": {
"id": 1,
"name": "256 MB Server",
},
"hostId": "9e107d9d372bb6826bd81d3542a419d6",
"status": "ACTIVE",
"addresses": {
"public": [
{
"version": 4,
"addr": "4.5.6.7",
},
{
"version": 4,
"addr": "5.6.9.8",
}],
"private": [{
"version": 4,
"addr": "10.13.12.13",
}],
},
"metadata": {
"Server Label": "DB 1"
},
"OS-EXT-SRV-ATTR:host": "computenode2",
"security_groups": [
{
'id': 1, 'name': 'securitygroup1',
'description': 'FAKE_SECURITY_GROUP',
'tenant_id': '4ffc664c198e435e9853f2538fbcd7a7'
},
{
'id': 2, 'name': 'securitygroup2',
'description': 'ANOTHER_FAKE_SECURITY_GROUP',
'tenant_id': '4ffc664c198e435e9853f2538fbcd7a7'
}],
}
self.server_9012 = {
"id": 9012,
"name": "sample-server3",
"image": "",
"flavor": {
"id": 1,
"name": "256 MB Server",
},
"hostId": "9e107d9d372bb6826bd81d3542a419d6",
"status": "ACTIVE",
"addresses": {
"public": [
{
"version": 4,
"addr": "4.5.6.7",
},
{
"version": 4,
"addr": "5.6.9.8",
}],
"private": [{
"version": 4,
"addr": "10.13.12.13",
}],
},
"metadata": {
"Server Label": "DB 1"
}
}
servers = [self.server_1234, self.server_5678, self.server_9012]
get_servers_detail = {"servers": servers}
self.requests.register_uri('GET', self.url('detail'),
json=get_servers_detail,
headers=self.json_headers)
self.server_1235 = self.server_1234.copy()
self.server_1235['id'] = 1235
self.server_1235['status'] = 'error'
self.server_1235['fault'] = {'message': 'something went wrong!'}
for s in servers + [self.server_1235]:
self.requests.register_uri('GET', self.url(s['id']),
json={'server': s},
headers=self.json_headers)
for s in (1234, 5678):
self.requests.register_uri('DELETE', self.url(s), status_code=202)
for k in ('test_key', 'key1', 'key2'):
self.requests.register_uri('DELETE',
self.url(1234, 'metadata', k),
status_code=204)
metadata1 = {'metadata': {'test_key': 'test_value'}}
self.requests.register_uri('POST', self.url(1234, 'metadata'),
json=metadata1,
headers=self.json_headers)
self.requests.register_uri('PUT',
self.url(1234, 'metadata', 'test_key'),
json=metadata1,
headers=self.json_headers)
self.diagnostic = {'data': 'Fake diagnostics'}
metadata2 = {'metadata': {'key1': 'val1'}}
for u in ('uuid1', 'uuid2', 'uuid3', 'uuid4'):
self.requests.register_uri('POST', self.url(u, 'metadata'),
json=metadata2, status_code=204)
self.requests.register_uri('DELETE',
self.url(u, 'metadata', 'key1'),
json=self.diagnostic,
headers=self.json_headers)
get_security_groups = {
"security_groups": [{
'id': 1,
'name': 'securitygroup1',
'description': 'FAKE_SECURITY_GROUP',
'tenant_id': '4ffc664c198e435e9853f2538fbcd7a7',
'rules': []}]
}
self.requests.register_uri('GET',
self.url('1234', 'os-security-groups'),
json=get_security_groups)
self.requests.register_uri('POST', self.url(),
json=self.post_servers,
headers=self.json_headers)
self.requests.register_uri('POST', self.url('1234', 'action'),
json=self.post_servers_1234_action,
headers=self.json_headers)
get_os_interface = {
"interfaceAttachments": [
{
"port_state": "ACTIVE",
"net_id": "net-id-1",
"port_id": "port-id-1",
"mac_address": "aa:bb:cc:dd:ee:ff",
"fixed_ips": [{"ip_address": "1.2.3.4"}],
},
{
"port_state": "ACTIVE",
"net_id": "net-id-1",
"port_id": "port-id-1",
"mac_address": "aa:bb:cc:dd:ee:ff",
"fixed_ips": [{"ip_address": "1.2.3.4"}],
}
]
}
self.requests.register_uri('GET',
self.url('1234', 'os-interface'),
json=get_os_interface,
headers=self.json_headers)
interface_data = {'interfaceAttachment': {}}
self.requests.register_uri('POST',
self.url('1234', 'os-interface'),
json=interface_data,
headers=self.json_headers)
def put_servers_1234(request, context):
body = jsonutils.loads(request.body)
assert list(body) == ['server']
fakes.assert_has_keys(body['server'],
optional=['name', 'adminPass'])
return request.body
self.requests.register_uri('PUT', self.url(1234),
text=put_servers_1234,
status_code=204,
headers=self.json_headers)
def post_os_volumes_boot(request, context):
body = jsonutils.loads(request.body)
assert (set(body.keys()) <=
set(['server', 'os:scheduler_hints']))
fakes.assert_has_keys(body['server'],
required=['name', 'flavorRef'],
optional=['imageRef'])
data = body['server']
# Require one, and only one, of the keys for bdm
if 'block_device_mapping' not in data:
if 'block_device_mapping_v2' not in data:
msg = "missing required keys: 'block_device_mapping'"
raise AssertionError(msg)
elif 'block_device_mapping_v2' in data:
msg = "found extra keys: 'block_device_mapping'"
raise AssertionError(msg)
return {'server': self.server_9012}
# NOTE(jamielennox): hack to make os_volumes mock go to the right place
base_url = self.base_url
self.base_url = None
self.requests.register_uri('POST', self.url('os-volumes_boot'),
json=post_os_volumes_boot,
status_code=202,
headers=self.json_headers)
self.base_url = base_url
#
# Server password
#
self.requests.register_uri('DELETE',
self.url(1234, 'os-server-password'),
status_code=202)
class V1(Base):
def setUp(self):
super(V1, self).setUp()
#
# Server Addresses
#
add = self.server_1234['addresses']
self.requests.register_uri('GET', self.url(1234, 'ips'),
json={'addresses': add},
headers=self.json_headers)
self.requests.register_uri('GET', self.url(1234, 'ips', 'public'),
json={'public': add['public']},
headers=self.json_headers)
self.requests.register_uri('GET', self.url(1234, 'ips', 'private'),
json={'private': add['private']},
headers=self.json_headers)
self.requests.register_uri('DELETE',
self.url(1234, 'ips', 'public', '1.2.3.4'),
status_code=202)
self.requests.register_uri('GET',
self.url('1234', 'diagnostics'),
json=self.diagnostic)
self.requests.register_uri('DELETE',
self.url('1234', 'os-interface', 'port-id'))
# Testing with the following password and key
#
# Clear password: FooBar123
#
# RSA Private Key: novaclient/tests/unit/idfake.pem
#
# Encrypted password
# OIuEuQttO8Rk93BcKlwHQsziDAnkAm/V6V8VPToA8ZeUaUBWwS0gwo2K6Y61Z96r
# qG447iRz0uTEEYq3RAYJk1mh3mMIRVl27t8MtIecR5ggVVbz1S9AwXJQypDKl0ho
# QFvhCBcMWPohyGewDJOhDbtuN1IoFI9G55ZvFwCm5y7m7B2aVcoLeIsJZE4PLsIw
# /y5a6Z3/AoJZYGG7IH5WN88UROU3B9JZGFB2qtPLQTOvDMZLUhoPRIJeHiVSlo1N
# tI2/++UsXVg3ow6ItqCJGgdNuGG5JB+bslDHWPxROpesEIHdczk46HCpHQN8f1sk
# Hi/fmZZNQQqj1Ijq0caOIw==
get_server_password = {
'password':
'OIuEuQttO8Rk93BcKlwHQsziDAnkAm/V6V8VPToA8ZeUaUBWwS0gwo2K6Y61Z96r'
'qG447iRz0uTEEYq3RAYJk1mh3mMIRVl27t8MtIecR5ggVVbz1S9AwXJQypDKl0ho'
'QFvhCBcMWPohyGewDJOhDbtuN1IoFI9G55ZvFwCm5y7m7B2aVcoLeIsJZE4PLsIw'
'/y5a6Z3/AoJZYGG7IH5WN88UROU3B9JZGFB2qtPLQTOvDMZLUhoPRIJeHiVSlo1N'
'tI2/++UsXVg3ow6ItqCJGgdNuGG5JB+bslDHWPxROpesEIHdczk46HCpHQN8f1sk'
'Hi/fmZZNQQqj1Ijq0caOIw=='}
self.requests.register_uri('GET',
self.url(1234, 'os-server-password'),
json=get_server_password)
def post_servers(self, request, context):
body = jsonutils.loads(request.body)
context.status_code = 202
assert (set(body.keys()) <=
set(['server', 'os:scheduler_hints']))
fakes.assert_has_keys(body['server'],
required=['name', 'imageRef', 'flavorRef'],
optional=['metadata', 'personality'])
if 'personality' in body['server']:
for pfile in body['server']['personality']:
fakes.assert_has_keys(pfile, required=['path', 'contents'])
if body['server']['name'] == 'some-bad-server':
body = self.server_1235
else:
body = self.server_1234
return {'server': body}
def post_servers_1234_action(self, request, context):
_body = ''
body = jsonutils.loads(request.body)
context.status_code = 202
assert len(body.keys()) == 1
action = list(body)[0]
if v2_fakes.FakeHTTPClient.check_server_actions(body):
# NOTE(snikitin): No need to do any operations here. This 'pass'
# is needed to avoid AssertionError in the last 'else' statement
# if we found 'action' in method check_server_actions and
# raise AssertionError if we didn't find 'action' at all.
pass
elif action == 'rebuild':
body = body[action]
adminPass = body.get('adminPass', 'randompassword')
assert 'imageRef' in body
_body = self.server_1234.copy()
_body['adminPass'] = adminPass
elif action == 'confirmResize':
assert body[action] is None
# This one method returns a different response code
context.status_code = 204
return None
elif action == 'rescue':
if body[action]:
keys = set(body[action].keys())
assert not (keys - set(['adminPass', 'rescue_image_ref']))
else:
assert body[action] is None
_body = {'adminPass': 'RescuePassword'}
elif action == 'createImage':
assert set(body[action].keys()) == set(['name', 'metadata'])
context.headers['location'] = "http://blah/images/456"
elif action == 'os-getConsoleOutput':
assert list(body[action]) == ['length']
context.status_code = 202
return {'output': 'foo'}
elif action == 'os-getSerialConsole':
assert list(body[action]) == ['type']
elif action == 'evacuate':
keys = list(body[action])
if 'adminPass' in keys:
keys.remove('adminPass')
assert set(keys) == set(['host', 'onSharedStorage'])
else:
raise AssertionError("Unexpected server action: %s" % action)
return {'server': _body}

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA9QstF/7prDY7a9La7GS9TpMX+MWWXQgK6pHRLakDFp1WX1Q3
Vly7rWitaZUGirUPMm181oJXBwkKlAxFD7hKjyHYaSswNszPYIAsVkc1+AO5epXz
g9kUBNtfg44Pg72UecwLrZ8JpmNZpJlKQOx6vF+yi7JmHrrIf6il/grIGUPzoT2L
yReimpyPoBrGtXhJYaCJ/XbKg1idRZiQdmwh1F/OmZWn9p0wunnsv08a0+qIywuw
WhG9/Zy9fjnEByfusS6gI0GIxDRL4RWzOqphd3PZzunwIBgEKFhgiki9+2DgcRVO
9I5wnDvfwQREJRZWh1uJa5ZTcfPa1EzZryVeOQIDAQABAoIBABxO3Te/cBk/7p9n
LXlPrfrszUEk+ljm+/PbQpIGy1+Kb5b1sKrebaP7ysS+vZG6lvXZZimVxx398mXm
APhu7tYYL9r+bUR3ZqGcTQLumRJ8w6mgtxANPN3Oxfr5p1stxIBJjTPSgpfhNFLq
joRvjUJDv+mZg2ibZVwyDHMLpdAdKp+3XMdyTLZcH9esqwii+natix7rHd1RuF85
L1dfpxjkItwhgHsfdYS++5X3fRByFOhQ+Nhabh/kPQbQMcteRn1bN6zeCWBSglNb
Ka/ZrXb6ApRUc22Ji62mNO2ZPPekLJeCHk2h2E7ezYX+sGDNvvd/jHVDJJ20FjD1
Z9KXuK0CgYEA/2vniy9yWd925QQtWbmrxgy6yj89feMH/LTv4qP298rGZ2nqxsyd
9pdBdb4NMsi4HmV5PG1hp3VRNBHl53DNh5eqzT8WEXnIF+sbrIU3KzrCVAx1kZTl
+OWKA6aVUsvvO3y85SOvInnsV+IsOGmU4/WBSjYoe39Bo7mq/YuZB9MCgYEA9ZlB
KBm6PjFdHQGNgedXahWzRcwC+ALCYqequPYqJolNzhrK4Uc2sWPSGdnldcHZ4XCQ
wbfCxUSwrMpA1oyuIQ0U4aowmOw5DjIueBWI8XBYEVRBlwvJwbXpBZ/DspGzTUDx
MBrrEwEaMadQvxhRnAzhp0rQAepatcz6Fgb1JkMCgYBMwDLiew5kfSav6JJsDMPW
DksurNQgeNEUmZYfx19V1EPMHWKj/CZXS9oqtEIpCXFyCNHmW4PlmvYcrGgmJJpN
7UAwzo0mES8UKNy2+Yy7W7u7H8dQSKrWILtZH3xtVcR8Xp4wSIm+1V40hkz9YpSP
71y7XQzLF1E1DnyYFZOVawKBgAFrmHfd5jjT2kD/sEzPBK9lXrsJmf7LLUqaw578
NXQxmRSXDRNOcR+Hf0CNBQmwTE1EdGHaaTLw2cC2Drfu6lbgl31SmaNYwl+1pJUn
MrqKtseq4BI6jDkljypsKRqQQyQwOvTXQwLCH9+nowzn3Bj17hwkj51jOJESlWOp
OKO3AoGBALm+jjqyqX7gSnqK3FAumB8mlhv3yI1Wr1ctwe18mKfKbz17HxXRu9pF
K/6e7WMCA1p+jhoE8gj1h2WBcH0nV2qt8Ye8gJBbCi4dhI08o4AfrIV47oZx1RlO
qYcA1U9lyaODY5SL8+6PHOy5J/aYtuA+wvfEnWiCIdKQrhWetcn3
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,328 @@
# 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.
import argparse
from keystoneclient import fixture
import mock
import pkg_resources
import requests
try:
import json
except ImportError:
import simplejson as json
from novaclient import auth_plugin
from novaclient import exceptions
from novaclient.tests.unit import utils
from novaclient.v2 import client
def mock_http_request(resp=None):
"""Mock an HTTP Request."""
if not resp:
resp = fixture.V2Token()
resp.set_scope()
s = resp.add_service('compute')
s.add_endpoint("http://localhost:8774/v1.1", region='RegionOne')
auth_response = utils.TestResponse({
"status_code": 200,
"text": json.dumps(resp),
})
return mock.Mock(return_value=(auth_response))
def requested_headers(cs):
"""Return requested passed headers."""
return {
'User-Agent': cs.client.USER_AGENT,
'Content-Type': 'application/json',
'Accept': 'application/json',
}
class DeprecatedAuthPluginTest(utils.TestCase):
def test_auth_system_success(self):
class MockEntrypoint(pkg_resources.EntryPoint):
def load(self):
return self.authenticate
def authenticate(self, cls, auth_url):
cls._authenticate(auth_url, {"fake": "me"})
def mock_iter_entry_points(_type, name):
if _type == 'openstack.client.authenticate':
return [MockEntrypoint("fake", "fake", ["fake"])]
else:
return []
mock_request = mock_http_request()
@mock.patch.object(pkg_resources, "iter_entry_points",
mock_iter_entry_points)
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
plugin = auth_plugin.DeprecatedAuthPlugin("fake")
cs = client.Client("username", "password", "project_id",
utils.AUTH_URL_V2, auth_system="fake",
auth_plugin=plugin)
cs.client.authenticate()
headers = requested_headers(cs)
token_url = cs.client.auth_url + "/tokens"
mock_request.assert_called_with(
"POST",
token_url,
headers=headers,
data='{"fake": "me"}',
allow_redirects=True,
**self.TEST_REQUEST_BASE)
test_auth_call()
def test_auth_system_not_exists(self):
def mock_iter_entry_points(_t, name=None):
return [pkg_resources.EntryPoint("fake", "fake", ["fake"])]
mock_request = mock_http_request()
@mock.patch.object(pkg_resources, "iter_entry_points",
mock_iter_entry_points)
@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",
utils.AUTH_URL_V2, auth_system="notexists",
auth_plugin=plugin)
self.assertRaises(exceptions.AuthSystemNotFound,
cs.client.authenticate)
test_auth_call()
def test_auth_system_defining_auth_url(self):
class MockAuthUrlEntrypoint(pkg_resources.EntryPoint):
def load(self):
return self.auth_url
def auth_url(self):
return "http://faked/v2.0"
class MockAuthenticateEntrypoint(pkg_resources.EntryPoint):
def load(self):
return self.authenticate
def authenticate(self, cls, auth_url):
cls._authenticate(auth_url, {"fake": "me"})
def mock_iter_entry_points(_type, name):
if _type == 'openstack.client.auth_url':
return [MockAuthUrlEntrypoint("fakewithauthurl",
"fakewithauthurl",
["auth_url"])]
elif _type == 'openstack.client.authenticate':
return [MockAuthenticateEntrypoint("fakewithauthurl",
"fakewithauthurl",
["authenticate"])]
else:
return []
mock_request = mock_http_request()
@mock.patch.object(pkg_resources, "iter_entry_points",
mock_iter_entry_points)
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
plugin = auth_plugin.DeprecatedAuthPlugin("fakewithauthurl")
cs = client.Client("username", "password", "project_id",
auth_system="fakewithauthurl",
auth_plugin=plugin)
cs.client.authenticate()
self.assertEqual("http://faked/v2.0", cs.client.auth_url)
test_auth_call()
@mock.patch.object(pkg_resources, "iter_entry_points")
def test_client_raises_exc_without_auth_url(self, mock_iter_entry_points):
class MockAuthUrlEntrypoint(pkg_resources.EntryPoint):
def load(self):
return self.auth_url
def auth_url(self):
return None
mock_iter_entry_points.side_effect = lambda _t, name: [
MockAuthUrlEntrypoint("fakewithauthurl",
"fakewithauthurl",
["auth_url"])]
plugin = auth_plugin.DeprecatedAuthPlugin("fakewithauthurl")
self.assertRaises(
exceptions.EndpointNotFound,
client.Client, "username", "password", "project_id",
auth_system="fakewithauthurl", auth_plugin=plugin)
class AuthPluginTest(utils.TestCase):
@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."""
class MockEntrypoint(pkg_resources.EntryPoint):
def load(self):
return FakePlugin
class FakePlugin(auth_plugin.BaseAuthPlugin):
def authenticate(self, cls, auth_url):
cls._authenticate(auth_url, {"fake": "me"})
mock_iter_entry_points.side_effect = lambda _t: [
MockEntrypoint("fake", "fake", ["FakePlugin"])]
mock_request.side_effect = mock_http_request()
auth_plugin.discover_auth_systems()
plugin = auth_plugin.load_plugin("fake")
cs = client.Client("username", "password", "project_id",
utils.AUTH_URL_V2, auth_system="fake",
auth_plugin=plugin)
cs.client.authenticate()
headers = requested_headers(cs)
token_url = cs.client.auth_url + "/tokens"
mock_request.assert_called_with(
"POST",
token_url,
headers=headers,
data='{"fake": "me"}',
allow_redirects=True,
**self.TEST_REQUEST_BASE)
@mock.patch.object(pkg_resources, "iter_entry_points")
def test_discover_auth_system_options(self, mock_iter_entry_points):
"""Test that we can load the auth system options."""
class FakePlugin(auth_plugin.BaseAuthPlugin):
@staticmethod
def add_opts(parser):
parser.add_argument('--auth_system_opt',
default=False,
action='store_true',
help="Fake option")
return parser
class MockEntrypoint(pkg_resources.EntryPoint):
def load(self):
return FakePlugin
mock_iter_entry_points.side_effect = lambda _t: [
MockEntrypoint("fake", "fake", ["FakePlugin"])]
parser = argparse.ArgumentParser()
auth_plugin.discover_auth_systems()
auth_plugin.load_auth_system_opts(parser)
opts, args = parser.parse_known_args(['--auth_system_opt'])
self.assertTrue(opts.auth_system_opt)
@mock.patch.object(pkg_resources, "iter_entry_points")
def test_parse_auth_system_options(self, mock_iter_entry_points):
"""Test that we can parse the auth system options."""
class MockEntrypoint(pkg_resources.EntryPoint):
def load(self):
return FakePlugin
class FakePlugin(auth_plugin.BaseAuthPlugin):
def __init__(self):
self.opts = {"fake_argument": True}
def parse_opts(self, args):
return self.opts
mock_iter_entry_points.side_effect = lambda _t: [
MockEntrypoint("fake", "fake", ["FakePlugin"])]
auth_plugin.discover_auth_systems()
plugin = auth_plugin.load_plugin("fake")
plugin.parse_opts([])
self.assertIn("fake_argument", plugin.opts)
@mock.patch.object(pkg_resources, "iter_entry_points")
def test_auth_system_defining_url(self, mock_iter_entry_points):
"""Test the auth_system defining an url."""
class MockEntrypoint(pkg_resources.EntryPoint):
def load(self):
return FakePlugin
class FakePlugin(auth_plugin.BaseAuthPlugin):
def get_auth_url(self):
return "http://faked/v2.0"
mock_iter_entry_points.side_effect = lambda _t: [
MockEntrypoint("fake", "fake", ["FakePlugin"])]
auth_plugin.discover_auth_systems()
plugin = auth_plugin.load_plugin("fake")
cs = client.Client("username", "password", "project_id",
auth_system="fakewithauthurl",
auth_plugin=plugin)
self.assertEqual("http://faked/v2.0", cs.client.auth_url)
@mock.patch.object(pkg_resources, "iter_entry_points")
def test_exception_if_no_authenticate(self, mock_iter_entry_points):
"""Test that no authenticate raises a proper exception."""
class MockEntrypoint(pkg_resources.EntryPoint):
def load(self):
return FakePlugin
class FakePlugin(auth_plugin.BaseAuthPlugin):
pass
mock_iter_entry_points.side_effect = lambda _t: [
MockEntrypoint("fake", "fake", ["FakePlugin"])]
auth_plugin.discover_auth_systems()
plugin = auth_plugin.load_plugin("fake")
self.assertRaises(
exceptions.EndpointNotFound,
client.Client, "username", "password", "project_id",
auth_system="fake", auth_plugin=plugin)
@mock.patch.object(pkg_resources, "iter_entry_points")
def test_exception_if_no_url(self, mock_iter_entry_points):
"""Test that no auth_url at all raises exception."""
class MockEntrypoint(pkg_resources.EntryPoint):
def load(self):
return FakePlugin
class FakePlugin(auth_plugin.BaseAuthPlugin):
pass
mock_iter_entry_points.side_effect = lambda _t: [
MockEntrypoint("fake", "fake", ["FakePlugin"])]
auth_plugin.discover_auth_systems()
plugin = auth_plugin.load_plugin("fake")
self.assertRaises(
exceptions.EndpointNotFound,
client.Client, "username", "password", "project_id",
auth_system="fake", auth_plugin=plugin)

View File

@ -0,0 +1,69 @@
#
# 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 base
from novaclient import exceptions
from novaclient.tests.unit import utils
from novaclient.tests.unit.v2 import fakes
from novaclient.v2 import flavors
cs = fakes.FakeClient()
class BaseTest(utils.TestCase):
def test_resource_repr(self):
r = base.Resource(None, dict(foo="bar", baz="spam"))
self.assertEqual("<Resource baz=spam, foo=bar>", repr(r))
def test_getid(self):
self.assertEqual(4, base.getid(4))
class TmpObject(object):
id = 4
self.assertEqual(4, base.getid(TmpObject))
def test_resource_lazy_getattr(self):
f = flavors.Flavor(cs.flavors, {'id': 1})
self.assertEqual('256 mb server', f.name)
cs.assert_called('GET', '/flavors/1')
# Missing stuff still fails after a second get
self.assertRaises(AttributeError, getattr, f, 'blahblah')
def test_eq(self):
# Two resources of the same type with the same id: equal
r1 = base.Resource(None, {'id': 1, 'name': 'hi'})
r2 = base.Resource(None, {'id': 1, 'name': 'hello'})
self.assertEqual(r1, r2)
# Two resoruces of different types: never equal
r1 = base.Resource(None, {'id': 1})
r2 = flavors.Flavor(None, {'id': 1})
self.assertNotEqual(r1, r2)
# Two resources with no ID: equal if their info is equal
r1 = base.Resource(None, {'name': 'joe', 'age': 12})
r2 = base.Resource(None, {'name': 'joe', 'age': 12})
self.assertEqual(r1, r2)
def test_findall_invalid_attribute(self):
# Make sure findall with an invalid attribute doesn't cause errors.
# The following should not raise an exception.
cs.flavors.findall(vegetable='carrot')
# However, find() should raise an error
self.assertRaises(exceptions.NotFound,
cs.flavors.find,
vegetable='carrot')

View File

@ -0,0 +1,390 @@
# 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.
import json
import logging
import socket
import fixtures
import mock
import requests
import novaclient.client
import novaclient.extension
from novaclient.tests.unit import utils
import novaclient.v2.client
class TCPKeepAliveAdapterTest(utils.TestCase):
@mock.patch.object(requests.adapters.HTTPAdapter, 'init_poolmanager')
def test_init_poolmanager(self, mock_init_poolmgr):
adapter = novaclient.client.TCPKeepAliveAdapter()
kwargs = {}
adapter.init_poolmanager(**kwargs)
if requests.__version__ >= '2.4.1':
kwargs.setdefault('socket_options', [
(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1),
(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1),
])
# NOTE(melwitt): This is called twice because
# HTTPAdapter.__init__ calls it first.
self.assertEqual(2, mock_init_poolmgr.call_count)
mock_init_poolmgr.assert_called_with(**kwargs)
class ClientConnectionPoolTest(utils.TestCase):
@mock.patch("novaclient.client.TCPKeepAliveAdapter")
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):
instance = novaclient.client.HTTPClient(user='user',
password='password',
projectid='project',
timeout=2,
auth_url="http://www.blah.com")
self.assertEqual(2, instance.timeout)
mock_request = mock.Mock()
mock_request.return_value = requests.Response()
mock_request.return_value.status_code = 200
mock_request.return_value.headers = {
'x-server-management-url': 'blah.com',
'x-auth-token': 'blah',
}
with mock.patch('requests.request', mock_request):
instance.authenticate()
requests.request.assert_called_with(
mock.ANY, mock.ANY, timeout=2, headers=mock.ANY,
verify=mock.ANY)
def test_client_reauth(self):
instance = novaclient.client.HTTPClient(user='user',
password='password',
projectid='project',
timeout=2,
auth_url="http://www.blah.com")
instance.auth_token = 'foobar'
instance.management_url = 'http://example.com'
instance.get_service_url = mock.Mock(return_value='http://example.com')
instance.version = 'v2.0'
mock_request = mock.Mock()
mock_request.side_effect = novaclient.exceptions.Unauthorized(401)
with mock.patch('requests.request', mock_request):
try:
instance.get('/servers/detail')
except Exception:
pass
get_headers = {'X-Auth-Project-Id': 'project',
'X-Auth-Token': 'foobar',
'User-Agent': 'python-novaclient',
'Accept': 'application/json'}
reauth_headers = {'Content-Type': 'application/json',
'Accept': 'application/json',
'User-Agent': 'python-novaclient'}
data = {
"auth": {
"tenantName": "project",
"passwordCredentials": {
"username": "user",
"password": "password"
}
}
}
expected = [mock.call('GET',
'http://example.com/servers/detail',
timeout=mock.ANY,
headers=get_headers,
verify=mock.ANY),
mock.call('POST', 'http://www.blah.com/tokens',
timeout=mock.ANY,
headers=reauth_headers,
allow_redirects=mock.ANY,
data=mock.ANY,
verify=mock.ANY)]
self.assertEqual(expected, mock_request.call_args_list)
token_post_call = mock_request.call_args_list[1]
self.assertEqual(data, json.loads(token_post_call[1]['data']))
@mock.patch.object(novaclient.client.HTTPClient, 'request',
return_value=(200, "{'versions':[]}"))
def _check_version_url(self, management_url, version_url, mock_request):
projectid = '25e469aa1848471b875e68cde6531bc5'
instance = novaclient.client.HTTPClient(user='user',
password='password',
projectid=projectid,
auth_url="http://www.blah.com")
instance.auth_token = 'foobar'
instance.management_url = management_url % projectid
mock_get_service_url = mock.Mock(return_value=instance.management_url)
instance.get_service_url = mock_get_service_url
instance.version = 'v2.0'
# If passing None as the part of url, a client accesses the url which
# doesn't include "v2/<projectid>" for getting API version info.
instance.get(None)
mock_request.assert_called_once_with(version_url, 'GET',
headers=mock.ANY)
mock_request.reset_mock()
# Otherwise, a client accesses the url which includes "v2/<projectid>".
instance.get('servers')
url = instance.management_url + 'servers'
mock_request.assert_called_once_with(url, 'GET', headers=mock.ANY)
def test_client_version_url(self):
self._check_version_url('http://foo.com/v2/%s', 'http://foo.com/')
def test_client_version_url_with_project_name(self):
self._check_version_url('http://foo.com/nova/v2/%s',
'http://foo.com/nova/')
def test_get_client_class_v3(self):
output = novaclient.client.get_client_class('3')
self.assertEqual(output, novaclient.v2.client.Client)
def test_get_client_class_v2(self):
output = novaclient.client.get_client_class('2')
self.assertEqual(output, novaclient.v2.client.Client)
def test_get_client_class_v2_int(self):
output = novaclient.client.get_client_class(2)
self.assertEqual(output, novaclient.v2.client.Client)
def test_get_client_class_v1_1(self):
output = novaclient.client.get_client_class('1.1')
self.assertEqual(output, novaclient.v2.client.Client)
def test_get_client_class_unknown(self):
self.assertRaises(novaclient.exceptions.UnsupportedVersion,
novaclient.client.get_client_class, '0')
def test_client_with_os_cache_enabled(self):
cs = novaclient.v2.client.Client("user", "password", "project_id",
auth_url="foo/v2", os_cache=True)
self.assertTrue(cs.os_cache)
self.assertTrue(cs.client.os_cache)
def test_client_with_os_cache_disabled(self):
cs = novaclient.v2.client.Client("user", "password", "project_id",
auth_url="foo/v2", os_cache=False)
self.assertFalse(cs.os_cache)
self.assertFalse(cs.client.os_cache)
def test_client_with_no_cache_enabled(self):
cs = novaclient.v2.client.Client("user", "password", "project_id",
auth_url="foo/v2", no_cache=True)
self.assertFalse(cs.os_cache)
self.assertFalse(cs.client.os_cache)
def test_client_with_no_cache_disabled(self):
cs = novaclient.v2.client.Client("user", "password", "project_id",
auth_url="foo/v2", no_cache=False)
self.assertTrue(cs.os_cache)
self.assertTrue(cs.client.os_cache)
def test_client_set_management_url_v1_1(self):
cs = novaclient.v2.client.Client("user", "password", "project_id",
auth_url="foo/v2")
cs.set_management_url("blabla")
self.assertEqual("blabla", cs.client.management_url)
def test_client_get_reset_timings_v1_1(self):
cs = novaclient.v2.client.Client("user", "password", "project_id",
auth_url="foo/v2")
self.assertEqual(0, len(cs.get_timings()))
cs.client.times.append("somevalue")
self.assertEqual(1, len(cs.get_timings()))
self.assertEqual("somevalue", cs.get_timings()[0])
cs.reset_timings()
self.assertEqual(0, len(cs.get_timings()))
@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.v2.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()
self.assertEqual("password", cs._get_password())
self.assertFalse(cs.password_func.called)
def test_get_password_none(self):
cs = novaclient.client.HTTPClient("user", None, "", "")
self.assertIsNone(cs._get_password())
def test_get_password_func(self):
cs = novaclient.client.HTTPClient("user", None, "", "")
cs.password_func = mock.Mock(return_value="password")
self.assertEqual("password", cs._get_password())
cs.password_func.assert_called_once_with()
cs.password_func = mock.Mock()
self.assertEqual("password", cs._get_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("foo/v2", cs.auth_url)
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("12345", cs.auth_token)
self.assertEqual("compute/v100", cs.bypass_url)
self.assertEqual("compute/v100", cs.management_url)
@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'}})
cs.http_log_req('GET', '/foo', {'headers': {},
'data':
'{"auth": {"passwordCredentials": '
'{"password": "zhaoqin"}}}'})
output = self.logger.output.split('\n')
self.assertIn("REQ: curl -g -i '/foo' -X GET", output)
self.assertIn(
"REQ: curl -g -i '/foo' -X GET -H "
'"X-Auth-Token: {SHA1}b42162b6ffdbd7c3c37b7c95b7ba9f51dda0236d"',
output)
self.assertIn(
"REQ: curl -g -i '/foo' -X GET -H "
'"X-Auth-Token: {SHA1}b42162b6ffdbd7c3c37b7c95b7ba9f51dda0236d"'
' -H "X-Foo: bar"',
output)
self.assertIn(
"REQ: curl -g -i '/foo' -X GET -d "
'\'{"auth": {"passwordCredentials": {"password":'
' "{SHA1}4fc49c6a671ce889078ff6b250f7066cf6d2ada2"}}}\'',
output)
def test_log_resp(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
text = ('{"access": {"token": {"id": "zhaoqin"}}}')
resp = utils.TestResponse({'status_code': 200, 'headers': {},
'text': text})
cs.http_log_resp(resp)
output = self.logger.output.split('\n')
self.assertIn('RESP: [200] {}', output)
self.assertIn('RESP BODY: {"access": {"token": {"id":'
' "{SHA1}4fc49c6a671ce889078ff6b250f7066cf6d2ada2"}}}',
output)

View File

@ -0,0 +1,80 @@
# 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.
import imp
import inspect
import mock
import pkg_resources
import novaclient.shell
from novaclient.tests.unit import utils
class DiscoverTest(utils.TestCase):
def test_discover_via_entry_points(self):
def mock_iter_entry_points(group):
if group == 'novaclient.extension':
fake_ep = mock.Mock()
fake_ep.name = 'foo'
fake_ep.module = imp.new_module('foo')
fake_ep.load.return_value = fake_ep.module
return [fake_ep]
@mock.patch.object(pkg_resources, 'iter_entry_points',
mock_iter_entry_points)
def test():
shell = novaclient.shell.OpenStackComputeShell()
for name, module in shell._discover_via_entry_points():
self.assertEqual('foo', name)
self.assertTrue(inspect.ismodule(module))
test()
def test_discover_extensions(self):
def mock_discover_via_python_path(self):
yield 'foo', imp.new_module('foo')
def mock_discover_via_contrib_path(self, version):
yield 'bar', imp.new_module('bar')
def mock_discover_via_entry_points(self):
yield 'baz', imp.new_module('baz')
@mock.patch.object(novaclient.shell.OpenStackComputeShell,
'_discover_via_python_path',
mock_discover_via_python_path)
@mock.patch.object(novaclient.shell.OpenStackComputeShell,
'_discover_via_contrib_path',
mock_discover_via_contrib_path)
@mock.patch.object(novaclient.shell.OpenStackComputeShell,
'_discover_via_entry_points',
mock_discover_via_entry_points)
def test():
shell = novaclient.shell.OpenStackComputeShell()
extensions = shell._discover_extensions('1.1')
self.assertEqual(3, len(extensions))
names = sorted(['foo', 'bar', 'baz'])
sorted_extensions = sorted(extensions, key=lambda ext: ext.name)
for i in range(len(names)):
ext = sorted_extensions[i]
name = names[i]
self.assertEqual(ext.name, name)
self.assertTrue(inspect.ismodule(ext.module))
test()

View File

@ -0,0 +1,217 @@
#
# 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 mock
import requests
import six
from novaclient import client
from novaclient import exceptions
from novaclient.tests.unit import utils
fake_response = utils.TestResponse({
"status_code": 200,
"text": '{"hi": "there"}',
})
mock_request = mock.Mock(return_value=(fake_response))
refused_response = utils.TestResponse({
"status_code": 400,
"text": '[Errno 111] Connection refused',
})
refused_mock_request = mock.Mock(return_value=(refused_response))
bad_req_response = utils.TestResponse({
"status_code": 400,
"text": '',
})
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)
retry_after_response = utils.TestResponse({
"status_code": 413,
"text": '',
"headers": {
"retry-after": "5"
},
})
retry_after_mock_request = mock.Mock(return_value=retry_after_response)
retry_after_no_headers_response = utils.TestResponse({
"status_code": 413,
"text": '',
})
retry_after_no_headers_mock_request = mock.Mock(
return_value=retry_after_no_headers_response)
retry_after_non_supporting_response = utils.TestResponse({
"status_code": 403,
"text": '',
"headers": {
"retry-after": "5"
},
})
retry_after_non_supporting_mock_request = mock.Mock(
return_value=retry_after_non_supporting_response)
def get_client():
cl = client.HTTPClient("username", "password",
"project_id",
utils.AUTH_URL_V2)
return cl
def get_authed_client():
cl = get_client()
cl.management_url = "http://example.com"
cl.auth_token = "token"
cl.get_service_url = mock.Mock(return_value="http://example.com")
return cl
class ClientTest(utils.TestCase):
def test_get(self):
cl = get_authed_client()
@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")
headers = {"X-Auth-Token": "token",
"X-Auth-Project-Id": "project_id",
"User-Agent": cl.USER_AGENT,
'Accept': 'application/json'}
mock_request.assert_called_with(
"GET",
"http://example.com/hi",
headers=headers,
**self.TEST_REQUEST_BASE)
# Automatic JSON parsing
self.assertEqual({"hi": "there"}, body)
test_get_call()
def test_post(self):
cl = get_authed_client()
@mock.patch.object(requests, "request", mock_request)
def test_post_call():
cl.post("/hi", body=[1, 2, 3])
headers = {
"X-Auth-Token": "token",
"X-Auth-Project-Id": "project_id",
"Content-Type": "application/json",
'Accept': 'application/json',
"User-Agent": cl.USER_AGENT
}
mock_request.assert_called_with(
"POST",
"http://example.com/hi",
headers=headers,
data='[1, 2, 3]',
**self.TEST_REQUEST_BASE)
test_post_call()
def test_auth_failure(self):
cl = get_client()
# response must not have x-server-management-url header
@mock.patch.object(requests.Session, "request", mock_request)
def test_auth_call():
self.assertRaises(exceptions.AuthorizationFailure, cl.authenticate)
test_auth_call()
def test_auth_failure_due_to_miss_of_auth_url(self):
cl = client.HTTPClient("username", "password")
self.assertRaises(exceptions.AuthorizationFailure, cl.authenticate)
def test_connection_refused(self):
cl = get_client()
@mock.patch.object(requests, "request", refused_mock_request)
def test_refused_call():
self.assertRaises(exceptions.ConnectionRefused, cl.get, "/hi")
test_refused_call()
def test_bad_request(self):
cl = get_client()
@mock.patch.object(requests, "request", bad_req_mock_request)
def test_refused_call():
self.assertRaises(exceptions.BadRequest, cl.get, "/hi")
test_refused_call()
def test_client_logger(self):
cl1 = client.HTTPClient("username", "password", "project_id",
"auth_test", http_log_debug=True)
self.assertEqual(1, len(cl1._logger.handlers))
cl2 = client.HTTPClient("username", "password", "project_id",
"auth_test", http_log_debug=True)
self.assertEqual(1, len(cl2._logger.handlers))
@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')
@mock.patch.object(requests, "request", retry_after_mock_request)
def test_retry_after_request(self):
cl = get_client()
try:
cl.get("/hi")
except exceptions.OverLimit as exc:
self.assertEqual(5, exc.retry_after)
else:
self.fail('Expected exceptions.OverLimit')
@mock.patch.object(requests, "request",
retry_after_no_headers_mock_request)
def test_retry_after_request_no_headers(self):
cl = get_client()
try:
cl.get("/hi")
except exceptions.OverLimit as exc:
self.assertEqual(0, exc.retry_after)
else:
self.fail('Expected exceptions.OverLimit')
@mock.patch.object(requests, "request",
retry_after_non_supporting_mock_request)
def test_retry_after_request_non_supporting_exc(self):
cl = get_client()
self.assertRaises(exceptions.Forbidden, cl.get, "/hi")

View File

@ -0,0 +1,73 @@
#
# 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 keystoneclient import fixture
from novaclient import exceptions
from novaclient import service_catalog
from novaclient.tests.unit import utils
SERVICE_CATALOG = fixture.V2Token()
SERVICE_CATALOG.set_scope()
_s = SERVICE_CATALOG.add_service('compute')
_e = _s.add_endpoint("https://compute1.host/v1/1")
_e["tenantId"] = "1"
_e["versionId"] = "1.0"
_e = _s.add_endpoint("https://compute1.host/v1.1/2", region="North")
_e["tenantId"] = "2"
_e["versionId"] = "1.1"
_e = _s.add_endpoint("https://compute1.host/v2/1", region="North")
_e["tenantId"] = "1"
_e["versionId"] = "2"
_s = SERVICE_CATALOG.add_service('volume')
_e = _s.add_endpoint("https://volume1.host/v1/1", region="South")
_e["tenantId"] = "1"
_e = _s.add_endpoint("https://volume1.host/v1.1/2", region="South")
_e["tenantId"] = "2"
class ServiceCatalogTest(utils.TestCase):
def test_building_a_service_catalog(self):
sc = service_catalog.ServiceCatalog(SERVICE_CATALOG)
self.assertRaises(exceptions.AmbiguousEndpoints, sc.url_for,
service_type='compute')
self.assertEqual("https://compute1.host/v2/1",
sc.url_for('tenantId', '1', service_type='compute'))
self.assertEqual("https://compute1.host/v1.1/2",
sc.url_for('tenantId', '2', service_type='compute'))
self.assertRaises(exceptions.EndpointNotFound, sc.url_for,
"region", "South", service_type='compute')
def test_building_a_service_catalog_insensitive_case(self):
sc = service_catalog.ServiceCatalog(SERVICE_CATALOG)
# Matching south (and catalog has South).
self.assertRaises(exceptions.AmbiguousEndpoints, sc.url_for,
'region', 'south', service_type='volume')
def test_alternate_service_type(self):
sc = service_catalog.ServiceCatalog(SERVICE_CATALOG)
self.assertRaises(exceptions.AmbiguousEndpoints, sc.url_for,
service_type='volume')
self.assertEqual("https://volume1.host/v1/1",
sc.url_for('tenantId', '1', service_type='volume'))
self.assertEqual("https://volume1.host/v1.1/2",
sc.url_for('tenantId', '2', service_type='volume'))
self.assertRaises(exceptions.EndpointNotFound, sc.url_for,
"region", "North", service_type='volume')

View File

@ -0,0 +1,389 @@
#
# 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 distutils.version as dist_version
import re
import sys
import fixtures
from keystoneclient import fixture
import mock
import prettytable
import requests_mock
import six
from testtools import matchers
import novaclient.client
from novaclient import exceptions
import novaclient.shell
from novaclient.tests.unit import utils
FAKE_ENV = {'OS_USERNAME': 'username',
'OS_PASSWORD': 'password',
'OS_TENANT_NAME': 'tenant_name',
'OS_AUTH_URL': 'http://no.where/v2.0'}
FAKE_ENV2 = {'OS_USER_ID': 'user_id',
'OS_PASSWORD': 'password',
'OS_TENANT_ID': 'tenant_id',
'OS_AUTH_URL': 'http://no.where/v2.0'}
FAKE_ENV3 = {'OS_USER_ID': 'user_id',
'OS_PASSWORD': 'password',
'OS_TENANT_ID': 'tenant_id',
'OS_AUTH_URL': 'http://no.where/v2.0',
'NOVA_ENDPOINT_TYPE': 'novaURL',
'OS_ENDPOINT_TYPE': 'osURL'}
FAKE_ENV4 = {'OS_USER_ID': 'user_id',
'OS_PASSWORD': 'password',
'OS_TENANT_ID': 'tenant_id',
'OS_AUTH_URL': 'http://no.where/v2.0',
'NOVA_ENDPOINT_TYPE': 'internal',
'OS_ENDPOINT_TYPE': 'osURL'}
def _create_ver_list(versions):
return {'versions': {'values': versions}}
class ParserTest(utils.TestCase):
def setUp(self):
super(ParserTest, self).setUp()
self.parser = novaclient.shell.NovaClientArgumentParser()
def test_ambiguous_option(self):
self.parser.add_argument('--tic')
self.parser.add_argument('--tac')
try:
self.parser.parse_args(['--t'])
except SystemExit as err:
self.assertEqual(2, err.code)
else:
self.fail('SystemExit not raised')
def test_not_really_ambiguous_option(self):
# current/deprecated forms of the same option
self.parser.add_argument('--tic-tac', action="store_true")
self.parser.add_argument('--tic_tac', action="store_true")
args = self.parser.parse_args(['--tic'])
self.assertTrue(args.tic_tac)
class ShellTest(utils.TestCase):
_msg_no_tenant_project = ("You must provide a project name or project"
" id via --os-project-name, --os-project-id,"
" env[OS_PROJECT_ID] or env[OS_PROJECT_NAME]."
" You may use os-project and os-tenant"
" interchangeably.")
def make_env(self, exclude=None, fake_env=FAKE_ENV):
env = dict((k, v) for k, v in fake_env.items() if k != exclude)
self.useFixture(fixtures.MonkeyPatch('os.environ', env))
def setUp(self):
super(ShellTest, self).setUp()
self.useFixture(fixtures.MonkeyPatch(
'novaclient.client.get_client_class',
mock.MagicMock))
self.nc_util = mock.patch(
'novaclient.openstack.common.cliutils.isunauthenticated').start()
self.nc_util.return_value = False
def shell(self, argstr, exitcodes=(0,)):
orig = sys.stdout
orig_stderr = sys.stderr
try:
sys.stdout = six.StringIO()
sys.stderr = six.StringIO()
_shell = novaclient.shell.OpenStackComputeShell()
_shell.main(argstr.split())
except SystemExit:
exc_type, exc_value, exc_traceback = sys.exc_info()
self.assertIn(exc_value.code, exitcodes)
finally:
stdout = sys.stdout.getvalue()
sys.stdout.close()
sys.stdout = orig
stderr = sys.stderr.getvalue()
sys.stderr.close()
sys.stderr = orig_stderr
return (stdout, stderr)
def register_keystone_discovery_fixture(self, mreq):
v2_url = "http://no.where/v2.0"
v2_version = fixture.V2Discovery(v2_url)
mreq.register_uri(
'GET', v2_url, json=_create_ver_list([v2_version]),
status_code=200)
def test_help_unknown_command(self):
self.assertRaises(exceptions.CommandError, self.shell, 'help foofoo')
def test_invalid_timeout(self):
for f in [0, -1, -10]:
cmd_text = '--timeout %s' % (f)
stdout, stderr = self.shell(cmd_text, exitcodes=[0, 2])
required = [
'argument --timeout: %s must be greater than 0' % (f),
]
for r in required:
self.assertIn(r, stderr)
def test_help(self):
required = [
'.*?^usage: ',
'.*?^\s+root-password\s+Change the admin password',
'.*?^See "nova help COMMAND" for help on a specific command',
]
stdout, stderr = self.shell('help')
for r in required:
self.assertThat((stdout + stderr),
matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE))
def test_help_on_subcommand(self):
required = [
'.*?^usage: nova root-password',
'.*?^Change the admin password',
'.*?^Positional arguments:',
]
stdout, stderr = self.shell('help root-password')
for r in required:
self.assertThat((stdout + stderr),
matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE))
def test_help_no_options(self):
required = [
'.*?^usage: ',
'.*?^\s+root-password\s+Change the admin password',
'.*?^See "nova help COMMAND" for help on a specific command',
]
stdout, stderr = self.shell('')
for r in required:
self.assertThat((stdout + stderr),
matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE))
def test_bash_completion(self):
stdout, stderr = self.shell('bash-completion')
# just check we have some output
required = [
'.*--matching',
'.*--wrap',
'.*help',
'.*secgroup-delete-rule',
'.*--priority']
for r in required:
self.assertThat((stdout + stderr),
matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE))
def test_no_username(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_USERNAME')
try:
self.shell('list')
except exceptions.CommandError as message:
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 = self._msg_no_tenant_project
self.make_env(exclude='OS_TENANT_NAME')
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_id(self):
required = self._msg_no_tenant_project
self.make_env(exclude='OS_TENANT_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_auth_url(self):
required = ('You must provide an auth url'
' via either --os-auth-url or env[OS_AUTH_URL] or'
' specify an auth_system which defines a default url'
' with --os-auth-system or env[OS_AUTH_SYSTEM]',)
self.make_env(exclude='OS_AUTH_URL')
try:
self.shell('list')
except exceptions.CommandError as message:
self.assertEqual(required, message.args)
else:
self.fail('CommandError not raised')
@mock.patch('novaclient.client.Client')
@requests_mock.Mocker()
def test_nova_endpoint_type(self, mock_client, m_requests):
self.make_env(fake_env=FAKE_ENV3)
self.register_keystone_discovery_fixture(m_requests)
self.shell('list')
client_kwargs = mock_client.call_args_list[0][1]
self.assertEqual(client_kwargs['endpoint_type'], 'novaURL')
@mock.patch('novaclient.client.Client')
@requests_mock.Mocker()
def test_endpoint_type_like_other_clients(self, mock_client, m_requests):
self.make_env(fake_env=FAKE_ENV4)
self.register_keystone_discovery_fixture(m_requests)
self.shell('list')
client_kwargs = mock_client.call_args_list[0][1]
self.assertEqual(client_kwargs['endpoint_type'], 'internalURL')
@mock.patch('novaclient.client.Client')
@requests_mock.Mocker()
def test_os_endpoint_type(self, mock_client, m_requests):
self.make_env(exclude='NOVA_ENDPOINT_TYPE', fake_env=FAKE_ENV3)
self.register_keystone_discovery_fixture(m_requests)
self.shell('list')
client_kwargs = mock_client.call_args_list[0][1]
self.assertEqual(client_kwargs['endpoint_type'], 'osURL')
@mock.patch('novaclient.client.Client')
def test_default_endpoint_type(self, mock_client):
self.make_env()
self.shell('list')
client_kwargs = mock_client.call_args_list[0][1]
self.assertEqual(client_kwargs['endpoint_type'], 'publicURL')
@mock.patch('sys.stdin', side_effect=mock.MagicMock)
@mock.patch('getpass.getpass', return_value='password')
@requests_mock.Mocker()
def test_password(self, mock_getpass, mock_stdin, m_requests):
mock_stdin.encoding = "utf-8"
# default output of empty tables differs depending between prettytable
# versions
if (hasattr(prettytable, '__version__') and
dist_version.StrictVersion(prettytable.__version__) <
dist_version.StrictVersion('0.7.2')):
ex = '\n'
else:
ex = '\n'.join([
'+----+------+--------+------------+-------------+----------+',
'| ID | Name | Status | Task State | Power State | Networks |',
'+----+------+--------+------------+-------------+----------+',
'+----+------+--------+------------+-------------+----------+',
''
])
self.make_env(exclude='OS_PASSWORD')
self.register_keystone_discovery_fixture(m_requests)
stdout, stderr = self.shell('list')
self.assertEqual((stdout + stderr), ex)
@mock.patch('sys.stdin', side_effect=mock.MagicMock)
@mock.patch('getpass.getpass', side_effect=EOFError)
def test_no_password(self, mock_getpass, mock_stdin):
required = ('Expecting a password provided'
' via either --os-password, env[OS_PASSWORD],'
' or prompted response',)
self.make_env(exclude='OS_PASSWORD')
try:
self.shell('list')
except exceptions.CommandError as message:
self.assertEqual(required, message.args)
else:
self.fail('CommandError not raised')
def _test_service_type(self, version, service_type, mock_client):
if version is None:
cmd = 'list'
else:
cmd = ('--service_type %s --os-compute-api-version %s list' %
(service_type, version))
self.make_env()
self.shell(cmd)
_, client_kwargs = mock_client.call_args_list[0]
self.assertEqual(service_type, client_kwargs['service_type'])
@mock.patch('novaclient.client.Client')
def test_default_service_type(self, mock_client):
self._test_service_type(None, 'compute', mock_client)
@mock.patch('novaclient.client.Client')
def test_v1_1_service_type(self, mock_client):
self._test_service_type('1.1', 'compute', mock_client)
@mock.patch('novaclient.client.Client')
def test_v2_service_type(self, mock_client):
self._test_service_type('2', 'compute', mock_client)
@mock.patch('novaclient.client.Client')
def test_v3_service_type(self, mock_client):
self._test_service_type('3', 'computev3', mock_client)
@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())
@mock.patch.object(novaclient.shell.OpenStackComputeShell, 'main')
def test_main_keyboard_interrupt(self, mock_compute_shell):
# Ensure that exit code is 130 for KeyboardInterrupt
mock_compute_shell.side_effect = KeyboardInterrupt()
try:
novaclient.shell.main()
except SystemExit as ex:
self.assertEqual(ex.code, 130)
class ShellTestKeystoneV3(ShellTest):
def make_env(self, exclude=None, fake_env=FAKE_ENV):
if 'OS_AUTH_URL' in fake_env:
fake_env.update({'OS_AUTH_URL': 'http://no.where/v3'})
env = dict((k, v) for k, v in fake_env.items() if k != exclude)
self.useFixture(fixtures.MonkeyPatch('os.environ', env))
def register_keystone_discovery_fixture(self, mreq):
v3_url = "http://no.where/v3"
v3_version = fixture.V3Discovery(v3_url)
mreq.register_uri(
'GET', v3_url, json=_create_ver_list([v3_version]),
status_code=200)

View File

@ -0,0 +1,357 @@
#
# 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 sys
import mock
import six
from novaclient import base
from novaclient import exceptions
from novaclient.tests.unit import utils as test_utils
from novaclient import utils
UUID = '8e8ec658-c7b0-4243-bdf8-6f7f2952c0d0'
class FakeResource(object):
NAME_ATTR = 'name'
def __init__(self, _id, properties):
self.id = _id
try:
self.name = properties['name']
except KeyError:
pass
class FakeManager(base.ManagerWithFind):
resource_class = FakeResource
resources = [
FakeResource('1234', {'name': 'entity_one'}),
FakeResource('12345', {'name': 'UPPER'}),
FakeResource('123456', {'name': 'lower'}),
FakeResource('1234567', {'name': 'Mixed'}),
FakeResource('12345678', {'name': 'mixed'}),
FakeResource(UUID, {'name': 'entity_two'}),
FakeResource('5678', {'name': '9876'}),
FakeResource('01234', {'name': 'entity_three'})
]
is_alphanum_id_allowed = None
def __init__(self, alphanum_id_allowed=False):
self.is_alphanum_id_allowed = alphanum_id_allowed
def get(self, resource_id):
for resource in self.resources:
if resource.id == str(resource_id):
return resource
raise exceptions.NotFound(resource_id)
def list(self):
return self.resources
class FakeDisplayResource(object):
NAME_ATTR = 'display_name'
def __init__(self, _id, properties):
self.id = _id
try:
self.display_name = properties['display_name']
except KeyError:
pass
class FakeDisplayManager(FakeManager):
resource_class = FakeDisplayResource
resources = [
FakeDisplayResource('4242', {'display_name': 'entity_three'}),
]
class FindResourceTestCase(test_utils.TestCase):
def setUp(self):
super(FindResourceTestCase, self).setUp()
self.manager = FakeManager(None)
def test_find_none(self):
"""Test a few non-valid inputs."""
self.assertRaises(exceptions.CommandError,
utils.find_resource,
self.manager,
'asdf')
self.assertRaises(exceptions.CommandError,
utils.find_resource,
self.manager,
None)
self.assertRaises(exceptions.CommandError,
utils.find_resource,
self.manager,
{})
def test_find_by_integer_id(self):
output = utils.find_resource(self.manager, 1234)
self.assertEqual(output, self.manager.get('1234'))
def test_find_by_str_id(self):
output = utils.find_resource(self.manager, '1234')
self.assertEqual(output, self.manager.get('1234'))
def test_find_by_uuid(self):
output = utils.find_resource(self.manager, UUID)
self.assertEqual(output, self.manager.get(UUID))
def test_find_by_str_name(self):
output = utils.find_resource(self.manager, 'entity_one')
self.assertEqual(output, self.manager.get('1234'))
def test_find_by_str_upper_name(self):
output = utils.find_resource(self.manager, 'UPPER')
self.assertEqual(output, self.manager.get('12345'))
def test_find_by_str_lower_name(self):
output = utils.find_resource(self.manager, 'lower')
self.assertEqual(output, self.manager.get('123456'))
def test_find_by_str_mix_name(self):
output = utils.find_resource(self.manager, 'Mixed')
self.assertEqual(output, self.manager.get('1234567'))
def test_find_by_str_lower_name_mixed(self):
output = utils.find_resource(self.manager, 'mixed')
self.assertEqual(output, self.manager.get('12345678'))
def test_find_by_str_display_name(self):
display_manager = FakeDisplayManager(None)
output = utils.find_resource(display_manager, 'entity_three')
self.assertEqual(output, display_manager.get('4242'))
def test_find_in_alphanum_allowed_manager_by_str_id_(self):
alphanum_manager = FakeManager(True)
output = utils.find_resource(alphanum_manager, '01234')
self.assertEqual(output, alphanum_manager.get('01234'))
class _FakeResult(object):
def __init__(self, name, value):
self.name = name
self.value = value
class PrintResultTestCase(test_utils.TestCase):
@mock.patch('sys.stdout', six.StringIO())
def test_print_dict(self):
dict = {'key': 'value'}
utils.print_dict(dict)
self.assertEqual('+----------+-------+\n'
'| Property | Value |\n'
'+----------+-------+\n'
'| key | value |\n'
'+----------+-------+\n',
sys.stdout.getvalue())
@mock.patch('sys.stdout', six.StringIO())
def test_print_dict_wrap(self):
dict = {'key1': 'not wrapped',
'key2': 'this will be wrapped'}
utils.print_dict(dict, wrap=16)
self.assertEqual('+----------+--------------+\n'
'| Property | Value |\n'
'+----------+--------------+\n'
'| key1 | not wrapped |\n'
'| key2 | this will be |\n'
'| | wrapped |\n'
'+----------+--------------+\n',
sys.stdout.getvalue())
@mock.patch('sys.stdout', six.StringIO())
def test_print_list_sort_by_str(self):
objs = [_FakeResult("k1", 1),
_FakeResult("k3", 2),
_FakeResult("k2", 3)]
utils.print_list(objs, ["Name", "Value"], sortby_index=0)
self.assertEqual('+------+-------+\n'
'| Name | Value |\n'
'+------+-------+\n'
'| k1 | 1 |\n'
'| k2 | 3 |\n'
'| k3 | 2 |\n'
'+------+-------+\n',
sys.stdout.getvalue())
@mock.patch('sys.stdout', six.StringIO())
def test_print_list_sort_by_integer(self):
objs = [_FakeResult("k1", 1),
_FakeResult("k3", 2),
_FakeResult("k2", 3)]
utils.print_list(objs, ["Name", "Value"], sortby_index=1)
self.assertEqual('+------+-------+\n'
'| Name | Value |\n'
'+------+-------+\n'
'| k1 | 1 |\n'
'| k3 | 2 |\n'
'| k2 | 3 |\n'
'+------+-------+\n',
sys.stdout.getvalue())
# without sorting
@mock.patch('sys.stdout', six.StringIO())
def test_print_list_sort_by_none(self):
objs = [_FakeResult("k1", 1),
_FakeResult("k3", 3),
_FakeResult("k2", 2)]
utils.print_list(objs, ["Name", "Value"], sortby_index=None)
self.assertEqual('+------+-------+\n'
'| Name | Value |\n'
'+------+-------+\n'
'| k1 | 1 |\n'
'| k3 | 3 |\n'
'| k2 | 2 |\n'
'+------+-------+\n',
sys.stdout.getvalue())
@mock.patch('sys.stdout', six.StringIO())
def test_print_dict_dictionary(self):
dict = {'k': {'foo': 'bar'}}
utils.print_dict(dict)
self.assertEqual('+----------+----------------+\n'
'| Property | Value |\n'
'+----------+----------------+\n'
'| k | {"foo": "bar"} |\n'
'+----------+----------------+\n',
sys.stdout.getvalue())
@mock.patch('sys.stdout', six.StringIO())
def test_print_dict_list_dictionary(self):
dict = {'k': [{'foo': 'bar'}]}
utils.print_dict(dict)
self.assertEqual('+----------+------------------+\n'
'| Property | Value |\n'
'+----------+------------------+\n'
'| k | [{"foo": "bar"}] |\n'
'+----------+------------------+\n',
sys.stdout.getvalue())
@mock.patch('sys.stdout', six.StringIO())
def test_print_dict_list(self):
dict = {'k': ['foo', 'bar']}
utils.print_dict(dict)
self.assertEqual('+----------+----------------+\n'
'| Property | Value |\n'
'+----------+----------------+\n'
'| k | ["foo", "bar"] |\n'
'+----------+----------------+\n',
sys.stdout.getvalue())
class FlattenTestCase(test_utils.TestCase):
def test_flattening(self):
squashed = utils.flatten_dict(
{'a1': {'b1': 1234,
'b2': 'string',
'b3': set((1, 2, 3)),
'b4': {'c1': ['l', 'l', ['l']],
'c2': 'string'}},
'a2': ['l'],
'a3': ('t',)})
self.assertEqual({'a1_b1': 1234,
'a1_b2': 'string',
'a1_b3': set([1, 2, 3]),
'a1_b4_c1': ['l', 'l', ['l']],
'a1_b4_c2': 'string',
'a2': ['l'],
'a3': ('t',)},
squashed)
def test_pretty_choice_dict(self):
d = {}
r = utils.pretty_choice_dict(d)
self.assertEqual("", r)
d = {"k1": "v1",
"k2": "v2",
"k3": "v3"}
r = utils.pretty_choice_dict(d)
self.assertEqual("'k1=v1', 'k2=v2', 'k3=v3'", r)
class ValidationsTestCase(test_utils.TestCase):
def test_validate_flavor_metadata_keys_with_valid_keys(self):
valid_keys = ['key1', 'month.price', 'I-Am:AK-ey.01-', 'spaces and _']
utils.validate_flavor_metadata_keys(valid_keys)
def test_validate_flavor_metadata_keys_with_invalid_keys(self):
invalid_keys = ['/1', '?1', '%1', '<', '>', '\1']
for key in invalid_keys:
try:
utils.validate_flavor_metadata_keys([key])
self.fail("Invalid key passed validation: %s" % key)
except exceptions.CommandError as ce:
self.assertIn(key, 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))
class DoActionOnManyTestCase(test_utils.TestCase):
def _test_do_action_on_many(self, side_effect, fail):
action = mock.Mock(side_effect=side_effect)
if fail:
self.assertRaises(exceptions.CommandError,
utils.do_action_on_many,
action, [1, 2], 'success with %s', 'error')
else:
utils.do_action_on_many(action, [1, 2], 'success with %s', 'error')
action.assert_has_calls([mock.call(1), mock.call(2)])
def test_do_action_on_many_success(self):
self._test_do_action_on_many([None, None], fail=False)
def test_do_action_on_many_first_fails(self):
self._test_do_action_on_many([Exception(), None], fail=True)
def test_do_action_on_many_last_fails(self):
self._test_do_action_on_many([None, Exception()], fail=True)

View File

@ -0,0 +1,128 @@
#
# 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 os
import fixtures
import mock
from oslo.serialization import jsonutils
import requests
from requests_mock.contrib import fixture as requests_mock_fixture
import six
import testscenarios
import testtools
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"
def _patch_mock_to_raise_for_invalid_assert_calls():
def raise_for_invalid_assert_calls(wrapped):
def wrapper(_self, name):
valid_asserts = [
'assert_called_with',
'assert_called_once_with',
'assert_has_calls',
'assert_any_calls']
if name.startswith('assert') and name not in valid_asserts:
raise AttributeError('%s is not a valid mock assert method'
% name)
return wrapped(_self, name)
return wrapper
mock.Mock.__getattr__ = raise_for_invalid_assert_calls(
mock.Mock.__getattr__)
# NOTE(gibi): needs to be called only once at import time
# to patch the mock lib
_patch_mock_to_raise_for_invalid_assert_calls()
class TestCase(testtools.TestCase):
TEST_REQUEST_BASE = {
'verify': True,
}
def setUp(self):
super(TestCase, self).setUp()
if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
os.environ.get('OS_STDOUT_CAPTURE') == '1'):
stdout = self.useFixture(fixtures.StringStream('stdout')).stream
self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
os.environ.get('OS_STDERR_CAPTURE') == '1'):
stderr = self.useFixture(fixtures.StringStream('stderr')).stream
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()
self.requests = self.useFixture(requests_mock_fixture.Fixture())
self.data_fixture = None
self.client_fixture = None
self.cs = None
if self.client_fixture_class:
fix = self.client_fixture_class(self.requests)
self.client_fixture = self.useFixture(fix)
self.cs = self.client_fixture.client
if self.data_fixture_class:
fix = self.data_fixture_class(self.requests)
self.data_fixture = self.useFixture(fix)
def assert_called(self, method, path, body=None):
self.assertEqual(self.requests.last_request.method, method)
self.assertEqual(self.requests.last_request.path_url, path)
if body:
req_data = self.requests.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
convenience to initialize with a dict
"""
def __init__(self, data):
super(TestResponse, self).__init__()
self._text = None
if isinstance(data, dict):
self.status_code = data.get('status_code')
self.headers = data.get('headers')
# Fake the text attribute to streamline Response creation
self._text = data.get('text')
else:
self.status_code = data
def __eq__(self, other):
return self.__dict__ == other.__dict__
@property
def text(self):
return self._text

View File

@ -0,0 +1,151 @@
# Copyright 2012 OpenStack Foundation
#
# 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.unit.v2 import fakes
from novaclient.v2 import client
class FakeClient(fakes.FakeClient):
def __init__(self, *args, **kwargs):
client.Client.__init__(self, 'username', 'password',
'project_id', 'auth_url',
extensions=kwargs.get('extensions'))
self.client = FakeHTTPClient(**kwargs)
class FakeHTTPClient(fakes.FakeHTTPClient):
def get_os_tenant_networks(self):
return (200, {}, {
'networks': [{"label": "1", "cidr": "10.0.0.0/24",
'project_id': '4ffc664c198e435e9853f2538fbcd7a7',
'id': '1'}]})
def get_os_tenant_networks_1(self, **kw):
return (200, {}, {
'network': {"label": "1", "cidr": "10.0.0.0/24",
'project_id': '4ffc664c198e435e9853f2538fbcd7a7',
'id': '1'}})
def post_os_tenant_networks(self, **kw):
return (201, {}, {
'network': {"label": "1", "cidr": "10.0.0.0/24",
'project_id': '4ffc664c198e435e9853f2538fbcd7a7',
'id': '1'}})
def delete_os_tenant_networks_1(self, **kw):
return (204, {}, None)
def get_os_baremetal_nodes(self, **kw):
return (
200, {}, {
'nodes': [
{
"id": 1,
"instance_uuid": None,
"interfaces": [],
"cpus": 2,
"local_gb": 10,
"memory_mb": 5,
"pm_address": "2.3.4.5",
"pm_user": "pmuser",
"pm_password": "pmpass",
"prov_mac_address": "aa:bb:cc:dd:ee:ff",
"prov_vlan_id": 1,
"service_host": "somehost",
"terminal_port": 8080,
}
]
}
)
def get_os_baremetal_nodes_1(self, **kw):
return (
200, {}, {
'node': {
"id": 1,
"instance_uuid": None,
"pm_address": "1.2.3.4",
"interfaces": [],
"cpus": 2,
"local_gb": 10,
"memory_mb": 5,
"pm_user": "pmuser",
"pm_password": "pmpass",
"prov_mac_address": "aa:bb:cc:dd:ee:ff",
"prov_vlan_id": 1,
"service_host": "somehost",
"terminal_port": 8080,
}
}
)
def post_os_baremetal_nodes(self, **kw):
return (
200, {}, {
'node': {
"id": 1,
"instance_uuid": None,
"cpus": 2,
"local_gb": 10,
"memory_mb": 5,
"pm_address": "2.3.4.5",
"pm_user": "pmuser",
"pm_password": "pmpass",
"prov_mac_address": "aa:bb:cc:dd:ee:ff",
"prov_vlan_id": 1,
"service_host": "somehost",
"terminal_port": 8080,
}
}
)
def delete_os_baremetal_nodes_1(self, **kw):
return (202, {}, {})
def post_os_baremetal_nodes_1_action(self, **kw):
body = kw['body']
action = list(body)[0]
if action == "add_interface":
return (
200, {}, {
'interface': {
"id": 2,
"address": "bb:cc:dd:ee:ff:aa",
"datapath_id": 1,
"port_no": 2,
}
}
)
elif action == "remove_interface":
return (202, {}, {})
else:
return (500, {}, {})
def post_os_assisted_volume_snapshots(self, **kw):
return (202, {}, {'snapshot': {'id': 'blah', 'volumeId': '1'}})
def delete_os_assisted_volume_snapshots_x(self, **kw):
return (202, {}, {})
def post_os_server_external_events(self, **kw):
return (200, {}, {
'events': [
{'name': 'test-event',
'status': 'completed',
'tag': 'tag',
'server_uuid': 'fake-uuid1'},
{'name': 'test-event',
'status': 'completed',
'tag': 'tag',
'server_uuid': 'fake-uuid2'}]})

View File

@ -0,0 +1,42 @@
# Copyright (C) 2013, Red Hat, Inc.
#
# 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.
"""
Assisted volume snapshots - to be used by Cinder and not end users.
"""
from novaclient import extension
from novaclient.tests.unit import utils
from novaclient.tests.unit.v2.contrib import fakes
from novaclient.v2.contrib import assisted_volume_snapshots as assisted_snaps
extensions = [
extension.Extension(assisted_snaps.__name__.split(".")[-1],
assisted_snaps),
]
cs = fakes.FakeClient(extensions=extensions)
class AssistedVolumeSnapshotsTestCase(utils.TestCase):
def test_create_snap(self):
cs.assisted_volume_snapshots.create('1', {})
cs.assert_called('POST', '/os-assisted-volume-snapshots')
def test_delete_snap(self):
cs.assisted_volume_snapshots.delete('x', {})
cs.assert_called(
'DELETE',
'/os-assisted-volume-snapshots/x?delete_info={}')

View File

@ -0,0 +1,64 @@
# Copyright 2013 Hewlett-Packard Development Company, L.P.
# 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.unit import utils
from novaclient.tests.unit.v2.contrib import fakes
from novaclient.v2.contrib import baremetal
extensions = [
extension.Extension(baremetal.__name__.split(".")[-1], baremetal),
]
cs = fakes.FakeClient(extensions=extensions)
class BaremetalExtensionTest(utils.TestCase):
def test_list_nodes(self):
nl = cs.baremetal.list()
cs.assert_called('GET', '/os-baremetal-nodes')
for n in nl:
self.assertIsInstance(n, baremetal.BareMetalNode)
def test_get_node(self):
n = cs.baremetal.get(1)
cs.assert_called('GET', '/os-baremetal-nodes/1')
self.assertIsInstance(n, baremetal.BareMetalNode)
def test_create_node(self):
n = cs.baremetal.create("service_host", 1, 1024, 2048,
"aa:bb:cc:dd:ee:ff")
cs.assert_called('POST', '/os-baremetal-nodes')
self.assertIsInstance(n, baremetal.BareMetalNode)
def test_delete_node(self):
n = cs.baremetal.get(1)
cs.baremetal.delete(n)
cs.assert_called('DELETE', '/os-baremetal-nodes/1')
def test_node_add_interface(self):
i = cs.baremetal.add_interface(1, "bb:cc:dd:ee:ff:aa", 1, 2)
cs.assert_called('POST', '/os-baremetal-nodes/1/action')
self.assertIsInstance(i, baremetal.BareMetalNodeInterface)
def test_node_remove_interface(self):
cs.baremetal.remove_interface(1, "bb:cc:dd:ee:ff:aa")
cs.assert_called('POST', '/os-baremetal-nodes/1/action')
def test_node_list_interfaces(self):
cs.baremetal.list_interfaces(1)
cs.assert_called('GET', '/os-baremetal-nodes/1')

View File

@ -0,0 +1,42 @@
# Copyright 2013 Rackspace Hosting
# 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.unit import utils
from novaclient.tests.unit.v2.contrib import fakes
from novaclient.v2.contrib import cells
extensions = [
extension.Extension(cells.__name__.split(".")[-1],
cells),
]
cs = fakes.FakeClient(extensions=extensions)
class CellsExtensionTests(utils.TestCase):
def test_get_cells(self):
cell_name = 'child_cell'
cs.cells.get(cell_name)
cs.assert_called('GET', '/os-cells/%s' % cell_name)
def test_get_capacities_for_a_given_cell(self):
cell_name = 'child_cell'
cs.cells.capacities(cell_name)
cs.assert_called('GET', '/os-cells/%s/capacities' % cell_name)
def test_get_capacities_for_all_cells(self):
cs.cells.capacities()
cs.assert_called('GET', '/os-cells/capacities')

View File

@ -0,0 +1,43 @@
# Copyright 2013 Rackspace Hosting
# 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.unit import utils
from novaclient.tests.unit.v2.contrib import fakes
from novaclient.v2.contrib import instance_action
extensions = [
extension.Extension(instance_action.__name__.split(".")[-1],
instance_action),
]
cs = fakes.FakeClient(extensions=extensions)
class InstanceActionExtensionTests(utils.TestCase):
def test_list_instance_actions(self):
server_uuid = '1234'
cs.instance_action.list(server_uuid)
cs.assert_called(
'GET', '/servers/%s/os-instance-actions' %
server_uuid)
def test_get_instance_action(self):
server_uuid = '1234'
request_id = 'req-abcde12345'
cs.instance_action.get(server_uuid, request_id)
cs.assert_called(
'GET', '/servers/%s/os-instance-actions/%s'
% (server_uuid, request_id))

View File

@ -0,0 +1,33 @@
#
# 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.unit import utils
from novaclient.tests.unit.v2 import fakes
from novaclient.v2.contrib import list_extensions
extensions = [
extension.Extension(list_extensions.__name__.split(".")[-1],
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

@ -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.
from novaclient import extension
from novaclient.tests.unit import utils
from novaclient.tests.unit.v2 import fakes
from novaclient.v2.contrib import migrations
extensions = [
extension.Extension(migrations.__name__.split(".")[-1],
migrations),
]
cs = fakes.FakeClient(extensions=extensions)
class MigrationsTest(utils.TestCase):
def test_list_migrations(self):
ml = cs.migrations.list()
cs.assert_called('GET', '/os-migrations')
for m in ml:
self.assertIsInstance(m, migrations.Migration)
def test_list_migrations_with_filters(self):
ml = cs.migrations.list('host1', 'finished', 'child1')
cs.assert_called('GET',
'/os-migrations?cell_name=child1&host=host1'
'&status=finished')
for m in ml:
self.assertIsInstance(m, migrations.Migration)

View File

@ -0,0 +1,44 @@
# Copyright (C) 2014, Red Hat, Inc.
#
# 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.
"""
External event triggering for servers, not to be used by users.
"""
from novaclient import extension
from novaclient.tests.unit import utils
from novaclient.tests.unit.v2.contrib import fakes
from novaclient.v2.contrib import server_external_events as ext_events
extensions = [
extension.Extension(ext_events.__name__.split(".")[-1],
ext_events),
]
cs = fakes.FakeClient(extensions=extensions)
class ServerExternalEventsTestCase(utils.TestCase):
def test_external_event(self):
events = [{'server_uuid': 'fake-uuid1',
'name': 'test-event',
'status': 'completed',
'tag': 'tag'},
{'server_uuid': 'fake-uuid2',
'name': 'test-event',
'status': 'completed',
'tag': 'tag'}]
result = cs.server_external_events.create(events)
self.assertEqual(events, result)
cs.assert_called('POST', '/os-server-external-events')

View File

@ -0,0 +1,46 @@
# 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.
from novaclient import extension
from novaclient.tests.unit import utils
from novaclient.tests.unit.v2.contrib import fakes
from novaclient.v2.contrib import tenant_networks
extensions = [
extension.Extension(tenant_networks.__name__.split(".")[-1],
tenant_networks),
]
cs = fakes.FakeClient(extensions=extensions)
class TenantNetworkExtensionTests(utils.TestCase):
def test_list_tenant_networks(self):
nets = cs.tenant_networks.list()
cs.assert_called('GET', '/os-tenant-networks')
self.assertTrue(len(nets) > 0)
def test_get_tenant_network(self):
cs.tenant_networks.get(1)
cs.assert_called('GET', '/os-tenant-networks/1')
def test_create_tenant_networks(self):
cs.tenant_networks.create(label="net",
cidr="10.0.0.0/24")
cs.assert_called('POST', '/os-tenant-networks')
def test_delete_tenant_networks(self):
cs.tenant_networks.delete(1)
cs.assert_called('DELETE', '/os-tenant-networks/1')

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,104 @@
# Copyright 2012 IBM Corp.
# 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.unit.fixture_data import agents as data
from novaclient.tests.unit.fixture_data import client
from novaclient.tests.unit import utils
from novaclient.v2 import agents
class AgentsTest(utils.FixturedTestCase):
data_fixture_class = data.Fixture
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
},
]
}
headers = {'Content-Type': 'application/json'}
self.requests.register_uri('GET', self.data_fixture.url(),
json=get_os_agents,
headers=headers)
def test_list_agents(self):
self.stub_hypervisors()
ags = self.cs.agents.list()
self.assert_called('GET', '/os-agents')
for a in ags:
self.assertIsInstance(a, agents.Agent)
self.assertEqual('kvm', a.hypervisor)
def test_list_agents_with_hypervisor(self):
self.stub_hypervisors('xen')
ags = self.cs.agents.list('xen')
self.assert_called('GET', '/os-agents?hypervisor=xen')
for a in ags:
self.assertIsInstance(a, agents.Agent)
self.assertEqual('xen', a.hypervisor)
def test_agents_create(self):
ag = self.cs.agents.create('win', 'x86', '7.0',
'/xxx/xxx/xxx',
'add6bb58e139be103324d04d82d8f546',
'xen')
body = {'agent': {'url': '/xxx/xxx/xxx',
'hypervisor': 'xen',
'md5hash': 'add6bb58e139be103324d04d82d8f546',
'version': '7.0',
'architecture': 'x86',
'os': 'win'}}
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.assert_called('DELETE', '/os-agents/1')
def _build_example_update_body(self):
return {"para": {
"url": "/yyy/yyyy/yyyy",
"version": "8.0",
"md5hash": "add6bb58e139be103324d04d82d8f546"}}
def test_agents_modify(self):
ag = self.cs.agents.update('1', '8.0',
'/yyy/yyyy/yyyy',
'add6bb58e139be103324d04d82d8f546')
body = self._build_example_update_body()
self.assert_called('PUT', '/os-agents/1', body)
self.assertEqual(1, ag.id)

View File

@ -0,0 +1,141 @@
# 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.
from novaclient.tests.unit.fixture_data import aggregates as data
from novaclient.tests.unit.fixture_data import client
from novaclient.tests.unit import utils
from novaclient.v2 import aggregates
class AggregatesTest(utils.FixturedTestCase):
data_fixture_class = data.Fixture
scenarios = [('original', {'client_fixture_class': client.V1}),
('session', {'client_fixture_class': client.SessionV1})]
def test_list_aggregates(self):
result = self.cs.aggregates.list()
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.assert_called('POST', '/os-aggregates', body)
self.assertIsInstance(aggregate, aggregates.Aggregate)
def test_get(self):
aggregate = self.cs.aggregates.get("1")
self.assert_called('GET', '/os-aggregates/1')
self.assertIsInstance(aggregate, aggregates.Aggregate)
aggregate2 = self.cs.aggregates.get(aggregate)
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.assert_called('GET', '/os-aggregates/1')
self.assertIsInstance(aggregate, aggregates.Aggregate)
aggregate2 = self.cs.aggregates.get_details(aggregate)
self.assert_called('GET', '/os-aggregates/1')
self.assertIsInstance(aggregate2, aggregates.Aggregate)
def test_update(self):
aggregate = self.cs.aggregates.get("1")
values = {"name": "foo"}
body = {"aggregate": values}
result1 = aggregate.update(values)
self.assert_called('PUT', '/os-aggregates/1', body)
self.assertIsInstance(result1, aggregates.Aggregate)
result2 = self.cs.aggregates.update(2, values)
self.assert_called('PUT', '/os-aggregates/2', body)
self.assertIsInstance(result2, aggregates.Aggregate)
def test_update_with_availability_zone(self):
aggregate = self.cs.aggregates.get("1")
values = {"name": "foo", "availability_zone": "new_zone"}
body = {"aggregate": values}
result3 = self.cs.aggregates.update(aggregate, values)
self.assert_called('PUT', '/os-aggregates/1', body)
self.assertIsInstance(result3, aggregates.Aggregate)
def test_add_host(self):
aggregate = self.cs.aggregates.get("1")
host = "host1"
body = {"add_host": {"host": "host1"}}
result1 = aggregate.add_host(host)
self.assert_called('POST', '/os-aggregates/1/action', body)
self.assertIsInstance(result1, aggregates.Aggregate)
result2 = self.cs.aggregates.add_host("2", host)
self.assert_called('POST', '/os-aggregates/2/action', body)
self.assertIsInstance(result2, aggregates.Aggregate)
result3 = self.cs.aggregates.add_host(aggregate, host)
self.assert_called('POST', '/os-aggregates/1/action', body)
self.assertIsInstance(result3, aggregates.Aggregate)
def test_remove_host(self):
aggregate = self.cs.aggregates.get("1")
host = "host1"
body = {"remove_host": {"host": "host1"}}
result1 = aggregate.remove_host(host)
self.assert_called('POST', '/os-aggregates/1/action', body)
self.assertIsInstance(result1, aggregates.Aggregate)
result2 = self.cs.aggregates.remove_host("2", host)
self.assert_called('POST', '/os-aggregates/2/action', body)
self.assertIsInstance(result2, aggregates.Aggregate)
result3 = self.cs.aggregates.remove_host(aggregate, host)
self.assert_called('POST', '/os-aggregates/1/action', body)
self.assertIsInstance(result3, aggregates.Aggregate)
def test_set_metadata(self):
aggregate = self.cs.aggregates.get("1")
metadata = {"foo": "bar"}
body = {"set_metadata": {"metadata": metadata}}
result1 = aggregate.set_metadata(metadata)
self.assert_called('POST', '/os-aggregates/1/action', body)
self.assertIsInstance(result1, aggregates.Aggregate)
result2 = self.cs.aggregates.set_metadata(2, metadata)
self.assert_called('POST', '/os-aggregates/2/action', body)
self.assertIsInstance(result2, aggregates.Aggregate)
result3 = self.cs.aggregates.set_metadata(aggregate, metadata)
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.assert_called('DELETE', '/os-aggregates/1')
self.cs.aggregates.delete('1')
self.assert_called('DELETE', '/os-aggregates/1')
self.cs.aggregates.delete(aggregate)
self.assert_called('DELETE', '/os-aggregates/1')

View File

@ -0,0 +1,387 @@
#
# 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 copy
import json
from keystoneclient import fixture
import mock
import requests
from novaclient import exceptions
from novaclient.tests.unit import utils
from novaclient.v2 import client
class AuthenticateAgainstKeystoneTests(utils.TestCase):
def get_token(self, **kwargs):
resp = fixture.V2Token(**kwargs)
resp.set_scope()
s = resp.add_service('compute')
s.add_endpoint('http://localhost:8774/v1.1', region='RegionOne')
return resp
def test_authenticate_success(self):
cs = client.Client("username", "password", "project_id",
utils.AUTH_URL_V2, service_type='compute')
resp = self.get_token()
auth_response = utils.TestResponse({
"status_code": 200,
"text": json.dumps(resp),
})
mock_request = mock.Mock(return_value=(auth_response))
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
cs.client.authenticate()
headers = {
'User-Agent': cs.client.USER_AGENT,
'Content-Type': 'application/json',
'Accept': 'application/json',
}
body = {
'auth': {
'passwordCredentials': {
'username': cs.client.user,
'password': cs.client.password,
},
'tenantName': cs.client.projectid,
},
}
token_url = cs.client.auth_url + "/tokens"
mock_request.assert_called_with(
"POST",
token_url,
headers=headers,
data=json.dumps(body),
allow_redirects=True,
**self.TEST_REQUEST_BASE)
endpoints = resp["access"]["serviceCatalog"][0]['endpoints']
public_url = endpoints[0]["publicURL"].rstrip('/')
self.assertEqual(cs.client.management_url, public_url)
token_id = resp["access"]["token"]["id"]
self.assertEqual(cs.client.auth_token, token_id)
test_auth_call()
def test_authenticate_failure(self):
cs = client.Client("username", "password", "project_id",
utils.AUTH_URL_V2)
resp = {"unauthorized": {"message": "Unauthorized", "code": "401"}}
auth_response = utils.TestResponse({
"status_code": 401,
"text": json.dumps(resp),
})
mock_request = mock.Mock(return_value=(auth_response))
@mock.patch.object(requests.Session, "request", mock_request)
def test_auth_call():
self.assertRaises(exceptions.Unauthorized, cs.client.authenticate)
test_auth_call()
def test_v1_auth_redirect(self):
cs = client.Client("username", "password", "project_id",
utils.AUTH_URL_V1, service_type='compute')
dict_correct_response = self.get_token()
correct_response = json.dumps(dict_correct_response)
dict_responses = [
{"headers": {'location': 'http://127.0.0.1:5001'},
"status_code": 305,
"text": "Use proxy"},
# Configured on admin port, nova redirects to v2.0 port.
# When trying to connect on it, keystone auth succeed by v1.0
# protocol (through headers) but tokens are being returned in
# body (looks like keystone bug). Leaved for compatibility.
{"headers": {},
"status_code": 200,
"text": correct_response},
{"headers": {},
"status_code": 200,
"text": correct_response}
]
responses = [(utils.TestResponse(resp)) for resp in dict_responses]
def side_effect(*args, **kwargs):
return responses.pop(0)
mock_request = mock.Mock(side_effect=side_effect)
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
cs.client.authenticate()
headers = {
'User-Agent': cs.client.USER_AGENT,
'Content-Type': 'application/json',
'Accept': 'application/json',
}
body = {
'auth': {
'passwordCredentials': {
'username': cs.client.user,
'password': cs.client.password,
},
'tenantName': cs.client.projectid,
},
}
token_url = cs.client.auth_url + "/tokens"
kwargs = copy.copy(self.TEST_REQUEST_BASE)
kwargs['headers'] = headers
kwargs['data'] = json.dumps(body)
mock_request.assert_called_with(
"POST",
token_url,
allow_redirects=True,
**kwargs)
resp = dict_correct_response
endpoints = resp["access"]["serviceCatalog"][0]['endpoints']
public_url = endpoints[0]["publicURL"].rstrip('/')
self.assertEqual(cs.client.management_url, public_url)
token_id = resp["access"]["token"]["id"]
self.assertEqual(cs.client.auth_token, token_id)
test_auth_call()
def test_v2_auth_redirect(self):
cs = client.Client("username", "password", "project_id",
utils.AUTH_URL_V2, service_type='compute')
dict_correct_response = self.get_token()
correct_response = json.dumps(dict_correct_response)
dict_responses = [
{"headers": {'location': 'http://127.0.0.1:5001'},
"status_code": 305,
"text": "Use proxy"},
# Configured on admin port, nova redirects to v2.0 port.
# When trying to connect on it, keystone auth succeed by v1.0
# protocol (through headers) but tokens are being returned in
# body (looks like keystone bug). Leaved for compatibility.
{"headers": {},
"status_code": 200,
"text": correct_response},
{"headers": {},
"status_code": 200,
"text": correct_response}
]
responses = [(utils.TestResponse(resp)) for resp in dict_responses]
def side_effect(*args, **kwargs):
return responses.pop(0)
mock_request = mock.Mock(side_effect=side_effect)
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
cs.client.authenticate()
headers = {
'User-Agent': cs.client.USER_AGENT,
'Content-Type': 'application/json',
'Accept': 'application/json',
}
body = {
'auth': {
'passwordCredentials': {
'username': cs.client.user,
'password': cs.client.password,
},
'tenantName': cs.client.projectid,
},
}
token_url = cs.client.auth_url + "/tokens"
kwargs = copy.copy(self.TEST_REQUEST_BASE)
kwargs['headers'] = headers
kwargs['data'] = json.dumps(body)
mock_request.assert_called_with(
"POST",
token_url,
allow_redirects=True,
**kwargs)
resp = dict_correct_response
endpoints = resp["access"]["serviceCatalog"][0]['endpoints']
public_url = endpoints[0]["publicURL"].rstrip('/')
self.assertEqual(cs.client.management_url, public_url)
token_id = resp["access"]["token"]["id"]
self.assertEqual(cs.client.auth_token, token_id)
test_auth_call()
def test_ambiguous_endpoints(self):
cs = client.Client("username", "password", "project_id",
utils.AUTH_URL_V2, service_type='compute')
resp = self.get_token()
# duplicate existing service
s = resp.add_service('compute')
s.add_endpoint('http://localhost:8774/v1.1', region='RegionOne')
auth_response = utils.TestResponse({
"status_code": 200,
"text": json.dumps(resp),
})
mock_request = mock.Mock(return_value=(auth_response))
@mock.patch.object(requests.Session, "request", mock_request)
def test_auth_call():
self.assertRaises(exceptions.AmbiguousEndpoints,
cs.client.authenticate)
test_auth_call()
def test_authenticate_with_token_success(self):
cs = client.Client("username", None, "project_id",
utils.AUTH_URL_V2, service_type='compute')
cs.client.auth_token = "FAKE_ID"
resp = self.get_token(token_id="FAKE_ID")
auth_response = utils.TestResponse({
"status_code": 200,
"text": json.dumps(resp),
})
mock_request = mock.Mock(return_value=(auth_response))
with mock.patch.object(requests, "request", mock_request):
cs.client.authenticate()
headers = {
'User-Agent': cs.client.USER_AGENT,
'Content-Type': 'application/json',
'Accept': 'application/json',
}
body = {
'auth': {
'token': {
'id': cs.client.auth_token,
},
'tenantName': cs.client.projectid,
},
}
token_url = cs.client.auth_url + "/tokens"
mock_request.assert_called_with(
"POST",
token_url,
headers=headers,
data=json.dumps(body),
allow_redirects=True,
**self.TEST_REQUEST_BASE)
endpoints = resp["access"]["serviceCatalog"][0]['endpoints']
public_url = endpoints[0]["publicURL"].rstrip('/')
self.assertEqual(cs.client.management_url, public_url)
token_id = resp["access"]["token"]["id"]
self.assertEqual(cs.client.auth_token, token_id)
def test_authenticate_with_token_failure(self):
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({
"status_code": 401,
"text": json.dumps(resp),
})
mock_request = mock.Mock(return_value=(auth_response))
with mock.patch.object(requests.Session, "request", mock_request):
self.assertRaises(exceptions.Unauthorized, cs.client.authenticate)
class AuthenticationTests(utils.TestCase):
def test_authenticate_success(self):
cs = client.Client("username", "password",
"project_id", utils.AUTH_URL)
management_url = 'https://localhost/v1.1/443470'
auth_response = utils.TestResponse({
'status_code': 204,
'headers': {
'x-server-management-url': management_url,
'x-auth-token': '1b751d74-de0c-46ae-84f0-915744b582d1',
},
})
mock_request = mock.Mock(return_value=(auth_response))
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
cs.client.authenticate()
headers = {
'Accept': 'application/json',
'X-Auth-User': 'username',
'X-Auth-Key': 'password',
'X-Auth-Project-Id': 'project_id',
'User-Agent': cs.client.USER_AGENT
}
mock_request.assert_called_with(
"GET",
cs.client.auth_url,
headers=headers,
**self.TEST_REQUEST_BASE)
self.assertEqual(cs.client.management_url,
auth_response.headers['x-server-management-url'])
self.assertEqual(cs.client.auth_token,
auth_response.headers['x-auth-token'])
test_auth_call()
def test_authenticate_failure(self):
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, "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", utils.AUTH_URL)
http_client = cs.client
http_client.management_url = ''
http_client.get_service_url = mock.Mock(return_value='')
mock_request = mock.Mock(return_value=(None, None))
@mock.patch.object(http_client, 'request', mock_request)
@mock.patch.object(http_client, 'authenticate')
def test_auth_call(m):
http_client.get('/')
self.assertTrue(m.called)
self.assertTrue(mock_request.called)
test_auth_call()
def test_auth_manual(self):
cs = client.Client("username", "password",
"project_id", utils.AUTH_URL)
@mock.patch.object(cs.client, 'authenticate')
def test_auth_call(m):
cs.authenticate()
self.assertTrue(m.called)
test_auth_call()

View File

@ -0,0 +1,102 @@
# Copyright 2011 OpenStack Foundation
# Copyright 2013 IBM Corp.
# 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.
import six
from novaclient.tests.unit.fixture_data import availability_zones as data
from novaclient.tests.unit.fixture_data import client
from novaclient.tests.unit import utils
from novaclient.v2 import availability_zones
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.v2 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.availability_zone_type = self._get_availability_zone_type()
def _get_availability_zone_type(self):
return availability_zones.AvailabilityZone
def _assertZone(self, zone, name, status):
self.assertEqual(zone.zoneName, name)
self.assertEqual(zone.zoneState, status)
def test_list_availability_zone(self):
zones = self.cs.availability_zones.list(detailed=False)
self.assert_called('GET', '/os-availability-zone')
for zone in zones:
self.assertIsInstance(zone, self.availability_zone_type)
self.assertEqual(2, len(zones))
l0 = [six.u('zone-1'), six.u('available')]
l1 = [six.u('zone-2'), six.u('not available')]
z0 = self.shell._treeizeAvailabilityZone(zones[0])
z1 = self.shell._treeizeAvailabilityZone(zones[1])
self.assertEqual((1, 1), (len(z0), len(z1)))
self._assertZone(z0[0], l0[0], l0[1])
self._assertZone(z1[0], l1[0], l1[1])
def test_detail_availability_zone(self):
zones = self.cs.availability_zones.list(detailed=True)
self.assert_called('GET', '/os-availability-zone/detail')
for zone in zones:
self.assertIsInstance(zone, self.availability_zone_type)
self.assertEqual(3, len(zones))
l0 = [six.u('zone-1'), six.u('available')]
l1 = [six.u('|- fake_host-1'), six.u('')]
l2 = [six.u('| |- nova-compute'),
six.u('enabled :-) 2012-12-26 14:45:25')]
l3 = [six.u('internal'), six.u('available')]
l4 = [six.u('|- fake_host-1'), six.u('')]
l5 = [six.u('| |- nova-sched'),
six.u('enabled :-) 2012-12-26 14:45:25')]
l6 = [six.u('|- fake_host-2'), six.u('')]
l7 = [six.u('| |- nova-network'),
six.u('enabled XXX 2012-12-26 14:45:24')]
l8 = [six.u('zone-2'), six.u('not available')]
z0 = self.shell._treeizeAvailabilityZone(zones[0])
z1 = self.shell._treeizeAvailabilityZone(zones[1])
z2 = self.shell._treeizeAvailabilityZone(zones[2])
self.assertEqual((3, 5, 1), (len(z0), len(z1), len(z2)))
self._assertZone(z0[0], l0[0], l0[1])
self._assertZone(z0[1], l1[0], l1[1])
self._assertZone(z0[2], l2[0], l2[1])
self._assertZone(z1[0], l3[0], l3[1])
self._assertZone(z1[1], l4[0], l4[1])
self._assertZone(z1[2], l5[0], l5[1])
self._assertZone(z1[3], l6[0], l6[1])
self._assertZone(z1[4], l7[0], l7[1])
self._assertZone(z2[0], l8[0], l8[1])

View File

@ -0,0 +1,36 @@
#
# 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.unit.fixture_data import certs as data
from novaclient.tests.unit.fixture_data import client
from novaclient.tests.unit import utils
from novaclient.v2 import certs
class CertsTest(utils.FixturedTestCase):
data_fixture_class = data.Fixture
cert_type = 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.assert_called('POST', '/os-certificates')
self.assertIsInstance(cert, self.cert_type)
def test_get_root_cert(self):
cert = self.cs.certs.get()
self.assert_called('GET', '/os-certificates/root')
self.assertIsInstance(cert, self.cert_type)

View File

@ -0,0 +1,45 @@
# 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 uuid
from keystoneclient import session
from novaclient.tests.unit import utils
from novaclient.v2 import client
class ClientTest(utils.TestCase):
def test_adapter_properties(self):
# sample of properties, there are many more
user_agent = uuid.uuid4().hex
endpoint_override = uuid.uuid4().hex
s = session.Session()
c = client.Client(session=s,
user_agent=user_agent,
endpoint_override=endpoint_override)
self.assertEqual(user_agent, c.client.user_agent)
self.assertEqual(endpoint_override, c.client.endpoint_override)
def test_passing_interface(self):
endpoint_type = uuid.uuid4().hex
interface = uuid.uuid4().hex
s = session.Session()
c = client.Client(session=s,
interface=interface,
endpoint_type=endpoint_type)
self.assertEqual(interface, c.client.interface)

View File

@ -0,0 +1,45 @@
#
# 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
from novaclient.tests.unit.fixture_data import client
from novaclient.tests.unit.fixture_data import cloudpipe as data
from novaclient.tests.unit import utils
from novaclient.v2 import cloudpipe
class CloudpipeTest(utils.FixturedTestCase):
data_fixture_class = data.Fixture
scenarios = [('original', {'client_fixture_class': client.V1}),
('session', {'client_fixture_class': client.SessionV1})]
def test_list_cloudpipes(self):
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 = self.cs.cloudpipe.create(project)
body = {'cloudpipe': {'project_id': project}}
self.assert_called('POST', '/os-cloudpipe', body)
self.assertIsInstance(cp, six.string_types)
def test_update(self):
self.cs.cloudpipe.update("192.168.1.1", 2345)
body = {'configure_project': {'vpn_ip': "192.168.1.1",
'vpn_port': 2345}}
self.assert_called('PUT', '/os-cloudpipe/configure-project', body)

View File

@ -0,0 +1,44 @@
# Copyright 2012 IBM Corp.
# 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.unit.fixture_data import client
from novaclient.tests.unit.fixture_data import fixedips as data
from novaclient.tests.unit import utils
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 = self.cs.fixed_ips.get(fixed_ip='192.168.1.1')
self.assert_called('GET', '/os-fixed-ips/192.168.1.1')
self.assertEqual('192.168.1.0/24', info.cidr)
self.assertEqual('192.168.1.1', info.address)
self.assertEqual('foo', info.hostname)
self.assertEqual('bar', info.host)
def test_reserve_fixed_ip(self):
body = {"reserve": None}
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}
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

@ -0,0 +1,70 @@
# 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.
from novaclient.tests.unit import utils
from novaclient.tests.unit.v2 import fakes
from novaclient.v2 import flavor_access
cs = fakes.FakeClient()
class FlavorAccessTest(utils.TestCase):
def test_list_access_by_flavor_private(self):
kwargs = {'flavor': cs.flavors.get(2)}
r = cs.flavor_access.list(**kwargs)
cs.assert_called('GET', '/flavors/2/os-flavor-access')
[self.assertIsInstance(a, flavor_access.FlavorAccess) for a in r]
def test_add_tenant_access(self):
flavor = cs.flavors.get(2)
tenant = 'proj2'
r = cs.flavor_access.add_tenant_access(flavor, tenant)
body = {
"addTenantAccess": {
"tenant": "proj2"
}
}
cs.assert_called('POST', '/flavors/2/action', body)
[self.assertIsInstance(a, flavor_access.FlavorAccess) for a in r]
def test_remove_tenant_access(self):
flavor = cs.flavors.get(2)
tenant = 'proj2'
r = cs.flavor_access.remove_tenant_access(flavor, tenant)
body = {
"removeTenantAccess": {
"tenant": "proj2"
}
}
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

@ -0,0 +1,219 @@
# Copyright (c) 2013, OpenStack
# 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.
import mock
from novaclient import exceptions
from novaclient.tests.unit import utils
from novaclient.tests.unit.v2 import fakes
from novaclient.v2 import flavors
class FlavorsTest(utils.TestCase):
def setUp(self):
super(FlavorsTest, self).setUp()
self.cs = self._get_fake_client()
self.flavor_type = self._get_flavor_type()
def _get_fake_client(self):
return fakes.FakeClient()
def _get_flavor_type(self):
return flavors.Flavor
def test_list_flavors(self):
fl = self.cs.flavors.list()
self.cs.assert_called('GET', '/flavors/detail')
for flavor in fl:
self.assertIsInstance(flavor, self.flavor_type)
def test_list_flavors_undetailed(self):
fl = self.cs.flavors.list(detailed=False)
self.cs.assert_called('GET', '/flavors')
for flavor in fl:
self.assertIsInstance(flavor, self.flavor_type)
def test_list_flavors_is_public_none(self):
fl = self.cs.flavors.list(is_public=None)
self.cs.assert_called('GET', '/flavors/detail?is_public=None')
for flavor in fl:
self.assertIsInstance(flavor, self.flavor_type)
def test_list_flavors_is_public_false(self):
fl = self.cs.flavors.list(is_public=False)
self.cs.assert_called('GET', '/flavors/detail?is_public=False')
for flavor in fl:
self.assertIsInstance(flavor, self.flavor_type)
def test_list_flavors_is_public_true(self):
fl = self.cs.flavors.list(is_public=True)
self.cs.assert_called('GET', '/flavors/detail')
for flavor in fl:
self.assertIsInstance(flavor, self.flavor_type)
def test_get_flavor_details(self):
f = self.cs.flavors.get(1)
self.cs.assert_called('GET', '/flavors/1')
self.assertIsInstance(f, self.flavor_type)
self.assertEqual(256, f.ram)
self.assertEqual(10, f.disk)
self.assertEqual(10, f.ephemeral)
self.assertTrue(f.is_public)
def test_get_flavor_details_alphanum_id(self):
f = self.cs.flavors.get('aa1')
self.cs.assert_called('GET', '/flavors/aa1')
self.assertIsInstance(f, self.flavor_type)
self.assertEqual(128, f.ram)
self.assertEqual(0, f.disk)
self.assertEqual(0, f.ephemeral)
self.assertTrue(f.is_public)
def test_get_flavor_details_diablo(self):
f = self.cs.flavors.get(3)
self.cs.assert_called('GET', '/flavors/3')
self.assertIsInstance(f, self.flavor_type)
self.assertEqual(256, f.ram)
self.assertEqual(10, f.disk)
self.assertEqual('N/A', f.ephemeral)
self.assertEqual('N/A', f.is_public)
def test_find(self):
f = self.cs.flavors.find(ram=256)
self.cs.assert_called('GET', '/flavors/detail')
self.assertEqual('256 mb server', f.name)
f = self.cs.flavors.find(disk=0)
self.assertEqual('128 mb server', f.name)
self.assertRaises(exceptions.NotFound, self.cs.flavors.find,
disk=12345)
def _create_body(self, name, ram, vcpus, disk, ephemeral, id, swap,
rxtx_factor, is_public):
return {
"flavor": {
"name": name,
"ram": ram,
"vcpus": vcpus,
"disk": disk,
"OS-FLV-EXT-DATA:ephemeral": ephemeral,
"id": id,
"swap": swap,
"rxtx_factor": rxtx_factor,
"os-flavor-access:is_public": is_public,
}
}
def test_create(self):
f = self.cs.flavors.create("flavorcreate", 512, 1, 10, 1234,
ephemeral=10, is_public=False)
body = self._create_body("flavorcreate", 512, 1, 10, 10, 1234, 0, 1.0,
False)
self.cs.assert_called('POST', '/flavors', body)
self.assertIsInstance(f, self.flavor_type)
def test_create_with_id_as_string(self):
flavor_id = 'foobar'
f = self.cs.flavors.create("flavorcreate", 512,
1, 10, flavor_id, ephemeral=10,
is_public=False)
body = self._create_body("flavorcreate", 512, 1, 10, 10, flavor_id, 0,
1.0, False)
self.cs.assert_called('POST', '/flavors', body)
self.assertIsInstance(f, self.flavor_type)
def test_create_ephemeral_ispublic_defaults(self):
f = self.cs.flavors.create("flavorcreate", 512, 1, 10, 1234)
body = self._create_body("flavorcreate", 512, 1, 10, 0, 1234, 0,
1.0, True)
self.cs.assert_called('POST', '/flavors', body)
self.assertIsInstance(f, self.flavor_type)
def test_invalid_parameters_create(self):
self.assertRaises(exceptions.CommandError, self.cs.flavors.create,
"flavorcreate", "invalid", 1, 10, 1234, swap=0,
ephemeral=0, rxtx_factor=1.0, is_public=True)
self.assertRaises(exceptions.CommandError, self.cs.flavors.create,
"flavorcreate", 512, "invalid", 10, 1234, swap=0,
ephemeral=0, rxtx_factor=1.0, is_public=True)
self.assertRaises(exceptions.CommandError, self.cs.flavors.create,
"flavorcreate", 512, 1, "invalid", 1234, swap=0,
ephemeral=0, rxtx_factor=1.0, is_public=True)
self.assertRaises(exceptions.CommandError, self.cs.flavors.create,
"flavorcreate", 512, 1, 10, 1234, swap="invalid",
ephemeral=0, rxtx_factor=1.0, is_public=True)
self.assertRaises(exceptions.CommandError, self.cs.flavors.create,
"flavorcreate", 512, 1, 10, 1234, swap=0,
ephemeral="invalid", rxtx_factor=1.0, is_public=True)
self.assertRaises(exceptions.CommandError, self.cs.flavors.create,
"flavorcreate", 512, 1, 10, 1234, swap=0,
ephemeral=0, rxtx_factor="invalid", is_public=True)
self.assertRaises(exceptions.CommandError, self.cs.flavors.create,
"flavorcreate", 512, 1, 10, 1234, swap=0,
ephemeral=0, rxtx_factor=1.0, is_public='invalid')
def test_delete(self):
self.cs.flavors.delete("flavordelete")
self.cs.assert_called('DELETE', '/flavors/flavordelete')
def test_delete_with_flavor_instance(self):
f = self.cs.flavors.get(2)
self.cs.flavors.delete(f)
self.cs.assert_called('DELETE', '/flavors/2')
def test_delete_with_flavor_instance_method(self):
f = self.cs.flavors.get(2)
f.delete()
self.cs.assert_called('DELETE', '/flavors/2')
def test_set_keys(self):
f = self.cs.flavors.get(1)
f.set_keys({'k1': 'v1'})
self.cs.assert_called('POST', '/flavors/1/os-extra_specs',
{"extra_specs": {'k1': 'v1'}})
def test_set_with_valid_keys(self):
valid_keys = ['key4', 'month.price', 'I-Am:AK-ey.44-',
'key with spaces and _']
f = self.cs.flavors.get(4)
for key in valid_keys:
f.set_keys({key: 'v4'})
self.cs.assert_called('POST', '/flavors/4/os-extra_specs',
{"extra_specs": {key: 'v4'}})
def test_set_with_invalid_keys(self):
invalid_keys = ['/1', '?1', '%1', '<', '>']
f = self.cs.flavors.get(1)
for key in invalid_keys:
self.assertRaises(exceptions.CommandError, f.set_keys, {key: 'v1'})
@mock.patch.object(flavors.FlavorManager, '_delete')
def test_unset_keys(self, mock_delete):
f = self.cs.flavors.get(1)
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

@ -0,0 +1,91 @@
#
# 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.unit.fixture_data import client
from novaclient.tests.unit.fixture_data import floatingips as data
from novaclient.tests.unit import utils
from novaclient.v2 import floating_ip_dns
class FloatingIPDNSDomainTest(utils.FixturedTestCase):
testdomain = "testdomain"
client_fixture_class = client.V1
data_fixture_class = data.DNSFixture
def test_dns_domains(self):
domainlist = self.cs.dns_domains.domains()
self.assertEqual(2, len(domainlist))
for entry in domainlist:
self.assertIsInstance(entry,
floating_ip_dns.FloatingIPDNSDomain)
self.assertEqual('example.com', domainlist[1].domain)
def test_create_private_domain(self):
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):
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):
self.cs.dns_domains.delete(self.testdomain)
self.assert_called('DELETE', '/os-floating-ip-dns/%s' %
self.testdomain)
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 = self.cs.dns_entries.get_for_ip(self.testdomain,
ip=self.testip)
self.assertEqual(2, len(entries))
for entry in entries:
self.assertIsInstance(entry,
floating_ip_dns.FloatingIPDNSEntry)
self.assertEqual('host2', entries[1].dns_entry['name'])
self.assertEqual(entries[1].dns_entry['ip'], self.testip)
def test_get_dns_entry_by_name(self):
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):
self.cs.dns_entries.create(self.testdomain,
self.testname,
self.testip,
self.testtype)
self.assert_called('PUT', '/os-floating-ip-dns/%s/entries/%s' %
(self.testdomain, self.testname))
def test_delete_entry(self):
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

@ -0,0 +1,32 @@
# Copyright (c) 2011 X.commerce, a business unit of eBay 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.unit.fixture_data import client
from novaclient.tests.unit.fixture_data import floatingips as data
from novaclient.tests.unit import utils
from novaclient.v2 import floating_ip_pools
class TestFloatingIPPools(utils.FixturedTestCase):
client_fixture_class = client.V1
data_fixture_class = data.PoolsFixture
def test_list_floating_ips(self):
fl = self.cs.floating_ip_pools.list()
self.assert_called('GET', '/os-floating-ip-pools')
for f in fl:
self.assertIsInstance(f, floating_ip_pools.FloatingIPPool)

View File

@ -0,0 +1,59 @@
# Copyright (c) 2011 X.commerce, a business unit of eBay 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.unit.fixture_data import client
from novaclient.tests.unit.fixture_data import floatingips as data
from novaclient.tests.unit import utils
from novaclient.v2 import floating_ips
class FloatingIPsTest(utils.FixturedTestCase):
client_fixture_class = client.V1
data_fixture_class = data.FloatingFixture
def test_list_floating_ips(self):
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 = self.cs.floating_ips.list()[0]
fl.delete()
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 = 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 = self.cs.floating_ips.create('nova')
self.assert_called('POST', '/os-floating-ips')
self.assertEqual('nova', fl.pool)
self.assertIsInstance(fl, floating_ips.FloatingIP)

View File

@ -0,0 +1,64 @@
# Copyright 2012 IBM Corp.
# 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.unit.fixture_data import client
from novaclient.tests.unit.fixture_data import floatingips as data
from novaclient.tests.unit import utils
from novaclient.v2 import floating_ips_bulk
class FloatingIPsBulkTest(utils.FixturedTestCase):
client_fixture_class = client.V1
data_fixture_class = data.BulkFixture
def test_list_floating_ips_bulk(self):
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 = 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 = self.cs.floating_ips_bulk.create('192.168.1.0/30')
body = {'floating_ips_bulk_create': {'ip_range': '192.168.1.0/30'}}
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 = 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'}}
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,
body['floating_ips_bulk_create']['pool'])
self.assertEqual(fl.interface,
body['floating_ips_bulk_create']['interface'])
def test_delete_floating_ips_bulk(self):
fl = self.cs.floating_ips_bulk.delete('192.168.1.0/30')
body = {'ip_range': '192.168.1.0/30'}
self.assert_called('PUT', '/os-floating-ips-bulk/delete', body)
self.assertEqual(fl.floating_ips_bulk_delete, body['ip_range'])

View File

@ -0,0 +1,62 @@
# 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.
from novaclient.tests.unit.fixture_data import client
from novaclient.tests.unit.fixture_data import fping as data
from novaclient.tests.unit import utils
from novaclient.v2 import fping
class FpingTest(utils.FixturedTestCase):
client_fixture_class = client.V1
data_fixture_class = data.Fixture
def test_fping_repr(self):
r = self.cs.fping.get(1)
self.assertEqual("<Fping: 1>", repr(r))
def test_list_fpings(self):
fl = self.cs.fping.list()
self.assert_called('GET', '/os-fping')
for f in fl:
self.assertIsInstance(f, fping.Fping)
self.assertEqual("fake-project", f.project_id)
self.assertTrue(f.alive)
def test_list_fpings_all_tenants(self):
fl = self.cs.fping.list(all_tenants=True)
for f in fl:
self.assertIsInstance(f, fping.Fping)
self.assert_called('GET', '/os-fping?all_tenants=1')
def test_list_fpings_exclude(self):
fl = self.cs.fping.list(exclude=['1'])
for f in fl:
self.assertIsInstance(f, fping.Fping)
self.assert_called('GET', '/os-fping?exclude=1')
def test_list_fpings_include(self):
fl = self.cs.fping.list(include=['1'])
for f in fl:
self.assertIsInstance(f, fping.Fping)
self.assert_called('GET', '/os-fping?include=1')
def test_get_fping(self):
f = self.cs.fping.get(1)
self.assert_called('GET', '/os-fping/1')
self.assertIsInstance(f, fping.Fping)
self.assertEqual("fake-project", f.project_id)
self.assertTrue(f.alive)

View File

@ -0,0 +1,84 @@
#
# 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.unit.fixture_data import client
from novaclient.tests.unit.fixture_data import hosts as data
from novaclient.tests.unit import utils
from novaclient.v2 import hosts
class HostsTest(utils.FixturedTestCase):
client_fixture_class = client.V1
data_fixture_class = data.V1
def test_describe_resource(self):
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 = 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 = 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 = self.cs.hosts.get('sample_host')[0]
values = {"status": "enabled"}
result = host.update(values)
self.assert_called('PUT', '/os-hosts/sample_host', values)
self.assertIsInstance(result, hosts.Host)
def test_update_maintenance(self):
host = self.cs.hosts.get('sample_host')[0]
values = {"maintenance_mode": "enable"}
result = host.update(values)
self.assert_called('PUT', '/os-hosts/sample_host', values)
self.assertIsInstance(result, hosts.Host)
def test_update_both(self):
host = self.cs.hosts.get('sample_host')[0]
values = {"status": "enabled",
"maintenance_mode": "enable"}
result = host.update(values)
self.assert_called('PUT', '/os-hosts/sample_host', values)
self.assertIsInstance(result, hosts.Host)
def test_host_startup(self):
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 = self.cs.hosts.get('sample_host')[0]
host.reboot()
self.assert_called(
'GET', '/os-hosts/sample_host/reboot')
def test_host_shutdown(self):
host = self.cs.hosts.get('sample_host')[0]
host.shutdown()
self.assert_called(
'GET', '/os-hosts/sample_host/shutdown')
def test_hosts_repr(self):
hs = self.cs.hosts.get('host')
self.assertEqual('<Host: dummy>', repr(hs[0]))

View File

@ -0,0 +1,178 @@
# 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.
from novaclient.tests.unit.fixture_data import client
from novaclient.tests.unit.fixture_data import hypervisors as data
from novaclient.tests.unit import utils
class HypervisorsTest(utils.FixturedTestCase):
client_fixture_class = client.V1
data_fixture_class = data.V1
def compare_to_expected(self, expected, hyper):
for key, value in expected.items():
self.assertEqual(getattr(hyper, key), value)
def test_hypervisor_index(self):
expected = [
dict(id=1234, hypervisor_hostname='hyper1'),
dict(id=5678, hypervisor_hostname='hyper2')]
result = self.cs.hypervisors.list(False)
self.assert_called('GET', '/os-hypervisors')
for idx, hyper in enumerate(result):
self.compare_to_expected(expected[idx], hyper)
def test_hypervisor_detail(self):
expected = [
dict(id=1234,
service=dict(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),
dict(id=2,
service=dict(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)]
result = self.cs.hypervisors.list()
self.assert_called('GET', '/os-hypervisors/detail')
for idx, hyper in enumerate(result):
self.compare_to_expected(expected[idx], hyper)
def test_hypervisor_search(self):
expected = [
dict(id=1234, hypervisor_hostname='hyper1'),
dict(id=5678, hypervisor_hostname='hyper2')]
result = self.cs.hypervisors.search('hyper')
self.assert_called('GET', '/os-hypervisors/hyper/search')
for idx, hyper in enumerate(result):
self.compare_to_expected(expected[idx], hyper)
def test_hypervisor_servers(self):
expected = [
dict(id=1234,
hypervisor_hostname='hyper1',
servers=[
dict(name='inst1', uuid='uuid1'),
dict(name='inst2', uuid='uuid2')]),
dict(id=5678,
hypervisor_hostname='hyper2',
servers=[
dict(name='inst3', uuid='uuid3'),
dict(name='inst4', uuid='uuid4')]),
]
result = self.cs.hypervisors.search('hyper', True)
self.assert_called('GET', '/os-hypervisors/hyper/servers')
for idx, hyper in enumerate(result):
self.compare_to_expected(expected[idx], hyper)
def test_hypervisor_get(self):
expected = dict(
id=1234,
service=dict(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)
result = self.cs.hypervisors.get(1234)
self.assert_called('GET', '/os-hypervisors/1234')
self.compare_to_expected(expected, result)
def test_hypervisor_uptime(self):
expected = dict(
id=1234,
hypervisor_hostname="hyper1",
uptime="fake uptime")
result = self.cs.hypervisors.uptime(1234)
self.assert_called('GET', '/os-hypervisors/1234/uptime')
self.compare_to_expected(expected, result)
def test_hypervisor_statistics(self):
expected = dict(
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,
)
result = self.cs.hypervisors.statistics()
self.assert_called('GET', '/os-hypervisors/statistics')
self.compare_to_expected(expected, result)
def test_hypervisor_statistics_data_model(self):
result = self.cs.hypervisor_stats.statistics()
self.assert_called('GET', '/os-hypervisors/statistics')
# Test for Bug #1370415, the line below used to raise AttributeError
self.assertEqual("<HypervisorStats: 2 Hypervisors>",
result.__repr__())

View File

@ -0,0 +1,67 @@
#
# 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.unit.fixture_data import client
from novaclient.tests.unit.fixture_data import images as data
from novaclient.tests.unit import utils
from novaclient.v2 import images
class ImagesTest(utils.FixturedTestCase):
client_fixture_class = client.V1
data_fixture_class = data.V1
def test_list_images(self):
il = self.cs.images.list()
self.assert_called('GET', '/images/detail')
[self.assertIsInstance(i, images.Image) for i in il]
self.assertEqual(2, len(il))
def test_list_images_undetailed(self):
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):
self.cs.images.list(limit=4)
self.assert_called('GET', '/images/detail?limit=4')
def test_get_image_details(self):
i = self.cs.images.get(1)
self.assert_called('GET', '/images/1')
self.assertIsInstance(i, images.Image)
self.assertEqual(1, i.id)
self.assertEqual('CentOS 5.2', i.name)
def test_delete_image(self):
self.cs.images.delete(1)
self.assert_called('DELETE', '/images/1')
def test_delete_meta(self):
self.cs.images.delete_meta(1, {'test_key': 'test_value'})
self.assert_called('DELETE', '/images/1/metadata/test_key')
def test_set_meta(self):
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 = self.cs.images.find(name="CentOS 5.2")
self.assertEqual(1, i.id)
self.assert_called('GET', '/images/1')
iml = self.cs.images.findall(status='SAVING')
self.assertEqual(1, len(iml))
self.assertEqual('My Server Backup', iml[0].name)

View File

@ -0,0 +1,64 @@
#
# 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.unit.fixture_data import client
from novaclient.tests.unit.fixture_data import keypairs as data
from novaclient.tests.unit import utils
from novaclient.v2 import keypairs
class KeypairsTest(utils.FixturedTestCase):
client_fixture_class = client.V1
data_fixture_class = data.V1
def setUp(self):
super(KeypairsTest, self).setUp()
self.keypair_type = self._get_keypair_type()
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.assert_called('GET', '/%s/test' % self.keypair_prefix)
self.assertIsInstance(kp, keypairs.Keypair)
self.assertEqual('test', kp.name)
def test_list_keypairs(self):
kps = self.cs.keypairs.list()
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.assert_called('DELETE', '/%s/test' % self.keypair_prefix)
self.cs.keypairs.delete('test')
self.assert_called('DELETE', '/%s/test' % self.keypair_prefix)
self.cs.keypairs.delete(kp)
self.assert_called('DELETE', '/%s/test' % self.keypair_prefix)
def test_create_keypair(self):
kp = self.cs.keypairs.create("foo")
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.assert_called('POST', '/%s' % self.keypair_prefix)
self.assertIsInstance(kp, keypairs.Keypair)

View File

@ -0,0 +1,88 @@
#
# 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.unit.fixture_data import client
from novaclient.tests.unit.fixture_data import limits as data
from novaclient.tests.unit import utils
from novaclient.v2 import limits
class LimitsTest(utils.FixturedTestCase):
client_fixture_class = client.V1
data_fixture_class = data.Fixture
def test_get_limits(self):
obj = self.cs.limits.get()
self.assert_called('GET', '/limits')
self.assertIsInstance(obj, limits.Limits)
def test_get_limits_for_a_tenant(self):
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 = self.cs.limits.get()
expected = (
limits.AbsoluteLimit("maxTotalRAMSize", 51200),
limits.AbsoluteLimit("maxServerMeta", 5),
limits.AbsoluteLimit("maxImageMeta", 5),
limits.AbsoluteLimit("maxPersonality", 5),
limits.AbsoluteLimit("maxPersonalitySize", 10240),
)
abs_limits = list(obj.absolute)
self.assertEqual(len(abs_limits), len(expected))
for limit in abs_limits:
self.assertIn(limit, expected)
def test_absolute_limits_reserved(self):
obj = self.cs.limits.get(reserved=True)
expected = (
limits.AbsoluteLimit("maxTotalRAMSize", 51200),
limits.AbsoluteLimit("maxServerMeta", 5),
limits.AbsoluteLimit("maxImageMeta", 5),
limits.AbsoluteLimit("maxPersonality", 5),
limits.AbsoluteLimit("maxPersonalitySize", 10240),
)
self.assert_called('GET', '/limits?reserved=1')
abs_limits = list(obj.absolute)
self.assertEqual(len(abs_limits), len(expected))
for limit in abs_limits:
self.assertIn(limit, expected)
def test_rate_limits(self):
obj = self.cs.limits.get()
expected = (
limits.RateLimit('POST', '*', '.*', 10, 2, 'MINUTE',
'2011-12-15T22:42:45Z'),
limits.RateLimit('PUT', '*', '.*', 10, 2, 'MINUTE',
'2011-12-15T22:42:45Z'),
limits.RateLimit('DELETE', '*', '.*', 100, 100, 'MINUTE',
'2011-12-15T22:42:45Z'),
limits.RateLimit('POST', '*/servers', '^/servers', 25, 24, 'DAY',
'2011-12-15T22:42:45Z'),
)
rate_limits = list(obj.rate)
self.assertEqual(len(rate_limits), len(expected))
for limit in rate_limits:
self.assertIn(limit, expected)

View File

@ -0,0 +1,106 @@
#
# 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.unit.fixture_data import client
from novaclient.tests.unit.fixture_data import networks as data
from novaclient.tests.unit import utils
from novaclient.v2 import networks
class NetworksTest(utils.FixturedTestCase):
client_fixture_class = client.V1
data_fixture_class = data.Fixture
def test_list_networks(self):
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 = self.cs.networks.get(1)
self.assert_called('GET', '/os-networks/1')
self.assertIsInstance(f, networks.Network)
def test_delete(self):
self.cs.networks.delete('networkdelete')
self.assert_called('DELETE', '/os-networks/networkdelete')
def test_create(self):
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):
params = {
'label': 'bar',
'bridge': 'br0',
'bridge_interface': 'int0',
'cidr': '192.0.2.0/24',
'cidr_v6': '2001:DB8::/32',
'dns1': '1.1.1.1',
'dns2': '1.1.1.2',
'fixed_cidr': '198.51.100.0/24',
'gateway': '192.0.2.1',
'gateway_v6': '2001:DB8::1',
'multi_host': 'T',
'priority': '1',
'project_id': '1',
'vlan': 5,
'vlan_start': 1,
'vpn_start': 1,
'mtu': 1500,
'enable_dhcp': 'T',
'dhcp_server': '1920.2.2',
'share_address': 'T',
'allowed_start': '192.0.2.10',
'allowed_end': '192.0.2.20',
}
f = self.cs.networks.create(**params)
self.assert_called('POST', '/os-networks', {'network': params})
self.assertIsInstance(f, networks.Network)
def test_associate_project(self):
self.cs.networks.associate_project('networktest')
self.assert_called('POST', '/os-networks/add',
{'id': 'networktest'})
def test_associate_host(self):
self.cs.networks.associate_host('networktest', 'testHost')
self.assert_called('POST', '/os-networks/networktest/action',
{'associate_host': 'testHost'})
def test_disassociate(self):
self.cs.networks.disassociate('networkdisassociate')
self.assert_called('POST',
'/os-networks/networkdisassociate/action',
{'disassociate': None})
def test_disassociate_host_only(self):
self.cs.networks.disassociate('networkdisassociate', True, False)
self.assert_called('POST',
'/os-networks/networkdisassociate/action',
{'disassociate_host': None})
def test_disassociate_project(self):
self.cs.networks.disassociate('networkdisassociate', False, True)
self.assert_called('POST',
'/os-networks/networkdisassociate/action',
{'disassociate_project': None})
def test_add(self):
self.cs.networks.add('networkadd')
self.assert_called('POST', '/os-networks/add',
{'id': 'networkadd'})

View File

@ -0,0 +1,42 @@
# Copyright 2011 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.
from novaclient.tests.unit import utils
from novaclient.tests.unit.v2 import fakes
cs = fakes.FakeClient()
class QuotaClassSetsTest(utils.TestCase):
def test_class_quotas_get(self):
class_name = 'test'
cs.quota_classes.get(class_name)
cs.assert_called('GET', '/os-quota-class-sets/%s' % class_name)
def test_update_quota(self):
q = cs.quota_classes.get('test')
q.update(cores=2)
cs.assert_called('PUT', '/os-quota-class-sets/test')
def test_refresh_quota(self):
q = cs.quota_classes.get('test')
q2 = cs.quota_classes.get('test')
self.assertEqual(q.cores, q2.cores)
q2.cores = 0
self.assertNotEqual(q.cores, q2.cores)
q2.get()
self.assertEqual(q.cores, q2.cores)

View File

@ -0,0 +1,62 @@
# Copyright 2011 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.
from novaclient.tests.unit.fixture_data import client
from novaclient.tests.unit.fixture_data import quotas as data
from novaclient.tests.unit import utils
class QuotaSetsTest(utils.FixturedTestCase):
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.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.assert_called('GET', url)
def test_tenant_quotas_defaults(self):
tenant_id = '97f4c221bff44578b0300df4ef119353'
self.cs.quotas.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.assert_called(
'PUT', '/os-quota-sets/97f4c221bff44578b0300df4ef119353',
{'quota_set': {'force': True,
'cores': 2,
'tenant_id': '97f4c221bff44578b0300df4ef119353'}})
def test_quotas_delete(self):
tenant_id = 'test'
self.cs.quotas.delete(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.assert_called('DELETE', url)

View File

@ -0,0 +1,89 @@
#
# 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 exceptions
from novaclient.tests.unit.fixture_data import client
from novaclient.tests.unit.fixture_data import security_group_rules as data
from novaclient.tests.unit import utils
from novaclient.v2 import security_group_rules
class SecurityGroupRulesTest(utils.FixturedTestCase):
client_fixture_class = client.V1
data_fixture_class = data.Fixture
def test_delete_security_group_rule(self):
self.cs.security_group_rules.delete(1)
self.assert_called('DELETE', '/os-security-group-rules/1')
def test_create_security_group_rule(self):
sg = self.cs.security_group_rules.create(1, "tcp", 1, 65535,
"10.0.0.0/16")
body = {
"security_group_rule": {
"ip_protocol": "tcp",
"from_port": 1,
"to_port": 65535,
"cidr": "10.0.0.0/16",
"group_id": None,
"parent_group_id": 1,
}
}
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 = self.cs.security_group_rules.create(1, "tcp", 1, 65535,
"10.0.0.0/16", 101)
body = {
"security_group_rule": {
"ip_protocol": "tcp",
"from_port": 1,
"to_port": 65535,
"cidr": "10.0.0.0/16",
"group_id": 101,
"parent_group_id": 1,
}
}
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,
self.cs.security_group_rules.create,
1, "invalid_ip_protocol", 1, 65535,
"10.0.0.0/16", 101)
self.assertRaises(exceptions.CommandError,
self.cs.security_group_rules.create,
1, "tcp", "invalid_from_port", 65535,
"10.0.0.0/16", 101)
self.assertRaises(exceptions.CommandError,
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 = 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 = self.cs.security_group_rules.create(1, "tcp", 1, 65535,
"10.0.0.0/16")
sg.delete()
self.assert_called('DELETE', '/os-security-group-rules/1')

View File

@ -0,0 +1,76 @@
#
# 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.unit.fixture_data import client
from novaclient.tests.unit.fixture_data import security_groups as data
from novaclient.tests.unit import utils
from novaclient.v2 import security_groups
class SecurityGroupsTest(utils.FixturedTestCase):
client_fixture_class = client.V1
data_fixture_class = data.Fixture
def _do_test_list_security_groups(self, search_opts, 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)
def test_list_security_groups_all_tenants_on(self):
self._do_test_list_security_groups(
None, '/os-security-groups')
def test_list_security_groups_all_tenants_on_with_search_opts(self):
self._do_test_list_security_groups(
{'all_tenants': 1}, '/os-security-groups?all_tenants=1')
def test_list_security_groups_all_tenants_off(self):
self._do_test_list_security_groups(
{'all_tenants': 0}, '/os-security-groups')
def test_get_security_groups(self):
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 = self.cs.security_groups.list()[0]
sg.delete()
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 = 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 = 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 = 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)
sg2.get()
self.assertEqual(sg.name, sg2.name)

View File

@ -0,0 +1,59 @@
# 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.unit.fixture_data import client
from novaclient.tests.unit.fixture_data import server_groups as data
from novaclient.tests.unit import utils
from novaclient.v2 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)
def test_delete_server_group_object(self):
id = '2cbd51f4-fafe-4cdb-801b-cf913a6f288b'
server_group = self.cs.server_groups.get(id)
server_group.delete()
self.assert_called('DELETE', '/os-server-groups/%s' % id)

View File

@ -0,0 +1,704 @@
# -*- coding: utf-8 -*-
#
# 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 mock
from oslo.serialization import jsonutils
import six
from novaclient import exceptions
from novaclient.tests.unit.fixture_data import client
from novaclient.tests.unit.fixture_data import floatingips
from novaclient.tests.unit.fixture_data import servers as data
from novaclient.tests.unit import utils
from novaclient.v2 import servers
class ServersTest(utils.FixturedTestCase):
client_fixture_class = client.V1
data_fixture_class = data.V1
def setUp(self):
super(ServersTest, self).setUp()
self.useFixture(floatingips.FloatingFixture(self.requests))
def test_list_servers(self):
sl = self.cs.servers.list()
self.assert_called('GET', '/servers/detail')
[self.assertIsInstance(s, servers.Server) for s in sl]
def test_list_servers_undetailed(self):
sl = self.cs.servers.list(detailed=False)
self.assert_called('GET', '/servers')
[self.assertIsInstance(s, servers.Server) for s in sl]
def test_list_servers_with_marker_limit(self):
sl = self.cs.servers.list(marker=1234, limit=2)
self.assert_called('GET', '/servers/detail?limit=2&marker=1234')
for s in sl:
self.assertIsInstance(s, servers.Server)
def test_list_servers_sort_single(self):
sl = self.cs.servers.list(sort_keys=['display_name'],
sort_dirs=['asc'])
self.assert_called(
'GET',
'/servers/detail?sort_dir=asc&sort_key=display_name')
for s in sl:
self.assertIsInstance(s, servers.Server)
def test_list_servers_sort_multiple(self):
sl = self.cs.servers.list(sort_keys=['display_name', 'id'],
sort_dirs=['asc', 'desc'])
self.assert_called(
'GET',
('/servers/detail?sort_dir=asc&sort_dir=desc&'
'sort_key=display_name&sort_key=id'))
for s in sl:
self.assertIsInstance(s, servers.Server)
def test_get_server_details(self):
s = self.cs.servers.get(1234)
self.assert_called('GET', '/servers/1234')
self.assertIsInstance(s, servers.Server)
self.assertEqual(1234, s.id)
self.assertEqual('BUILD', s.status)
def test_get_server_promote_details(self):
s1 = self.cs.servers.list(detailed=False)[0]
s2 = self.cs.servers.list(detailed=True)[0]
self.assertNotEqual(s1._info, s2._info)
s1.get()
self.assertEqual(s1._info, s2._info)
def test_create_server(self):
s = self.cs.servers.create(
name="My server",
image=1,
flavor=1,
meta={'foo': 'bar'},
userdata="hello moto",
key_name="fakekey",
files={
'/etc/passwd': 'some data', # a file
'/tmp/foo.txt': six.StringIO('data'), # a stream
}
)
self.assert_called('POST', '/servers')
self.assertIsInstance(s, servers.Server)
def test_create_server_boot_from_volume_with_nics(self):
old_boot = self.cs.servers._boot
nics = [{'net-id': '11111111-1111-1111-1111-111111111111',
'v4-fixed-ip': '10.0.0.7'}]
bdm = {"volume_size": "1",
"volume_id": "11111111-1111-1111-1111-111111111111",
"delete_on_termination": "0",
"device_name": "vda"}
def wrapped_boot(url, key, *boot_args, **boot_kwargs):
self.assertEqual(boot_kwargs['block_device_mapping'], bdm)
self.assertEqual(boot_kwargs['nics'], nics)
return old_boot(url, key, *boot_args, **boot_kwargs)
@mock.patch.object(self.cs.servers, '_boot', wrapped_boot)
def test_create_server_from_volume():
s = self.cs.servers.create(
name="My server",
image=1,
flavor=1,
meta={'foo': 'bar'},
userdata="hello moto",
key_name="fakekey",
block_device_mapping=bdm,
nics=nics
)
self.assert_called('POST', '/os-volumes_boot')
self.assertIsInstance(s, servers.Server)
test_create_server_from_volume()
def test_create_server_boot_with_nics_ipv6(self):
old_boot = self.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(self.cs.servers, '_boot', wrapped_boot):
s = self.cs.servers.create(
name="My server",
image=1,
flavor=1,
meta={'foo': 'bar'},
userdata="hello moto",
key_name="fakekey",
nics=nics
)
self.assert_called('POST', '/servers')
self.assertIsInstance(s, servers.Server)
def test_create_server_userdata_file_object(self):
s = self.cs.servers.create(
name="My server",
image=1,
flavor=1,
meta={'foo': 'bar'},
userdata=six.StringIO('hello moto'),
files={
'/etc/passwd': 'some data', # a file
'/tmp/foo.txt': six.StringIO('data'), # a stream
},
)
self.assert_called('POST', '/servers')
self.assertIsInstance(s, servers.Server)
def test_create_server_userdata_unicode(self):
s = self.cs.servers.create(
name="My server",
image=1,
flavor=1,
meta={'foo': 'bar'},
userdata=six.u('こんにちは'),
key_name="fakekey",
files={
'/etc/passwd': 'some data', # a file
'/tmp/foo.txt': six.StringIO('data'), # a stream
},
)
self.assert_called('POST', '/servers')
self.assertIsInstance(s, servers.Server)
def test_create_server_userdata_utf8(self):
s = self.cs.servers.create(
name="My server",
image=1,
flavor=1,
meta={'foo': 'bar'},
userdata='こんにちは',
key_name="fakekey",
files={
'/etc/passwd': 'some data', # a file
'/tmp/foo.txt': six.StringIO('data'), # a stream
},
)
self.assert_called('POST', '/servers')
self.assertIsInstance(s, servers.Server)
def _create_disk_config(self, disk_config):
s = self.cs.servers.create(
name="My server",
image=1,
flavor=1,
disk_config=disk_config
)
self.assert_called('POST', '/servers')
self.assertIsInstance(s, servers.Server)
# verify disk config param was used in the request:
body = jsonutils.loads(self.requests.last_request.body)
server = body['server']
self.assertIn('OS-DCF:diskConfig', server)
self.assertEqual(disk_config, server['OS-DCF:diskConfig'])
def test_create_server_disk_config_auto(self):
self._create_disk_config('AUTO')
def test_create_server_disk_config_manual(self):
self._create_disk_config('MANUAL')
def test_update_server(self):
s = self.cs.servers.get(1234)
# Update via instance
s.update(name='hi')
self.assert_called('PUT', '/servers/1234')
s.update(name='hi')
self.assert_called('PUT', '/servers/1234')
# Silly, but not an error
s.update()
# Update via manager
self.cs.servers.update(s, name='hi')
self.assert_called('PUT', '/servers/1234')
def test_delete_server(self):
s = self.cs.servers.get(1234)
s.delete()
self.assert_called('DELETE', '/servers/1234')
self.cs.servers.delete(1234)
self.assert_called('DELETE', '/servers/1234')
self.cs.servers.delete(s)
self.assert_called('DELETE', '/servers/1234')
def test_delete_server_meta(self):
self.cs.servers.delete_meta(1234, ['test_key'])
self.assert_called('DELETE', '/servers/1234/metadata/test_key')
def test_set_server_meta(self):
self.cs.servers.set_meta(1234, {'test_key': 'test_value'})
self.assert_called('POST', '/servers/1234/metadata',
{'metadata': {'test_key': 'test_value'}})
def test_set_server_meta_item(self):
self.cs.servers.set_meta_item(1234, 'test_key', 'test_value')
self.assert_called('PUT', '/servers/1234/metadata/test_key',
{'meta': {'test_key': 'test_value'}})
def test_find(self):
server = self.cs.servers.find(name='sample-server')
self.assert_called('GET', '/servers/1234')
self.assertEqual('sample-server', server.name)
self.assertRaises(exceptions.NoUniqueMatch, self.cs.servers.find,
flavor={"id": 1, "name": "256 MB Server"})
sl = self.cs.servers.findall(flavor={"id": 1, "name": "256 MB Server"})
self.assertEqual([1234, 5678, 9012], [s.id for s in sl])
def test_reboot_server(self):
s = self.cs.servers.get(1234)
s.reboot()
self.assert_called('POST', '/servers/1234/action')
self.cs.servers.reboot(s, reboot_type='HARD')
self.assert_called('POST', '/servers/1234/action')
def test_rebuild_server(self):
s = self.cs.servers.get(1234)
s.rebuild(image=1)
self.assert_called('POST', '/servers/1234/action')
self.cs.servers.rebuild(s, image=1)
self.assert_called('POST', '/servers/1234/action')
s.rebuild(image=1, password='5678')
self.assert_called('POST', '/servers/1234/action')
self.cs.servers.rebuild(s, image=1, password='5678')
self.assert_called('POST', '/servers/1234/action')
def _rebuild_resize_disk_config(self, disk_config, operation="rebuild"):
s = self.cs.servers.get(1234)
if operation == "rebuild":
s.rebuild(image=1, disk_config=disk_config)
elif operation == "resize":
s.resize(flavor=1, disk_config=disk_config)
self.assert_called('POST', '/servers/1234/action')
# verify disk config param was used in the request:
body = jsonutils.loads(self.requests.last_request.body)
d = body[operation]
self.assertIn('OS-DCF:diskConfig', d)
self.assertEqual(disk_config, d['OS-DCF:diskConfig'])
def test_rebuild_server_disk_config_auto(self):
self._rebuild_resize_disk_config('AUTO')
def test_rebuild_server_disk_config_manual(self):
self._rebuild_resize_disk_config('MANUAL')
def test_rebuild_server_preserve_ephemeral(self):
s = self.cs.servers.get(1234)
s.rebuild(image=1, preserve_ephemeral=True)
self.assert_called('POST', '/servers/1234/action')
body = jsonutils.loads(self.requests.last_request.body)
d = body['rebuild']
self.assertIn('preserve_ephemeral', d)
self.assertTrue(d['preserve_ephemeral'])
def test_rebuild_server_name_meta_files(self):
files = {'/etc/passwd': 'some data'}
s = self.cs.servers.get(1234)
s.rebuild(image=1, name='new', meta={'foo': 'bar'}, files=files)
body = jsonutils.loads(self.requests.last_request.body)
d = body['rebuild']
self.assertEqual('new', d['name'])
self.assertEqual({'foo': 'bar'}, d['metadata'])
self.assertEqual('/etc/passwd',
d['personality'][0]['path'])
def test_resize_server(self):
s = self.cs.servers.get(1234)
s.resize(flavor=1)
self.assert_called('POST', '/servers/1234/action')
self.cs.servers.resize(s, flavor=1)
self.assert_called('POST', '/servers/1234/action')
def test_resize_server_disk_config_auto(self):
self._rebuild_resize_disk_config('AUTO', 'resize')
def test_resize_server_disk_config_manual(self):
self._rebuild_resize_disk_config('MANUAL', 'resize')
def test_confirm_resized_server(self):
s = self.cs.servers.get(1234)
s.confirm_resize()
self.assert_called('POST', '/servers/1234/action')
self.cs.servers.confirm_resize(s)
self.assert_called('POST', '/servers/1234/action')
def test_revert_resized_server(self):
s = self.cs.servers.get(1234)
s.revert_resize()
self.assert_called('POST', '/servers/1234/action')
self.cs.servers.revert_resize(s)
self.assert_called('POST', '/servers/1234/action')
def test_migrate_server(self):
s = self.cs.servers.get(1234)
s.migrate()
self.assert_called('POST', '/servers/1234/action')
self.cs.servers.migrate(s)
self.assert_called('POST', '/servers/1234/action')
def test_add_fixed_ip(self):
s = self.cs.servers.get(1234)
s.add_fixed_ip(1)
self.assert_called('POST', '/servers/1234/action')
self.cs.servers.add_fixed_ip(s, 1)
self.assert_called('POST', '/servers/1234/action')
def test_remove_fixed_ip(self):
s = self.cs.servers.get(1234)
s.remove_fixed_ip('10.0.0.1')
self.assert_called('POST', '/servers/1234/action')
self.cs.servers.remove_fixed_ip(s, '10.0.0.1')
self.assert_called('POST', '/servers/1234/action')
def test_add_floating_ip(self):
s = self.cs.servers.get(1234)
s.add_floating_ip('11.0.0.1')
self.assert_called('POST', '/servers/1234/action')
self.cs.servers.add_floating_ip(s, '11.0.0.1')
self.assert_called('POST', '/servers/1234/action')
f = self.cs.floating_ips.list()[0]
self.cs.servers.add_floating_ip(s, f)
self.assert_called('POST', '/servers/1234/action')
s.add_floating_ip(f)
self.assert_called('POST', '/servers/1234/action')
def test_add_floating_ip_to_fixed(self):
s = self.cs.servers.get(1234)
s.add_floating_ip('11.0.0.1', fixed_address='12.0.0.1')
self.assert_called('POST', '/servers/1234/action')
self.cs.servers.add_floating_ip(s, '11.0.0.1',
fixed_address='12.0.0.1')
self.assert_called('POST', '/servers/1234/action')
f = self.cs.floating_ips.list()[0]
self.cs.servers.add_floating_ip(s, f)
self.assert_called('POST', '/servers/1234/action')
s.add_floating_ip(f)
self.assert_called('POST', '/servers/1234/action')
def test_remove_floating_ip(self):
s = self.cs.servers.get(1234)
s.remove_floating_ip('11.0.0.1')
self.assert_called('POST', '/servers/1234/action')
self.cs.servers.remove_floating_ip(s, '11.0.0.1')
self.assert_called('POST', '/servers/1234/action')
f = self.cs.floating_ips.list()[0]
self.cs.servers.remove_floating_ip(s, f)
self.assert_called('POST', '/servers/1234/action')
s.remove_floating_ip(f)
self.assert_called('POST', '/servers/1234/action')
def test_stop(self):
s = self.cs.servers.get(1234)
s.stop()
self.assert_called('POST', '/servers/1234/action')
self.cs.servers.stop(s)
self.assert_called('POST', '/servers/1234/action')
def test_force_delete(self):
s = self.cs.servers.get(1234)
s.force_delete()
self.assert_called('POST', '/servers/1234/action')
self.cs.servers.force_delete(s)
self.assert_called('POST', '/servers/1234/action')
def test_restore(self):
s = self.cs.servers.get(1234)
s.restore()
self.assert_called('POST', '/servers/1234/action')
self.cs.servers.restore(s)
self.assert_called('POST', '/servers/1234/action')
def test_start(self):
s = self.cs.servers.get(1234)
s.start()
self.assert_called('POST', '/servers/1234/action')
self.cs.servers.start(s)
self.assert_called('POST', '/servers/1234/action')
def test_rescue(self):
s = self.cs.servers.get(1234)
s.rescue()
self.assert_called('POST', '/servers/1234/action')
self.cs.servers.rescue(s)
self.assert_called('POST', '/servers/1234/action')
def test_rescue_password(self):
s = self.cs.servers.get(1234)
s.rescue(password='asdf')
self.assert_called('POST', '/servers/1234/action',
{'rescue': {'adminPass': 'asdf'}})
self.cs.servers.rescue(s, password='asdf')
self.assert_called('POST', '/servers/1234/action',
{'rescue': {'adminPass': 'asdf'}})
def test_rescue_image(self):
s = self.cs.servers.get(1234)
s.rescue(image=1)
self.assert_called('POST', '/servers/1234/action',
{'rescue': {'rescue_image_ref': 1}})
self.cs.servers.rescue(s, image=1)
self.assert_called('POST', '/servers/1234/action',
{'rescue': {'rescue_image_ref': 1}})
def test_unrescue(self):
s = self.cs.servers.get(1234)
s.unrescue()
self.assert_called('POST', '/servers/1234/action')
self.cs.servers.unrescue(s)
self.assert_called('POST', '/servers/1234/action')
def test_lock(self):
s = self.cs.servers.get(1234)
s.lock()
self.assert_called('POST', '/servers/1234/action')
self.cs.servers.lock(s)
self.assert_called('POST', '/servers/1234/action')
def test_unlock(self):
s = self.cs.servers.get(1234)
s.unlock()
self.assert_called('POST', '/servers/1234/action')
self.cs.servers.unlock(s)
self.assert_called('POST', '/servers/1234/action')
def test_backup(self):
s = self.cs.servers.get(1234)
s.backup('back1', 'daily', 1)
self.assert_called('POST', '/servers/1234/action')
self.cs.servers.backup(s, 'back1', 'daily', 2)
self.assert_called('POST', '/servers/1234/action')
def test_get_console_output_without_length(self):
success = 'foo'
s = self.cs.servers.get(1234)
s.get_console_output()
self.assertEqual(success, s.get_console_output())
self.assert_called('POST', '/servers/1234/action')
self.cs.servers.get_console_output(s)
self.assertEqual(success, self.cs.servers.get_console_output(s))
self.assert_called('POST', '/servers/1234/action')
def test_get_console_output_with_length(self):
success = 'foo'
s = self.cs.servers.get(1234)
s.get_console_output(length=50)
self.assertEqual(success, s.get_console_output(length=50))
self.assert_called('POST', '/servers/1234/action')
self.cs.servers.get_console_output(s, length=50)
self.assertEqual(success,
self.cs.servers.get_console_output(s, length=50))
self.assert_called('POST', '/servers/1234/action')
# Testing password methods with the following password and key
#
# Clear password: FooBar123
#
# RSA Private Key: novaclient/tests/unit/idfake.pem
#
# Encrypted password
# OIuEuQttO8Rk93BcKlwHQsziDAnkAm/V6V8VPToA8ZeUaUBWwS0gwo2K6Y61Z96r
# qG447iRz0uTEEYq3RAYJk1mh3mMIRVl27t8MtIecR5ggVVbz1S9AwXJQypDKl0ho
# QFvhCBcMWPohyGewDJOhDbtuN1IoFI9G55ZvFwCm5y7m7B2aVcoLeIsJZE4PLsIw
# /y5a6Z3/AoJZYGG7IH5WN88UROU3B9JZGFB2qtPLQTOvDMZLUhoPRIJeHiVSlo1N
# tI2/++UsXVg3ow6ItqCJGgdNuGG5JB+bslDHWPxROpesEIHdczk46HCpHQN8f1sk
# Hi/fmZZNQQqj1Ijq0caOIw==
def test_get_password(self):
s = self.cs.servers.get(1234)
self.assertEqual(b'FooBar123',
s.get_password('novaclient/tests/unit/idfake.pem'))
self.assert_called('GET', '/servers/1234/os-server-password')
def test_get_password_without_key(self):
s = self.cs.servers.get(1234)
self.assertEqual(
'OIuEuQttO8Rk93BcKlwHQsziDAnkAm/V6V8VPToA8ZeUaUBWwS0gwo2K6Y61Z96r'
'qG447iRz0uTEEYq3RAYJk1mh3mMIRVl27t8MtIecR5ggVVbz1S9AwXJQypDKl0ho'
'QFvhCBcMWPohyGewDJOhDbtuN1IoFI9G55ZvFwCm5y7m7B2aVcoLeIsJZE4PLsIw'
'/y5a6Z3/AoJZYGG7IH5WN88UROU3B9JZGFB2qtPLQTOvDMZLUhoPRIJeHiVSlo1N'
'tI2/++UsXVg3ow6ItqCJGgdNuGG5JB+bslDHWPxROpesEIHdczk46HCpHQN8f1sk'
'Hi/fmZZNQQqj1Ijq0caOIw==', s.get_password())
self.assert_called('GET', '/servers/1234/os-server-password')
def test_clear_password(self):
s = self.cs.servers.get(1234)
s.clear_password()
self.assert_called('DELETE', '/servers/1234/os-server-password')
def test_get_server_diagnostics(self):
s = self.cs.servers.get(1234)
diagnostics = s.diagnostics()
self.assertIsNotNone(diagnostics)
self.assert_called('GET', '/servers/1234/diagnostics')
diagnostics_from_manager = self.cs.servers.diagnostics(1234)
self.assertIsNotNone(diagnostics_from_manager)
self.assert_called('GET', '/servers/1234/diagnostics')
self.assertEqual(diagnostics[1], diagnostics_from_manager[1])
def test_get_vnc_console(self):
s = self.cs.servers.get(1234)
s.get_vnc_console('fake')
self.assert_called('POST', '/servers/1234/action')
self.cs.servers.get_vnc_console(s, 'fake')
self.assert_called('POST', '/servers/1234/action')
def test_get_spice_console(self):
s = self.cs.servers.get(1234)
s.get_spice_console('fake')
self.assert_called('POST', '/servers/1234/action')
self.cs.servers.get_spice_console(s, 'fake')
self.assert_called('POST', '/servers/1234/action')
def test_get_serial_console(self):
s = self.cs.servers.get(1234)
s.get_serial_console('fake')
self.assert_called('POST', '/servers/1234/action')
self.cs.servers.get_serial_console(s, 'fake')
self.assert_called('POST', '/servers/1234/action')
def test_get_rdp_console(self):
s = self.cs.servers.get(1234)
s.get_rdp_console('fake')
self.assert_called('POST', '/servers/1234/action')
self.cs.servers.get_rdp_console(s, 'fake')
self.assert_called('POST', '/servers/1234/action')
def test_create_image(self):
s = self.cs.servers.get(1234)
s.create_image('123')
self.assert_called('POST', '/servers/1234/action')
s.create_image('123', {})
self.assert_called('POST', '/servers/1234/action')
self.cs.servers.create_image(s, '123')
self.assert_called('POST', '/servers/1234/action')
self.cs.servers.create_image(s, '123', {})
def test_live_migrate_server(self):
s = self.cs.servers.get(1234)
s.live_migrate(host='hostname', block_migration=False,
disk_over_commit=False)
self.assert_called('POST', '/servers/1234/action')
self.cs.servers.live_migrate(s, host='hostname', block_migration=False,
disk_over_commit=False)
self.assert_called('POST', '/servers/1234/action')
def test_reset_state(self):
s = self.cs.servers.get(1234)
s.reset_state('newstate')
self.assert_called('POST', '/servers/1234/action')
self.cs.servers.reset_state(s, 'newstate')
self.assert_called('POST', '/servers/1234/action')
def test_reset_network(self):
s = self.cs.servers.get(1234)
s.reset_network()
self.assert_called('POST', '/servers/1234/action')
self.cs.servers.reset_network(s)
self.assert_called('POST', '/servers/1234/action')
def test_add_security_group(self):
s = self.cs.servers.get(1234)
s.add_security_group('newsg')
self.assert_called('POST', '/servers/1234/action')
self.cs.servers.add_security_group(s, 'newsg')
self.assert_called('POST', '/servers/1234/action')
def test_remove_security_group(self):
s = self.cs.servers.get(1234)
s.remove_security_group('oldsg')
self.assert_called('POST', '/servers/1234/action')
self.cs.servers.remove_security_group(s, 'oldsg')
self.assert_called('POST', '/servers/1234/action')
def test_list_security_group(self):
s = self.cs.servers.get(1234)
s.list_security_group()
self.assert_called('GET', '/servers/1234/os-security-groups')
def test_evacuate(self):
s = self.cs.servers.get(1234)
s.evacuate('fake_target_host', 'True')
self.assert_called('POST', '/servers/1234/action')
self.cs.servers.evacuate(s, 'fake_target_host',
'False', 'NewAdminPassword')
self.assert_called('POST', '/servers/1234/action')
def test_interface_list(self):
s = self.cs.servers.get(1234)
s.interface_list()
self.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 = self.cs.servers.get(1234)
s.interface_attach(None, None, None)
self.assert_called('POST', '/servers/1234/os-interface')
def test_interface_detach(self):
s = self.cs.servers.get(1234)
s.interface_detach('port-id')
self.assert_called('DELETE', '/servers/1234/os-interface/port-id')

View File

@ -0,0 +1,99 @@
# Copyright 2012 IBM Corp.
# 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.unit import utils
from novaclient.tests.unit.v2 import fakes
from novaclient.v2 import services
class ServicesTest(utils.TestCase):
def setUp(self):
super(ServicesTest, self).setUp()
self.cs = self._get_fake_client()
self.service_type = self._get_service_type()
def _get_fake_client(self):
return fakes.FakeClient()
def _get_service_type(self):
return services.Service
def test_list_services(self):
svs = self.cs.services.list()
self.cs.assert_called('GET', '/os-services')
for s in svs:
self.assertIsInstance(s, self._get_service_type())
self.assertEqual('nova-compute', s.binary)
self.assertEqual('host1', s.host)
self.assertTrue(str(s).startswith('<Service: '))
def test_list_services_with_hostname(self):
svs = self.cs.services.list(host='host2')
self.cs.assert_called('GET', '/os-services?host=host2')
for s in svs:
self.assertIsInstance(s, self._get_service_type())
self.assertEqual('nova-compute', s.binary)
self.assertEqual('host2', s.host)
def test_list_services_with_binary(self):
svs = self.cs.services.list(binary='nova-cert')
self.cs.assert_called('GET', '/os-services?binary=nova-cert')
for s in svs:
self.assertIsInstance(s, self._get_service_type())
self.assertEqual('nova-cert', s.binary)
self.assertEqual('host1', s.host)
def test_list_services_with_host_binary(self):
svs = self.cs.services.list(host='host2', binary='nova-cert')
self.cs.assert_called('GET',
'/os-services?host=host2&binary=nova-cert')
for s in svs:
self.assertIsInstance(s, self._get_service_type())
self.assertEqual('nova-cert', s.binary)
self.assertEqual('host2', s.host)
def _update_body(self, host, binary, disabled_reason=None):
body = {"host": host,
"binary": binary}
if disabled_reason is not None:
body["disabled_reason"] = disabled_reason
return body
def test_services_enable(self):
service = self.cs.services.enable('host1', 'nova-cert')
values = self._update_body("host1", "nova-cert")
self.cs.assert_called('PUT', '/os-services/enable', values)
self.assertIsInstance(service, self._get_service_type())
self.assertEqual('enabled', service.status)
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")
self.cs.assert_called('PUT', '/os-services/disable', values)
self.assertIsInstance(service, self._get_service_type())
self.assertEqual('disabled', service.status)
def test_services_disable_log_reason(self):
service = self.cs.services.disable_log_reason(
'compute1', 'nova-compute', 'disable bad host')
values = self._update_body("compute1", "nova-compute",
"disable bad host")
self.cs.assert_called('PUT', '/os-services/disable-log-reason', values)
self.assertIsInstance(service, self._get_service_type())
self.assertEqual('disabled', service.status)

File diff suppressed because it is too large Load Diff

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 datetime
from novaclient.tests.unit import utils
from novaclient.tests.unit.v2 import fakes
from novaclient.v2 import usage
class UsageTest(utils.TestCase):
def setUp(self):
super(UsageTest, self).setUp()
self.cs = self._get_fake_client()
self.usage_type = self._get_usage_type()
def _get_fake_client(self):
return fakes.FakeClient()
def _get_usage_type(self):
return usage.Usage
def test_usage_list(self, detailed=False):
now = datetime.datetime.now()
usages = self.cs.usage.list(now, now, detailed)
self.cs.assert_called(
'GET',
"/os-simple-tenant-usage?" +
("start=%s&" % now.isoformat()) +
("end=%s&" % now.isoformat()) +
("detailed=%s" % int(bool(detailed))))
[self.assertIsInstance(u, usage.Usage) for u in usages]
def test_usage_list_detailed(self):
self.test_usage_list(True)
def test_usage_get(self):
now = datetime.datetime.now()
u = self.cs.usage.get("tenantfoo", now, now)
self.cs.assert_called(
'GET',
"/os-simple-tenant-usage/tenantfoo?" +
("start=%s&" % now.isoformat()) +
("end=%s" % now.isoformat()))
self.assertIsInstance(u, usage.Usage)

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