diff --git a/neutronclient/client.py b/neutronclient/client.py index e41511f1d..8720f1fbf 100644 --- a/neutronclient/client.py +++ b/neutronclient/client.py @@ -22,6 +22,7 @@ import logging import os from keystoneclient import access +from keystoneclient.auth.identity.base import BaseIdentityPlugin import requests from neutronclient.common import exceptions @@ -41,11 +42,25 @@ else: logging.getLogger("requests").setLevel(_requests_log_level) -class HTTPClient(object): - """Handles the REST calls and responses, include authn.""" +class NeutronClientMixin(object): USER_AGENT = 'python-neutronclient' + def get_status_code(self, response): + """Returns the integer status code from the response. + + Either a Webob.Response (used in testing) or requests.Response + is returned. + """ + if hasattr(response, 'status_int'): + return response.status_int + else: + return response.status_code + + +class HTTPClient(NeutronClientMixin): + """Handles the REST calls and responses, include authn.""" + def __init__(self, username=None, user_id=None, tenant_name=None, tenant_id=None, password=None, auth_url=None, @@ -265,13 +280,122 @@ class HTTPClient(object): 'auth_user_id': self.auth_user_id, 'endpoint_url': self.endpoint_url} - def get_status_code(self, response): - """Returns the integer status code from the response. - Either a Webob.Response (used in testing) or requests.Response - is returned. - """ - if hasattr(response, 'status_int'): - return response.status_int - else: - return response.status_code +class SessionClient(NeutronClientMixin): + + def __init__(self, + session, + auth, + interface=None, + service_type=None, + region_name=None): + + self.session = session + self.auth = auth + self.interface = interface + self.service_type = service_type + self.region_name = region_name + self.auth_token = None + self.endpoint_url = None + + def request(self, url, method, **kwargs): + kwargs.setdefault('user_agent', self.USER_AGENT) + kwargs.setdefault('auth', self.auth) + kwargs.setdefault('authenticated', False) + + try: + kwargs['data'] = kwargs.pop('body') + except KeyError: + pass + + endpoint_filter = kwargs.setdefault('endpoint_filter', {}) + endpoint_filter.setdefault('interface', self.interface) + endpoint_filter.setdefault('service_type', self.service_type) + endpoint_filter.setdefault('region_name', self.region_name) + + kwargs = utils.safe_encode_dict(kwargs) + resp = self.session.request(url, method, **kwargs) + return resp, resp.text + + def do_request(self, url, method, **kwargs): + kwargs.setdefault('authenticated', True) + return self.request(url, method, **kwargs) + + def authenticate(self): + # This method is provided for backward compatibility only. + # We only care about setting the service endpoint. + self.endpoint_url = self.session.get_endpoint( + self.auth, + service_type=self.service_type, + region_name=self.region_name, + interface=self.interface) + + def authenticate_and_fetch_endpoint_url(self): + # This method is provided for backward compatibility only. + # We only care about setting the service endpoint. + self.authenticate() + + def get_auth_info(self): + # This method is provided for backward compatibility only. + if not isinstance(self.auth, BaseIdentityPlugin): + msg = ('Auth info not available. Auth plugin is not an identity ' + 'auth plugin.') + raise exceptions.NeutronClientException(message=msg) + access_info = self.auth.get_access(self.session) + endpoint_url = self.auth.get_endpoint(self.session, + service_type=self.service_type, + region_name=self.region_name, + interface=self.interface) + return {'auth_token': access_info.auth_token, + 'auth_tenant_id': access_info.tenant_id, + 'auth_user_id': access_info.user_id, + 'endpoint_url': endpoint_url} + + +# FIXME(bklei): Should refactor this to use kwargs and only +# explicitly list arguments that are not None. +def construct_http_client(username=None, + user_id=None, + tenant_name=None, + tenant_id=None, + password=None, + auth_url=None, + token=None, + region_name=None, + timeout=None, + endpoint_url=None, + insecure=False, + endpoint_type='publicURL', + log_credentials=None, + auth_strategy='keystone', + ca_cert=None, + service_type='network', + session=None, + auth=None): + + if session: + return SessionClient(session=session, + auth=auth, + interface=endpoint_type, + service_type=service_type, + region_name=region_name) + else: + # FIXME(bklei): username and password are now optional. Need + # to test that they were provided in this mode. Should also + # refactor to use kwargs. + return HTTPClient(username=username, + password=password, + tenant_id=tenant_id, + tenant_name=tenant_name, + user_id=user_id, + auth_url=auth_url, + token=token, + endpoint_url=endpoint_url, + insecure=insecure, + timeout=timeout, + region_name=region_name, + endpoint_type=endpoint_type, + service_type=service_type, + ca_cert=ca_cert, + log_credentials=log_credentials, + auth_strategy=auth_strategy) diff --git a/neutronclient/common/clientmanager.py b/neutronclient/common/clientmanager.py index c159e05d3..283bc77bf 100644 --- a/neutronclient/common/clientmanager.py +++ b/neutronclient/common/clientmanager.py @@ -66,7 +66,9 @@ class ClientManager(object): service_type=None, timeout=None, retries=0, - raise_errors=True + raise_errors=True, + session=None, + auth=None, ): self._token = token self._url = url @@ -88,10 +90,13 @@ class ClientManager(object): self._timeout = timeout self._retries = retries self._raise_errors = raise_errors + self._session = session + self._auth = auth + return def initialize(self): if not self._url: - httpclient = client.HTTPClient( + httpclient = client.construct_http_client( username=self._username, user_id=self._user_id, tenant_name=self._tenant_name, @@ -103,8 +108,10 @@ class ClientManager(object): endpoint_type=self._endpoint_type, insecure=self._insecure, ca_cert=self._ca_cert, - log_credentials=self._log_credentials, - timeout=self._timeout) + timeout=self._timeout, + session=self._session, + auth=self._auth, + log_credentials=self._log_credentials) httpclient.authenticate() # Populate other password flow attributes self._token = httpclient.auth_token diff --git a/neutronclient/neutron/client.py b/neutronclient/neutron/client.py index 2dd40dea5..df7ef2996 100644 --- a/neutronclient/neutron/client.py +++ b/neutronclient/neutron/client.py @@ -48,7 +48,9 @@ def make_client(instance): insecure=instance._insecure, ca_cert=instance._ca_cert, retries=instance._retries, - raise_errors=instance._raise_errors) + raise_errors=instance._raise_errors, + session=instance._session, + auth=instance._auth) return client else: raise exceptions.UnsupportedVersion(_("API version %s is not " diff --git a/neutronclient/shell.py b/neutronclient/shell.py index a3e51080b..ce7bed9c9 100644 --- a/neutronclient/shell.py +++ b/neutronclient/shell.py @@ -25,6 +25,13 @@ import logging import os import sys +from keystoneclient.auth.identity import v2 as v2_auth +from keystoneclient.auth.identity import v3 as v3_auth +from keystoneclient import discover +from keystoneclient.openstack.common.apiclient import exceptions as ks_exc +from keystoneclient import session +import six.moves.urllib.parse as urlparse + from cliff import app from cliff import commandmanager @@ -386,13 +393,50 @@ class NeutronShell(app.App): default=0, help=_("How many times the request to the Neutron server should " "be retried if it fails.")) - # Global arguments + # FIXME(bklei): this method should come from python-keystoneclient + self._append_global_identity_args(parser) + + return parser + + def _append_global_identity_args(self, parser): + # FIXME(bklei): these are global identity (Keystone) arguments which + # should be consistent and shared by all service clients. Therefore, + # they should be provided by python-keystoneclient. We will need to + # refactor this code once this functionality is available in + # python-keystoneclient. + # + # Note: At that time we'll need to decide if we can just abandon + # the deprecated args (--service-type and --endpoint-type). + + parser.add_argument( + '--os-service-type', metavar='', + default=env('OS_NETWORK_SERVICE_TYPE', default='network'), + help=_('Defaults to env[OS_NETWORK_SERVICE_TYPE] or network.')) + + parser.add_argument( + '--os-endpoint-type', metavar='', + default=env('OS_ENDPOINT_TYPE', default='publicURL'), + help=_('Defaults to env[OS_ENDPOINT_TYPE] or publicURL.')) + + # FIXME(bklei): --service-type is deprecated but kept in for + # backward compatibility. + parser.add_argument( + '--service-type', metavar='', + default=env('OS_NETWORK_SERVICE_TYPE', default='network'), + help=_('DEPRECATED! Use --os-service-type.')) + + # FIXME(bklei): --endpoint-type is deprecated but kept in for + # backward compatibility. + parser.add_argument( + '--endpoint-type', metavar='', + default=env('OS_ENDPOINT_TYPE', default='publicURL'), + help=_('DEPRECATED! Use --os-endpoint-type.')) + parser.add_argument( '--os-auth-strategy', metavar='', default=env('OS_AUTH_STRATEGY', default='keystone'), - help=_('Authentication strategy, defaults to ' - 'env[OS_AUTH_STRATEGY] or keystone. For now, any ' - 'other value will disable the authentication.')) + help=_('DEPRECATED! Only keystone is supported.')) + parser.add_argument( '--os_auth_strategy', help=argparse.SUPPRESS) @@ -405,20 +449,39 @@ class NeutronShell(app.App): '--os_auth_url', help=argparse.SUPPRESS) - parser.add_argument( + project_name_group = parser.add_mutually_exclusive_group() + project_name_group.add_argument( '--os-tenant-name', metavar='', default=env('OS_TENANT_NAME'), help=_('Authentication tenant name, defaults to ' 'env[OS_TENANT_NAME].')) + project_name_group.add_argument( + '--os-project-name', + metavar='', + default=utils.env('OS_PROJECT_NAME'), + help='Another way to specify tenant name. ' + 'This option is mutually exclusive with ' + ' --os-tenant-name. ' + 'Defaults to env[OS_PROJECT_NAME].') + parser.add_argument( '--os_tenant_name', help=argparse.SUPPRESS) - parser.add_argument( + project_id_group = parser.add_mutually_exclusive_group() + project_id_group.add_argument( '--os-tenant-id', metavar='', default=env('OS_TENANT_ID'), help=_('Authentication tenant ID, defaults to ' 'env[OS_TENANT_ID].')) + project_id_group.add_argument( + '--os-project-id', + metavar='', + default=utils.env('OS_PROJECT_ID'), + help='Another way to specify tenant ID. ' + 'This option is mutually exclusive with ' + ' --os-tenant-id. ' + 'Defaults to env[OS_PROJECT_ID].') parser.add_argument( '--os-username', metavar='', @@ -433,6 +496,78 @@ class NeutronShell(app.App): default=env('OS_USER_ID'), help=_('Authentication user ID (Env: OS_USER_ID)')) + parser.add_argument( + '--os_user_id', + help=argparse.SUPPRESS) + + parser.add_argument( + '--os-user-domain-id', + metavar='', + default=utils.env('OS_USER_DOMAIN_ID'), + help='OpenStack user domain ID. ' + 'Defaults to env[OS_USER_DOMAIN_ID].') + + parser.add_argument( + '--os_user_domain_id', + help=argparse.SUPPRESS) + + parser.add_argument( + '--os-user-domain-name', + metavar='', + default=utils.env('OS_USER_DOMAIN_NAME'), + help='OpenStack user domain name. ' + 'Defaults to env[OS_USER_DOMAIN_NAME].') + + parser.add_argument( + '--os_user_domain_name', + help=argparse.SUPPRESS) + + parser.add_argument( + '--os_project_id', + help=argparse.SUPPRESS) + + parser.add_argument( + '--os_project_name', + help=argparse.SUPPRESS) + + parser.add_argument( + '--os-project-domain-id', + metavar='', + default=utils.env('OS_PROJECT_DOMAIN_ID'), + help='Defaults to env[OS_PROJECT_DOMAIN_ID].') + + parser.add_argument( + '--os-project-domain-name', + metavar='', + default=utils.env('OS_PROJECT_DOMAIN_NAME'), + help='Defaults to env[OS_PROJECT_DOMAIN_NAME].') + + parser.add_argument( + '--os-cert', + metavar='', + default=utils.env('OS_CERT'), + help=_("Path of certificate file to use in SSL " + "connection. This file can optionally be " + "prepended with the private key. Defaults " + "to env[OS_CERT]")) + + parser.add_argument( + '--os-cacert', + metavar='', + default=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( + '--os-key', + metavar='', + default=utils.env('OS_KEY'), + help=_("Path of client key to use in SSL " + "connection. This option is not necessary " + "if your key is prepended to your certificate " + "file. Defaults to env[OS_KEY]")) + parser.add_argument( '--os-password', metavar='', default=utils.env('OS_PASSWORD'), @@ -458,22 +593,12 @@ class NeutronShell(app.App): '--os_token', help=argparse.SUPPRESS) - parser.add_argument( - '--service-type', metavar='', - default=env('OS_NETWORK_SERVICE_TYPE', default='network'), - help=_('Defaults to env[OS_NETWORK_SERVICE_TYPE] or network.')) - parser.add_argument( '--timeout', metavar='', default=env('OS_NETWORK_TIMEOUT', default=None), type=float, help=_('Timeout in seconds to wait for an HTTP response. Defaults ' 'to env[OS_NETWORK_TIMEOUT] or None if not specified.')) - parser.add_argument( - '--endpoint-type', metavar='', - default=env('OS_ENDPOINT_TYPE', default='publicURL'), - help=_('Defaults to env[OS_ENDPOINT_TYPE] or publicURL.')) - parser.add_argument( '--os-url', metavar='', default=env('OS_URL'), @@ -482,14 +607,6 @@ class NeutronShell(app.App): '--os_url', help=argparse.SUPPRESS) - parser.add_argument( - '--os-cacert', - metavar='', - default=env('OS_CACERT', default=None), - help=_("Specify a CA bundle file to use in " - "verifying a TLS (https) server certificate. " - "Defaults to env[OS_CACERT].")) - parser.add_argument( '--insecure', action='store_true', @@ -499,8 +616,6 @@ class NeutronShell(app.App): "not be verified against any certificate authorities. " "This option should be used with caution.")) - return parser - def _bash_completion(self): """Prints all of the commands and options for bash-completion.""" commands = set() @@ -622,6 +737,13 @@ class NeutronShell(app.App): else: # Validate password flow auth + project_info = (self.options.os_tenant_name or + self.options.os_tenant_id or + (self.options.os_project_name and + (self.options.project_domain_name or + self.options.project_domain_id)) or + self.options.os_project_id) + if (not self.options.os_username and not self.options.os_user_id): raise exc.CommandError( @@ -634,12 +756,19 @@ class NeutronShell(app.App): _("You must provide a password via" " either --os-password or env[OS_PASSWORD]")) - if (not self.options.os_tenant_name - and not self.options.os_tenant_id): + if (not project_info): + # tenent is deprecated in Keystone v3. Use the latest + # terminology instead. raise exc.CommandError( - _("You must provide a tenant_name or tenant_id via" - " --os-tenant-name, env[OS_TENANT_NAME]" - " --os-tenant-id, or via env[OS_TENANT_ID]")) + _("You must provide a project_id or project_name (" + "with project_domain_name or project_domain_id) " + "via " + " --os-project-id (env[OS_PROJECT_ID])" + " --os-project-name (env[OS_PROJECT_NAME])," + " --os-project-domain-id " + "(env[OS_PROJECT_DOMAIN_ID])" + " --os-project-domain-name " + "(env[OS_PROJECT_DOMAIN_NAME])")) if not self.options.os_auth_url: raise exc.CommandError( @@ -651,6 +780,8 @@ class NeutronShell(app.App): _("You must provide a service URL via" " either --os-url or env[OS_URL]")) + auth_session = self._get_keystone_session() + self.client_manager = clientmanager.ClientManager( token=self.options.os_token, url=self.options.os_url, @@ -663,13 +794,18 @@ class NeutronShell(app.App): region_name=self.options.os_region_name, api_version=self.api_version, auth_strategy=self.options.os_auth_strategy, - service_type=self.options.service_type, - endpoint_type=self.options.endpoint_type, + # FIXME (bklei) honor deprecated service_type and + # endpoint type until they are removed + service_type=self.options.os_service_type or + self.options.service_type, + endpoint_type=self.options.os_endpoint_type or self.endpoint_type, insecure=self.options.insecure, ca_cert=self.options.os_cacert, timeout=self.options.timeout, retries=self.options.retries, raise_errors=False, + session=auth_session, + auth=auth_session.auth, log_credentials=True) return @@ -721,6 +857,89 @@ class NeutronShell(app.App): root_logger.addHandler(console) return + def get_v2_auth(self, v2_auth_url): + return v2_auth.Password( + v2_auth_url, + username=self.options.os_username, + password=self.options.os_password, + tenant_id=self.options.os_tenant_id, + tenant_name=self.options.os_tenant_name) + + def get_v3_auth(self, v3_auth_url): + project_id = self.options.os_project_id or self.options.os_tenant_id + project_name = (self.options.os_project_name or + self.options.os_tenant_name) + + return v3_auth.Password( + v3_auth_url, + username=self.options.os_username, + password=self.options.os_password, + user_id=self.options.os_user_id, + user_domain_name=self.options.os_user_domain_name, + user_domain_id=self.options.os_user_domain_id, + project_id=project_id, + project_name=project_name, + project_domain_name=self.options.os_project_domain_name, + project_domain_id=self.options.os_project_domain_id + ) + + def _discover_auth_versions(self, session, auth_url): + # discover the API versions the server is supporting base on the + # given URL + try: + ks_discover = discover.Discover(session=session, auth_url=auth_url) + return (ks_discover.url_for('2.0'), ks_discover.url_for('3.0')) + except ks_exc.ClientException: + # Identity service may not support discover API version. + # Lets try to figure out the API version from the original URL. + url_parts = urlparse.urlparse(auth_url) + (scheme, netloc, path, params, query, fragment) = url_parts + path = path.lower() + if path.startswith('/v3'): + return (None, auth_url) + elif path.startswith('/v2'): + return (auth_url, None) + else: + # not enough information to determine the auth version + msg = _('Unable to determine the Keystone version ' + 'to authenticate with using the given ' + 'auth_url. Identity service may not support API ' + 'version discovery. Please provide a versioned ' + 'auth_url instead.') + raise exc.CommandError(msg) + + def _get_keystone_session(self): + # first create a Keystone session + cacert = self.options.os_cacert or None + cert = self.options.os_cert or None + key = self.options.os_key or None + insecure = self.options.insecure or False + ks_session = session.Session.construct(dict(cacert=cacert, + cert=cert, + key=key, + insecure=insecure)) + # discover the supported keystone versions using the given url + (v2_auth_url, v3_auth_url) = self._discover_auth_versions( + session=ks_session, + auth_url=self.options.os_auth_url) + + # Determine which authentication plugin to use. First inspect the + # auth_url to see the supported version. If both v3 and v2 are + # supported, then use the highest version if possible. + user_domain_name = self.options.os_user_domain_name or None + user_domain_id = self.options.os_user_domain_id or None + project_domain_name = self.options.os_project_domain_name or None + project_domain_id = self.options.os_project_domain_id or None + domain_info = (user_domain_name or user_domain_id or + project_domain_name or project_domain_id) + + if (v2_auth_url and not domain_info) or not v3_auth_url: + ks_session.auth = self.get_v2_auth(v2_auth_url) + else: + ks_session.auth = self.get_v3_auth(v3_auth_url) + + return ks_session + def main(argv=sys.argv[1:]): try: diff --git a/neutronclient/tests/unit/test_auth.py b/neutronclient/tests/unit/test_auth.py index 34ef71d4e..537236a60 100644 --- a/neutronclient/tests/unit/test_auth.py +++ b/neutronclient/tests/unit/test_auth.py @@ -14,18 +14,25 @@ # under the License. # -import copy import json import uuid -from keystoneclient import exceptions as k_exceptions +import httpretty from mox3 import mox import requests import testtools +from keystoneclient.auth.identity import v2 as ks_v2_auth +from keystoneclient.auth.identity import v3 as ks_v3_auth +from keystoneclient import exceptions as ks_exceptions +from keystoneclient.fixture import v2 as ks_v2_fixture +from keystoneclient.fixture import v3 as ks_v3_fixture +from keystoneclient import session + from neutronclient import client from neutronclient.common import exceptions from neutronclient.common import utils +from neutronclient.openstack.common import jsonutils USERNAME = 'testuser' @@ -33,11 +40,14 @@ USER_ID = 'testuser_id' TENANT_NAME = 'testtenant' TENANT_ID = 'testtenant_id' PASSWORD = 'password' -AUTH_URL = 'authurl' ENDPOINT_URL = 'localurl' +PUBLIC_ENDPOINT_URL = 'public_%s' % ENDPOINT_URL +ADMIN_ENDPOINT_URL = 'admin_%s' % ENDPOINT_URL +INTERNAL_ENDPOINT_URL = 'internal_%s' % ENDPOINT_URL ENDPOINT_OVERRIDE = 'otherurl' TOKEN = 'tokentoken' -REGION = 'RegionTest' +TOKENID = uuid.uuid4().hex +REGION = 'RegionOne' NOAUTH = 'noauth' KS_TOKEN_RESULT = { @@ -69,6 +79,55 @@ ENDPOINTS_RESULT = { }] } +BASE_HOST = 'http://keystone.example.com' +BASE_URL = "%s:5000/" % BASE_HOST +UPDATED = '2013-03-06T00:00:00Z' + +# FIXME (bklei): A future release of keystoneclient will support +# a discovery fixture which can replace these constants and clean +# this up. +V2_URL = "%sv2.0" % BASE_URL +V2_DESCRIBED_BY_HTML = {'href': 'http://docs.openstack.org/api/' + 'openstack-identity-service/2.0/content/', + 'rel': 'describedby', + 'type': 'text/html'} + +V2_DESCRIBED_BY_PDF = {'href': 'http://docs.openstack.org/api/openstack-ident' + 'ity-service/2.0/identity-dev-guide-2.0.pdf', + 'rel': 'describedby', + 'type': 'application/pdf'} + +V2_VERSION = {'id': 'v2.0', + 'links': [{'href': V2_URL, 'rel': 'self'}, + V2_DESCRIBED_BY_HTML, V2_DESCRIBED_BY_PDF], + 'status': 'stable', + 'updated': UPDATED} + +V3_URL = "%sv3" % BASE_URL +V3_MEDIA_TYPES = [{'base': 'application/json', + 'type': 'application/vnd.openstack.identity-v3+json'}, + {'base': 'application/xml', + 'type': 'application/vnd.openstack.identity-v3+xml'}] + +V3_VERSION = {'id': 'v3.0', + 'links': [{'href': V3_URL, 'rel': 'self'}], + 'media-types': V3_MEDIA_TYPES, + 'status': 'stable', + 'updated': UPDATED} + + +def _create_version_entry(version): + return jsonutils.dumps({'version': version}) + + +def _create_version_list(versions): + return jsonutils.dumps({'versions': {'values': versions}}) + + +V3_VERSION_LIST = _create_version_list([V3_VERSION, V2_VERSION]) +V3_VERSION_ENTRY = _create_version_entry(V3_VERSION) +V2_VERSION_ENTRY = _create_version_entry(V2_VERSION) + def get_response(status_code, headers=None): response = mox.Mox().CreateMock(requests.Response) @@ -77,6 +136,49 @@ def get_response(status_code, headers=None): return response +def setup_keystone_v2(): + v2_token = ks_v2_fixture.Token(token_id=TOKENID) + service = v2_token.add_service('network') + service.add_endpoint(PUBLIC_ENDPOINT_URL, region=REGION) + + httpretty.register_uri(httpretty.POST, + '%s/tokens' % (V2_URL), + body=json.dumps(v2_token)) + + auth_session = session.Session() + auth_plugin = ks_v2_auth.Password(V2_URL, 'xx', 'xx') + return auth_session, auth_plugin + + +def setup_keystone_v3(): + httpretty.register_uri(httpretty.GET, + V3_URL, + body=V3_VERSION_ENTRY) + + v3_token = ks_v3_fixture.Token() + service = v3_token.add_service('network') + service.add_standard_endpoints(public=PUBLIC_ENDPOINT_URL, + admin=ADMIN_ENDPOINT_URL, + internal=INTERNAL_ENDPOINT_URL, + region=REGION) + + httpretty.register_uri(httpretty.POST, + '%s/auth/tokens' % (V3_URL), + body=json.dumps(v3_token), + adding_headers={'X-Subject-Token': TOKENID}) + + auth_session = session.Session() + auth_plugin = ks_v3_auth.Password(V3_URL, + username='xx', + user_id='xx', + user_domain_name='xx', + user_domain_id='xx') + return auth_session, auth_plugin + + +AUTH_URL = V2_URL + + class CLITestAuthNoAuth(testtools.TestCase): def setUp(self): @@ -109,20 +211,18 @@ class CLITestAuthNoAuth(testtools.TestCase): class CLITestAuthKeystone(testtools.TestCase): - # Auth Body expected - auth_body = ('{"auth": {"tenantName": "testtenant", ' - '"passwordCredentials": ' - '{"username": "testuser", "password": "password"}}}') - def setUp(self): """Prepare the test environment.""" super(CLITestAuthKeystone, self).setUp() self.mox = mox.Mox() - self.client = client.HTTPClient(username=USERNAME, - tenant_name=TENANT_NAME, - password=PASSWORD, - auth_url=AUTH_URL, - region_name=REGION) + + self.client = client.construct_http_client( + username=USERNAME, + tenant_name=TENANT_NAME, + password=PASSWORD, + auth_url=AUTH_URL, + region_name=REGION) + self.addCleanup(self.mox.VerifyAll) self.addCleanup(self.mox.UnsetStubs) @@ -142,24 +242,30 @@ class CLITestAuthKeystone(testtools.TestCase): 'endpoint_url': self.client.endpoint_url} self.assertEqual(client_.get_auth_info(), expected) + @httpretty.activate def test_get_token(self): - self.mox.StubOutWithMock(self.client, "request") + auth_session, auth_plugin = setup_keystone_v2() + self.client = client.construct_http_client( + username=USERNAME, + tenant_name=TENANT_NAME, + password=PASSWORD, + auth_url=AUTH_URL, + region_name=REGION, + session=auth_session, + auth=auth_plugin) + + self.mox.StubOutWithMock(self.client, "request") res200 = get_response(200) self.client.request( - AUTH_URL + '/tokens', 'POST', - body=self.auth_body, headers=mox.IsA(dict) - ).AndReturn((res200, json.dumps(KS_TOKEN_RESULT))) - self.client.request( - mox.StrContains(ENDPOINT_URL + '/resource'), 'GET', - headers=mox.ContainsKeyValue('X-Auth-Token', TOKEN) + '/resource', 'GET', + authenticated=True ).AndReturn((res200, '')) + self.mox.ReplayAll() self.client.do_request('/resource', 'GET') - self.assertEqual(self.client.endpoint_url, ENDPOINT_URL) - self.assertEqual(self.client.auth_token, TOKEN) def test_refresh_token(self): self.mox.StubOutWithMock(self.client, "request") @@ -292,53 +398,55 @@ class CLITestAuthKeystone(testtools.TestCase): self.mox.ReplayAll() self.client.do_request('/resource', 'GET') + @httpretty.activate def test_endpoint_type(self): - resources = copy.deepcopy(KS_TOKEN_RESULT) - endpoints = resources['access']['serviceCatalog'][0]['endpoints'][0] - endpoints['internalURL'] = 'internal' - endpoints['adminURL'] = 'admin' - endpoints['publicURL'] = 'public' + auth_session, auth_plugin = setup_keystone_v3() # Test default behavior is to choose public. - self.client = client.HTTPClient( + self.client = client.construct_http_client( username=USERNAME, tenant_name=TENANT_NAME, password=PASSWORD, - auth_url=AUTH_URL, region_name=REGION) + auth_url=AUTH_URL, region_name=REGION, + session=auth_session, auth=auth_plugin) - self.client._extract_service_catalog(resources) - self.assertEqual(self.client.endpoint_url, 'public') + self.client.authenticate() + self.assertEqual(self.client.endpoint_url, PUBLIC_ENDPOINT_URL) # Test admin url - self.client = client.HTTPClient( + self.client = client.construct_http_client( username=USERNAME, tenant_name=TENANT_NAME, password=PASSWORD, - auth_url=AUTH_URL, region_name=REGION, endpoint_type='adminURL') + auth_url=AUTH_URL, region_name=REGION, endpoint_type='adminURL', + session=auth_session, auth=auth_plugin) - self.client._extract_service_catalog(resources) - self.assertEqual(self.client.endpoint_url, 'admin') + self.client.authenticate() + self.assertEqual(self.client.endpoint_url, ADMIN_ENDPOINT_URL) # Test public url - self.client = client.HTTPClient( + self.client = client.construct_http_client( username=USERNAME, tenant_name=TENANT_NAME, password=PASSWORD, - auth_url=AUTH_URL, region_name=REGION, endpoint_type='publicURL') + auth_url=AUTH_URL, region_name=REGION, endpoint_type='publicURL', + session=auth_session, auth=auth_plugin) - self.client._extract_service_catalog(resources) - self.assertEqual(self.client.endpoint_url, 'public') + self.client.authenticate() + self.assertEqual(self.client.endpoint_url, PUBLIC_ENDPOINT_URL) # Test internal url - self.client = client.HTTPClient( + self.client = client.construct_http_client( username=USERNAME, tenant_name=TENANT_NAME, password=PASSWORD, - auth_url=AUTH_URL, region_name=REGION, endpoint_type='internalURL') + auth_url=AUTH_URL, region_name=REGION, endpoint_type='internalURL', + session=auth_session, auth=auth_plugin) - self.client._extract_service_catalog(resources) - self.assertEqual(self.client.endpoint_url, 'internal') + self.client.authenticate() + self.assertEqual(self.client.endpoint_url, INTERNAL_ENDPOINT_URL) # Test url that isn't found in the service catalog - self.client = client.HTTPClient( + self.client = client.construct_http_client( username=USERNAME, tenant_name=TENANT_NAME, password=PASSWORD, - auth_url=AUTH_URL, region_name=REGION, endpoint_type='privateURL') + auth_url=AUTH_URL, region_name=REGION, endpoint_type='privateURL', + session=auth_session, auth=auth_plugin) - self.assertRaises(k_exceptions.EndpointNotFound, - self.client._extract_service_catalog, - resources) + self.assertRaises( + ks_exceptions.EndpointNotFound, + self.client.authenticate) def test_strip_credentials_from_log(self): def verify_no_credentials(kwargs): @@ -370,11 +478,6 @@ class CLITestAuthKeystone(testtools.TestCase): class CLITestAuthKeystoneWithId(CLITestAuthKeystone): - # Auth Body expected - auth_body = ('{"auth": {"passwordCredentials": ' - '{"password": "password", "userId": "testuser_id"}, ' - '"tenantId": "testtenant_id"}}') - def setUp(self): """Prepare the test environment.""" super(CLITestAuthKeystoneWithId, self).setUp() @@ -387,11 +490,6 @@ class CLITestAuthKeystoneWithId(CLITestAuthKeystone): class CLITestAuthKeystoneWithIdandName(CLITestAuthKeystone): - # Auth Body expected - auth_body = ('{"auth": {"passwordCredentials": ' - '{"password": "password", "userId": "testuser_id"}, ' - '"tenantId": "testtenant_id"}}') - def setUp(self): """Prepare the test environment.""" super(CLITestAuthKeystoneWithIdandName, self).setUp() @@ -402,3 +500,61 @@ class CLITestAuthKeystoneWithIdandName(CLITestAuthKeystone): password=PASSWORD, auth_url=AUTH_URL, region_name=REGION) + + +class TestKeystoneClientVersions(testtools.TestCase): + + def setUp(self): + """Prepare the test environment.""" + super(TestKeystoneClientVersions, self).setUp() + self.mox = mox.Mox() + self.addCleanup(self.mox.VerifyAll) + self.addCleanup(self.mox.UnsetStubs) + + @httpretty.activate + def test_v2_auth(self): + auth_session, auth_plugin = setup_keystone_v2() + res200 = get_response(200) + + self.client = client.construct_http_client( + username=USERNAME, + tenant_name=TENANT_NAME, + password=PASSWORD, + auth_url=AUTH_URL, + region_name=REGION, + session=auth_session, + auth=auth_plugin) + + self.mox.StubOutWithMock(self.client, "request") + + self.client.request( + '/resource', 'GET', + authenticated=True + ).AndReturn((res200, '')) + + self.mox.ReplayAll() + self.client.do_request('/resource', 'GET') + + @httpretty.activate + def test_v3_auth(self): + auth_session, auth_plugin = setup_keystone_v3() + res200 = get_response(200) + + self.client = client.construct_http_client( + user_id=USER_ID, + tenant_id=TENANT_ID, + password=PASSWORD, + auth_url=V3_URL, + region_name=REGION, + session=auth_session, + auth=auth_plugin) + + self.mox.StubOutWithMock(self.client, "request") + + self.client.request( + '/resource', 'GET', + authenticated=True + ).AndReturn((res200, '')) + + self.mox.ReplayAll() + self.client.do_request('/resource', 'GET') diff --git a/neutronclient/tests/unit/test_shell.py b/neutronclient/tests/unit/test_shell.py index 0324f1464..3eb372b74 100644 --- a/neutronclient/tests/unit/test_shell.py +++ b/neutronclient/tests/unit/test_shell.py @@ -20,13 +20,19 @@ import re import sys import fixtures +import httpretty from mox3 import mox import six import testtools from testtools import matchers +from keystoneclient.auth.identity import v2 as v2_auth +from keystoneclient.auth.identity import v3 as v3_auth +from keystoneclient import session + from neutronclient.common import clientmanager from neutronclient import shell as openstack_shell +from neutronclient.tests.unit import test_auth as auth DEFAULT_USERNAME = 'username' @@ -91,6 +97,14 @@ class ShellTest(testtools.TestCase): matchers.MatchesRegex(required)) self.assertFalse(stderr) + def test_bash_completion(self): + required = '.*os_user_domain_id.*' + bash_completion, stderr = self.shell('bash-completion') + self.assertThat( + bash_completion, + matchers.MatchesRegex(required)) + self.assertFalse(stderr) + def test_help_on_subcommand(self): required = [ '.*?^usage: .* quota-list'] @@ -116,27 +130,287 @@ class ShellTest(testtools.TestCase): self.assertEqual('You must provide a service URL via ' 'either --os-url or env[OS_URL]', stderr.strip()) + @httpretty.activate def test_auth(self): - #import pdb; pdb.set_trace() + # emulate Keystone version discovery + httpretty.register_uri(httpretty.GET, + auth.V3_URL, + body=auth.V3_VERSION_ENTRY) + neutron_shell = openstack_shell.NeutronShell('2.0') self.addCleanup(self.mox.UnsetStubs) self.mox.StubOutWithMock(clientmanager.ClientManager, '__init__') self.mox.StubOutWithMock(neutron_shell, 'run_subcommand') clientmanager.ClientManager.__init__( - token='', url='', auth_url='http://127.0.0.1:5000/', + token='', url='', auth_url=auth.V3_URL, tenant_name='test', tenant_id='tenant_id', username='test', user_id='', password='test', region_name='', api_version={'network': '2.0'}, auth_strategy='keystone', service_type='network', - endpoint_type='publicURL', insecure=False, ca_cert=None, retries=0, - raise_errors=False, log_credentials=True, timeout=None) + endpoint_type='publicURL', insecure=False, ca_cert=None, + timeout=None, + raise_errors=False, + retries=0, + auth=mox.IsA(v3_auth.Password), + session=mox.IsA(session.Session), + log_credentials=True) neutron_shell.run_subcommand(['quota-list']) self.mox.ReplayAll() cmdline = ('--os-username test ' '--os-password test ' '--os-tenant-name test ' - '--os-auth-url http://127.0.0.1:5000/ ' - '--os-auth-strategy keystone quota-list') + '--os-auth-url %s ' + '--os-auth-strategy keystone quota-list' + % auth.V3_URL) + neutron_shell.run(cmdline.split()) + self.mox.VerifyAll() + + @httpretty.activate + def test_auth_cert_and_key(self): + # emulate Keystone version discovery + httpretty.register_uri(httpretty.GET, + auth.V3_URL, + body=auth.V3_VERSION_ENTRY) + + neutron_shell = openstack_shell.NeutronShell('2.0') + self.addCleanup(self.mox.UnsetStubs) + self.mox.StubOutWithMock(clientmanager.ClientManager, '__init__') + self.mox.StubOutWithMock(neutron_shell, 'run_subcommand') + clientmanager.ClientManager.__init__( + token='', url='', auth_url=auth.V3_URL, + tenant_name='test', tenant_id='tenant_id', + username='test', user_id='', + password='test', region_name='', api_version={'network': '2.0'}, + auth_strategy='keystone', service_type='network', + raise_errors=False, + endpoint_type='publicURL', insecure=False, ca_cert=None, retries=0, + timeout=None, + auth=mox.IsA(v3_auth.Password), + session=mox.IsA(session.Session), + log_credentials=True) + neutron_shell.run_subcommand(['quota-list']) + self.mox.ReplayAll() + cmdline = ('--os-username test ' + '--os-password test ' + '--os-tenant-name test ' + '--os-cert test ' + '--os-key test ' + '--os-auth-url %s ' + '--os-auth-strategy keystone quota-list' + % auth.V3_URL) + neutron_shell.run(cmdline.split()) + self.mox.VerifyAll() + + @httpretty.activate + def test_v2_auth(self): + # emulate Keystone version discovery + httpretty.register_uri(httpretty.GET, + auth.V2_URL, + body=auth.V2_VERSION_ENTRY) + + neutron_shell = openstack_shell.NeutronShell('2.0') + self.addCleanup(self.mox.UnsetStubs) + self.mox.StubOutWithMock(clientmanager.ClientManager, '__init__') + self.mox.StubOutWithMock(neutron_shell, 'run_subcommand') + clientmanager.ClientManager.__init__( + token='', url='', auth_url=auth.V2_URL, + tenant_name='test', tenant_id='tenant_id', + username='test', user_id='', + password='test', region_name='', api_version={'network': '2.0'}, + auth_strategy='keystone', service_type='network', + endpoint_type='publicURL', insecure=False, ca_cert=None, + timeout=None, + raise_errors=False, + retries=0, + auth=mox.IsA(v2_auth.Password), + session=mox.IsA(session.Session), + log_credentials=True) + neutron_shell.run_subcommand(['quota-list']) + self.mox.ReplayAll() + cmdline = ('--os-username test ' + '--os-password test ' + '--os-tenant-name test ' + '--os-auth-url %s ' + '--os-auth-strategy keystone quota-list' + % auth.V2_URL) + neutron_shell.run(cmdline.split()) + self.mox.VerifyAll() + + @httpretty.activate + def test_failed_auth_version_discovery_v3_auth_url(self): + # emulate Keystone version discovery + httpretty.register_uri(httpretty.GET, + auth.V3_URL, + status=405) + + neutron_shell = openstack_shell.NeutronShell('2.0') + self.addCleanup(self.mox.UnsetStubs) + self.mox.StubOutWithMock(clientmanager.ClientManager, '__init__') + self.mox.StubOutWithMock(neutron_shell, 'run_subcommand') + clientmanager.ClientManager.__init__( + token='', url='', auth_url=auth.V3_URL, + tenant_name='test', tenant_id='tenant_id', + username='test', user_id='', + password='test', region_name='', api_version={'network': '2.0'}, + auth_strategy='keystone', service_type='network', + endpoint_type='publicURL', insecure=False, ca_cert=None, + timeout=None, + raise_errors=False, + retries=0, + auth=mox.IsA(v3_auth.Password), + session=mox.IsA(session.Session), + log_credentials=True) + neutron_shell.run_subcommand(['quota-list']) + self.mox.ReplayAll() + cmdline = ('--os-username test ' + '--os-password test ' + '--os-user-domain-name test ' + '--os-tenant-name test ' + '--os-auth-url %s ' + '--os-auth-strategy keystone quota-list' + % auth.V3_URL) + neutron_shell.run(cmdline.split()) + self.mox.VerifyAll() + + @httpretty.activate + def test_failed_auth_version_discovery_v2_auth_url(self): + # emulate Keystone version discovery + httpretty.register_uri(httpretty.GET, + auth.V2_URL, + status=405) + + neutron_shell = openstack_shell.NeutronShell('2.0') + self.addCleanup(self.mox.UnsetStubs) + self.mox.StubOutWithMock(clientmanager.ClientManager, '__init__') + self.mox.StubOutWithMock(neutron_shell, 'run_subcommand') + clientmanager.ClientManager.__init__( + token='', url='', auth_url=auth.V2_URL, + tenant_name='test', tenant_id='tenant_id', + username='test', user_id='', + password='test', region_name='', api_version={'network': '2.0'}, + auth_strategy='keystone', service_type='network', + endpoint_type='publicURL', insecure=False, ca_cert=None, + timeout=None, + raise_errors=False, + retries=0, + auth=mox.IsA(v2_auth.Password), + session=mox.IsA(session.Session), + log_credentials=True) + neutron_shell.run_subcommand(['quota-list']) + self.mox.ReplayAll() + cmdline = ('--os-username test ' + '--os-password test ' + '--os-tenant-name test ' + '--os-auth-url %s ' + '--os-auth-strategy keystone quota-list' + % auth.V2_URL) + neutron_shell.run(cmdline.split()) + self.mox.VerifyAll() + + @httpretty.activate + def test_auth_version_discovery_v3(self): + # emulate Keystone version discovery + httpretty.register_uri(httpretty.GET, + auth.BASE_URL, + body=auth.V3_VERSION_LIST) + + neutron_shell = openstack_shell.NeutronShell('2.0') + self.addCleanup(self.mox.UnsetStubs) + self.mox.StubOutWithMock(clientmanager.ClientManager, '__init__') + self.mox.StubOutWithMock(neutron_shell, 'run_subcommand') + clientmanager.ClientManager.__init__( + token='', url='', auth_url=auth.BASE_URL, + tenant_name='test', tenant_id='tenant_id', + username='test', user_id='', + password='test', region_name='', api_version={'network': '2.0'}, + auth_strategy='keystone', service_type='network', + endpoint_type='publicURL', insecure=False, ca_cert=None, + timeout=None, + raise_errors=False, + retries=0, + auth=mox.IsA(v3_auth.Password), + session=mox.IsA(session.Session), + log_credentials=True) + neutron_shell.run_subcommand(['quota-list']) + self.mox.ReplayAll() + cmdline = ('--os-username test ' + '--os-password test ' + '--os-user-domain-name test ' + '--os-tenant-name test ' + '--os-auth-url %s ' + '--os-auth-strategy keystone quota-list' + % auth.BASE_URL) + neutron_shell.run(cmdline.split()) + self.mox.VerifyAll() + + @httpretty.activate + def test_auth_version_discovery_v2(self): + # emulate Keystone version discovery + httpretty.register_uri(httpretty.GET, + auth.BASE_URL, + body=auth.V3_VERSION_LIST) + + neutron_shell = openstack_shell.NeutronShell('2.0') + self.addCleanup(self.mox.UnsetStubs) + self.mox.StubOutWithMock(clientmanager.ClientManager, '__init__') + self.mox.StubOutWithMock(neutron_shell, 'run_subcommand') + clientmanager.ClientManager.__init__( + token='', url='', auth_url=auth.BASE_URL, + tenant_name='test', tenant_id='tenant_id', + username='test', user_id='', + password='test', region_name='', api_version={'network': '2.0'}, + auth_strategy='keystone', service_type='network', + endpoint_type='publicURL', insecure=False, ca_cert=None, + timeout=None, + raise_errors=False, + retries=0, + auth=mox.IsA(v2_auth.Password), + session=mox.IsA(session.Session), + log_credentials=True) + neutron_shell.run_subcommand(['quota-list']) + self.mox.ReplayAll() + cmdline = ('--os-username test ' + '--os-password test ' + '--os-tenant-name test ' + '--os-auth-url %s ' + '--os-auth-strategy keystone quota-list' + % auth.BASE_URL) + neutron_shell.run(cmdline.split()) + self.mox.VerifyAll() + + @httpretty.activate + def test_insecure_auth(self): + # emulate Keystone version discovery + httpretty.register_uri(httpretty.GET, + auth.V2_URL, + body=auth.V2_VERSION_ENTRY) + + neutron_shell = openstack_shell.NeutronShell('2.0') + self.addCleanup(self.mox.UnsetStubs) + self.mox.StubOutWithMock(clientmanager.ClientManager, '__init__') + self.mox.StubOutWithMock(neutron_shell, 'run_subcommand') + clientmanager.ClientManager.__init__( + token='', url='', auth_url=auth.V2_URL, + tenant_name='test', tenant_id='tenant_id', + username='test', user_id='', + password='test', region_name='', api_version={'network': '2.0'}, + auth_strategy='keystone', service_type='network', + endpoint_type='publicURL', insecure=True, ca_cert=None, + timeout=None, + raise_errors=False, + retries=0, + auth=mox.IgnoreArg(), + session=mox.IgnoreArg(), + log_credentials=True) + neutron_shell.run_subcommand(['quota-list']) + self.mox.ReplayAll() + cmdline = ('--os-username test ' + '--os-password test ' + '--os-tenant-name test ' + '--insecure ' + '--os-auth-url %s ' + '--os-auth-strategy keystone quota-list' + % auth.V2_URL) neutron_shell.run(cmdline.split()) self.mox.VerifyAll() @@ -162,13 +436,13 @@ class ShellTest(testtools.TestCase): shell = openstack_shell.NeutronShell('2.0') parser = shell.build_option_parser('descr', '2.0') - # Neither $OS_ENDPOINT_TYPE nor --endpoint-type + # Neither $OS_ENDPOINT_TYPE nor --os-endpoint-type namespace = parser.parse_args([]) - self.assertEqual('publicURL', namespace.endpoint_type) + self.assertEqual('publicURL', namespace.os_endpoint_type) # --endpoint-type but not $OS_ENDPOINT_TYPE - namespace = parser.parse_args(['--endpoint-type=admin']) - self.assertEqual('admin', namespace.endpoint_type) + namespace = parser.parse_args(['--os-endpoint-type=admin']) + self.assertEqual('admin', namespace.os_endpoint_type) def test_endpoint_environment_variable(self): fixture = fixtures.EnvironmentVariable("OS_ENDPOINT_TYPE", @@ -180,7 +454,7 @@ class ShellTest(testtools.TestCase): # $OS_ENDPOINT_TYPE but not --endpoint-type namespace = parser.parse_args([]) - self.assertEqual("public", namespace.endpoint_type) + self.assertEqual("public", namespace.os_endpoint_type) # --endpoint-type and $OS_ENDPOINT_TYPE namespace = parser.parse_args(['--endpoint-type=admin']) diff --git a/neutronclient/tests/unit/test_ssl.py b/neutronclient/tests/unit/test_ssl.py index 9c0816d45..bc0e50078 100644 --- a/neutronclient/tests/unit/test_ssl.py +++ b/neutronclient/tests/unit/test_ssl.py @@ -14,15 +14,17 @@ # under the License. import fixtures -from mox3 import mox import requests import testtools +import httpretty +from mox3 import mox + from neutronclient.client import HTTPClient from neutronclient.common.clientmanager import ClientManager from neutronclient.common import exceptions from neutronclient import shell as openstack_shell - +from neutronclient.tests.unit import test_auth as auth AUTH_TOKEN = 'test_token' END_URL = 'test_url' @@ -41,7 +43,13 @@ class TestSSL(testtools.TestCase): self.mox = mox.Mox() self.addCleanup(self.mox.UnsetStubs) + @httpretty.activate def test_ca_cert_passed(self): + # emulate Keystone version discovery + httpretty.register_uri(httpretty.GET, + auth.V3_URL, + body=auth.V3_VERSION_ENTRY) + self.mox.StubOutWithMock(ClientManager, '__init__') self.mox.StubOutWithMock(openstack_shell.NeutronShell, 'interact') @@ -66,14 +74,27 @@ class TestSSL(testtools.TestCase): raise_errors=mox.IgnoreArg(), log_credentials=mox.IgnoreArg(), timeout=mox.IgnoreArg(), + auth=mox.IgnoreArg(), + session=mox.IgnoreArg() ) openstack_shell.NeutronShell.interact().AndReturn(0) self.mox.ReplayAll() - openstack_shell.NeutronShell('2.0').run(['--os-cacert', CA_CERT]) + cmdline = ( + '--os-cacert %s --os-auth-url %s' % + (CA_CERT, auth.V3_URL)) + + openstack_shell.NeutronShell('2.0').run(cmdline.split()) self.mox.VerifyAll() + @httpretty.activate def test_ca_cert_passed_as_env_var(self): + + # emulate Keystone version discovery + httpretty.register_uri(httpretty.GET, + auth.V3_URL, + body=auth.V3_VERSION_ENTRY) + self.useFixture(fixtures.EnvironmentVariable('OS_CACERT', CA_CERT)) self.mox.StubOutWithMock(ClientManager, '__init__') @@ -100,11 +121,15 @@ class TestSSL(testtools.TestCase): raise_errors=mox.IgnoreArg(), log_credentials=mox.IgnoreArg(), timeout=mox.IgnoreArg(), + auth=mox.IgnoreArg(), + session=mox.IgnoreArg() ) openstack_shell.NeutronShell.interact().AndReturn(0) self.mox.ReplayAll() - openstack_shell.NeutronShell('2.0').run([]) + cmdline = ('--os-auth-url %s' % auth.V3_URL) + openstack_shell.NeutronShell('2.0').run(cmdline.split()) + self.mox.VerifyAll() def test_client_manager_properly_creates_httpclient_instance(self): @@ -121,8 +146,12 @@ class TestSSL(testtools.TestCase): tenant_name=mox.IgnoreArg(), token=mox.IgnoreArg(), username=mox.IgnoreArg(), - retries=mox.IgnoreArg(), - raise_errors=mox.IgnoreArg(), + user_id=mox.IgnoreArg(), + tenant_id=mox.IgnoreArg(), + timeout=mox.IgnoreArg(), + log_credentials=mox.IgnoreArg(), + service_type=mox.IgnoreArg(), + endpoint_type=mox.IgnoreArg() ) self.mox.ReplayAll() diff --git a/neutronclient/v2_0/client.py b/neutronclient/v2_0/client.py index 630d41341..49a49f2ad 100644 --- a/neutronclient/v2_0/client.py +++ b/neutronclient/v2_0/client.py @@ -135,6 +135,8 @@ class Client(object): :param bool raise_errors: If True then exceptions caused by connection failure are propagated to the caller. (default: True) + :param session: Keystone client auth session to use. (optional) + :param auth: Keystone auth plugin to use. (optional) Example:: @@ -1196,12 +1198,12 @@ class Client(object): def __init__(self, **kwargs): """Initialize a new client for the Neutron v2.0 API.""" super(Client, self).__init__() - self.httpclient = client.HTTPClient(**kwargs) + self.retries = kwargs.pop('retries', 0) + self.raise_errors = kwargs.pop('raise_errors', True) + self.httpclient = client.construct_http_client(**kwargs) self.version = '2.0' self.format = 'json' self.action_prefix = "/v%s" % (self.version) - self.retries = kwargs.get('retries', 0) - self.raise_errors = kwargs.get('raise_errors', True) self.retry_interval = 1 def _handle_fault_response(self, status_code, response_body): diff --git a/test-requirements.txt b/test-requirements.txt index ad976c6cb..9a4d7e311 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -4,6 +4,7 @@ cliff-tablib>=1.0 coverage>=3.6 discover fixtures>=0.3.14 +httpretty>=0.8.0,!=0.8.1,!=0.8.2 mox3>=0.7.0 oslosphinx oslotest