From d27223eb767c5a6d1d11699ff7891d87f4dcf8e7 Mon Sep 17 00:00:00 2001
From: David Hu <david.hu@hp.com>
Date: Thu, 23 Oct 2014 01:50:49 -0700
Subject: [PATCH 2/2] Support using the Keystone V3 API from the Nova CLI

This changeset enables support for Keystone V3 authentication
on the Nova CLI. This provides consistency between using
novaclient as the Nova CLI and using it as a library
as the Keystone V3 support already exists for the libary usecase.

The bulk of the change surrounds the use of the keystoneclient
session object for authentication, retriving the service catalog,
and HTTP connection/session management.

Co-Authored-By: Morgan Fainberg <morgan.fainberg@gmail.com>
Change-Id: Iece9f41320a8770176c7eeb5acd86be4d80cc58f
(cherry picked from commit 8597a0c234ef643905d8e356a3986b79b52989c3)
---
 novaclient/client.py                |  15 +++++
 novaclient/shell.py                 | 112 ++++++++++++++++++++++++++----------
 novaclient/tests/test_shell.py      |  91 +++++++++++++++++++++++++----
 novaclient/tests/v1_1/test_shell.py |   1 +
 novaclient/tests/v3/test_shell.py   |   1 +
 novaclient/v1_1/client.py           |   2 -
 novaclient/v1_1/shell.py            |  63 +++++++++++++-------
 novaclient/v3/client.py             |   2 -
 novaclient/v3/shell.py              |  64 ++++++++++++++-------
 9 files changed, 268 insertions(+), 83 deletions(-)

diff --git a/novaclient/client.py b/novaclient/client.py
index b0d9aea..f4ce0eb 100644
--- a/novaclient/client.py
+++ b/novaclient/client.py
@@ -136,20 +136,35 @@ class CompletionCache(object):
 
 class SessionClient(adapter.LegacyJsonAdapter):
 
+    def __init__(self, *args, **kwargs):
+        self.times = []
+        super(SessionClient, self).__init__(*args, **kwargs)
+
     def request(self, url, method, **kwargs):
         # NOTE(jamielennox): The standard call raises errors from
         # keystoneclient, where we need to raise the novaclient errors.
         raise_exc = kwargs.pop('raise_exc', True)
+        start_time = time.time()
         resp, body = super(SessionClient, self).request(url,
                                                         method,
                                                         raise_exc=False,
                                                         **kwargs)
 
+        end_time = time.time()
+        self.times.append(('%s %s' % (method, url),
+                          start_time, end_time))
+
         if raise_exc and resp.status_code >= 400:
             raise exceptions.from_response(resp, body, url, method)
 
         return resp, body
 
