Add keystone v3 support to client
All clients should support keystone v3 in kilo. This patch adds support for keystone sessions, keystone endpoint discovery and support for the CLI to provide keystone v3 options. Bumps python-keystoneclient to >=0.11.1 to bring in session support. This patch was previously dependent on a separate one to add session support but that work has been squashed into this and its review (https://review.openstack.org/#/c/131309) will be abandoned. Co-Authored-By: David Hu <david.hu@hp.com> Change-Id: I0f4931f1e6566a7b83280302f884248c8db909c5 Implements: blueprint support-keystone-v3
This commit is contained in:
parent
8efcc05d63
commit
65f51420e5
|
@ -18,6 +18,7 @@ import logging
|
|||
import os
|
||||
import socket
|
||||
|
||||
import keystoneclient.adapter as keystone_adapter
|
||||
import requests
|
||||
import six
|
||||
from six.moves.urllib import parse
|
||||
|
@ -289,3 +290,79 @@ class HTTPClient(object):
|
|||
|
||||
def patch(self, url, **kwargs):
|
||||
return self.client_request("PATCH", url, **kwargs)
|
||||
|
||||
|
||||
class SessionClient(keystone_adapter.LegacyJsonAdapter):
|
||||
|
||||
def request(self, url, method, **kwargs):
|
||||
raise_exc = kwargs.pop('raise_exc', True)
|
||||
resp, body = super(SessionClient, self).request(url,
|
||||
method,
|
||||
raise_exc=False,
|
||||
**kwargs)
|
||||
|
||||
if raise_exc and resp.status_code >= 400:
|
||||
raise exc.from_response(resp)
|
||||
|
||||
return resp, body
|
||||
|
||||
def json_request(self, method, url, **kwargs):
|
||||
# Legacy adapter expects the payload in 'body', but
|
||||
# will pass it to the non-legacy adapter in the
|
||||
# 'json' spot so encoding happens later
|
||||
if 'data' in kwargs:
|
||||
if 'body' in kwargs:
|
||||
raise ValueError("Can't provide both 'data' and "
|
||||
"'body' to a request")
|
||||
kwargs['body'] = kwargs.pop('data')
|
||||
|
||||
# The argument order is different, beware
|
||||
return self.request(url, method, **kwargs)
|
||||
|
||||
def json_patch_request(self, url, method='PATCH', **kwargs):
|
||||
content_type = 'application/murano-packages-json-patch'
|
||||
return self.json_request(
|
||||
method, url, content_type=content_type, **kwargs)
|
||||
|
||||
def raw_request(self, method, url, **kwargs):
|
||||
# A non-json request; instead of calling
|
||||
# super.request, need to call the grandparent
|
||||
# adapter.request
|
||||
raise_exc = kwargs.pop('raise_exc', True)
|
||||
if 'body' in kwargs:
|
||||
if 'data' in kwargs:
|
||||
raise ValueError("Can't provide both 'data' and "
|
||||
"'body' to a request")
|
||||
kwargs['data'] = kwargs.pop('body')
|
||||
resp = keystone_adapter.Adapter.request(self,
|
||||
url,
|
||||
method,
|
||||
raise_exc=False,
|
||||
**kwargs)
|
||||
body = resp.text
|
||||
|
||||
if raise_exc and resp.status_code >= 400:
|
||||
raise exc.from_response(resp)
|
||||
|
||||
return resp, body
|
||||
|
||||
|
||||
def _construct_http_client(*args, **kwargs):
|
||||
session = kwargs.pop('session', None)
|
||||
auth = kwargs.pop('auth', None)
|
||||
|
||||
if session:
|
||||
service_type = kwargs.pop('service_type', None)
|
||||
endpoint_type = kwargs.pop('endpoint_type', None)
|
||||
region_name = kwargs.pop('region_name', None)
|
||||
service_name = kwargs.pop('service_name', None)
|
||||
return SessionClient(session=session,
|
||||
auth=auth,
|
||||
interface=endpoint_type,
|
||||
service_type=service_type,
|
||||
region_name=region_name,
|
||||
service_name=service_name,
|
||||
user_agent='python-muranoclient',
|
||||
**kwargs)
|
||||
else:
|
||||
return HTTPClient(*args, **kwargs)
|
||||
|
|
|
@ -22,10 +22,13 @@ import argparse
|
|||
import logging
|
||||
import sys
|
||||
|
||||
from keystoneclient.v2_0 import client as ksclient
|
||||
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
|
||||
import six
|
||||
|
||||
from muranoclient import client as apiclient
|
||||
from muranoclient import client as murano_client
|
||||
from muranoclient.common import utils
|
||||
from muranoclient.openstack.common.apiclient import exceptions as exc
|
||||
from muranoclient.openstack.common import strutils
|
||||
|
@ -35,7 +38,15 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
class MuranoShell(object):
|
||||
|
||||
def _append_global_identity_args(self, parser):
|
||||
# Register the CLI arguments that have moved to the session object.
|
||||
ksession.Session.register_cli_options(parser)
|
||||
|
||||
identity.Password.register_argparse_arguments(parser)
|
||||
|
||||
def get_base_parser(self):
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
prog='murano',
|
||||
description=__doc__.strip(),
|
||||
|
@ -59,52 +70,25 @@ class MuranoShell(object):
|
|||
default=False, action="store_true",
|
||||
help="Print more verbose output")
|
||||
|
||||
parser.add_argument('-k', '--insecure',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help="Explicitly allow muranoclient 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('--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]')
|
||||
|
||||
# os-cert, os-key, insecure, ca-file are all added
|
||||
# by keystone session register_cli_opts later
|
||||
parser.add_argument('--cert-file',
|
||||
help='Path of certificate file to use in SSL '
|
||||
'connection. This file can optionally be '
|
||||
'prepended with the private key.')
|
||||
dest='os_cert',
|
||||
help='DEPRECATED! Use --os-cert.')
|
||||
|
||||
parser.add_argument('--key-file',
|
||||
help='Path of client key to use in SSL connection.'
|
||||
' This option is not necessary if your '
|
||||
'key is prepended to your cert file.')
|
||||
dest='os_key',
|
||||
help='DEPRECATED! Use --os-key.')
|
||||
|
||||
parser.add_argument('--ca-file',
|
||||
help='Path of CA SSL certificate(s) used to verify'
|
||||
' the remote server certificate. Without '
|
||||
'this option glance looks for the default '
|
||||
'system CA certificates.')
|
||||
dest='os_cacert',
|
||||
help='DEPRECATED! Use --os-cacert.')
|
||||
|
||||
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'),
|
||||
help='Defaults to env[OS_USERNAME]')
|
||||
|
||||
parser.add_argument('--os-password',
|
||||
default=utils.env('OS_PASSWORD'),
|
||||
help='Defaults to env[OS_PASSWORD]')
|
||||
|
||||
parser.add_argument('--os-tenant-id',
|
||||
default=utils.env('OS_TENANT_ID'),
|
||||
help='Defaults to env[OS_TENANT_ID]')
|
||||
|
@ -113,10 +97,6 @@ class MuranoShell(object):
|
|||
default=utils.env('OS_TENANT_NAME'),
|
||||
help='Defaults to env[OS_TENANT_NAME]')
|
||||
|
||||
parser.add_argument('--os-auth-url',
|
||||
default=utils.env('OS_AUTH_URL'),
|
||||
help='Defaults to env[OS_AUTH_URL]')
|
||||
|
||||
parser.add_argument('--os-region-name',
|
||||
default=utils.env('OS_REGION_NAME'),
|
||||
help='Defaults to env[OS_REGION_NAME]')
|
||||
|
@ -130,7 +110,6 @@ class MuranoShell(object):
|
|||
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]')
|
||||
|
@ -154,6 +133,8 @@ class MuranoShell(object):
|
|||
action='store_true',
|
||||
help='Send os-username and os-password to murano.')
|
||||
|
||||
self._append_global_identity_args(parser)
|
||||
|
||||
return parser
|
||||
|
||||
def get_subcommand_parser(self, version):
|
||||
|
@ -196,38 +177,20 @@ class MuranoShell(object):
|
|||
subparser.add_argument(*args, **kwargs)
|
||||
subparser.set_defaults(func=callback)
|
||||
|
||||
def _get_ksclient(self, **kwargs):
|
||||
"""Get an endpoint and auth token from Keystone.
|
||||
|
||||
:param username: name of user
|
||||
:param password: user's password
|
||||
:param tenant_id: unique identifier of tenant
|
||||
:param tenant_name: name of tenant
|
||||
:param auth_url: endpoint to authenticate against
|
||||
"""
|
||||
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')
|
||||
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:
|
||||
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."""
|
||||
return client.service_catalog.url_for(
|
||||
service_type=kwargs.get('service_type') or 'application_catalog',
|
||||
endpoint_type=kwargs.get('endpoint_type') or 'publicURL')
|
||||
user_domain_id = kwargs.pop('user_domain_id')
|
||||
user_domain_name = kwargs.pop('user_domain_name')
|
||||
return password.Password(auth_url,
|
||||
username=kwargs.pop('username'),
|
||||
user_id=kwargs.pop('user_id'),
|
||||
password=kwargs.pop('password'),
|
||||
user_domain_id=user_domain_id,
|
||||
user_domain_name=user_domain_name,
|
||||
**kwargs)
|
||||
|
||||
def _setup_logging(self, debug):
|
||||
log_lvl = logging.DEBUG if debug else logging.WARNING
|
||||
|
@ -251,6 +214,9 @@ class MuranoShell(object):
|
|||
subcommand_parser = self.get_subcommand_parser(api_version)
|
||||
self.parser = subcommand_parser
|
||||
|
||||
keystone_session = None
|
||||
keystone_auth = None
|
||||
|
||||
# Handle top-level --help/-h before attempting to parse
|
||||
# a command off the command line.
|
||||
if (not args and options.help) or not argv:
|
||||
|
@ -274,11 +240,14 @@ class MuranoShell(object):
|
|||
" 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 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 args.os_no_client_auth:
|
||||
if not args.murano_url:
|
||||
|
@ -291,58 +260,76 @@ class MuranoShell(object):
|
|||
# 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 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]")
|
||||
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] OR a project name "
|
||||
"or project id via --os-project-name, "
|
||||
"--os-project-id, env[OS_PROJECT_ID] or "
|
||||
"env[OS_PROJECT_NAME]")
|
||||
|
||||
if not args.os_auth_url:
|
||||
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
|
||||
|
||||
if args.os_no_client_auth:
|
||||
# Authenticate through murano, don't use session
|
||||
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
|
||||
'auth_token': args.os_auth_token,
|
||||
'auth_url': args.os_auth_url,
|
||||
'token': args.os_auth_token,
|
||||
'insecure': args.insecure,
|
||||
'timeout': args.api_timeout
|
||||
}
|
||||
else:
|
||||
# Create a keystone session and keystone auth
|
||||
keystone_session = ksession.Session.load_from_cli_options(args)
|
||||
project_id = args.os_project_id or args.os_tenant_id
|
||||
project_name = args.os_project_name or args.os_tenant_name
|
||||
|
||||
if args.os_region_name:
|
||||
kwargs['region_name'] = args.os_region_name
|
||||
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)
|
||||
|
||||
endpoint_type = args.os_endpoint_type or 'publicURL'
|
||||
service_type = args.os_service_type or 'application_catalog'
|
||||
|
||||
if not endpoint:
|
||||
endpoint = self._get_endpoint(_ksclient, **kwargs)
|
||||
endpoint = keystone_auth.get_endpoint(
|
||||
keystone_session,
|
||||
service_type=service_type,
|
||||
region_name=args.os_region_name)
|
||||
|
||||
kwargs = {
|
||||
'session': keystone_session,
|
||||
'auth': keystone_auth,
|
||||
'service_type': service_type,
|
||||
'endpoint_type': endpoint_type,
|
||||
'region_name': args.os_region_name,
|
||||
}
|
||||
|
||||
if args.api_timeout:
|
||||
kwargs['timeout'] = args.api_timeout
|
||||
|
||||
client = apiclient.Client(api_version, endpoint, **kwargs)
|
||||
client = murano_client.Client(api_version, endpoint, **kwargs)
|
||||
|
||||
args.func(client, args)
|
||||
|
||||
|
|
|
@ -18,7 +18,11 @@ import sys
|
|||
import tempfile
|
||||
|
||||
import fixtures
|
||||
from keystoneclient import fixture
|
||||
from keystoneclient.fixture import v2 as ks_v2_fixture
|
||||
from keystoneclient.fixture import v3 as ks_v3_fixture
|
||||
import mock
|
||||
import requests_mock
|
||||
import six
|
||||
from testtools import matchers
|
||||
|
||||
|
@ -34,12 +38,22 @@ FIXTURE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__),
|
|||
FAKE_ENV = {'OS_USERNAME': 'username',
|
||||
'OS_PASSWORD': 'password',
|
||||
'OS_TENANT_NAME': 'tenant_name',
|
||||
'OS_AUTH_URL': 'http://no.where'}
|
||||
'OS_AUTH_URL': 'http://no.where/v2.0'}
|
||||
|
||||
FAKE_ENV2 = {'OS_USERNAME': 'username',
|
||||
'OS_PASSWORD': 'password',
|
||||
'OS_TENANT_ID': 'tenant_id',
|
||||
'OS_AUTH_URL': 'http://no.where'}
|
||||
'OS_AUTH_URL': 'http://no.where/v2.0'}
|
||||
|
||||
FAKE_ENV_v3 = {'OS_USERNAME': 'username',
|
||||
'OS_PASSWORD': 'password',
|
||||
'OS_TENANT_ID': 'tenant_id',
|
||||
'OS_USER_DOMAIN_NAME': 'domain_name',
|
||||
'OS_AUTH_URL': 'http://no.where/3'}
|
||||
|
||||
|
||||
def _create_ver_list(versions):
|
||||
return {'versions': {'values': versions}}
|
||||
|
||||
|
||||
class TestArgs(object):
|
||||
|
@ -52,18 +66,24 @@ class ShellTest(base.TestCaseShell):
|
|||
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(
|
||||
'keystoneclient.v2_0.client.Client', mock.MagicMock))
|
||||
self.client = mock.MagicMock()
|
||||
|
||||
# We don't set an endpoint (client.service_catalog.url_for is a mock)
|
||||
# and get_proxy_url doesn't like that. We don't care about testing
|
||||
# that functionality, so mock it out.
|
||||
class ShellCommandTest(ShellTest):
|
||||
|
||||
_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 setUp(self):
|
||||
super(ShellCommandTest, self).setUp()
|
||||
|
||||
def get_auth_endpoint(bound_self, args):
|
||||
return ('test', {})
|
||||
self.useFixture(fixtures.MonkeyPatch(
|
||||
'muranoclient.common.http.HTTPClient.get_proxy_url',
|
||||
mock.MagicMock))
|
||||
'muranoclient.shell.MuranoShell._get_endpoint_and_kwargs',
|
||||
get_auth_endpoint))
|
||||
self.client = mock.MagicMock()
|
||||
|
||||
def shell(self, argstr, exitcodes=(0,)):
|
||||
orig = sys.stdout
|
||||
|
@ -85,6 +105,21 @@ class ShellTest(base.TestCaseShell):
|
|||
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 register_keystone_token_fixture(self, mreq):
|
||||
v2_token = ks_v2_fixture.Token(token_id='token')
|
||||
service = v2_token.add_service('application_catalog')
|
||||
service.add_endpoint('http://no.where', region='RegionOne')
|
||||
mreq.register_uri('POST',
|
||||
'http://no.where/v2.0/tokens',
|
||||
json=v2_token,
|
||||
status_code=200)
|
||||
|
||||
def test_help_unknown_command(self):
|
||||
self.assertRaises(exceptions.CommandError, self.shell, 'help foofoo')
|
||||
|
||||
|
@ -133,10 +168,7 @@ class ShellTest(base.TestCaseShell):
|
|||
self.fail('CommandError not raised')
|
||||
|
||||
def test_no_tenant_name(self):
|
||||
required = ('You must provide a tenant name '
|
||||
'or tenant id via --os-tenant-name, '
|
||||
'--os-tenant-id, env[OS_TENANT_NAME] '
|
||||
'or env[OS_TENANT_ID]',)
|
||||
required = self._msg_no_tenant_project
|
||||
self.make_env(exclude='OS_TENANT_NAME')
|
||||
try:
|
||||
self.shell('package-list')
|
||||
|
@ -146,10 +178,7 @@ class ShellTest(base.TestCaseShell):
|
|||
self.fail('CommandError not raised')
|
||||
|
||||
def test_no_tenant_id(self):
|
||||
required = ('You must provide a tenant name '
|
||||
'or tenant id via --os-tenant-name, '
|
||||
'--os-tenant-id, env[OS_TENANT_NAME] '
|
||||
'or env[OS_TENANT_ID]',)
|
||||
required = self._msg_no_tenant_project
|
||||
self.make_env(exclude='OS_TENANT_ID', fake_env=FAKE_ENV2)
|
||||
try:
|
||||
self.shell('package-list')
|
||||
|
@ -170,15 +199,19 @@ class ShellTest(base.TestCaseShell):
|
|||
self.fail('CommandError not raised')
|
||||
|
||||
@mock.patch('muranoclient.v1.packages.PackageManager')
|
||||
def test_package_list(self, mock_package_manager):
|
||||
@requests_mock.Mocker()
|
||||
def test_package_list(self, mock_package_manager, m_requests):
|
||||
self.client.packages = mock_package_manager()
|
||||
self.make_env()
|
||||
self.register_keystone_discovery_fixture(m_requests)
|
||||
self.register_keystone_token_fixture(m_requests)
|
||||
self.shell('package-list')
|
||||
self.client.packages.filter.assert_called_once_with(
|
||||
include_disabled=False)
|
||||
|
||||
@mock.patch('muranoclient.v1.packages.PackageManager')
|
||||
def test_package_show(self, mock_package_manager):
|
||||
@requests_mock.Mocker()
|
||||
def test_package_show(self, mock_package_manager, m_requests):
|
||||
self.client.packages = mock_package_manager()
|
||||
mock_package = mock.MagicMock()
|
||||
mock_package.class_definitions = ''
|
||||
|
@ -187,57 +220,77 @@ class ShellTest(base.TestCaseShell):
|
|||
mock_package.description = ''
|
||||
self.client.packages.get.return_value = mock_package
|
||||
self.make_env()
|
||||
self.register_keystone_discovery_fixture(m_requests)
|
||||
self.register_keystone_token_fixture(m_requests)
|
||||
self.shell('package-show 1234')
|
||||
self.client.packages.get.assert_called_once_with('1234')
|
||||
|
||||
@mock.patch('muranoclient.v1.packages.PackageManager')
|
||||
def test_package_delete(self, mock_package_manager):
|
||||
@requests_mock.Mocker()
|
||||
def test_package_delete(self, mock_package_manager, m_requests):
|
||||
self.client.packages = mock_package_manager()
|
||||
self.make_env()
|
||||
self.register_keystone_discovery_fixture(m_requests)
|
||||
self.register_keystone_token_fixture(m_requests)
|
||||
self.shell('package-delete 1234')
|
||||
self.client.packages.delete.assert_called_once_with('1234')
|
||||
|
||||
@mock.patch('muranoclient.v1.environments.EnvironmentManager')
|
||||
def test_environment_delete(self, mock_manager):
|
||||
@requests_mock.Mocker()
|
||||
def test_environment_delete(self, mock_manager, m_requests):
|
||||
self.client.environments = mock_manager()
|
||||
self.make_env()
|
||||
self.register_keystone_discovery_fixture(m_requests)
|
||||
self.register_keystone_token_fixture(m_requests)
|
||||
self.shell('environment-delete env1 env2')
|
||||
self.client.environments.delete.assert_has_calls([
|
||||
mock.call('env1'), mock.call('env2')])
|
||||
|
||||
@mock.patch('muranoclient.v1.environments.EnvironmentManager')
|
||||
def test_environment_rename(self, mock_manager):
|
||||
@requests_mock.Mocker()
|
||||
def test_environment_rename(self, mock_manager, m_requests):
|
||||
self.client.environments = mock_manager()
|
||||
self.make_env()
|
||||
self.register_keystone_discovery_fixture(m_requests)
|
||||
self.register_keystone_token_fixture(m_requests)
|
||||
self.shell('environment-rename env-id new-name')
|
||||
self.client.environments.update.assert_called_once_with(
|
||||
'env-id', 'new-name')
|
||||
|
||||
@mock.patch('muranoclient.v1.environments.EnvironmentManager')
|
||||
def test_environment_show(self, mock_manager):
|
||||
@requests_mock.Mocker()
|
||||
def test_environment_show(self, mock_manager, m_requests):
|
||||
self.client.environments = mock_manager()
|
||||
self.make_env()
|
||||
self.register_keystone_discovery_fixture(m_requests)
|
||||
self.register_keystone_token_fixture(m_requests)
|
||||
self.shell('environment-show env-id')
|
||||
self.client.environments.get.assert_called_once_with('env-id')
|
||||
|
||||
@mock.patch('muranoclient.v1.deployments.DeploymentManager')
|
||||
def test_deployments_show(self, mock_manager):
|
||||
@requests_mock.Mocker()
|
||||
def test_deployments_show(self, mock_manager, m_requests):
|
||||
self.client.deployments = mock_manager()
|
||||
self.make_env()
|
||||
self.register_keystone_discovery_fixture(m_requests)
|
||||
self.register_keystone_token_fixture(m_requests)
|
||||
self.shell('deployment-list env-id')
|
||||
self.client.deployments.list.assert_called_once_with('env-id')
|
||||
|
||||
|
||||
class ShellPackagesOperations(ShellTest):
|
||||
class ShellPackagesOperations(ShellCommandTest):
|
||||
def tearDown(self):
|
||||
super(ShellPackagesOperations, self).tearDown()
|
||||
|
||||
def test_create_hot_based_package(self):
|
||||
@requests_mock.Mocker()
|
||||
def test_create_hot_based_package(self, m_requests):
|
||||
self.useFixture(fixtures.MonkeyPatch(
|
||||
'muranoclient.v1.client.Client', mock.MagicMock))
|
||||
heat_template = os.path.join(FIXTURE_DIR, 'heat-template.yaml')
|
||||
logo = os.path.join(FIXTURE_DIR, 'logo.png')
|
||||
self.make_env()
|
||||
self.register_keystone_discovery_fixture(m_requests)
|
||||
self.register_keystone_token_fixture(m_requests)
|
||||
with tempfile.NamedTemporaryFile() as f:
|
||||
RESULT_PACKAGE = f.name
|
||||
c = "package-create --template={0} --output={1} -l={2}".format(
|
||||
|
@ -247,13 +300,16 @@ class ShellPackagesOperations(ShellTest):
|
|||
"Application package "
|
||||
"is available at {0}".format(RESULT_PACKAGE))
|
||||
|
||||
def test_create_mpl_package(self):
|
||||
@requests_mock.Mocker()
|
||||
def test_create_mpl_package(self, m_requests):
|
||||
self.useFixture(fixtures.MonkeyPatch(
|
||||
'muranoclient.v1.client.Client', mock.MagicMock))
|
||||
classes_dir = os.path.join(FIXTURE_DIR, 'test-app', 'Classes')
|
||||
resources_dir = os.path.join(FIXTURE_DIR, 'test-app', 'Resources')
|
||||
ui = os.path.join(FIXTURE_DIR, 'test-app', 'ui.yaml')
|
||||
self.make_env()
|
||||
self.register_keystone_discovery_fixture(m_requests)
|
||||
self.register_keystone_token_fixture(m_requests)
|
||||
with tempfile.NamedTemporaryFile() as f:
|
||||
RESULT_PACKAGE = f.name
|
||||
stdout, stderr = self.shell(
|
||||
|
@ -299,3 +355,27 @@ class ShellPackagesOperations(ShellTest):
|
|||
|
||||
self.assertRaises(IOError,
|
||||
v1_shell.do_package_import, self.client, args)
|
||||
|
||||
|
||||
class ShellPackagesOperationsV3(ShellPackagesOperations):
|
||||
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)
|
||||
|
||||
def register_keystone_token_fixture(self, mreq):
|
||||
v3_token = ks_v3_fixture.Token()
|
||||
service = v3_token.add_service('application_catalog')
|
||||
service.add_standard_endpoints(public='http://no.where')
|
||||
mreq.register_uri('POST',
|
||||
'http://no.where/v3/auth/tokens',
|
||||
json=v3_token,
|
||||
headers={'X-Subject-Token': 'tokenid'},
|
||||
status_code=200)
|
||||
|
|
|
@ -23,7 +23,7 @@ from muranoclient.v1 import services
|
|||
from muranoclient.v1 import sessions
|
||||
|
||||
|
||||
class Client(http.HTTPClient):
|
||||
class Client(object):
|
||||
"""Client for the Murano v1 API.
|
||||
|
||||
:param string endpoint: A user-supplied endpoint URL for the service.
|
||||
|
@ -34,14 +34,14 @@ class Client(http.HTTPClient):
|
|||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize a new client for the Murano v1 API."""
|
||||
super(Client, self).__init__(*args, **kwargs)
|
||||
self.environments = environments.EnvironmentManager(self)
|
||||
self.sessions = sessions.SessionManager(self)
|
||||
self.services = services.ServiceManager(self)
|
||||
self.deployments = deployments.DeploymentManager(self)
|
||||
self.http_client = http._construct_http_client(*args, **kwargs)
|
||||
self.environments = environments.EnvironmentManager(self.http_client)
|
||||
self.sessions = sessions.SessionManager(self.http_client)
|
||||
self.services = services.ServiceManager(self.http_client)
|
||||
self.deployments = deployments.DeploymentManager(self.http_client)
|
||||
self.request_statistics = \
|
||||
request_statistics.RequestStatisticsManager(self)
|
||||
request_statistics.RequestStatisticsManager(self.http_client)
|
||||
self.instance_statistics = \
|
||||
instance_statistics.InstanceStatisticsManager(self)
|
||||
self.packages = packages.PackageManager(self)
|
||||
self.actions = actions.ActionManager(self)
|
||||
instance_statistics.InstanceStatisticsManager(self.http_client)
|
||||
self.packages = packages.PackageManager(self.http_client)
|
||||
self.actions = actions.ActionManager(self.http_client)
|
||||
|
|
|
@ -12,16 +12,13 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
import urllib
|
||||
|
||||
import requests
|
||||
import yaml
|
||||
|
||||
from muranoclient.common import base
|
||||
from muranoclient.common import exceptions
|
||||
from muranoclient.common import http
|
||||
|
||||
from muranoclient.openstack.common import jsonutils
|
||||
|
||||
DEFAULT_PAGE_SIZE = 20
|
||||
|
||||
|
@ -53,15 +50,16 @@ class PackageManager(base.Manager):
|
|||
response_key='categories', obj_class=Category)
|
||||
|
||||
def create(self, data, files):
|
||||
data = {'data': json.dumps(data)}
|
||||
url = '{0}/v1/catalog/packages'.format(self.api.endpoint)
|
||||
headers = {'X-Auth-Token': self.api.auth_token}
|
||||
response = requests.post(url, data=data, files=files, headers=headers)
|
||||
http.HTTPClient.log_http_response(response)
|
||||
response, body = self.api.raw_request(
|
||||
'POST',
|
||||
'/v1/catalog/packages',
|
||||
body=data,
|
||||
files=files)
|
||||
if not response.ok:
|
||||
setattr(response, 'status', response.status_code)
|
||||
raise exceptions.from_response(response)
|
||||
return self.resource_class(self, json.loads(response.content))
|
||||
body = jsonutils.loads(response.text)
|
||||
return self.resource_class(self, body)
|
||||
|
||||
def get(self, app_id):
|
||||
return self._get('/v1/catalog/packages/{0}'.format(app_id))
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
pbr>=0.6,!=0.7,<1.0
|
||||
argparse
|
||||
PrettyTable>=0.7,<0.8
|
||||
python-keystoneclient>=0.10.0
|
||||
python-keystoneclient>=0.11.1
|
||||
httplib2>=0.7.5
|
||||
iso8601>=0.1.9
|
||||
six>=1.7.0
|
||||
|
|
|
@ -4,6 +4,7 @@ coverage>=3.6
|
|||
discover
|
||||
fixtures>=0.3.14
|
||||
mock>=1.0
|
||||
requests-mock>=0.5.1 # Apache-2.0
|
||||
testrepository>=0.0.18
|
||||
testscenarios>=0.4
|
||||
testtools>=0.9.34
|
||||
|
|
Loading…
Reference in New Issue