Infrastructure update
* Update Openstack common (aa7c658156e1e46315cdce1d580718f30054da2f): - apiclient - importutils - strutils - jsonutils - cliutils - apiclient * Switch from nosetests to testtools * Update shell.py to the actual state Change-Id: I2b8f3393ba3e700f65f1a794caae713f12856035
This commit is contained in:
parent
9c31110512
commit
1a7db5f125
|
@ -0,0 +1,7 @@
|
|||
[DEFAULT]
|
||||
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
|
||||
OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
|
||||
OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \
|
||||
${PYTHON:-python} -m subunit.run discover $DISCOVER_DIRECTORY $LISTOPT $IDOPTION
|
||||
test_id_option=--load-list $IDFILE
|
||||
test_list_option=--list
|
|
@ -25,10 +25,6 @@ class BaseException(Exception):
|
|||
return self.message or self.__class__.__doc__
|
||||
|
||||
|
||||
class CommandError(BaseException):
|
||||
"""Invalid usage of CLI."""
|
||||
|
||||
|
||||
class InvalidEndpoint(BaseException):
|
||||
"""The provided endpoint is invalid."""
|
||||
|
||||
|
|
|
@ -0,0 +1,221 @@
|
|||
# Copyright 2013 OpenStack Foundation
|
||||
# Copyright 2013 Spanish National Research Council.
|
||||
# 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.
|
||||
|
||||
# E0202: An attribute inherited from %s hide this method
|
||||
# pylint: disable=E0202
|
||||
|
||||
import abc
|
||||
import argparse
|
||||
import os
|
||||
|
||||
import six
|
||||
from stevedore import extension
|
||||
|
||||
from muranoclient.openstack.common.apiclient import exceptions
|
||||
|
||||
|
||||
_discovered_plugins = {}
|
||||
|
||||
|
||||
def discover_auth_systems():
|
||||
"""Discover the available auth-systems.
|
||||
|
||||
This won't take into account the old style auth-systems.
|
||||
"""
|
||||
global _discovered_plugins
|
||||
_discovered_plugins = {}
|
||||
|
||||
def add_plugin(ext):
|
||||
_discovered_plugins[ext.name] = ext.plugin
|
||||
|
||||
ep_namespace = "muranoclient.openstack.common.apiclient.auth"
|
||||
mgr = extension.ExtensionManager(ep_namespace)
|
||||
mgr.map(add_plugin)
|
||||
|
||||
|
||||
def load_auth_system_opts(parser):
|
||||
"""Load options needed by the available auth-systems into a parser.
|
||||
|
||||
This function will try to populate the parser with options from the
|
||||
available plugins.
|
||||
"""
|
||||
group = parser.add_argument_group("Common auth options")
|
||||
BaseAuthPlugin.add_common_opts(group)
|
||||
for name, auth_plugin in six.iteritems(_discovered_plugins):
|
||||
group = parser.add_argument_group(
|
||||
"Auth-system '%s' options" % name,
|
||||
conflict_handler="resolve")
|
||||
auth_plugin.add_opts(group)
|
||||
|
||||
|
||||
def load_plugin(auth_system):
|
||||
try:
|
||||
plugin_class = _discovered_plugins[auth_system]
|
||||
except KeyError:
|
||||
raise exceptions.AuthSystemNotFound(auth_system)
|
||||
return plugin_class(auth_system=auth_system)
|
||||
|
||||
|
||||
def load_plugin_from_args(args):
|
||||
"""Load required plugin and populate it with options.
|
||||
|
||||
Try to guess auth system if it is not specified. Systems are tried in
|
||||
alphabetical order.
|
||||
|
||||
:type args: argparse.Namespace
|
||||
:raises: AuthPluginOptionsMissing
|
||||
"""
|
||||
auth_system = args.os_auth_system
|
||||
if auth_system:
|
||||
plugin = load_plugin(auth_system)
|
||||
plugin.parse_opts(args)
|
||||
plugin.sufficient_options()
|
||||
return plugin
|
||||
|
||||
for plugin_auth_system in sorted(six.iterkeys(_discovered_plugins)):
|
||||
plugin_class = _discovered_plugins[plugin_auth_system]
|
||||
plugin = plugin_class()
|
||||
plugin.parse_opts(args)
|
||||
try:
|
||||
plugin.sufficient_options()
|
||||
except exceptions.AuthPluginOptionsMissing:
|
||||
continue
|
||||
return plugin
|
||||
raise exceptions.AuthPluginOptionsMissing(["auth_system"])
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BaseAuthPlugin(object):
|
||||
"""Base class for authentication plugins.
|
||||
|
||||
An authentication plugin needs to override at least the authenticate
|
||||
method to be a valid plugin.
|
||||
"""
|
||||
|
||||
auth_system = None
|
||||
opt_names = []
|
||||
common_opt_names = [
|
||||
"auth_system",
|
||||
"username",
|
||||
"password",
|
||||
"tenant_name",
|
||||
"token",
|
||||
"auth_url",
|
||||
]
|
||||
|
||||
def __init__(self, auth_system=None, **kwargs):
|
||||
self.auth_system = auth_system or self.auth_system
|
||||
self.opts = dict((name, kwargs.get(name))
|
||||
for name in self.opt_names)
|
||||
|
||||
@staticmethod
|
||||
def _parser_add_opt(parser, opt):
|
||||
"""Add an option to parser in two variants.
|
||||
|
||||
:param opt: option name (with underscores)
|
||||
"""
|
||||
dashed_opt = opt.replace("_", "-")
|
||||
env_var = "OS_%s" % opt.upper()
|
||||
arg_default = os.environ.get(env_var, "")
|
||||
arg_help = "Defaults to env[%s]." % env_var
|
||||
parser.add_argument(
|
||||
"--os-%s" % dashed_opt,
|
||||
metavar="<%s>" % dashed_opt,
|
||||
default=arg_default,
|
||||
help=arg_help)
|
||||
parser.add_argument(
|
||||
"--os_%s" % opt,
|
||||
metavar="<%s>" % dashed_opt,
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
@classmethod
|
||||
def add_opts(cls, parser):
|
||||
"""Populate the parser with the options for this plugin.
|
||||
"""
|
||||
for opt in cls.opt_names:
|
||||
# use `BaseAuthPlugin.common_opt_names` since it is never
|
||||
# changed in child classes
|
||||
if opt not in BaseAuthPlugin.common_opt_names:
|
||||
cls._parser_add_opt(parser, opt)
|
||||
|
||||
@classmethod
|
||||
def add_common_opts(cls, parser):
|
||||
"""Add options that are common for several plugins.
|
||||
"""
|
||||
for opt in cls.common_opt_names:
|
||||
cls._parser_add_opt(parser, opt)
|
||||
|
||||
@staticmethod
|
||||
def get_opt(opt_name, args):
|
||||
"""Return option name and value.
|
||||
|
||||
:param opt_name: name of the option, e.g., "username"
|
||||
:param args: parsed arguments
|
||||
"""
|
||||
return (opt_name, getattr(args, "os_%s" % opt_name, None))
|
||||
|
||||
def parse_opts(self, args):
|
||||
"""Parse the actual auth-system options if any.
|
||||
|
||||
This method is expected to populate the attribute `self.opts` with a
|
||||
dict containing the options and values needed to make authentication.
|
||||
"""
|
||||
self.opts.update(dict(self.get_opt(opt_name, args)
|
||||
for opt_name in self.opt_names))
|
||||
|
||||
def authenticate(self, http_client):
|
||||
"""Authenticate using plugin defined method.
|
||||
|
||||
The method usually analyses `self.opts` and performs
|
||||
a request to authentication server.
|
||||
|
||||
:param http_client: client object that needs authentication
|
||||
:type http_client: HTTPClient
|
||||
:raises: AuthorizationFailure
|
||||
"""
|
||||
self.sufficient_options()
|
||||
self._do_authenticate(http_client)
|
||||
|
||||
@abc.abstractmethod
|
||||
def _do_authenticate(self, http_client):
|
||||
"""Protected method for authentication.
|
||||
"""
|
||||
|
||||
def sufficient_options(self):
|
||||
"""Check if all required options are present.
|
||||
|
||||
:raises: AuthPluginOptionsMissing
|
||||
"""
|
||||
missing = [opt
|
||||
for opt in self.opt_names
|
||||
if not self.opts.get(opt)]
|
||||
if missing:
|
||||
raise exceptions.AuthPluginOptionsMissing(missing)
|
||||
|
||||
@abc.abstractmethod
|
||||
def token_and_endpoint(self, endpoint_type, service_type):
|
||||
"""Return token and endpoint.
|
||||
|
||||
:param service_type: Service type of the endpoint
|
||||
:type service_type: string
|
||||
:param endpoint_type: Type of endpoint.
|
||||
Possible values: public or publicURL,
|
||||
internal or internalURL,
|
||||
admin or adminURL
|
||||
:type endpoint_type: string
|
||||
:returns: tuple of token and endpoint strings
|
||||
:raises: EndpointException
|
||||
"""
|
|
@ -32,6 +32,7 @@ from six.moves.urllib import parse
|
|||
from muranoclient.openstack.common.apiclient import exceptions
|
||||
from muranoclient.openstack.common.gettextutils import _
|
||||
from muranoclient.openstack.common import strutils
|
||||
from muranoclient.openstack.common import uuidutils
|
||||
|
||||
|
||||
def getid(obj):
|
||||
|
@ -75,8 +76,8 @@ class HookableMixin(object):
|
|||
|
||||
:param cls: class that registers hooks
|
||||
:param hook_type: hook type, e.g., '__pre_parse_args__'
|
||||
:param **args: args to be passed to every hook function
|
||||
:param **kwargs: kwargs to be passed to every hook function
|
||||
:param args: args to be passed to every hook function
|
||||
:param kwargs: kwargs to be passed to every hook function
|
||||
"""
|
||||
hook_funcs = cls._hooks_map.get(hook_type) or []
|
||||
for hook_func in hook_funcs:
|
||||
|
@ -436,6 +437,21 @@ class Resource(object):
|
|||
self._info = info
|
||||
self._add_details(info)
|
||||
self._loaded = loaded
|
||||
self._init_completion_cache()
|
||||
|
||||
def _init_completion_cache(self):
|
||||
cache_write = getattr(self.manager, 'write_to_completion_cache', None)
|
||||
if not cache_write:
|
||||
return
|
||||
|
||||
# NOTE(sirp): ensure `id` is already present because if it isn't we'll
|
||||
# enter an infinite loop of __getattr__ -> get -> __init__ ->
|
||||
# __getattr__ -> ...
|
||||
if 'id' in self.__dict__ and uuidutils.is_uuid_like(self.id):
|
||||
cache_write('uuid', self.id)
|
||||
|
||||
if self.human_id:
|
||||
cache_write('human_id', self.human_id)
|
||||
|
||||
def __repr__(self):
|
||||
reprkeys = sorted(k
|
||||
|
@ -448,8 +464,10 @@ class Resource(object):
|
|||
def human_id(self):
|
||||
"""Human-readable ID which can be used for bash completion.
|
||||
"""
|
||||
if self.NAME_ATTR in self.__dict__ and self.HUMAN_ID:
|
||||
return strutils.to_slug(getattr(self, self.NAME_ATTR))
|
||||
if self.HUMAN_ID:
|
||||
name = getattr(self, self.NAME_ATTR, None)
|
||||
if name is not None:
|
||||
return strutils.to_slug(name)
|
||||
return None
|
||||
|
||||
def _add_details(self, info):
|
||||
|
|
|
@ -0,0 +1,364 @@
|
|||
# Copyright 2010 Jacob Kaplan-Moss
|
||||
# Copyright 2011 OpenStack Foundation
|
||||
# Copyright 2011 Piston Cloud Computing, Inc.
|
||||
# Copyright 2013 Alessio Ababilov
|
||||
# Copyright 2013 Grid Dynamics
|
||||
# Copyright 2013 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.
|
||||
|
||||
"""
|
||||
OpenStack Client interface. Handles the REST calls and responses.
|
||||
"""
|
||||
|
||||
# E0202: An attribute inherited from %s hide this method
|
||||
# pylint: disable=E0202
|
||||
|
||||
import logging
|
||||
import time
|
||||
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
import json
|
||||
|
||||
import requests
|
||||
|
||||
from muranoclient.openstack.common.apiclient import exceptions
|
||||
from muranoclient.openstack.common.gettextutils import _
|
||||
from muranoclient.openstack.common import importutils
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HTTPClient(object):
|
||||
"""This client handles sending HTTP requests to OpenStack servers.
|
||||
|
||||
Features:
|
||||
|
||||
- share authentication information between several clients to different
|
||||
services (e.g., for compute and image clients);
|
||||
- reissue authentication request for expired tokens;
|
||||
- encode/decode JSON bodies;
|
||||
- raise exceptions on HTTP errors;
|
||||
- pluggable authentication;
|
||||
- store authentication information in a keyring;
|
||||
- store time spent for requests;
|
||||
- register clients for particular services, so one can use
|
||||
`http_client.identity` or `http_client.compute`;
|
||||
- log requests and responses in a format that is easy to copy-and-paste
|
||||
into terminal and send the same request with curl.
|
||||
"""
|
||||
|
||||
user_agent = "muranoclient.openstack.common.apiclient"
|
||||
|
||||
def __init__(self,
|
||||
auth_plugin,
|
||||
region_name=None,
|
||||
endpoint_type="publicURL",
|
||||
original_ip=None,
|
||||
verify=True,
|
||||
cert=None,
|
||||
timeout=None,
|
||||
timings=False,
|
||||
keyring_saver=None,
|
||||
debug=False,
|
||||
user_agent=None,
|
||||
http=None):
|
||||
self.auth_plugin = auth_plugin
|
||||
|
||||
self.endpoint_type = endpoint_type
|
||||
self.region_name = region_name
|
||||
|
||||
self.original_ip = original_ip
|
||||
self.timeout = timeout
|
||||
self.verify = verify
|
||||
self.cert = cert
|
||||
|
||||
self.keyring_saver = keyring_saver
|
||||
self.debug = debug
|
||||
self.user_agent = user_agent or self.user_agent
|
||||
|
||||
self.times = [] # [("item", starttime, endtime), ...]
|
||||
self.timings = timings
|
||||
|
||||
# requests within the same session can reuse TCP connections from pool
|
||||
self.http = http or requests.Session()
|
||||
|
||||
self.cached_token = None
|
||||
|
||||
def _http_log_req(self, method, url, kwargs):
|
||||
if not self.debug:
|
||||
return
|
||||
|
||||
string_parts = [
|
||||
"curl -i",
|
||||
"-X '%s'" % method,
|
||||
"'%s'" % url,
|
||||
]
|
||||
|
||||
for element in kwargs['headers']:
|
||||
header = "-H '%s: %s'" % (element, kwargs['headers'][element])
|
||||
string_parts.append(header)
|
||||
|
||||
_logger.debug("REQ: %s" % " ".join(string_parts))
|
||||
if 'data' in kwargs:
|
||||
_logger.debug("REQ BODY: %s\n" % (kwargs['data']))
|
||||
|
||||
def _http_log_resp(self, resp):
|
||||
if not self.debug:
|
||||
return
|
||||
_logger.debug(
|
||||
"RESP: [%s] %s\n",
|
||||
resp.status_code,
|
||||
resp.headers)
|
||||
if resp._content_consumed:
|
||||
_logger.debug(
|
||||
"RESP BODY: %s\n",
|
||||
resp.text)
|
||||
|
||||
def serialize(self, kwargs):
|
||||
if kwargs.get('json') is not None:
|
||||
kwargs['headers']['Content-Type'] = 'application/json'
|
||||
kwargs['data'] = json.dumps(kwargs['json'])
|
||||
try:
|
||||
del kwargs['json']
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def get_timings(self):
|
||||
return self.times
|
||||
|
||||
def reset_timings(self):
|
||||
self.times = []
|
||||
|
||||
def request(self, method, url, **kwargs):
|
||||
"""Send an http request with the specified characteristics.
|
||||
|
||||
Wrapper around `requests.Session.request` to handle tasks such as
|
||||
setting headers, JSON encoding/decoding, and error handling.
|
||||
|
||||
:param method: method of HTTP request
|
||||
:param url: URL of HTTP request
|
||||
:param kwargs: any other parameter that can be passed to
|
||||
requests.Session.request (such as `headers`) or `json`
|
||||
that will be encoded as JSON and used as `data` argument
|
||||
"""
|
||||
kwargs.setdefault("headers", kwargs.get("headers", {}))
|
||||
kwargs["headers"]["User-Agent"] = self.user_agent
|
||||
if self.original_ip:
|
||||
kwargs["headers"]["Forwarded"] = "for=%s;by=%s" % (
|
||||
self.original_ip, self.user_agent)
|
||||
if self.timeout is not None:
|
||||
kwargs.setdefault("timeout", self.timeout)
|
||||
kwargs.setdefault("verify", self.verify)
|
||||
if self.cert is not None:
|
||||
kwargs.setdefault("cert", self.cert)
|
||||
self.serialize(kwargs)
|
||||
|
||||
self._http_log_req(method, url, kwargs)
|
||||
if self.timings:
|
||||
start_time = time.time()
|
||||
resp = self.http.request(method, url, **kwargs)
|
||||
if self.timings:
|
||||
self.times.append(("%s %s" % (method, url),
|
||||
start_time, time.time()))
|
||||
self._http_log_resp(resp)
|
||||
|
||||
if resp.status_code >= 400:
|
||||
_logger.debug(
|
||||
"Request returned failure status: %s",
|
||||
resp.status_code)
|
||||
raise exceptions.from_response(resp, method, url)
|
||||
|
||||
return resp
|
||||
|
||||
@staticmethod
|
||||
def concat_url(endpoint, url):
|
||||
"""Concatenate endpoint and final URL.
|
||||
|
||||
E.g., "http://keystone/v2.0/" and "/tokens" are concatenated to
|
||||
"http://keystone/v2.0/tokens".
|
||||
|
||||
:param endpoint: the base URL
|
||||
:param url: the final URL
|
||||
"""
|
||||
return "%s/%s" % (endpoint.rstrip("/"), url.strip("/"))
|
||||
|
||||
def client_request(self, client, method, url, **kwargs):
|
||||
"""Send an http request using `client`'s endpoint and specified `url`.
|
||||
|
||||
If request was rejected as unauthorized (possibly because the token is
|
||||
expired), issue one authorization attempt and send the request once
|
||||
again.
|
||||
|
||||
:param client: instance of BaseClient descendant
|
||||
:param method: method of HTTP request
|
||||
:param url: URL of HTTP request
|
||||
:param kwargs: any other parameter that can be passed to
|
||||
`HTTPClient.request`
|
||||
"""
|
||||
|
||||
filter_args = {
|
||||
"endpoint_type": client.endpoint_type or self.endpoint_type,
|
||||
"service_type": client.service_type,
|
||||
}
|
||||
token, endpoint = (self.cached_token, client.cached_endpoint)
|
||||
just_authenticated = False
|
||||
if not (token and endpoint):
|
||||
try:
|
||||
token, endpoint = self.auth_plugin.token_and_endpoint(
|
||||
**filter_args)
|
||||
except exceptions.EndpointException:
|
||||
pass
|
||||
if not (token and endpoint):
|
||||
self.authenticate()
|
||||
just_authenticated = True
|
||||
token, endpoint = self.auth_plugin.token_and_endpoint(
|
||||
**filter_args)
|
||||
if not (token and endpoint):
|
||||
raise exceptions.AuthorizationFailure(
|
||||
_("Cannot find endpoint or token for request"))
|
||||
|
||||
old_token_endpoint = (token, endpoint)
|
||||
kwargs.setdefault("headers", {})["X-Auth-Token"] = token
|
||||
self.cached_token = token
|
||||
client.cached_endpoint = endpoint
|
||||
# Perform the request once. If we get Unauthorized, then it
|
||||
# might be because the auth token expired, so try to
|
||||
# re-authenticate and try again. If it still fails, bail.
|
||||
try:
|
||||
return self.request(
|
||||
method, self.concat_url(endpoint, url), **kwargs)
|
||||
except exceptions.Unauthorized as unauth_ex:
|
||||
if just_authenticated:
|
||||
raise
|
||||
self.cached_token = None
|
||||
client.cached_endpoint = None
|
||||
self.authenticate()
|
||||
try:
|
||||
token, endpoint = self.auth_plugin.token_and_endpoint(
|
||||
**filter_args)
|
||||
except exceptions.EndpointException:
|
||||
raise unauth_ex
|
||||
if (not (token and endpoint) or
|
||||
old_token_endpoint == (token, endpoint)):
|
||||
raise unauth_ex
|
||||
self.cached_token = token
|
||||
client.cached_endpoint = endpoint
|
||||
kwargs["headers"]["X-Auth-Token"] = token
|
||||
return self.request(
|
||||
method, self.concat_url(endpoint, url), **kwargs)
|
||||
|
||||
def add_client(self, base_client_instance):
|
||||
"""Add a new instance of :class:`BaseClient` descendant.
|
||||
|
||||
`self` will store a reference to `base_client_instance`.
|
||||
|
||||
Example:
|
||||
|
||||
>>> def test_clients():
|
||||
... from keystoneclient.auth import keystone
|
||||
... from openstack.common.apiclient import client
|
||||
... auth = keystone.KeystoneAuthPlugin(
|
||||
... username="user", password="pass", tenant_name="tenant",
|
||||
... auth_url="http://auth:5000/v2.0")
|
||||
... openstack_client = client.HTTPClient(auth)
|
||||
... # create nova client
|
||||
... from novaclient.v1_1 import client
|
||||
... client.Client(openstack_client)
|
||||
... # create keystone client
|
||||
... from keystoneclient.v2_0 import client
|
||||
... client.Client(openstack_client)
|
||||
... # use them
|
||||
... openstack_client.identity.tenants.list()
|
||||
... openstack_client.compute.servers.list()
|
||||
"""
|
||||
service_type = base_client_instance.service_type
|
||||
if service_type and not hasattr(self, service_type):
|
||||
setattr(self, service_type, base_client_instance)
|
||||
|
||||
def authenticate(self):
|
||||
self.auth_plugin.authenticate(self)
|
||||
# Store the authentication results in the keyring for later requests
|
||||
if self.keyring_saver:
|
||||
self.keyring_saver.save(self)
|
||||
|
||||
|
||||
class BaseClient(object):
|
||||
"""Top-level object to access the OpenStack API.
|
||||
|
||||
This client uses :class:`HTTPClient` to send requests. :class:`HTTPClient`
|
||||
will handle a bunch of issues such as authentication.
|
||||
"""
|
||||
|
||||
service_type = None
|
||||
endpoint_type = None # "publicURL" will be used
|
||||
cached_endpoint = None
|
||||
|
||||
def __init__(self, http_client, extensions=None):
|
||||
self.http_client = http_client
|
||||
http_client.add_client(self)
|
||||
|
||||
# Add in any extensions...
|
||||
if extensions:
|
||||
for extension in extensions:
|
||||
if extension.manager_class:
|
||||
setattr(self, extension.name,
|
||||
extension.manager_class(self))
|
||||
|
||||
def client_request(self, method, url, **kwargs):
|
||||
return self.http_client.client_request(
|
||||
self, method, url, **kwargs)
|
||||
|
||||
def head(self, url, **kwargs):
|
||||
return self.client_request("HEAD", url, **kwargs)
|
||||
|
||||
def get(self, url, **kwargs):
|
||||
return self.client_request("GET", url, **kwargs)
|
||||
|
||||
def post(self, url, **kwargs):
|
||||
return self.client_request("POST", url, **kwargs)
|
||||
|
||||
def put(self, url, **kwargs):
|
||||
return self.client_request("PUT", url, **kwargs)
|
||||
|
||||
def delete(self, url, **kwargs):
|
||||
return self.client_request("DELETE", url, **kwargs)
|
||||
|
||||
def patch(self, url, **kwargs):
|
||||
return self.client_request("PATCH", url, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def get_class(api_name, version, version_map):
|
||||
"""Returns the client class for the requested API version
|
||||
|
||||
:param api_name: the name of the API, e.g. 'compute', 'image', etc
|
||||
:param version: the requested API version
|
||||
:param version_map: a dict of client classes keyed by version
|
||||
:rtype: a client class for the requested API version
|
||||
"""
|
||||
try:
|
||||
client_path = version_map[str(version)]
|
||||
except (KeyError, ValueError):
|
||||
msg = _("Invalid %(api_name)s client version '%(version)s'. "
|
||||
"Must be one of: %(version_map)s") % {
|
||||
'api_name': api_name,
|
||||
'version': version,
|
||||
'version_map': ', '.join(version_map.keys())
|
||||
}
|
||||
raise exceptions.UnsupportedVersion(msg)
|
||||
|
||||
return importutils.import_class(client_path)
|
|
@ -77,7 +77,7 @@ class AuthPluginOptionsMissing(AuthorizationFailure):
|
|||
|
||||
|
||||
class AuthSystemNotFound(AuthorizationFailure):
|
||||
"""User has specified a AuthSystem that is not installed."""
|
||||
"""User has specified an AuthSystem that is not installed."""
|
||||
def __init__(self, auth_system):
|
||||
super(AuthSystemNotFound, self).__init__(
|
||||
_("AuthSystemNotFound: %s") % repr(auth_system))
|
||||
|
|
|
@ -0,0 +1,173 @@
|
|||
# Copyright 2013 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.
|
||||
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
# W0102: Dangerous default value %s as argument
|
||||
# pylint: disable=W0102
|
||||
|
||||
import json
|
||||
|
||||
import requests
|
||||
import six
|
||||
from six.moves.urllib import parse
|
||||
|
||||
from muranoclient.openstack.common.apiclient import client
|
||||
|
||||
|
||||
def assert_has_keys(dct, required=[], optional=[]):
|
||||
for k in required:
|
||||
try:
|
||||
assert k in dct
|
||||
except AssertionError:
|
||||
extra_keys = set(dct.keys()).difference(set(required + optional))
|
||||
raise AssertionError("found unexpected keys: %s" %
|
||||
list(extra_keys))
|
||||
|
||||
|
||||
class TestResponse(requests.Response):
|
||||
"""Wrap requests.Response and provide a convenient initialization.
|
||||
"""
|
||||
|
||||
def __init__(self, data):
|
||||
super(TestResponse, self).__init__()
|
||||
self._content_consumed = True
|
||||
if isinstance(data, dict):
|
||||
self.status_code = data.get('status_code', 200)
|
||||
# Fake the text attribute to streamline Response creation
|
||||
text = data.get('text', "")
|
||||
if isinstance(text, (dict, list)):
|
||||
self._content = json.dumps(text)
|
||||
default_headers = {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
else:
|
||||
self._content = text
|
||||
default_headers = {}
|
||||
if six.PY3 and isinstance(self._content, six.string_types):
|
||||
self._content = self._content.encode('utf-8', 'strict')
|
||||
self.headers = data.get('headers') or default_headers
|
||||
else:
|
||||
self.status_code = data
|
||||
|
||||
def __eq__(self, other):
|
||||
return (self.status_code == other.status_code and
|
||||
self.headers == other.headers and
|
||||
self._content == other._content)
|
||||
|
||||
|
||||
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:
|
||||
args = (None, )
|
||||
super(FakeHTTPClient, self).__init__(*args, **kwargs)
|
||||
|
||||
def assert_called(self, method, url, body=None, pos=-1):
|
||||
"""Assert than an API method was just called.
|
||||
"""
|
||||
expected = (method, url)
|
||||
called = self.callstack[pos][0:2]
|
||||
assert self.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.callstack[pos][3] != body:
|
||||
raise AssertionError('%r != %r' %
|
||||
(self.callstack[pos][3], 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.callstack, \
|
||||
"Expected %s %s but no calls were made." % expected
|
||||
|
||||
found = False
|
||||
entry = None
|
||||
for entry in self.callstack:
|
||||
if expected == entry[0:2]:
|
||||
found = True
|
||||
break
|
||||
|
||||
assert found, 'Expected %s %s; got %s' % \
|
||||
(method, url, self.callstack)
|
||||
if body is not None:
|
||||
assert entry[3] == body, "%s != %s" % (entry[3], body)
|
||||
|
||||
self.callstack = []
|
||||
|
||||
def clear_callstack(self):
|
||||
self.callstack = []
|
||||
|
||||
def authenticate(self):
|
||||
pass
|
||||
|
||||
def client_request(self, client, method, url, **kwargs):
|
||||
# Check that certain things are called correctly
|
||||
if method in ["GET", "DELETE"]:
|
||||
assert "json" not in kwargs
|
||||
|
||||
# Note the call
|
||||
self.callstack.append(
|
||||
(method,
|
||||
url,
|
||||
kwargs.get("headers") or {},
|
||||
kwargs.get("json") or kwargs.get("data")))
|
||||
try:
|
||||
fixture = self.fixtures[url][method]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
return TestResponse({"headers": fixture[0],
|
||||
"text": fixture[1]})
|
||||
|
||||
# Call the method
|
||||
args = parse.parse_qsl(parse.urlparse(url)[4])
|
||||
kwargs.update(args)
|
||||
munged_url = url.rsplit('?', 1)[0]
|
||||
munged_url = munged_url.strip('/').replace('/', '_').replace('.', '_')
|
||||
munged_url = munged_url.replace('-', '_')
|
||||
|
||||
callback = "%s_%s" % (method.lower(), munged_url)
|
||||
|
||||
if not hasattr(self, callback):
|
||||
raise AssertionError('Called unknown API method: %s %s, '
|
||||
'expected fakes method name: %s' %
|
||||
(method, url, callback))
|
||||
|
||||
resp = getattr(self, callback)(**kwargs)
|
||||
if len(resp) == 3:
|
||||
status, headers, body = resp
|
||||
else:
|
||||
status, body = resp
|
||||
headers = {}
|
||||
return TestResponse({
|
||||
"status_code": status,
|
||||
"text": body,
|
||||
"headers": headers,
|
||||
})
|
|
@ -0,0 +1,317 @@
|
|||
# Copyright 2012 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.
|
||||
|
||||
# W0603: Using the global statement
|
||||
# W0621: Redefining name %s from outer scope
|
||||
# pylint: disable=W0603,W0621
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import getpass
|
||||
import inspect
|
||||
import os
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
import prettytable
|
||||
import six
|
||||
from six import moves
|
||||
|
||||
from muranoclient.openstack.common.apiclient import exceptions
|
||||
from muranoclient.openstack.common.gettextutils import _
|
||||
from muranoclient.openstack.common import strutils
|
||||
from muranoclient.openstack.common import uuidutils
|
||||
|
||||
|
||||
def validate_args(fn, *args, **kwargs):
|
||||
"""Check that the supplied args are sufficient for calling a function.
|
||||
|
||||
>>> validate_args(lambda a: None)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
MissingArgs: Missing argument(s): a
|
||||
>>> validate_args(lambda a, b, c, d: None, 0, c=1)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
MissingArgs: Missing argument(s): b, d
|
||||
|
||||
:param fn: the function to check
|
||||
:param arg: the positional arguments supplied
|
||||
:param kwargs: the keyword arguments supplied
|
||||
"""
|
||||
argspec = inspect.getargspec(fn)
|
||||
|
||||
num_defaults = len(argspec.defaults or [])
|
||||
required_args = argspec.args[:len(argspec.args) - num_defaults]
|
||||
|
||||
def isbound(method):
|
||||
return getattr(method, '__self__', None) is not None
|
||||
|
||||
if isbound(fn):
|
||||
required_args.pop(0)
|
||||
|
||||
missing = [arg for arg in required_args if arg not in kwargs]
|
||||
missing = missing[len(args):]
|
||||
if missing:
|
||||
raise exceptions.MissingArgs(missing)
|
||||
|
||||
|
||||
def arg(*args, **kwargs):
|
||||
"""Decorator for CLI args.
|
||||
|
||||
Example:
|
||||
|
||||
>>> @arg("name", help="Name of the new entity")
|
||||
... def entity_create(args):
|
||||
... pass
|
||||
"""
|
||||
def _decorator(func):
|
||||
add_arg(func, *args, **kwargs)
|
||||
return func
|
||||
return _decorator
|
||||
|
||||
|
||||
def env(*args, **kwargs):
|
||||
"""Returns the first environment variable set.
|
||||
|
||||
If all are empty, defaults to '' or keyword arg `default`.
|
||||
"""
|
||||
for arg in args:
|
||||
value = os.environ.get(arg)
|
||||
if value:
|
||||
return value
|
||||
return kwargs.get('default', '')
|
||||
|
||||
|
||||
def add_arg(func, *args, **kwargs):
|
||||
"""Bind CLI arguments to a shell.py `do_foo` function."""
|
||||
|
||||
if not hasattr(func, 'arguments'):
|
||||
func.arguments = []
|
||||
|
||||
# NOTE(sirp): avoid dups that can occur when the module is shared across
|
||||
# tests.
|
||||
if (args, kwargs) not in func.arguments:
|
||||
# Because of the semantics of decorator composition if we just append
|
||||
# to the options list positional options will appear to be backwards.
|
||||
func.arguments.insert(0, (args, kwargs))
|
||||
|
||||
|
||||
def unauthenticated(func):
|
||||
"""Adds 'unauthenticated' attribute to decorated function.
|
||||
|
||||
Usage:
|
||||
|
||||
>>> @unauthenticated
|
||||
... def mymethod(f):
|
||||
... pass
|
||||
"""
|
||||
func.unauthenticated = True
|
||||
return func
|
||||
|
||||
|
||||
def isunauthenticated(func):
|
||||
"""Checks if the function does not require authentication.
|
||||
|
||||
Mark such functions with the `@unauthenticated` decorator.
|
||||
|
||||
:returns: bool
|
||||
"""
|
||||
return getattr(func, 'unauthenticated', False)
|
||||
|
||||
|
||||
def print_list(objs, fields, formatters=None, sortby_index=0,
|
||||
mixed_case_fields=None):
|
||||
"""Print a list or objects as a table, one row per object.
|
||||
|
||||
:param objs: iterable of :class:`Resource`
|
||||
:param fields: attributes that correspond to columns, in order
|
||||
:param formatters: `dict` of callables for field formatting
|
||||
: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')
|
||||
"""
|
||||
formatters = formatters or {}
|
||||
mixed_case_fields = mixed_case_fields or []
|
||||
if sortby_index is None:
|
||||
kwargs = {}
|
||||
else:
|
||||
kwargs = {'sortby': fields[sortby_index]}
|
||||
pt = prettytable.PrettyTable(fields, caching=False)
|
||||
pt.align = 'l'
|
||||
|
||||
for o in objs:
|
||||
row = []
|
||||
for field in fields:
|
||||
if field in formatters:
|
||||
row.append(formatters[field](o))
|
||||
else:
|
||||
if field in mixed_case_fields:
|
||||
field_name = field.replace(' ', '_')
|
||||
else:
|
||||
field_name = field.lower().replace(' ', '_')
|
||||
data = getattr(o, field_name, '')
|
||||
row.append(data)
|
||||
pt.add_row(row)
|
||||
|
||||
print(strutils.safe_encode(pt.get_string(**kwargs)))
|
||||
|
||||
|
||||
def print_dict(dct, dict_property="Property", wrap=0):
|
||||
"""Print a `dict` as a table of two columns.
|
||||
|
||||
:param dct: `dict` to print
|
||||
:param dict_property: name of the first column
|
||||
:param wrap: wrapping for the second column
|
||||
"""
|
||||
pt = prettytable.PrettyTable([dict_property, 'Value'], caching=False)
|
||||
pt.align = 'l'
|
||||
for k, v in six.iteritems(dct):
|
||||
# convert dict to str to check length
|
||||
if isinstance(v, dict):
|
||||
v = six.text_type(v)
|
||||
if wrap > 0:
|
||||
v = textwrap.fill(six.text_type(v), wrap)
|
||||
# if value has a newline, add in multiple rows
|
||||
# e.g. fault with stacktrace
|
||||
if v and isinstance(v, six.string_types) and r'\n' in v:
|
||||
lines = v.strip().split(r'\n')
|
||||
col1 = k
|
||||
for line in lines:
|
||||
pt.add_row([col1, line])
|
||||
col1 = ''
|
||||
else:
|
||||
pt.add_row([k, v])
|
||||
print(strutils.safe_encode(pt.get_string()))
|
||||
|
||||
|
||||
def get_password(max_password_prompts=3):
|
||||
"""Read password from TTY."""
|
||||
verify = strutils.bool_from_string(env("OS_VERIFY_PASSWORD"))
|
||||
pw = None
|
||||
if hasattr(sys.stdin, "isatty") and sys.stdin.isatty():
|
||||
# Check for Ctrl-D
|
||||
try:
|
||||
for __ in moves.range(max_password_prompts):
|
||||
pw1 = getpass.getpass("OS Password: ")
|
||||
if verify:
|
||||
pw2 = getpass.getpass("Please verify: ")
|
||||
else:
|
||||
pw2 = pw1
|
||||
if pw1 == pw2 and pw1:
|
||||
pw = pw1
|
||||
break
|
||||
except EOFError:
|
||||
pass
|
||||
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:
|
||||
|
||||
.. 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 = strutils.safe_encode(name_or_id)
|
||||
else:
|
||||
tmp_id = strutils.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)
|
||||
|
||||
|
||||
def service_type(stype):
|
||||
"""Adds 'service_type' attribute to decorated function.
|
||||
|
||||
Usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@service_type('volume')
|
||||
def mymethod(f):
|
||||
...
|
||||
"""
|
||||
def inner(f):
|
||||
f.service_type = stype
|
||||
return f
|
||||
return inner
|
||||
|
||||
|
||||
def get_service_type(f):
|
||||
"""Retrieves service type from function."""
|
||||
return getattr(f, 'service_type', None)
|
||||
|
||||
|
||||
def pretty_choice_list(l):
|
||||
return ', '.join("'%s'" % i for i in l)
|
||||
|
||||
|
||||
def exit(msg=''):
|
||||
if msg:
|
||||
print (msg, file=sys.stderr)
|
||||
sys.exit(1)
|
|
@ -24,10 +24,10 @@ import traceback
|
|||
def import_class(import_str):
|
||||
"""Returns a class from a string including module and class."""
|
||||
mod_str, _sep, class_str = import_str.rpartition('.')
|
||||
__import__(mod_str)
|
||||
try:
|
||||
__import__(mod_str)
|
||||
return getattr(sys.modules[mod_str], class_str)
|
||||
except (ValueError, AttributeError):
|
||||
except AttributeError:
|
||||
raise ImportError('Class %s cannot be found (%s)' %
|
||||
(class_str,
|
||||
traceback.format_exception(*sys.exc_info())))
|
||||
|
|
|
@ -31,6 +31,7 @@ This module provides a few things:
|
|||
'''
|
||||
|
||||
|
||||
import codecs
|
||||
import datetime
|
||||
import functools
|
||||
import inspect
|
||||
|
@ -52,6 +53,7 @@ import six.moves.xmlrpc_client as xmlrpclib
|
|||
|
||||
from muranoclient.openstack.common import gettextutils
|
||||
from muranoclient.openstack.common import importutils
|
||||
from muranoclient.openstack.common import strutils
|
||||
from muranoclient.openstack.common import timeutils
|
||||
|
||||
netaddr = importutils.try_import("netaddr")
|
||||
|
@ -166,12 +168,12 @@ def dumps(value, default=to_primitive, **kwargs):
|
|||
return json.dumps(value, default=default, **kwargs)
|
||||
|
||||
|
||||
def loads(s):
|
||||
return json.loads(s)
|
||||
def loads(s, encoding='utf-8', **kwargs):
|
||||
return json.loads(strutils.safe_decode(s, encoding), **kwargs)
|
||||
|
||||
|
||||
def load(fp):
|
||||
return json.load(fp)
|
||||
def load(fp, encoding='utf-8', **kwargs):
|
||||
return json.load(codecs.getreader(encoding)(fp), **kwargs)
|
||||
|
||||
|
||||
try:
|
||||
|
|
|
@ -114,7 +114,7 @@ def utcnow():
|
|||
|
||||
|
||||
def iso8601_from_timestamp(timestamp):
|
||||
"""Returns a iso8601 formatted date from timestamp."""
|
||||
"""Returns an iso8601 formatted date from timestamp."""
|
||||
return isotime(datetime.datetime.utcfromtimestamp(timestamp))
|
||||
|
||||
|
||||
|
@ -134,7 +134,7 @@ def set_time_override(override_time=None):
|
|||
|
||||
def advance_time_delta(timedelta):
|
||||
"""Advance overridden time using a datetime.timedelta."""
|
||||
assert(not utcnow.override_time is None)
|
||||
assert utcnow.override_time is not None
|
||||
try:
|
||||
for dt in utcnow.override_time:
|
||||
dt += timedelta
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
# Copyright (c) 2012 Intel Corporation.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
UUID related utilities and helper functions.
|
||||
"""
|
||||
|
||||
import uuid
|
||||
|
||||
|
||||
def generate_uuid():
|
||||
return str(uuid.uuid4())
|
||||
|
||||
|
||||
def is_uuid_like(val):
|
||||
"""Returns validation of a value as a UUID.
|
||||
|
||||
For our purposes, a UUID is a canonical form string:
|
||||
aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
|
||||
|
||||
"""
|
||||
try:
|
||||
return str(uuid.UUID(val)) == val
|
||||
except (TypeError, ValueError, AttributeError):
|
||||
return False
|
|
@ -19,15 +19,14 @@ Command-line interface to the Murano Project.
|
|||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import httplib2
|
||||
import logging
|
||||
import six
|
||||
import sys
|
||||
|
||||
from keystoneclient.v2_0 import client as ksclient
|
||||
from muranoclient import client as apiclient
|
||||
from muranoclient.common import exceptions
|
||||
from muranoclient.common import utils
|
||||
from muranoclient.openstack.common.apiclient import exceptions as exc
|
||||
from muranoclient.openstack.common import strutils
|
||||
|
||||
|
||||
|
@ -69,6 +68,13 @@ class MuranoShell(object):
|
|||
"authorities. This option should be used "
|
||||
"with caution.")
|
||||
|
||||
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('--cert-file',
|
||||
help='Path of certificate file to use in SSL '
|
||||
'connection. This file can optionally be '
|
||||
|
@ -85,9 +91,10 @@ class MuranoShell(object):
|
|||
'this option glance looks for the default '
|
||||
'system CA certificates.')
|
||||
|
||||
parser.add_argument('--timeout',
|
||||
default=600,
|
||||
help='Number of seconds to wait for a response')
|
||||
parser.add_argument('--api-timeout',
|
||||
help='Number of seconds to wait for an '
|
||||
'API response, '
|
||||
'defaults to system socket timeout')
|
||||
|
||||
parser.add_argument('--os-username',
|
||||
default=utils.env('OS_USERNAME'),
|
||||
|
@ -117,6 +124,12 @@ class MuranoShell(object):
|
|||
default=utils.env('OS_AUTH_TOKEN'),
|
||||
help='Defaults to env[OS_AUTH_TOKEN]')
|
||||
|
||||
parser.add_argument('--os-no-client-auth',
|
||||
default=utils.env('OS_NO_CLIENT_AUTH'),
|
||||
action='store_true',
|
||||
help="Do not contact keystone for a token. "
|
||||
"Defaults to env[OS_NO_CLIENT_AUTH].")
|
||||
|
||||
parser.add_argument('--murano-url',
|
||||
default=utils.env('MURANO_URL'),
|
||||
help='Defaults to env[MURANO_URL]')
|
||||
|
@ -135,6 +148,11 @@ class MuranoShell(object):
|
|||
default=utils.env('OS_ENDPOINT_TYPE'),
|
||||
help='Defaults to env[OS_ENDPOINT_TYPE]')
|
||||
|
||||
parser.add_argument('--include-password',
|
||||
default=bool(utils.env('MURANO_INCLUDE_PASSWORD')),
|
||||
action='store_true',
|
||||
help='Send os-username and os-password to murano.')
|
||||
|
||||
return parser
|
||||
|
||||
def get_subcommand_parser(self, version):
|
||||
|
@ -148,6 +166,15 @@ class MuranoShell(object):
|
|||
|
||||
return parser
|
||||
|
||||
def _add_bash_completion_subparser(self, subparsers):
|
||||
subparser = subparsers.add_parser(
|
||||
'bash_completion',
|
||||
add_help=False,
|
||||
formatter_class=HelpFormatter
|
||||
)
|
||||
self.subcommands['bash_completion'] = subparser
|
||||
subparser.set_defaults(func=self.do_bash_completion)
|
||||
|
||||
def _find_actions(self, subparsers, actions_module):
|
||||
for attr in (a for a in dir(actions_module) if a.startswith('do_')):
|
||||
# I prefer to be hypen-separated instead of underscores.
|
||||
|
@ -177,12 +204,23 @@ class MuranoShell(object):
|
|||
:param tenant_name: name of tenant
|
||||
:param auth_url: endpoint to authenticate against
|
||||
"""
|
||||
return ksclient.Client(username=kwargs.get('username'),
|
||||
password=kwargs.get('password'),
|
||||
tenant_id=kwargs.get('tenant_id'),
|
||||
tenant_name=kwargs.get('tenant_name'),
|
||||
auth_url=kwargs.get('auth_url'),
|
||||
insecure=kwargs.get('insecure'))
|
||||
kc_args = {
|
||||
'auth_url': kwargs.get('auth_url'),
|
||||
'insecure': kwargs.get('insecure'),
|
||||
'cacert': kwargs.get('cacert')}
|
||||
|
||||
if kwargs.get('tenant_id'):
|
||||
kc_args['tenant_id'] = kwargs.get('tenant_id')
|
||||
else:
|
||||
kc_args['tenant_name'] = kwargs.get('tenant_name')
|
||||
|
||||
if kwargs.get('token'):
|
||||
kc_args['token'] = kwargs.get('token')
|
||||
else:
|
||||
kc_args['username'] = kwargs.get('username')
|
||||
kc_args['password'] = kwargs.get('password')
|
||||
|
||||
return ksclient.Client(**kc_args)
|
||||
|
||||
def _get_endpoint(self, client, **kwargs):
|
||||
"""Get an endpoint using the provided keystone client."""
|
||||
|
@ -190,23 +228,22 @@ class MuranoShell(object):
|
|||
service_type=kwargs.get('service_type') or 'application_catalog',
|
||||
endpoint_type=kwargs.get('endpoint_type') or 'publicURL')
|
||||
|
||||
def _setup_debugging(self, debug):
|
||||
if debug:
|
||||
logging.basicConfig(
|
||||
format="%(levelname)s (%(module)s:%(lineno)d) %(message)s",
|
||||
level=logging.DEBUG)
|
||||
def _setup_logging(self, debug):
|
||||
log_lvl = logging.DEBUG if debug else logging.WARNING
|
||||
logging.basicConfig(
|
||||
format="%(levelname)s (%(module)s:%(lineno)d) %(message)s",
|
||||
level=log_lvl)
|
||||
|
||||
httplib2.debuglevel = 1
|
||||
else:
|
||||
logging.basicConfig(
|
||||
format="%(levelname)s %(message)s",
|
||||
level=logging.WARNING)
|
||||
def _setup_verbose(self, verbose):
|
||||
if verbose:
|
||||
exc.verbose = 1
|
||||
|
||||
def main(self, argv):
|
||||
# Parse args once to find version
|
||||
parser = self.get_base_parser()
|
||||
(options, args) = parser.parse_known_args(argv)
|
||||
self._setup_debugging(options.debug)
|
||||
self._setup_logging(options.debug)
|
||||
self._setup_verbose(options.verbose)
|
||||
|
||||
# build available subcommands based on version
|
||||
api_version = options.murano_api_version
|
||||
|
@ -214,74 +251,112 @@ class MuranoShell(object):
|
|||
self.parser = subcommand_parser
|
||||
|
||||
# Handle top-level --help/-h before attempting to parse
|
||||
# a command off the command line
|
||||
if options.help or not argv:
|
||||
# a command off the command line.
|
||||
if (not args and options.help) or not argv:
|
||||
self.do_help(options)
|
||||
return 0
|
||||
|
||||
# Parse args again and call whatever callback was selected
|
||||
# Parse args again and call whatever callback was selected.
|
||||
args = subcommand_parser.parse_args(argv)
|
||||
|
||||
# Short-circuit and deal with help command right away.
|
||||
if args.func == self.do_help:
|
||||
self.do_help(args)
|
||||
return 0
|
||||
elif args.func == self.do_bash_completion:
|
||||
self.do_bash_completion(args)
|
||||
return 0
|
||||
|
||||
if args.os_auth_token and args.murano_url:
|
||||
token = args.os_auth_token
|
||||
endpoint = args.murano_url
|
||||
if not args.os_username and not args.os_auth_token:
|
||||
raise exc.CommandError("You must provide a username via"
|
||||
" either --os-username or env[OS_USERNAME]"
|
||||
" or a token via --os-auth-token or"
|
||||
" env[OS_AUTH_TOKEN]")
|
||||
|
||||
if not args.os_password and not args.os_auth_token:
|
||||
raise exc.CommandError("You must provide a password via"
|
||||
" either --os-password or env[OS_PASSWORD]"
|
||||
" or a token via --os-auth-token or"
|
||||
" env[OS_AUTH_TOKEN]")
|
||||
|
||||
if args.os_no_client_auth:
|
||||
if not args.murano_url:
|
||||
raise exc.CommandError(
|
||||
"If you specify --os-no-client-auth"
|
||||
" you must also specify a Murano API URL"
|
||||
" via either --murano-url or env[MURANO_URL]")
|
||||
else:
|
||||
if not args.os_username:
|
||||
raise exceptions.CommandError("You must provide a username "
|
||||
"via either --os-username "
|
||||
"or via env[OS_USERNAME]")
|
||||
|
||||
if not args.os_password:
|
||||
raise exceptions.CommandError("You must provide a password "
|
||||
"via either --os-password "
|
||||
"or via env[OS_PASSWORD]")
|
||||
|
||||
# Tenant name or ID is needed to make keystoneclient retrieve a
|
||||
# service catalog, it's not required if os_no_client_auth is
|
||||
# specified, neither is the auth URL.
|
||||
if not (args.os_tenant_id or args.os_tenant_name):
|
||||
raise exceptions.CommandError("You must provide a tenant_id "
|
||||
"via either --os-tenant-id "
|
||||
"or via env[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 args.os_auth_url:
|
||||
raise exceptions.CommandError("You must provide an auth url "
|
||||
"via either --os-auth-url or "
|
||||
"via env[OS_AUTH_URL]")
|
||||
kwargs = {
|
||||
'username': args.os_username,
|
||||
'password': args.os_password,
|
||||
'tenant_id': args.os_tenant_id,
|
||||
'tenant_name': args.os_tenant_name,
|
||||
'auth_url': args.os_auth_url,
|
||||
'service_type': args.os_service_type,
|
||||
'endpoint_type': args.os_endpoint_type,
|
||||
'insecure': args.insecure
|
||||
}
|
||||
raise exc.CommandError("You must provide an auth url via"
|
||||
" either --os-auth-url or via"
|
||||
" env[OS_AUTH_URL]")
|
||||
|
||||
kwargs = {
|
||||
'username': args.os_username,
|
||||
'password': args.os_password,
|
||||
'token': args.os_auth_token,
|
||||
'tenant_id': args.os_tenant_id,
|
||||
'tenant_name': args.os_tenant_name,
|
||||
'auth_url': args.os_auth_url,
|
||||
'service_type': args.os_service_type,
|
||||
'endpoint_type': args.os_endpoint_type,
|
||||
'insecure': args.insecure,
|
||||
'cacert': args.os_cacert,
|
||||
'include_pass': args.include_password
|
||||
}
|
||||
|
||||
endpoint = args.murano_url
|
||||
|
||||
if not args.os_no_client_auth:
|
||||
_ksclient = self._get_ksclient(**kwargs)
|
||||
token = args.os_auth_token or _ksclient.auth_token
|
||||
|
||||
url = args.murano_url
|
||||
endpoint = url or self._get_endpoint(_ksclient, **kwargs)
|
||||
kwargs = {
|
||||
'token': token,
|
||||
'insecure': args.insecure,
|
||||
'ca_file': args.ca_file,
|
||||
'cert_file': args.cert_file,
|
||||
'key_file': args.key_file,
|
||||
'username': args.os_username,
|
||||
'password': args.os_password,
|
||||
'endpoint_type': args.os_endpoint_type,
|
||||
'include_pass': args.include_password
|
||||
}
|
||||
|
||||
kwargs = {
|
||||
'token': token,
|
||||
'insecure': args.insecure,
|
||||
'timeout': args.timeout,
|
||||
'ca_file': args.ca_file,
|
||||
'cert_file': args.cert_file,
|
||||
'key_file': args.key_file,
|
||||
}
|
||||
if args.os_region_name:
|
||||
kwargs['region_name'] = args.os_region_name
|
||||
|
||||
if not endpoint:
|
||||
endpoint = self._get_endpoint(_ksclient, **kwargs)
|
||||
|
||||
if args.api_timeout:
|
||||
kwargs['timeout'] = args.api_timeout
|
||||
|
||||
client = apiclient.Client(api_version, endpoint, **kwargs)
|
||||
|
||||
try:
|
||||
args.func(client, args)
|
||||
except exceptions.Unauthorized:
|
||||
msg = "Invalid OpenStack Identity credentials."
|
||||
raise exceptions.CommandError(msg)
|
||||
args.func(client, args)
|
||||
|
||||
def do_bash_completion(self, args):
|
||||
"""Prints all of the commands and options to stdout."""
|
||||
commands = set()
|
||||
options = set()
|
||||
for sc_str, sc in self.subcommands.items():
|
||||
commands.add(sc_str)
|
||||
for option in list(sc._optionals._option_string_actions):
|
||||
options.add(option)
|
||||
|
||||
commands.remove('bash-completion')
|
||||
commands.remove('bash_completion')
|
||||
print(' '.join(commands | options))
|
||||
|
||||
@utils.arg('command', metavar='<subcommand>', nargs='?',
|
||||
help='Display help for <subcommand>')
|
||||
|
@ -293,7 +368,7 @@ class MuranoShell(object):
|
|||
self.subcommands[args.command].print_help()
|
||||
else:
|
||||
msg = "'%s' is not a valid subcommand"
|
||||
raise exceptions.CommandError(msg % args.command)
|
||||
raise exc.CommandError(msg % args.command)
|
||||
else:
|
||||
self.parser.print_help()
|
||||
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
[DEFAULT]
|
||||
|
||||
# The list of modules to copy from openstack-common
|
||||
modules=importutils,strutils,gettextutils,apiclient.base,apiclient.exceptions,jsonutils
|
||||
module=apiclient.exceptions
|
||||
module=importutils
|
||||
module=strutils
|
||||
module=jsonutils
|
||||
module=cliutils
|
||||
module=apiclient
|
||||
|
||||
# The base module to hold the copy of openstack.common
|
||||
base=muranoclient
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
hacking>=0.8.0,<0.9
|
||||
|
||||
coverage>=3.6
|
||||
discover
|
||||
fixtures>=0.3.14
|
||||
mock>=1.0
|
||||
anyjson>=0.3.3
|
||||
mox>=0.5.3
|
||||
nose
|
||||
nose-exclude
|
||||
nosexcover
|
||||
openstack.nose_plugin>=0.7
|
||||
nosehtmloutput>=0.0.3
|
||||
sphinx>=1.1.2,<1.2
|
||||
testrepository>=0.0.18
|
||||
testscenarios>=0.4
|
||||
testtools>=0.9.34
|
||||
httpretty>=0.6.3
|
||||
|
||||
# doc build requirements
|
||||
sphinx>=1.1.2,<1.2
|
||||
|
|
|
@ -1,513 +0,0 @@
|
|||
# Copyright (c) 2013 Mirantis, 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.
|
||||
|
||||
import logging
|
||||
|
||||
import httpretty as http
|
||||
import testtools
|
||||
|
||||
from muranoclient import client
|
||||
|
||||
|
||||
LOG = logging.getLogger('Unit tests')
|
||||
|
||||
|
||||
class UnitTestsForClassesAndFunctions(testtools.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(UnitTestsForClassesAndFunctions, cls).setUpClass()
|
||||
|
||||
raise cls.skipException()
|
||||
|
||||
@http.httprettified
|
||||
def test_client_env_list_with_empty_list(self):
|
||||
http.HTTPretty.register_uri(
|
||||
http.HTTPretty.GET,
|
||||
"http://no-resolved-host:8001/environments",
|
||||
body='{"environments": []}',
|
||||
adding_headers={'Content-Type': 'application/json', })
|
||||
endpoint = 'http://no-resolved-host:8001'
|
||||
test_client = client.Client('1', endpoint=endpoint,
|
||||
token='1', timeout=10)
|
||||
|
||||
result = test_client.environments.list()
|
||||
|
||||
self.assertEqual([], result)
|
||||
|
||||
@http.httprettified
|
||||
def test_client_env_list_with_elements(self):
|
||||
body = ('{"environments":['
|
||||
'{"id": "0ce373a477f211e187a55404a662f968",'
|
||||
'"name": "dc1",'
|
||||
'"created": "2010-11-30T03:23:42Z",'
|
||||
'"updated": "2010-11-30T03:23:44Z",'
|
||||
'"tenant-id": "0849006f7ce94961b3aab4e46d6f229a"},'
|
||||
'{"id": "0ce373a477f211e187a55404a662f961",'
|
||||
'"name": "dc2",'
|
||||
'"created": "2010-11-30T03:23:42Z",'
|
||||
'"updated": "2010-11-30T03:23:44Z",'
|
||||
'"tenant-id": "0849006f7ce94961b3aab4e4626f229a"}'
|
||||
']}')
|
||||
http.HTTPretty.register_uri(
|
||||
http.HTTPretty.GET,
|
||||
"http://no-resolved-host:8001/environments",
|
||||
body=body,
|
||||
adding_headers={'Content-Type': 'application/json', })
|
||||
endpoint = 'http://no-resolved-host:8001'
|
||||
test_client = client.Client('1', endpoint=endpoint,
|
||||
token='1', timeout=10)
|
||||
|
||||
result = test_client.environments.list()
|
||||
|
||||
self.assertEqual('dc1', result[0].name)
|
||||
self.assertEqual('dc2', result[-1].name)
|
||||
|
||||
@http.httprettified
|
||||
def test_client_env_create(self):
|
||||
body = ('{"id": "0ce373a477f211e187a55404a662f968",'
|
||||
'"name": "test",'
|
||||
'"created": "2010-11-30T03:23:42Z",'
|
||||
'"updated": "2010-11-30T03:23:44Z",'
|
||||
'"tenant-id": "0849006f7ce94961b3aab4e46d6f229a"}')
|
||||
http.HTTPretty.register_uri(
|
||||
http.HTTPretty.POST,
|
||||
"http://no-resolved-host:8001/environments",
|
||||
body=body,
|
||||
adding_headers={'Content-Type': 'application/json', })
|
||||
endpoint = 'http://no-resolved-host:8001'
|
||||
test_client = client.Client('1', endpoint=endpoint,
|
||||
token='1', timeout=10)
|
||||
|
||||
result = test_client.environments.create('test')
|
||||
|
||||
self.assertEqual('test', result.name)
|
||||
|
||||
@http.httprettified
|
||||
def test_client_ad_list(self):
|
||||
body = ('{"activeDirectories": [{'
|
||||
'"id": "1",'
|
||||
'"name": "dc1",'
|
||||
'"created": "2010-11-30T03:23:42Z",'
|
||||
'"updated": "2010-11-30T03:23:44Z",'
|
||||
'"configuration": "standalone",'
|
||||
'"units": [{'
|
||||
'"id": "0ce373a477f211e187a55404a662f961",'
|
||||
'"type": "master",'
|
||||
'"location": "test"}]}]}')
|
||||
url = ("http://no-resolved-host:8001/environments"
|
||||
"/1/activeDirectories")
|
||||
http.HTTPretty.register_uri(
|
||||
http.HTTPretty.GET, url,
|
||||
body=body,
|
||||
adding_headers={'Content-Type': 'application/json', })
|
||||
endpoint = 'http://no-resolved-host:8001'
|
||||
test_client = client.Client('1', endpoint=endpoint,
|
||||
token='1', timeout=10)
|
||||
|
||||
result = test_client.activeDirectories.list('1', 'test')
|
||||
|
||||
self.assertEqual('dc1', result[0].name)
|
||||
|
||||
@http.httprettified
|
||||
def test_client_ad_create(self):
|
||||
body = ('{'
|
||||
'"id": "1",'
|
||||
'"name": "ad1",'
|
||||
'"created": "2010-11-30T03:23:42Z",'
|
||||
'"updated": "2010-11-30T03:23:44Z",'
|
||||
'"configuration": "standalone",'
|
||||
'"units": [{'
|
||||
'"id": "0ce373a477f211e187a55404a662f961",'
|
||||
'"type": "master",'
|
||||
'"location": "test"}]}')
|
||||
url = ("http://no-resolved-host:8001/environments"
|
||||
"/1/activeDirectories")
|
||||
http.HTTPretty.register_uri(
|
||||
http.HTTPretty.POST, url,
|
||||
body=body,
|
||||
adding_headers={'Content-Type': 'application/json', })
|
||||
endpoint = 'http://no-resolved-host:8001'
|
||||
test_client = client.Client('1', endpoint=endpoint,
|
||||
token='1', timeout=10)
|
||||
|
||||
result = test_client.services.post('1', 'test', 'ad1')
|
||||
|
||||
self.assertEqual('ad1', result.name)
|
||||
|
||||
@http.httprettified
|
||||
def test_client_ad_list_without_elements(self):
|
||||
body = '{"activeDirectories": []}'
|
||||
url = ("http://no-resolved-host:8001/environments"
|
||||
"/1/activeDirectories")
|
||||
http.HTTPretty.register_uri(
|
||||
http.HTTPretty.GET, url,
|
||||
body=body,
|
||||
adding_headers={'Content-Type': 'application/json', })
|
||||
endpoint = 'http://no-resolved-host:8001'
|
||||
test_client = client.Client('1', endpoint=endpoint,
|
||||
token='1', timeout=10)
|
||||
|
||||
result = test_client.activeDirectories.list('1', 'test')
|
||||
|
||||
self.assertEqual([], result)
|
||||
|
||||
@http.httprettified
|
||||
def test_client_iis_list(self):
|
||||
body = ('{"webServers": [{'
|
||||
'"id": "1",'
|
||||
'"name": "iis11",'
|
||||
'"created": "2010-11-30T03:23:42Z",'
|
||||
'"updated": "2010-11-30T03:23:44Z",'
|
||||
'"domain": "acme",'
|
||||
'"units": [{'
|
||||
'"id": "0ce373a477f211e187a55404a662f961",'
|
||||
'"endpoint": {"host": "1.1.1.1"},'
|
||||
'"location": "test"}]}]}')
|
||||
url = ("http://no-resolved-host:8001/environments"
|
||||
"/1/webServers")
|
||||
http.HTTPretty.register_uri(
|
||||
http.HTTPretty.GET, url,
|
||||
body=body,
|
||||
adding_headers={'Content-Type': 'application/json', })
|
||||
endpoint = 'http://no-resolved-host:8001'
|
||||
test_client = client.Client('1', endpoint=endpoint,
|
||||
token='1', timeout=10)
|
||||
|
||||
result = test_client.webServers.list('1', 'test')
|
||||
|
||||
self.assertEqual('iis11', result[0].name)
|
||||
|
||||
@http.httprettified
|
||||
def test_client_iis_create(self):
|
||||
body = ('{'
|
||||
'"id": "1",'
|
||||
'"name": "iis12",'
|
||||
'"created": "2010-11-30T03:23:42Z",'
|
||||
'"updated": "2010-11-30T03:23:44Z",'
|
||||
'"domain": "acme",'
|
||||
'"units": [{'
|
||||
'"id": "0ce373a477f211e187a55404a662f961",'
|
||||
'"endpoint": {"host": "1.1.1.1"},'
|
||||
'"location": "test"}]}')
|
||||
url = ("http://no-resolved-host:8001/environments"
|
||||
"/1/webServers")
|
||||
http.HTTPretty.register_uri(
|
||||
http.HTTPretty.POST, url,
|
||||
body=body,
|
||||
adding_headers={'Content-Type': 'application/json', })
|
||||
endpoint = 'http://no-resolved-host:8001'
|
||||
test_client = client.Client('1', endpoint=endpoint,
|
||||
token='1', timeout=10)
|
||||
|
||||
result = test_client.webServers.create('1', 'test', 'iis12')
|
||||
|
||||
self.assertEqual('iis12', result.name)
|
||||
|
||||
@http.httprettified
|
||||
def test_client_iis_list_without_elements(self):
|
||||
body = '{"webServers": []}'
|
||||
url = ("http://no-resolved-host:8001/environments"
|
||||
"/1/webServers")
|
||||
http.HTTPretty.register_uri(
|
||||
http.HTTPretty.GET, url,
|
||||
body=body,
|
||||
adding_headers={'Content-Type': 'application/json', })
|
||||
endpoint = 'http://no-resolved-host:8001'
|
||||
test_client = client.Client('1', endpoint=endpoint,
|
||||
token='1', timeout=10)
|
||||
|
||||
result = test_client.webServers.list('1', 'test')
|
||||
|
||||
self.assertEqual([], result)
|
||||
|
||||
@http.httprettified
|
||||
def test_client_aspapp_list(self):
|
||||
body = '''
|
||||
{
|
||||
"aspNetApps":
|
||||
[
|
||||
{
|
||||
"id": "88f6ed99ff3645bcb84e1e37ab9ece3d",
|
||||
"name": "frontend",
|
||||
"created": "2010-11-30T03:23:42Z",
|
||||
"updated": "2010-11-30T03:23:44Z",
|
||||
"domain": "ACME",
|
||||
"repository": "https://github.com/Mirantis/murano-mvc-demo.git",
|
||||
"uri": "http://10.0.0.2",
|
||||
"units": [{
|
||||
"id": "59255829f0574297acc1cd3a18ff6fd7",
|
||||
"address": "10.0.0.2",
|
||||
"location": "west-dc"
|
||||
}]
|
||||
}, {
|
||||
"id": "aa49dcaff9914b8abb26855f0799b0e0",
|
||||
"name": "backend",
|
||||
"created": "2010-11-30T03:23:42Z",
|
||||
"updated": "2010-11-30T03:23:44Z",
|
||||
"repository": "https://github.com/Mirantis/murano-mvc-demo.git",
|
||||
"uri": "http://10.0.0.3",
|
||||
"domain": "ACME2" ,
|
||||
"units": [{
|
||||
"id": "274b54f6bbe6493690e7107aa947e112",
|
||||
"address": "10.0.0.3",
|
||||
"location": "west-dc"
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
'''
|
||||
url = 'http://no-resolved-host:8001/environments' \
|
||||
'/1/aspNetApps'
|
||||
http.HTTPretty.register_uri(
|
||||
http.HTTPretty.GET, url,
|
||||
body=body,
|
||||
adding_headers={'Content-Type': 'application/json', })
|
||||
endpoint = 'http://no-resolved-host:8001'
|
||||
test_client = client.Client('1', endpoint=endpoint,
|
||||
token='1', timeout=10)
|
||||
|
||||
result = test_client.Client.aspNetApps.list('1', 'test')
|
||||
|
||||
self.assertEqual('frontend', result[0].name)
|
||||
|
||||
@http.httprettified
|
||||
def test_client_aspapp_create(self):
|
||||
body = '''
|
||||
{
|
||||
"id": "88f6ed99ff3645bcb84e1e37ab9ece3d",
|
||||
"name": "frontend",
|
||||
"created": "2010-11-30T03:23:42Z",
|
||||
"updated": "2010-11-30T03:23:44Z",
|
||||
"domain": "ACME",
|
||||
"repository": "https://github.com/Mirantis/murano-mvc-demo.git",
|
||||
"uri": "http://10.0.0.2",
|
||||
"units": [{
|
||||
"id": "59255829f0574297acc1cd3a18ff6fd7",
|
||||
"address": "10.0.0.2",
|
||||
"location": "west-dc"
|
||||
}]
|
||||
}
|
||||
'''
|
||||
url = 'http://no-resolved-host:8001/environments' \
|
||||
'/1/aspNetApps'
|
||||
http.HTTPretty.register_uri(
|
||||
http.HTTPretty.POST, url,
|
||||
body=body,
|
||||
adding_headers={'Content-Type': 'application/json', })
|
||||
endpoint = 'http://no-resolved-host:8001'
|
||||
test_client = client.Client('1', endpoint=endpoint,
|
||||
token='1', timeout=10)
|
||||
|
||||
result = test_client.aspNetApps.create('1', 'test', 'test')
|
||||
|
||||
self.assertEqual('frontend', result.name)
|
||||
|
||||
@http.httprettified
|
||||
def test_client_aspapp_list_without_elements(self):
|
||||
body = '{"aspNetApps": []}'
|
||||
url = 'http://no-resolved-host:8001/environments' \
|
||||
'/1/aspNetApps'
|
||||
http.HTTPretty.register_uri(
|
||||
http.HTTPretty.GET, url,
|
||||
body=body,
|
||||
adding_headers={'Content-Type': 'application/json', })
|
||||
endpoint = 'http://no-resolved-host:8001'
|
||||
test_client = client.Client('1', endpoint=endpoint,
|
||||
token='1', timeout=10)
|
||||
|
||||
result = test_client.aspNetApps.list('1', 'test')
|
||||
|
||||
self.assertEqual([], result)
|
||||
|
||||
@http.httprettified
|
||||
def test_client_webfarm_list(self):
|
||||
body = '''
|
||||
{
|
||||
"webServerFarms":
|
||||
[
|
||||
{
|
||||
"id": "01fa4412ab4849acb27394aaf307ca88",
|
||||
"name": "frontend",
|
||||
"created": "2010-11-30T03:23:42Z",
|
||||
"updated": "2010-11-30T03:23:44Z",
|
||||
"domain": "ACME",
|
||||
"loadBalancerPort": 80,
|
||||
"uri": "http://192.168.1.1:80",
|
||||
"units":
|
||||
[
|
||||
{
|
||||
"id": "a34992c8634b482798187d3c0e1c999a",
|
||||
"address": "10.0.0.2",
|
||||
"location": "west-dc"
|
||||
},
|
||||
{
|
||||
"id": "fcd60488bb6f4acf97ccdb8f8fc6bc9a",
|
||||
"address": "10.0.0.3",
|
||||
"location": "west-dc"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
'''
|
||||
url = 'http://no-resolved-host:8001/environments' \
|
||||
'/1/webServerFarms'
|
||||
http.HTTPretty.register_uri(
|
||||
http.HTTPretty.GET, url,
|
||||
body=body,
|
||||
adding_headers={'Content-Type': 'application/json', })
|
||||
endpoint = 'http://no-resolved-host:8001'
|
||||
test_client = client.Client('1', endpoint=endpoint,
|
||||
token='1', timeout=10)
|
||||
|
||||
result = test_client.webServerFarms.list('1', 'test')
|
||||
|
||||
self.assertEqual('frontend', result[0].name)
|
||||
|
||||
@http.httprettified
|
||||
def test_client_webfarm_create(self):
|
||||
body = '''
|
||||
{
|
||||
"name": "frontend",
|
||||
"domain": "ACME",
|
||||
"loadBalancerPort": 80,
|
||||
"uri": "http://192.168.1.1:80",
|
||||
"units":
|
||||
[
|
||||
{"location": "west-dc"},
|
||||
{"location": "west-dc"}
|
||||
]
|
||||
}
|
||||
'''
|
||||
url = 'http://no-resolved-host:8001/environments' \
|
||||
'/1/webServerFarms'
|
||||
http.HTTPretty.register_uri(
|
||||
http.HTTPretty.POST, url,
|
||||
body=body,
|
||||
adding_headers={'Content-Type': 'application/json', })
|
||||
endpoint = 'http://no-resolved-host:8001'
|
||||
test_client = client.Client('1', endpoint=endpoint,
|
||||
token='1', timeout=10)
|
||||
|
||||
result = test_client.webServerFarms.create('1', 'test', 'test')
|
||||
|
||||
self.assertEqual('frontend', result.name)
|
||||
|
||||
@http.httprettified
|
||||
def test_client_webfarm_list_without_elements(self):
|
||||
body = '{"webServerFarms": []}'
|
||||
url = 'http://no-resolved-host:8001/environments' \
|
||||
'/1/webServerFarms'
|
||||
http.HTTPretty.register_uri(
|
||||
http.HTTPretty.GET, url,
|
||||
body=body,
|
||||
adding_headers={'Content-Type': 'application/json', })
|
||||
endpoint = 'http://no-resolved-host:8001'
|
||||
test_client = client.Client('1', endpoint=endpoint,
|
||||
token='1', timeout=10)
|
||||
|
||||
result = test_client.webServerFarms.list('1', 'test')
|
||||
|
||||
self.assertEqual([], result)
|
||||
|
||||
@http.httprettified
|
||||
def test_client_aspappfarm_list(self):
|
||||
body = '''
|
||||
{
|
||||
"aspNetAppFarms":
|
||||
[
|
||||
{
|
||||
"id": "01fa4412ab4849acb27394aaf307ca88",
|
||||
"name": "frontend",
|
||||
"created": "2010-11-30T03:23:42Z",
|
||||
"updated": "2010-11-30T03:23:44Z",
|
||||
"domain": "ACME",
|
||||
"loadBalancerPort": 80,
|
||||
"uri": "http://192.168.1.1:80",
|
||||
"units":
|
||||
[
|
||||
{
|
||||
"id": "3374f4eb850e4b27bf734649d7004cc0",
|
||||
"address": "10.0.0.2",
|
||||
"location": "west-dc"
|
||||
},
|
||||
{
|
||||
"id": "fcd60488bb6f4acf97ccdb8f8fc6bc9a",
|
||||
"address": "10.0.0.3",
|
||||
"location": "west-dc"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
'''
|
||||
url = 'http://no-resolved-host:8001/environments' \
|
||||
'/1/aspNetAppFarms'
|
||||
http.HTTPretty.register_uri(
|
||||
http.HTTPretty.GET, url,
|
||||
body=body,
|
||||
adding_headers={'Content-Type': 'application/json', })
|
||||
endpoint = 'http://no-resolved-host:8001'
|
||||
test_client = client.Client('1', endpoint=endpoint,
|
||||
token='1', timeout=10)
|
||||
|
||||
result = test_client.aspNetAppFarms.list('1', 'test')
|
||||
|
||||
self.assertEqual('frontend', result[0].name)
|
||||
|
||||
@http.httprettified
|
||||
def test_client_aspappfarm_create(self):
|
||||
body = '''
|
||||
{
|
||||
"name": "frontend",
|
||||
"adminPassword": "password",
|
||||
"domain": "acme.dc",
|
||||
"loadBalancerPort": 80,
|
||||
"repository": "https://github.com/Mirantis/murano-mvc-demo.git",
|
||||
"units": [{
|
||||
"location": "west-dc"
|
||||
}]
|
||||
}
|
||||
'''
|
||||
url = 'http://no-resolved-host:8001/environments' \
|
||||
'/1/aspNetAppFarms'
|
||||
http.HTTPretty.register_uri(
|
||||
http.HTTPretty.POST, url,
|
||||
body=body,
|
||||
adding_headers={'Content-Type': 'application/json', })
|
||||
endpoint = 'http://no-resolved-host:8001'
|
||||
test_client = client.Client('1', endpoint=endpoint,
|
||||
token='1', timeout=10)
|
||||
|
||||
result = test_client.aspNetAppFarms.create('1', 'test', 'test')
|
||||
|
||||
self.assertEqual('frontend', result.name)
|
||||
|
||||
@http.httprettified
|
||||
def test_client_aspappfarm_list_without_elements(self):
|
||||
body = '{"aspNetAppFarms": []}'
|
||||
url = 'http://no-resolved-host:8001/environments' \
|
||||
'/1/aspNetAppFarms'
|
||||
http.HTTPretty.register_uri(
|
||||
http.HTTPretty.GET, url,
|
||||
body=body,
|
||||
adding_headers={'Content-Type': 'application/json', })
|
||||
endpoint = 'http://no-resolved-host:8001'
|
||||
test_client = client.Client('1', endpoint=endpoint,
|
||||
token='1', timeout=10)
|
||||
|
||||
result = test_client.aspNetAppFarms.list('1', 'test')
|
||||
|
||||
self.assertEqual([], result)
|
13
tox.ini
13
tox.ini
|
@ -6,15 +6,12 @@ skipsdist = True
|
|||
[testenv]
|
||||
usedevelop = True
|
||||
install_command = pip install -U {opts} {packages}
|
||||
setenv = VIRTUAL_ENV={envdir}
|
||||
NOSE_WITH_OPENSTACK=1
|
||||
NOSE_OPENSTACK_COLOR=1
|
||||
NOSE_OPENSTACK_RED=0.05
|
||||
NOSE_OPENSTACK_YELLOW=0.025
|
||||
NOSE_OPENSTACK_SHOW_ELAPSED=1
|
||||
setenv =
|
||||
VIRTUAL_ENV={envdir}
|
||||
DISCOVER_DIRECTORY=muranoclient/tests
|
||||
deps = -r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
commands = nosetests -w tests
|
||||
commands = python setup.py test --slowest --testr-args="{posargs}"
|
||||
|
||||
[testenv:pep8]
|
||||
commands =
|
||||
|
@ -24,7 +21,7 @@ commands =
|
|||
commands = {posargs}
|
||||
|
||||
[testenv:cover]
|
||||
commands = nosetests --cover-erase --cover-package=muranoclient --with-xcoverage
|
||||
commands = python setup.py test --coverage --testr-args='{posargs}'
|
||||
|
||||
[tox:jenkins]
|
||||
downloadcache = ~/cache/pip
|
||||
|
|
Loading…
Reference in New Issue