+    def get_timings(self):
+        return self.times
+
+    def reset_timings(self):
+        self.times = []
+
 
 def _original_only(f):
     """Indicates and enforces that this function can only be used if we are
diff --git a/novaclient/shell.py b/novaclient/shell.py
index d8da433..53169d5 100644
--- a/novaclient/shell.py
+++ b/novaclient/shell.py
@@ -28,7 +28,11 @@ import logging
 import os
 import pkgutil
 import sys
+import time
 
+from keystoneclient.auth.identity.generic import password
+from keystoneclient.auth.identity.generic import token
+from keystoneclient.auth.identity import v3 as identity
 from keystoneclient import session as ksession
 from oslo.utils import encodeutils
 from oslo.utils import strutils
@@ -232,6 +236,7 @@ class NovaClientArgumentParser(argparse.ArgumentParser):
 
 
 class OpenStackComputeShell(object):
+    times = []
 
     def _append_global_identity_args(self, parser):
         # Register the CLI arguments that have moved to the session object.
@@ -240,6 +245,14 @@ class OpenStackComputeShell(object):
         parser.set_defaults(insecure=utils.env('NOVACLIENT_INSECURE',
                             default=False))
 
+        identity.Password.register_argparse_arguments(parser)
+
+        parser.set_defaults(os_username=utils.env('OS_USERNAME',
+                            'NOVA_USERNAME'))
+        parser.set_defaults(os_password=utils.env('OS_PASSWORD',
+                            'NOVA_PASSWORD'))
+        parser.set_defaults(os_auth_url=utils.env('OS_AUTH_URL', 'NOVA_URL'))
+
     def get_base_parser(self):
         parser = NovaClientArgumentParser(
             prog='nova',
@@ -281,22 +294,9 @@ class OpenStackComputeShell(object):
                 default=utils.env('OS_AUTH_TOKEN'),
                 help='Defaults to env[OS_AUTH_TOKEN]')
 
-        parser.add_argument('--os-username',
-            metavar='<auth-user-name>',
-            default=utils.env('OS_USERNAME', 'NOVA_USERNAME'),
-            help=_('Defaults to env[OS_USERNAME].'))
         parser.add_argument('--os_username',
             help=argparse.SUPPRESS)
 
-        parser.add_argument('--os-user-id',
-            metavar='<auth-user-id>',
-            default=utils.env('OS_USER_ID'),
-            help=_('Defaults to env[OS_USER_ID].'))
-
-        parser.add_argument('--os-password',
-            metavar='<auth-password>',
-            default=utils.env('OS_PASSWORD', 'NOVA_PASSWORD'),
-            help=_('Defaults to env[OS_PASSWORD].'))
         parser.add_argument('--os_password',
             help=argparse.SUPPRESS)
 
@@ -312,10 +312,6 @@ class OpenStackComputeShell(object):
             default=utils.env('OS_TENANT_ID'),
             help=_('Defaults to env[OS_TENANT_ID].'))
 
-        parser.add_argument('--os-auth-url',
-            metavar='<auth-url>',
-            default=utils.env('OS_AUTH_URL', 'NOVA_URL'),
-            help=_('Defaults to env[OS_AUTH_URL].'))
         parser.add_argument('--os_auth_url',
             help=argparse.SUPPRESS)
 
@@ -511,6 +507,19 @@ class OpenStackComputeShell(object):
         logging.basicConfig(level=logging.DEBUG,
                             format=streamformat)
 
+    def _get_keystone_auth(self, session, auth_url, **kwargs):
+        auth_token = kwargs.pop('auth_token', None)
+        if auth_token:
+            return token.Token(auth_url, auth_token, **kwargs)
+        else:
+            return password.Password(auth_url,
+                username=kwargs.pop('username'),
+                user_id=kwargs.pop('user_id'),
+                password=kwargs.pop('password'),
+                user_domain_id=kwargs.pop('user_domain_id'),
+                user_domain_name=kwargs.pop('user_domain_name'),
+                **kwargs)
+
     def main(self, argv):
         # Parse args once to find version and debug settings
         parser = self.get_base_parser()
@@ -570,6 +579,9 @@ class OpenStackComputeShell(object):
         cacert = args.os_cacert
         timeout = args.timeout
 
+        keystone_session = None
+        keystone_auth = None
+
         # We may have either, both or none of these.
         # If we have both, we don't need USERNAME, PASSWORD etc.
         # Fill in the blanks from the SecretsHelper if possible.
@@ -603,6 +615,13 @@ class OpenStackComputeShell(object):
         must_auth = not (cliutils.isunauthenticated(args.func)
                          or (auth_token and management_url))
 
+        # Do not use Keystone session for cases with no session support. The
+        # presence of auth_plugin means os_auth_system is present and is not
+        # keystone.
+        use_session = True
+        if auth_plugin or bypass_url or os_cache or volume_service_name:
+            use_session = False
+
         # FIXME(usrleon): Here should be restrict for project id same as
         # for os_username or os_password but for compatibility it is not.
         if must_auth:
@@ -615,11 +634,14 @@ class OpenStackComputeShell(object):
                             "or user id via --os-username, --os-user-id, "
                             "env[OS_USERNAME] or env[OS_USER_ID]"))
 
-            if not os_tenant_name and not os_tenant_id:
-                raise exc.CommandError(_("You must provide a tenant name "
-                        "or tenant id via --os-tenant-name, "
-                        "--os-tenant-id, env[OS_TENANT_NAME] "
-                        "or env[OS_TENANT_ID]"))
+            if not any([args.os_tenant_name, args.os_tenant_id,
+                        args.os_project_id, args.os_project_name]):
+                raise exc.CommandError(_("You must provide a project name or"
+                                         " project id via --os-project-name,"
+                                         " --os-project-id, env[OS_PROJECT_ID]"
+                                         " or env[OS_PROJECT_NAME]. You may"
+                                         " use os-project and os-tenant"
+                                         " interchangeably."))
 
             if not os_auth_url:
                 if os_auth_system and os_auth_system != 'keystone':
@@ -632,13 +654,39 @@ class OpenStackComputeShell(object):
                             "default url with --os-auth-system "
                             "or env[OS_AUTH_SYSTEM]"))
 
+            project_id = args.os_project_id or args.os_tenant_id
+            project_name = args.os_project_name or args.os_tenant_name
+            if use_session:
+                # Not using Nova auth plugin, so use keystone
+                start_time = time.time()
+                keystone_session = ksession.Session.load_from_cli_options(args)
+                keystone_auth = self._get_keystone_auth(
+                    keystone_session,
+                    args.os_auth_url,
+                    username=args.os_username,
+                    user_id=args.os_user_id,
+                    user_domain_id=args.os_user_domain_id,
+                    user_domain_name=args.os_user_domain_name,
+                    password=args.os_password,
+                    auth_token=args.os_auth_token,
+                    project_id=project_id,
+                    project_name=project_name,
+                    project_domain_id=args.os_project_domain_id,
+                    project_domain_name=args.os_project_domain_name)
+                end_time = time.time()
+                self.times.append(('%s %s' % ('auth_url', args.os_auth_url),
+                    start_time, end_time))
+
         if (options.os_compute_api_version and
                 options.os_compute_api_version != '1.0'):
-            if not os_tenant_name and not os_tenant_id:
-                raise exc.CommandError(_("You must provide a tenant name "
-                        "or tenant id via --os-tenant-name, "
-                        "--os-tenant-id, env[OS_TENANT_NAME] "
-                        "or env[OS_TENANT_ID]"))
+            if not any([args.os_tenant_id, args.os_tenant_name,
+                        args.os_project_id, args.os_project_name]):
+                raise exc.CommandError(_("You must provide a project name or"
+                                         " project id via --os-project-name,"
+                                         " --os-project-id, env[OS_PROJECT_ID]"
+                                         " or env[OS_PROJECT_NAME]. You may"
+                                         " use os-project and os-tenant"
+                                         " interchangeably."))
 
             if not os_auth_url:
                 raise exc.CommandError(_("You must provide an auth url "
@@ -658,6 +706,7 @@ class OpenStackComputeShell(object):
                 timings=args.timings, bypass_url=bypass_url,
                 os_cache=os_cache, http_log_debug=options.debug,
                 cacert=cacert, timeout=timeout,
+                session=keystone_session, auth=keystone_auth,
                 completion_cache=completion_cache)
 
         # Now check for the password/token of which pieces of the
@@ -691,7 +740,11 @@ class OpenStackComputeShell(object):
             # This does a couple of bits which are useful even if we've
             # got the token + service URL already. It exits fast in that case.
             if not cliutils.isunauthenticated(args.func):
-                self.cs.authenticate()
+                if not use_session:
+                    # Only call authenticate() if Nova auth plugin is used.
+                    # If keystone is used, authentication is handled as part
+                    # of session.
+                    self.cs.authenticate()
         except exc.Unauthorized:
             raise exc.CommandError(_("Invalid OpenStack Nova credentials."))
         except exc.AuthorizationFailure:
@@ -720,12 +773,13 @@ class OpenStackComputeShell(object):
                 volume_service_name=volume_service_name,
                 timings=args.timings, bypass_url=bypass_url,
                 os_cache=os_cache, http_log_debug=options.debug,
+                session=keystone_session, auth=keystone_auth,
                 cacert=cacert, timeout=timeout)
 
         args.func(self.cs, args)
 
         if args.timings:
-            self._dump_timings(self.cs.get_timings())
+            self._dump_timings(self.times + self.cs.get_timings())
 
     def _dump_timings(self, timings):
         class Tyme(object):
diff --git a/novaclient/tests/test_shell.py b/novaclient/tests/test_shell.py
index 240965d..3fcbb6e 100644
--- a/novaclient/tests/test_shell.py
+++ b/novaclient/tests/test_shell.py
@@ -16,8 +16,10 @@ import re
 import sys
 
 import fixtures
+from keystoneclient import fixture
 import mock
 import prettytable
+import requests_mock
 import six
 from testtools import matchers
 
@@ -29,16 +31,33 @@ from novaclient.tests import utils
 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_USER_ID': 'user_id',
              'OS_PASSWORD': 'password',
              'OS_TENANT_ID': 'tenant_id',
-             'OS_AUTH_URL': 'http://no.where'}
+             'OS_AUTH_URL': 'http://no.where/v2.0'}
+
+FAKE_ENV3 = {'OS_USER_ID': 'user_id',
+             'OS_PASSWORD': 'password',
+             'OS_TENANT_ID': 'tenant_id',
+             'OS_AUTH_URL': 'http://no.where/v2.0',
+             'NOVA_ENDPOINT_TYPE': 'novaURL',
+             'OS_ENDPOINT_TYPE': 'osURL'}
+
+
+def _create_ver_list(versions):
+    return {'versions': {'values': versions}}
 
 
 class ShellTest(utils.TestCase):
 
+    _msg_no_tenant_project = ("You must provide a project name or project"
+                              " id via --os-project-name, --os-project-id,"
+                              " env[OS_PROJECT_ID] or env[OS_PROJECT_NAME]."
+                              " You may use os-project and os-tenant"
+                              " interchangeably.")
+
     def make_env(self, exclude=None, fake_env=FAKE_ENV):
         env = dict((k, v) for k, v in fake_env.items() if k != exclude)
         self.useFixture(fixtures.MonkeyPatch('os.environ', env))
@@ -72,6 +91,12 @@ class ShellTest(utils.TestCase):
             sys.stderr = orig_stderr
         return (stdout, stderr)
 
+    def register_keystone_discovery_fixture(self, mreq):
+        v2_url = "http://no.where/v2.0"
+        v2_version = fixture.V2Discovery(v2_url)
+        mreq.register_uri('GET', v2_url, json=_create_ver_list([v2_version]),
+            status_code=200)
+
     def test_help_unknown_command(self):
         self.assertRaises(exceptions.CommandError, self.shell, 'help foofoo')
 
@@ -156,9 +181,7 @@ class ShellTest(utils.TestCase):
             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('list')
@@ -168,14 +191,12 @@ class ShellTest(utils.TestCase):
             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('list')
         except exceptions.CommandError as message:
-            self.assertEqual(required, message.args)
+            self.assertEqual(required, message.args[0])
         else:
             self.fail('CommandError not raised')
 
@@ -192,9 +213,35 @@ class ShellTest(utils.TestCase):
         else:
             self.fail('CommandError not raised')
 
+    @mock.patch('novaclient.client.Client')
+    @requests_mock.Mocker()
+    def test_nova_endpoint_type(self, mock_client, m_requests):
+        self.make_env(fake_env=FAKE_ENV3)
+        self.register_keystone_discovery_fixture(m_requests)
+        self.shell('list')
+        client_kwargs = mock_client.call_args_list[0][1]
+        self.assertEqual(client_kwargs['endpoint_type'], 'novaURL')
+
+    @mock.patch('novaclient.client.Client')
+    @requests_mock.Mocker()
+    def test_os_endpoint_type(self, mock_client, m_requests):
+        self.make_env(exclude='NOVA_ENDPOINT_TYPE', fake_env=FAKE_ENV3)
+        self.register_keystone_discovery_fixture(m_requests)
+        self.shell('list')
+        client_kwargs = mock_client.call_args_list[0][1]
+        self.assertEqual(client_kwargs['endpoint_type'], 'osURL')
+
+    @mock.patch('novaclient.client.Client')
+    def test_default_endpoint_type(self, mock_client):
+        self.make_env()
+        self.shell('list')
+        client_kwargs = mock_client.call_args_list[0][1]
+        self.assertEqual(client_kwargs['endpoint_type'], 'publicURL')
+
     @mock.patch('sys.stdin', side_effect=mock.MagicMock)
     @mock.patch('getpass.getpass', return_value='password')
-    def test_password(self, mock_getpass, mock_stdin):
+    @requests_mock.Mocker()
+    def test_password(self, mock_getpass, mock_stdin, m_requests):
         mock_stdin.encoding = "utf-8"
 
         # default output of empty tables differs depending between prettytable
@@ -211,6 +258,7 @@ class ShellTest(utils.TestCase):
               '+----+------+--------+------------+-------------+----------+\n'
             )
         self.make_env(exclude='OS_PASSWORD')
+        self.register_keystone_discovery_fixture(m_requests)
         stdout, stderr = self.shell('list')
         self.assertEqual((stdout + stderr), ex)
 
@@ -272,3 +320,26 @@ class ShellTest(utils.TestCase):
         # We expect the normal usage as a result
         self.assertIn('Command-line interface to the OpenStack Nova API',
                       sys.stdout.getvalue())
+
+    @mock.patch.object(novaclient.shell.OpenStackComputeShell, 'main')
+    def test_main_keyboard_interrupt(self, mock_compute_shell):
+        # Ensure that exit code is 130 for KeyboardInterrupt
+        mock_compute_shell.side_effect = KeyboardInterrupt()
+        try:
+            novaclient.shell.main()
+        except SystemExit as ex:
+            self.assertEqual(ex.code, 1)
+
+
+class ShellTestKeystoneV3(ShellTest):
+    def make_env(self, exclude=None, fake_env=FAKE_ENV):
+        if 'OS_AUTH_URL' in fake_env:
+            fake_env.update({'OS_AUTH_URL': 'http://no.where/v3'})
+        env = dict((k, v) for k, v in fake_env.items() if k != exclude)
+        self.useFixture(fixtures.MonkeyPatch('os.environ', env))
+
+    def register_keystone_discovery_fixture(self, mreq):
+        v3_url = "http://no.where/v3"
+        v3_version = fixture.V3Discovery(v3_url)
+        mreq.register_uri('GET', v3_url, json=_create_ver_list([v3_version]),
+            status_code=200)
diff --git a/novaclient/tests/v1_1/test_shell.py b/novaclient/tests/v1_1/test_shell.py
index 9d44d65..faca3ca 100644
--- a/novaclient/tests/v1_1/test_shell.py
+++ b/novaclient/tests/v1_1/test_shell.py
@@ -56,6 +56,7 @@ class ShellTest(utils.TestCase):
         'NOVA_PROJECT_ID': 'project_id',
         'OS_COMPUTE_API_VERSION': '1.1',
         'NOVA_URL': 'http://no.where',
+        'OS_AUTH_URL': 'http://no.where/v2.0',
     }
 
     def setUp(self):
diff --git a/novaclient/tests/v3/test_shell.py b/novaclient/tests/v3/test_shell.py
index 68931a1..5f202ab 100644
--- a/novaclient/tests/v3/test_shell.py
+++ b/novaclient/tests/v3/test_shell.py
@@ -51,6 +51,7 @@ class ShellTest(utils.TestCase):
         'NOVA_PROJECT_ID': 'project_id',
         'OS_COMPUTE_API_VERSION': '3',
         'NOVA_URL': 'http://no.where',
+        'OS_AUTH_URL': 'http://no.where/v2.0',
     }
 
     def setUp(self):
diff --git a/novaclient/v1_1/client.py b/novaclient/v1_1/client.py
index 2e79e5b..70d5298 100644
--- a/novaclient/v1_1/client.py
+++ b/novaclient/v1_1/client.py
@@ -217,11 +217,9 @@ class Client(object):
     def set_management_url(self, url):
         self.client.set_management_url(url)
 
-    @client._original_only
     def get_timings(self):
         return self.client.get_timings()
 
-    @client._original_only
     def reset_timings(self):
         self.client.reset_timings()
 
diff --git a/novaclient/v1_1/shell.py b/novaclient/v1_1/shell.py
index 457ebc1..bc703df 100644
--- a/novaclient/v1_1/shell.py
+++ b/novaclient/v1_1/shell.py
@@ -33,6 +33,7 @@ from oslo.utils import strutils
 from oslo.utils import timeutils
 import six
 
+from novaclient import client
 from novaclient import exceptions
 from novaclient.openstack.common.gettextutils import _
 from novaclient.openstack.common import uuidutils
@@ -2735,7 +2736,12 @@ def do_usage(cs, args):
     if args.tenant:
         usage = cs.usage.get(args.tenant, start, end)
     else:
-        usage = cs.usage.get(cs.client.tenant_id, start, end)
+        if isinstance(cs.client, client.SessionClient):
+            auth = cs.client.auth
+            project_id = auth.get_auth_ref(cs.client.session).project_id
+            usage = cs.usage.get(project_id, start, end)
+        else:
+            usage = cs.usage.get(cs.client.tenant_id, start, end)
 
     print(_("Usage from %(start)s to %(end)s:") %
           {'start': start.strftime(dateformat),
@@ -3232,23 +3238,32 @@ def ensure_service_catalog_present(cs):
 
 def do_endpoints(cs, _args):
     """Discover endpoints that get returned from the authenticate services."""
-    ensure_service_catalog_present(cs)
+    if isinstance(cs.client, client.SessionClient):
+        auth = cs.client.auth
+        sc = auth.get_access(cs.client.session).service_catalog
+        for service in sc.get_data():
+            _print_endpoints(service, cs.client.region_name)
+    else:
+        ensure_service_catalog_present(cs)
 
-    catalog = cs.client.service_catalog.catalog
-    region = cs.client.region_name
+        catalog = cs.client.service_catalog.catalog
+        region = cs.client.region_name
+        for service in catalog['access']['serviceCatalog']:
+            _print_endpoints(service, region)
 
-    for service in catalog['access']['serviceCatalog']:
-        name, endpoints = service["name"], service["endpoints"]
 
-        try:
-            endpoint = _get_first_endpoint(endpoints, region)
-            utils.print_dict(endpoint, name)
-        except LookupError:
-            print(_("WARNING: %(service)s has no endpoint in %(region)s! "
-                    "Available endpoints for this service:") %
-                  {'service': name, 'region': region})
-            for other_endpoint in endpoints:
-                utils.print_dict(other_endpoint, name)
+def _print_endpoints(service, region):
+    name, endpoints = service["name"], service["endpoints"]
+
+    try:
+        endpoint = _get_first_endpoint(endpoints, region)
+        utils.print_dict(endpoint, name)
+    except LookupError:
+        print(_("WARNING: %(service)s has no endpoint in %(region)s! "
+                "Available endpoints for this service:") %
+              {'service': name, 'region': region})
+        for other_endpoint in endpoints:
+            utils.print_dict(other_endpoint, name)
 
 
 def _get_first_endpoint(endpoints, region):
@@ -3274,11 +3289,19 @@ def _get_first_endpoint(endpoints, region):
            help=_('wrap PKI tokens to a specified length, or 0 to disable'))
 def do_credentials(cs, _args):
     """Show user credentials returned from auth."""
-    ensure_service_catalog_present(cs)
-    catalog = cs.client.service_catalog.catalog
-    utils.print_dict(catalog['access']['user'], "User Credentials",
-                     wrap=int(_args.wrap))
-    utils.print_dict(catalog['access']['token'], "Token", wrap=int(_args.wrap))
+    if isinstance(cs.client, client.SessionClient):
+        auth = cs.client.auth
+        sc = auth.get_access(cs.client.session).service_catalog
+        utils.print_dict(sc.catalog['user'], 'User Credentials',
+                         wrap=int(_args.wrap))
+        utils.print_dict(sc.get_token(), 'Token', wrap=int(_args.wrap))
+    else:
+        ensure_service_catalog_present(cs)
+        catalog = cs.client.service_catalog.catalog
+        utils.print_dict(catalog['access']['user'], "User Credentials",
+                         wrap=int(_args.wrap))
+        utils.print_dict(catalog['access']['token'], "Token",
+                         wrap=int(_args.wrap))
 
 
 @utils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
diff --git a/novaclient/v3/client.py b/novaclient/v3/client.py
index a30f919..6967ea4 100644
--- a/novaclient/v3/client.py
+++ b/novaclient/v3/client.py
@@ -174,11 +174,9 @@ class Client(object):
     def set_management_url(self, url):
         self.client.set_management_url(url)
 
-    @client._original_only
     def get_timings(self):
         return self.client.get_timings()
 
-    @client._original_only
     def reset_timings(self):
         self.client.reset_timings()
 
diff --git a/novaclient/v3/shell.py b/novaclient/v3/shell.py
index 3d17e28..af19809 100644
--- a/novaclient/v3/shell.py
+++ b/novaclient/v3/shell.py
@@ -31,6 +31,7 @@ from oslo.utils import strutils
 from oslo.utils import timeutils
 import six
 
+from novaclient import client
 from novaclient import exceptions
 from novaclient.openstack.common.gettextutils import _
 from novaclient.openstack.common import uuidutils
@@ -2140,7 +2141,12 @@ def do_usage(cs, args):
     if args.tenant:
         usage = cs.usage.get(args.tenant, start, end)
     else:
-        usage = cs.usage.get(cs.client.tenant_id, start, end)
+        if isinstance(cs.client, client.SessionClient):
+            auth = cs.client.auth
+            project_id = auth.get_auth_ref(cs.client.session).project_id
+            usage = cs.usage.get(project_id, start, end)
+        else:
+            usage = cs.usage.get(cs.client.tenant_id, start, end)
 
     print("Usage from %s to %s:" % (start.strftime(dateformat),
                                     end.strftime(dateformat)))
@@ -2645,23 +2651,33 @@ def ensure_service_catalog_present(cs):
 
 def do_endpoints(cs, _args):
     """Discover endpoints that get returned from the authenticate services."""
-    ensure_service_catalog_present(cs)
+    if isinstance(cs.client, client.SessionClient):
+        auth = cs.client.auth
+        sc = auth.get_Access(cs.client.session).service_catalog
+        for service in sc.get_data():
+            _print_endpoints(service, cs.client.region_name)
+    else:
+        ensure_service_catalog_present(cs)
 
-    catalog = cs.client.service_catalog.catalog
-    region = cs.client.region_name
+        catalog = cs.client.service_catalog.catalog
+        region = cs.client.region_name
 
-    for service in catalog['access']['serviceCatalog']:
-        name, endpoints = service["name"], service["endpoints"]
+        for service in catalog['access']['serviceCatalog']:
+            _print_endpoints(service, region)
 
-        try:
-            endpoint = _get_first_endpoint(endpoints, region)
-            utils.print_dict(endpoint, name)
-        except LookupError:
-            print(_("WARNING: %(service)s has no endpoint in %(region)s! "
-                    "Available endpoints for this service:") %
-                  {'service': name, 'region': region})
-            for other_endpoint in endpoints:
-                utils.print_dict(other_endpoint, name)
+
+def _print_endpoints(service, region):
+    name, endpoints = service["name"], service["endpoints"]
+
+    try:
+        endpoint = _get_first_endpoint(endpoints, region)
+        utils.print_dict(endpoint, name)
+    except LookupError:
+        print(_("WARNING: %(service)s has no endpoint in %(region)s! "
+                "Available endpoints for this service:") %
+              {'service': name, 'region': region})
+        for other_endpoint in endpoints:
+            utils.print_dict(other_endpoint, name)
 
 
 def _get_first_endpoint(endpoints, region):
@@ -2687,11 +2703,19 @@ def _get_first_endpoint(endpoints, region):
            help='wrap PKI tokens to a specified length, or 0 to disable')
 def do_credentials(cs, _args):
     """Show user credentials returned from auth."""
-    ensure_service_catalog_present(cs)
-    catalog = cs.client.service_catalog.catalog
-    utils.print_dict(catalog['access']['user'], "User Credentials",
-                     wrap=int(_args.wrap))
-    utils.print_dict(catalog['access']['token'], "Token", wrap=int(_args.wrap))
+    if isinstance(cs.client, client.SessionClient):
+        auth = cs.client.auth
+        sc = auth.get_access(cs.client.session).service_catalog
+        utils.print_dict(sc.catalog['user'], 'User Credentials',
+                         wrap=int(_args.wrap))
+        utils.print_dict(sc.get_token(), 'Token', wrap=int(_args.wrap))
+    else:
+        ensure_service_catalog_present(cs)
+        catalog = cs.client.service_catalog.catalog
+        utils.print_dict(catalog['access']['user'], "User Credentials",
+                         wrap=int(_args.wrap))
+        utils.print_dict(catalog['access']['token'], "Token",
+                         wrap=int(_args.wrap))
 
 
 def do_extension_list(cs, _args):
-- 
2.3.7

