diff options
author | Jenkins <jenkins@review.openstack.org> | 2014-11-24 11:59:00 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2014-11-24 11:59:00 +0000 |
commit | f09c4c8d3de632dc7b7b3b3a1c4eb32d7ccd3652 (patch) | |
tree | 4146a970a47f86deec11643547388a3f3549f6ee | |
parent | 781e0cb4837cd04a3dcd730d349507b019cd15db (diff) | |
parent | f248bf6d72247dd985539ca79af86ba4de32ba89 (diff) | |
download | python-saharaclient-f09c4c8d3de632dc7b7b3b3a1c4eb32d7ccd3652.tar.gz |
Merge "Add Support for Keystone V3 CLI"
-rw-r--r-- | saharaclient/api/client.py | 51 | ||||
-rw-r--r-- | saharaclient/shell.py | 129 | ||||
-rw-r--r-- | saharaclient/tests/unit/nova/test_shell.py | 96 |
3 files changed, 214 insertions, 62 deletions
diff --git a/saharaclient/api/client.py b/saharaclient/api/client.py index 8cfb8f3..c8b2a2e 100644 --- a/saharaclient/api/client.py +++ b/saharaclient/api/client.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from keystoneclient import adapter from keystoneclient.v2_0 import client as keystone_client_v2 from keystoneclient.v3 import client as keystone_client_v3 @@ -33,26 +34,38 @@ class Client(object): def __init__(self, username=None, api_key=None, project_id=None, project_name=None, auth_url=None, sahara_url=None, endpoint_type='publicURL', service_type='data_processing', - input_auth_token=None): + service_name=None, region_name=None, + input_auth_token=None, session=None, auth=None): + + keystone = None + sahara_catalog_url = sahara_url if not input_auth_token: - keystone = self.get_keystone_client(username=username, - api_key=api_key, - auth_url=auth_url, - project_id=project_id, - project_name=project_name) - input_auth_token = keystone.auth_token + + if session: + keystone = adapter.LegacyJsonAdapter( + session=session, + auth=auth, + interface=endpoint_type, + service_type=service_type, + service_name=service_name, + region_name=region_name) + input_auth_token = keystone.session.get_token(auth) + sahara_catalog_url = keystone.session.get_endpoint( + auth, interface=endpoint_type, service_type=service_type) + else: + keystone = self.get_keystone_client( + username=username, + api_key=api_key, + auth_url=auth_url, + project_id=project_id, + project_name=project_name) + input_auth_token = keystone.auth_token + if not input_auth_token: raise RuntimeError("Not Authorized") - sahara_catalog_url = sahara_url - if not sahara_url: - keystone = self.get_keystone_client(username=username, - api_key=api_key, - auth_url=auth_url, - token=input_auth_token, - project_id=project_id, - project_name=project_name) + if not sahara_catalog_url: catalog = keystone.service_catalog.get_endpoints(service_type) if service_type not in catalog: service_type = service_type.replace('-', '_') @@ -86,10 +99,12 @@ class Client(object): def get_keystone_client(self, username=None, api_key=None, auth_url=None, token=None, project_id=None, project_name=None): if not auth_url: - raise RuntimeError("No auth url specified") - imported_client = (keystone_client_v2 if "v2.0" in auth_url - else keystone_client_v3) + raise RuntimeError("No auth url specified") + if not getattr(self, "keystone_client", None): + imported_client = (keystone_client_v2 if "v2.0" in auth_url + else keystone_client_v3) + self.keystone_client = imported_client.Client( username=username, password=api_key, diff --git a/saharaclient/shell.py b/saharaclient/shell.py index 883e000..489138f 100644 --- a/saharaclient/shell.py +++ b/saharaclient/shell.py @@ -47,6 +47,11 @@ try: except ImportError: pass +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 + from saharaclient.api import client from saharaclient.api import shell as shell_api from saharaclient.openstack.common.apiclient import auth @@ -251,6 +256,7 @@ class OpenStackSaharaShell(object): help="Use the auth token cache. Defaults to False " "if env[OS_CACHE] is not set.") + # TODO(mattf) - add get_timings support to Client # parser.add_argument('--timings', # default=False, @@ -264,11 +270,6 @@ class OpenStackSaharaShell(object): # type=positive_non_zero_float, # help="Set HTTP call timeout (in seconds)") - parser.add_argument('--os-tenant-id', - metavar='<auth-tenant-id>', - default=cliutils.env('OS_TENANT_ID'), - help='Defaults to env[OS_TENANT_ID].') - # NA # parser.add_argument('--os-region-name', # metavar='<region-name>', @@ -324,22 +325,6 @@ class OpenStackSaharaShell(object): parser.add_argument('--sahara_api_version', help=argparse.SUPPRESS) - parser.add_argument('--os-cacert', - metavar='<ca-certificate>', - default=cliutils.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].') - -# NA -# parser.add_argument('--insecure', -# default=utils.env('NOVACLIENT_INSECURE', default=False), -# action='store_true', -# help="Explicitly allow novaclient to perform \"insecure\" " -# "SSL (https) requests. The server's certificate will " -# "not be verified against any certificate authorities. " -# "This option should be used with caution.") - parser.add_argument('--bypass-url', metavar='<bypass-url>', default=cliutils.env('BYPASS_URL', default=None), @@ -349,8 +334,25 @@ class OpenStackSaharaShell(object): parser.add_argument('--bypass_url', help=argparse.SUPPRESS) - # The auth-system-plugins might require some extra options - auth.load_auth_system_opts(parser) + parser.add_argument('--os-tenant-name', + default=cliutils.env('OS_TENANT_NAME'), + help='Defaults to env[OS_TENANT_NAME].') + + parser.add_argument('--os-tenant-id', + default=cliutils.env('OS_TENANT_ID'), + help='Defaults to env[OS_TENANT_ID].') + + parser.add_argument('--os-auth-system', + default=cliutils.env('OS_AUTH_SYSTEM'), + help='Defaults to env[OS_AUTH_SYSTEM].') + + parser.add_argument('--os-auth-token', + default=cliutils.env('OS_AUTH_TOKEN'), + help='Defaults to env[OS_AUTH_TOKEN].') + + # Use Keystoneclient API to parse authentication arguments + session.Session.register_cli_options(parser) + identity.Password.register_argparse_arguments(parser) return parser @@ -418,12 +420,27 @@ class OpenStackSaharaShell(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() (options, args) = parser.parse_known_args(argv) self.setup_debugging(options.debug) + self.options = options # NOTE(dtroyer): Hackery to handle --endpoint_type due to argparse # thinking usage-list --end is ambiguous; but it @@ -502,12 +519,6 @@ class OpenStackSaharaShell(object): "via either --os-username or " "env[OS_USERNAME]") - 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 os_auth_url: if os_auth_system and os_auth_system != 'keystone': os_auth_url = auth_plugin.get_auth_url() @@ -574,21 +585,63 @@ class OpenStackSaharaShell(object): # self.cs.client.password = os_password # self.cs.client.keyring_saver = helper -# NA -# try: -# if not utils.isunauthenticated(args.func): -# self.cs.authenticate() -# except exc.Unauthorized: -# raise exc.CommandError("Invalid OpenStack Sahara credentials.") -# except exc.AuthorizationFailure: -# raise exc.CommandError("Unable to authorize user") + # V3 stuff + project_info_provided = (self.options.os_tenant_name or + self.options.os_tenant_id or + (self.options.os_project_name and + (self.options.os_project_domain_name or + self.options.os_project_domain_id)) or + self.options.os_project_id) + + if (not project_info_provided): + raise exc.CommandError( + ("You must provide a tenant_name, tenant_id, " + "project_id or project_name (with " + "project_domain_name or project_domain_id) via " + " --os-tenant-name (env[OS_TENANT_NAME])," + " --os-tenant-id (env[OS_TENANT_ID])," + " --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 os_auth_url: + raise exc.CommandError( + "You must provide an auth url " + "via either --os-auth-url or env[OS_AUTH_URL]") + + keystone_session = None + keystone_auth = None + if not auth_plugin: + project_id = args.os_project_id or args.os_tenant_id + project_name = args.os_project_name or args.os_tenant_name + + keystone_session = (session.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) self.cs = client.Client(username=os_username, api_key=os_password, project_id=os_tenant_id, project_name=os_tenant_name, auth_url=os_auth_url, - sahara_url=bypass_url) + sahara_url=bypass_url, + session=keystone_session, + auth=keystone_auth) args.func(self.cs, args) diff --git a/saharaclient/tests/unit/nova/test_shell.py b/saharaclient/tests/unit/nova/test_shell.py index 95e7e96..c9572ae 100644 --- a/saharaclient/tests/unit/nova/test_shell.py +++ b/saharaclient/tests/unit/nova/test_shell.py @@ -170,9 +170,19 @@ 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 = ( + 'You must provide a tenant_name, tenant_id, ' + 'project_id or project_name (with ' + 'project_domain_name or project_domain_id) via ' + ' --os-tenant-name (env[OS_TENANT_NAME]),' + ' --os-tenant-id (env[OS_TENANT_ID]),' + ' --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])', + ) self.make_env(exclude='OS_TENANT_NAME') try: self.shell('plugin-list') @@ -182,9 +192,19 @@ 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 = ( + 'You must provide a tenant_name, tenant_id, ' + 'project_id or project_name (with ' + 'project_domain_name or project_domain_id) via ' + ' --os-tenant-name (env[OS_TENANT_NAME]),' + ' --os-tenant-id (env[OS_TENANT_ID]),' + ' --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])', + ) self.make_env(exclude='OS_TENANT_ID', fake_env=FAKE_ENV2) try: self.shell('plugin-list') @@ -281,3 +301,67 @@ class ShellTest(utils.TestCase): self.make_env() stdout, stderr = self.shell('image-list') self.assertEqual(ex, (stdout + stderr)) + + +class ShellTestKeystoneV3(ShellTest): + + FAKE_V3_ENV = {'OS_USERNAME': 'username', + 'OS_PASSWORD': 'password', + 'OS_PROJECT_NAME': 'project_name', + 'OS_PROJECT_DOMAIN_NAME': 'project_domain_name', + 'OS_USER_DOMAIN_NAME': 'user_domain_name', + 'OS_AUTH_URL': 'http://no.where/v3'} + + version_id = u'v3' + links = [{u'href': u'http://no.where/v3', u'rel': u'self'}] + + def make_env(self, exclude=None, fake_env=FAKE_V3_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 test_no_tenant_name(self): + # In V3, tenant_name = project_name + required = ( + 'You must provide a tenant_name, tenant_id, ' + 'project_id or project_name (with ' + 'project_domain_name or project_domain_id) via ' + ' --os-tenant-name (env[OS_TENANT_NAME]),' + ' --os-tenant-id (env[OS_TENANT_ID]),' + ' --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])', + ) + self.make_env(exclude='OS_PROJECT_NAME') + try: + self.shell('plugin-list') + except exceptions.CommandError as message: + self.assertEqual(required, message.args) + else: + self.fail('CommandError not raised') + + def test_job_list(self): + expected = '\n'.join([ + '+----+------------+--------+', + '| id | cluster_id | status |', + '+----+------------+--------+', + '+----+------------+--------+', + '' + ]) + + mock_session_class_name = 'keystoneclient.adapter.LegacyJsonAdapter' + mock_job_executions_class_name = ( + 'saharaclient.api.job_executions.JobExecutionsManager') + + with mock.patch(mock_session_class_name) as mock_session: + with mock.patch(mock_job_executions_class_name): + ms = mock_session.return_value + ms.session.get_endpoint.return_value = 'http://no.where' + + self.make_env() + stdout, stderr = self.shell('job-list') + self.assertEqual((stdout + stderr), expected) |