From 06d6a356f6bc30b413a932bb2c3d97cd082d508d Mon Sep 17 00:00:00 2001 From: lvdongbing Date: Tue, 10 May 2016 22:48:09 -0400 Subject: [PATCH] Modify v1 client --- .gitignore | 2 + bileanclient/tests/unit/test_shell.py | 689 ++++++++++---------------- bileanclient/v1/policies.py | 28 +- bileanclient/v1/resources.py | 16 +- bileanclient/v1/rules.py | 37 +- bileanclient/v1/users.py | 4 +- 6 files changed, 345 insertions(+), 431 deletions(-) diff --git a/.gitignore b/.gitignore index 9652773..bcd00b1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ __pycache__/ *.py[cod] +*.sw? + # C extensions *.so diff --git a/bileanclient/tests/unit/test_shell.py b/bileanclient/tests/unit/test_shell.py index 0869544..40a90bf 100644 --- a/bileanclient/tests/unit/test_shell.py +++ b/bileanclient/tests/unit/test_shell.py @@ -41,7 +41,7 @@ from bileanclient import shell as openstack_shell from bileanclient.tests.unit import utils as testutils -DEFAULT_IMAGE_URL = 'http://127.0.0.1:8770/' +DEFAULT_BILEAN_URL = 'http://127.0.0.1:5000/' DEFAULT_USERNAME = 'username' DEFAULT_PASSWORD = 'password' DEFAULT_TENANT_ID = 'tenant_id' @@ -51,33 +51,33 @@ DEFAULT_USER_DOMAIN_NAME = 'user_domain_name' DEFAULT_UNVERSIONED_AUTH_URL = 'http://127.0.0.1:5000/' DEFAULT_V2_AUTH_URL = '%sv2.0' % DEFAULT_UNVERSIONED_AUTH_URL DEFAULT_V3_AUTH_URL = '%sv3' % DEFAULT_UNVERSIONED_AUTH_URL -DEFAULT_AUTH_TOKEN = ' 3bcc3d3a03f44e3d8377f9247b0ad155' -TEST_SERVICE_URL = 'http://127.0.0.1:8770/' +DEFAULT_AUTH_TOKEN = 'ba5501a434914768824374764adb8014' +TEST_SERVICE_URL = 'http://127.0.0.1:5000/' FAKE_V2_ENV = {'OS_USERNAME': DEFAULT_USERNAME, 'OS_PASSWORD': DEFAULT_PASSWORD, 'OS_TENANT_NAME': DEFAULT_TENANT_NAME, 'OS_AUTH_URL': DEFAULT_V2_AUTH_URL, - 'OS_IMAGE_URL': DEFAULT_IMAGE_URL} + 'OS_IMAGE_URL': DEFAULT_BILEAN_URL} FAKE_V3_ENV = {'OS_USERNAME': DEFAULT_USERNAME, 'OS_PASSWORD': DEFAULT_PASSWORD, 'OS_PROJECT_ID': DEFAULT_PROJECT_ID, 'OS_USER_DOMAIN_NAME': DEFAULT_USER_DOMAIN_NAME, 'OS_AUTH_URL': DEFAULT_V3_AUTH_URL, - 'OS_IMAGE_URL': DEFAULT_IMAGE_URL} + 'OS_IMAGE_URL': DEFAULT_BILEAN_URL} TOKEN_ID = uuid.uuid4().hex V2_TOKEN = ks_fixture.V2Token(token_id=TOKEN_ID) V2_TOKEN.set_scope() _s = V2_TOKEN.add_service('billing', name='bilean') -_s.add_endpoint(DEFAULT_IMAGE_URL) +_s.add_endpoint(DEFAULT_BILEAN_URL) V3_TOKEN = ks_fixture.V3Token() V3_TOKEN.set_project_scope() _s = V3_TOKEN.add_service('billing', name='bilean') -_s.add_standard_endpoints(public=DEFAULT_IMAGE_URL) +_s.add_standard_endpoints(public=DEFAULT_BILEAN_URL) class ShellTest(testutils.TestCase): @@ -99,7 +99,7 @@ class ShellTest(testutils.TestCase): self.requests = self.useFixture(rm_fixture.Fixture()) json_list = ks_fixture.DiscoveryList(DEFAULT_UNVERSIONED_AUTH_URL) - self.requests.get(DEFAULT_IMAGE_URL, json=json_list, status_code=300) + self.requests.get(DEFAULT_BILEAN_URL, json=json_list, status_code=300) json_v2 = {'version': ks_fixture.V2Discovery(DEFAULT_V2_AUTH_URL)} self.requests.get(DEFAULT_V2_AUTH_URL, json=json_v2) @@ -217,30 +217,35 @@ class ShellTest(testutils.TestCase): formatter_class=openstack_shell.HelpFormatter,) self.assertEqual(str(expected), str(actual_parser)) -# @mock.patch.object(openstack_shell.BileanShell, -# '_get_keystone_session') -# @mock.patch.object(openstack_shell.BileanShell, -# '_get_keystone_auth') -# def test_cert_and_key_args_interchangeable(self, -# mock_keystone_session, -# mock_keystone_auth): -# # make sure --os-cert and --os-key are passed correctly -# args = ('--bilean-api-version 1 ' -# '--os-cert mycert ' -# '--os-key mykey user-list') -# shell(args) -# assert mock_keystone_session.called -# args, kwargs = mock_keystone_session.call_args -# -# self.assertEqual('mycert', kwargs['cert']) -# self.assertEqual('mykey', kwargs['key']) -# + @mock.patch.object(openstack_shell.BileanShell, + '_get_versioned_client') + def test_cert_and_key_args_interchangeable(self, + mock_versioned_client): + # make sure --os-cert and --os-key are passed correctly + args = ('--os-cert mycert ' + '--os-key mykey user-list') + shell(args) + assert mock_versioned_client.called + ((api_version, args), kwargs) = mock_versioned_client.call_args + self.assertEqual('mycert', args.os_cert) + self.assertEqual('mykey', args.os_key) + + # make sure we get the same thing with --cert-file and --key-file + args = ('--cert-file mycertfile ' + '--key-file mykeyfile user-list') + bilean_shell = openstack_shell.BileanShell() + bilean_shell.main(args.split()) + assert mock_versioned_client.called + ((api_version, args), kwargs) = mock_versioned_client.call_args + self.assertEqual('mycertfile', args.os_cert) + self.assertEqual('mykeyfile', args.os_key) + @mock.patch('bileanclient.v1.client.Client') def test_no_auth_with_token_and_bilean_url(self, mock_client): # test no authentication is required if both token and endpoint url # are specified - args = ('--os-bilean-api-version 1 --os-auth-token mytoken' - ' --os-bilean-url http://host:1234/v1 user-list') + args = ('--os-auth-token mytoken ' + '--os-bilean-url http://host:1234/v1 user-list') bilean_shell = openstack_shell.BileanShell() bilean_shell.main(args.split()) assert mock_client.called @@ -248,389 +253,245 @@ class ShellTest(testutils.TestCase): self.assertEqual('mytoken', kwargs['token']) self.assertEqual('http://host:1234', args[0]) -# @mock.patch('bileanclient.v1.client.Client') -# def test_no_auth_with_token_and_image_url_with_v1(self, v1_client): -# # test no authentication is required if both token and endpoint url -# # are specified -# args = ('--os-image-api-version 1 --os-auth-token mytoken' -# ' --os-image-url https://image:1234/v1 image-list') -# bilean_shell = openstack_shell.OpenStackImagesShell() -# bilean_shell.main(args.split()) -# assert v1_client.called -# (args, kwargs) = v1_client.call_args -# self.assertEqual('mytoken', kwargs['token']) -# self.assertEqual('https://image:1234', args[0]) -# -# @mock.patch('bileanclient.v2.client.Client') -# def test_no_auth_with_token_and_image_url_with_v2(self, v2_client): -# # test no authentication is required if both token and endpoint url -# # are specified -# args = ('--os-image-api-version 2 --os-auth-token mytoken ' -# '--os-image-url https://image:1234 image-list') -# bilean_shell = openstack_shell.OpenStackImagesShell() -# bilean_shell.main(args.split()) -# self.assertTrue(v2_client.called) -# (args, kwargs) = v2_client.call_args -# self.assertEqual('mytoken', kwargs['token']) -# self.assertEqual('https://image:1234', args[0]) -# -# def _assert_auth_plugin_args(self): -# # make sure our auth plugin is invoked with the correct args -# self.assertFalse(self.v3_auth.called) -# -# body = json.loads(self.v2_auth.last_request.body) -# -# self.assertEqual(self.auth_env['OS_TENANT_NAME'], -# body['auth']['tenantName']) -# self.assertEqual(self.auth_env['OS_USERNAME'], -# body['auth']['passwordCredentials']['username']) -# self.assertEqual(self.auth_env['OS_PASSWORD'], -# body['auth']['passwordCredentials']['password']) -# -# @mock.patch.object(openstack_shell.OpenStackImagesShell, '_cache_schemas', -# return_value=False) -# @mock.patch('bileanclient.v2.client.Client') -# def test_auth_plugin_invocation_without_version(self, -# v2_client, -# cache_schemas): -# -# cli2 = mock.MagicMock() -# v2_client.return_value = cli2 -# cli2.http_client.get.return_value = (None, {'versions': -# [{'id': 'v2'}]}) -# -# args = 'image-list' -# bilean_shell = openstack_shell.OpenStackImagesShell() -# bilean_shell.main(args.split()) -# # NOTE(flaper87): this currently calls auth twice since it'll -# # authenticate to get the version list *and* to execute the command. -# # This is not the ideal behavior and it should be fixed in a follow -# # up patch. -# -# @mock.patch('bileanclient.v1.client.Client') -# def test_auth_plugin_invocation_with_v1(self, v1_client): -# args = '--os-image-api-version 1 image-list' -# bilean_shell = openstack_shell.OpenStackImagesShell() -# bilean_shell.main(args.split()) -# self.assertEqual(0, self.v2_auth.call_count) -# -# @mock.patch('bileanclient.v2.client.Client') -# def test_auth_plugin_invocation_with_v2(self, -# v2_client): -# args = '--os-image-api-version 2 image-list' -# bilean_shell = openstack_shell.OpenStackImagesShell() -# bilean_shell.main(args.split()) -# self.assertEqual(0, self.v2_auth.call_count) -# -# @mock.patch('bileanclient.v1.client.Client') -# def test_auth_plugin_invocation_with_unversioned_auth_url_with_v1( -# self, v1_client): -# args = ('--os-image-api-version 1 --os-auth-url %s image-list' % -# DEFAULT_UNVERSIONED_AUTH_URL) -# bilean_shell = openstack_shell.OpenStackImagesShell() -# bilean_shell.main(args.split()) -# -# @mock.patch('bileanclient.v2.client.Client') -# @mock.patch.object(openstack_shell.OpenStackImagesShell, '_cache_schemas', -# return_value=False) -# def test_auth_plugin_invocation_with_unversioned_auth_url_with_v2( -# self, v2_client, cache_schemas): -# args = ('--os-auth-url %s --os-image-api-version 2 ' -# 'image-list') % DEFAULT_UNVERSIONED_AUTH_URL -# bilean_shell = openstack_shell.OpenStackImagesShell() -# bilean_shell.main(args.split()) -# -# @mock.patch('bileanclient.Client') -# def test_endpoint_token_no_auth_req(self, mock_client): -# -# def verify_input(version=None, endpoint=None, *args, **kwargs): -# self.assertIn('token', kwargs) -# self.assertEqual(TOKEN_ID, kwargs['token']) -# self.assertEqual(DEFAULT_IMAGE_URL, endpoint) -# return mock.MagicMock() -# -# mock_client.side_effect = verify_input -# bilean_shell = openstack_shell.OpenStackImagesShell() -# args = ['--os-image-api-version', '2', -# '--os-auth-token', TOKEN_ID, -# '--os-image-url', DEFAULT_IMAGE_URL, -# 'image-list'] -# -# bilean_shell.main(args) -# self.assertEqual(1, mock_client.call_count) -# -# @mock.patch('bileanclient.v2.client.Client') -# def test_password_prompted_with_v2(self, v2_client): -# self.requests.post(self.token_url, exc=requests.ConnectionError) -# -# cli2 = mock.MagicMock() -# v2_client.return_value = cli2 -# cli2.http_client.get.return_value = (None, {'versions': []}) -# bilean_shell = openstack_shell.OpenStackImagesShell() -# os.environ['OS_PASSWORD'] = 'password' -# self.assertRaises(exc.CommunicationError, -# bilean_shell.main, ['image-list']) -# -# @mock.patch('sys.stdin', side_effect=mock.MagicMock) -# @mock.patch('getpass.getpass', side_effect=EOFError) -# @mock.patch('bileanclient.v2.client.Client') -# def test_password_prompted_ctrlD_with_v2(self, v2_client, -# mock_getpass, mock_stdin): -# cli2 = mock.MagicMock() -# v2_client.return_value = cli2 -# cli2.http_client.get.return_value = (None, {'versions': []}) -# -# bilean_shell = openstack_shell.OpenStackImagesShell() -# self.make_env(exclude='OS_PASSWORD') -# # We should get Command Error because we mock Ctl-D. -# self.assertRaises(exc.CommandError, bilean_shell.main, ['image-list']) -# # Make sure we are actually prompted. -# mock_getpass.assert_called_with('OS Password: ') -# -# @mock.patch( -# 'bileanclient.shell.OpenStackImagesShell._get_keystone_session') -# @mock.patch.object(openstack_shell.OpenStackImagesShell, '_cache_schemas', -# return_value=False) -# def test_no_auth_with_proj_name(self, cache_schemas, session): -# with mock.patch('bileanclient.v2.client.Client'): -# args = ('--os-project-name myname ' -# '--os-project-domain-name mydomain ' -# '--os-project-domain-id myid ' -# '--os-image-api-version 2 image-list') -# bilean_shell = openstack_shell.OpenStackImagesShell() -# bilean_shell.main(args.split()) -# ((args), kwargs) = session.call_args -# self.assertEqual('myname', kwargs['project_name']) -# self.assertEqual('mydomain', kwargs['project_domain_name']) -# self.assertEqual('myid', kwargs['project_domain_id']) -# -# @mock.patch.object(openstack_shell.OpenStackImagesShell, 'main') -# def test_shell_keyboard_interrupt(self, mock_bilean_shell): -# # Ensure that exit code is 130 for KeyboardInterrupt -# try: -# mock_bilean_shell.side_effect = KeyboardInterrupt() -# openstack_shell.main() -# except SystemExit as ex: -# self.assertEqual(130, ex.code) -# -# @mock.patch('bileanclient.common.utils.exit', side_effect=utils.exit) -# def test_shell_illegal_version(self, mock_exit): -# # Only int versions are allowed on cli -# shell = openstack_shell.OpenStackImagesShell() -# argstr = '--os-image-api-version 1.1 image-list' -# try: -# shell.main(argstr.split()) -# except SystemExit as ex: -# self.assertEqual(1, ex.code) -# msg = ("Invalid API version parameter. " -# "Supported values are %s" % openstack_shell.SUPPORTED_VERSIONS) -# mock_exit.assert_called_with(msg=msg) -# -# @mock.patch('bileanclient.common.utils.exit', side_effect=utils.exit) -# def test_shell_unsupported_version(self, mock_exit): -# # Test an integer version which is not supported (-1) -# shell = openstack_shell.OpenStackImagesShell() -# argstr = '--os-image-api-version -1 image-list' -# try: -# shell.main(argstr.split()) -# except SystemExit as ex: -# self.assertEqual(1, ex.code) -# msg = ("Invalid API version parameter. " -# "Supported values are %s" % openstack_shell.SUPPORTED_VERSIONS) -# mock_exit.assert_called_with(msg=msg) -# -# @mock.patch.object(openstack_shell.OpenStackImagesShell, -# 'get_subcommand_parser') -# def test_shell_import_error_with_mesage(self, mock_parser): -# msg = 'Unable to import module xxx' -# mock_parser.side_effect = ImportError('%s' % msg) -# shell = openstack_shell.OpenStackImagesShell() -# argstr = '--os-image-api-version 2 image-list' -# try: -# shell.main(argstr.split()) -# self.fail('No import error returned') -# except ImportError as e: -# self.assertEqual(msg, str(e)) -# -# @mock.patch.object(openstack_shell.OpenStackImagesShell, -# 'get_subcommand_parser') -# def test_shell_import_error_default_message(self, mock_parser): -# mock_parser.side_effect = ImportError -# shell = openstack_shell.OpenStackImagesShell() -# argstr = '--os-image-api-version 2 image-list' -# try: -# shell.main(argstr.split()) -# self.fail('No import error returned') -# except ImportError as e: -# msg = 'Unable to import module. Re-run with --debug for more info.' -# self.assertEqual(msg, str(e)) -# -# @mock.patch('bileanclient.v2.client.Client') -# @mock.patch('bileanclient.v1.images.ImageManager.list') -# def test_shell_v1_fallback_from_v2(self, v1_imgs, v2_client): -# self.make_env() -# cli2 = mock.MagicMock() -# v2_client.return_value = cli2 -# cli2.http_client.get.return_value = (None, {'versions': []}) -# args = 'image-list' -# bilean_shell = openstack_shell.OpenStackImagesShell() -# bilean_shell.main(args.split()) -# self.assertFalse(cli2.schemas.get.called) -# self.assertTrue(v1_imgs.called) -# -# @mock.patch.object(openstack_shell.OpenStackImagesShell, -# '_cache_schemas') -# @mock.patch('bileanclient.v2.client.Client') -# def test_shell_no_fallback_from_v2(self, v2_client, cache_schemas): -# self.make_env() -# cli2 = mock.MagicMock() -# v2_client.return_value = cli2 -# cli2.http_client.get.return_value = (None, -# {'versions': [{'id': 'v2'}]}) -# cache_schemas.return_value = False -# args = 'image-list' -# bilean_shell = openstack_shell.OpenStackImagesShell() -# bilean_shell.main(args.split()) -# self.assertTrue(cli2.images.list.called) -# -# @mock.patch('bileanclient.v1.client.Client') -# def test_auth_plugin_invocation_without_username_with_v1(self, v1_client): -# self.make_env(exclude='OS_USERNAME') -# args = '--os-image-api-version 2 image-list' -# bilean_shell = openstack_shell.OpenStackImagesShell() -# self.assertRaises(exc.CommandError, bilean_shell.main, args.split()) -# -# @mock.patch('bileanclient.v2.client.Client') -# def test_auth_plugin_invocation_without_username_with_v2(self, v2_client): -# self.make_env(exclude='OS_USERNAME') -# args = '--os-image-api-version 2 image-list' -# bilean_shell = openstack_shell.OpenStackImagesShell() -# self.assertRaises(exc.CommandError, bilean_shell.main, args.split()) -# -# @mock.patch('bileanclient.v1.client.Client') -# def test_auth_plugin_invocation_without_auth_url_with_v1(self, v1_client): -# self.make_env(exclude='OS_AUTH_URL') -# args = '--os-image-api-version 1 image-list' -# bilean_shell = openstack_shell.OpenStackImagesShell() -# self.assertRaises(exc.CommandError, bilean_shell.main, args.split()) -# -# @mock.patch('bileanclient.v2.client.Client') -# def test_auth_plugin_invocation_without_auth_url_with_v2(self, v2_client): -# self.make_env(exclude='OS_AUTH_URL') -# args = '--os-image-api-version 2 image-list' -# bilean_shell = openstack_shell.OpenStackImagesShell() -# self.assertRaises(exc.CommandError, bilean_shell.main, args.split()) -# -# @mock.patch('bileanclient.v1.client.Client') -# def test_auth_plugin_invocation_without_tenant_with_v1(self, v1_client): -# if 'OS_TENANT_NAME' in os.environ: -# self.make_env(exclude='OS_TENANT_NAME') -# if 'OS_PROJECT_ID' in os.environ: -# self.make_env(exclude='OS_PROJECT_ID') -# args = '--os-image-api-version 1 image-list' -# bilean_shell = openstack_shell.OpenStackImagesShell() -# self.assertRaises(exc.CommandError, bilean_shell.main, args.split()) -# -# @mock.patch('bileanclient.v2.client.Client') -# @mock.patch.object(openstack_shell.OpenStackImagesShell, '_cache_schemas', -# return_value=False) -# def test_auth_plugin_invocation_without_tenant_with_v2(self, v2_client, -# cache_schemas): -# if 'OS_TENANT_NAME' in os.environ: -# self.make_env(exclude='OS_TENANT_NAME') -# if 'OS_PROJECT_ID' in os.environ: -# self.make_env(exclude='OS_PROJECT_ID') -# args = '--os-image-api-version 2 image-list' -# bilean_shell = openstack_shell.OpenStackImagesShell() -# self.assertRaises(exc.CommandError, bilean_shell.main, args.split()) -# -# @mock.patch('sys.argv', ['bilean']) -# @mock.patch('sys.stdout', six.StringIO()) -# @mock.patch('sys.stderr', six.StringIO()) -# def test_main_noargs(self): -# # Ensure that main works with no command-line arguments -# try: -# openstack_shell.main() -# except SystemExit: -# self.fail('Unexpected SystemExit') -# -# # We expect the normal v2 usage as a result -# expected = ['Command-line interface to the OpenStack Images API', -# 'image-list', -# 'image-deactivate', -# 'location-add'] -# for output in expected: -# self.assertIn(output, -# sys.stdout.getvalue()) -# -# @mock.patch('bileanclient.v2.client.Client') -# @mock.patch('bileanclient.v1.shell.do_image_list') -# @mock.patch('bileanclient.shell.logging.basicConfig') -# def test_setup_debug(self, conf, func, v2_client): -# cli2 = mock.MagicMock() -# v2_client.return_value = cli2 -# cli2.http_client.get.return_value = (None, {'versions': []}) -# args = '--debug image-list' -# bilean_shell = openstack_shell.OpenStackImagesShell() -# bilean_shell.main(args.split()) -# bilean_logger = logging.getLogger('bileanclient') -# self.assertEqual(bilean_logger.getEffectiveLevel(), logging.DEBUG) -# conf.assert_called_with(level=logging.DEBUG) -# -# -#class ShellTestWithKeystoneV3Auth(ShellTest): -# # auth environment to use -# auth_env = FAKE_V3_ENV.copy() -# token_url = DEFAULT_V3_AUTH_URL + '/auth/tokens' -# -# def _assert_auth_plugin_args(self): -# self.assertFalse(self.v2_auth.called) -# -# body = json.loads(self.v3_auth.last_request.body) -# user = body['auth']['identity']['password']['user'] -# -# self.assertEqual(self.auth_env['OS_USERNAME'], user['name']) -# self.assertEqual(self.auth_env['OS_PASSWORD'], user['password']) -# self.assertEqual(self.auth_env['OS_USER_DOMAIN_NAME'], -# user['domain']['name']) -# self.assertEqual(self.auth_env['OS_PROJECT_ID'], -# body['auth']['scope']['project']['id']) -# -# @mock.patch('bileanclient.v1.client.Client') -# def test_auth_plugin_invocation_with_v1(self, v1_client): -# args = '--os-image-api-version 1 image-list' -# bilean_shell = openstack_shell.OpenStackImagesShell() -# bilean_shell.main(args.split()) -# self.assertEqual(0, self.v3_auth.call_count) -# -# @mock.patch('bileanclient.v2.client.Client') -# def test_auth_plugin_invocation_with_v2(self, v2_client): -# args = '--os-image-api-version 2 image-list' -# bilean_shell = openstack_shell.OpenStackImagesShell() -# bilean_shell.main(args.split()) -# self.assertEqual(0, self.v3_auth.call_count) -# -# @mock.patch('keystoneclient.discover.Discover', -# side_effect=ks_exc.ClientException()) -# def test_api_discovery_failed_with_unversioned_auth_url(self, -# discover): -# args = ('--os-image-api-version 2 --os-auth-url %s image-list' -# % DEFAULT_UNVERSIONED_AUTH_URL) -# bilean_shell = openstack_shell.OpenStackImagesShell() -# self.assertRaises(exc.CommandError, bilean_shell.main, args.split()) -# -# def test_bash_completion(self): -# stdout, stderr = self.shell('--os-image-api-version 2 bash_completion') -# # just check we have some output -# required = [ -# '--status', -# 'image-create', -# 'help', -# '--size'] -# for r in required: -# self.assertIn(r, stdout.split()) -# avoided = [ -# 'bash_completion', -# 'bash-completion'] -# for r in avoided: -# self.assertNotIn(r, stdout.split()) + def _assert_auth_plugin_args(self): + # make sure our auth plugin is invoked with the correct args + self.assertFalse(self.v3_auth.called) + + body = json.loads(self.v2_auth.last_request.body) + + self.assertEqual(self.auth_env['OS_TENANT_NAME'], + body['auth']['tenantName']) + self.assertEqual(self.auth_env['OS_USERNAME'], + body['auth']['passwordCredentials']['username']) + self.assertEqual(self.auth_env['OS_PASSWORD'], + body['auth']['passwordCredentials']['password']) + + @mock.patch('bileanclient.v1.client.Client') + def test_auth_plugin_invocation(self, v1_client): + args = 'user-list' + bilean_shell = openstack_shell.BileanShell() + bilean_shell.main(args.split()) + self.assertEqual(0, self.v2_auth.call_count) + + @mock.patch('bileanclient.v1.client.Client') + def test_auth_plugin_invocation_with_unversioned_auth_url( + self, v1_client): + args = ('--os-auth-url %s user-list' % DEFAULT_UNVERSIONED_AUTH_URL) + bilean_shell = openstack_shell.BileanShell() + bilean_shell.main(args.split()) + + @mock.patch('bileanclient.Client') + def test_endpoint_token_no_auth_req(self, mock_client): + + def verify_input(version=None, endpoint=None, *args, **kwargs): + self.assertIn('token', kwargs) + self.assertEqual(TOKEN_ID, kwargs['token']) + self.assertEqual(DEFAULT_BILEAN_URL, endpoint) + return mock.MagicMock() + + mock_client.side_effect = verify_input + bilean_shell = openstack_shell.BileanShell() + args = ['--os-auth-token', TOKEN_ID, + '--os-bilean-url', DEFAULT_BILEAN_URL, + 'user-list'] + + bilean_shell.main(args) + self.assertEqual(1, mock_client.call_count) + + @mock.patch('sys.stdin', side_effect=mock.MagicMock) + @mock.patch('getpass.getpass', side_effect=EOFError) + @mock.patch('bileanclient.v1.client.Client') + def test_password_prompted_ctrlD(self, v1_client, + mock_getpass, mock_stdin): + cli = mock.MagicMock() + v1_client.return_value = cli + cli.http_client.get.return_value = (None, {'versions': []}) + + bilean_shell = openstack_shell.BileanShell() + self.make_env(exclude='OS_PASSWORD') + # We should get Command Error because we mock Ctl-D. + self.assertRaises(exc.CommandError, bilean_shell.main, ['user-list']) + # Make sure we are actually prompted. + mock_getpass.assert_called_with('OS Password: ') + + @mock.patch( + 'bileanclient.shell.BileanShell._get_keystone_session') + def test_no_auth_with_proj_name(self, session): + with mock.patch('bileanclient.v1.client.Client'): + args = ('--os-project-name myname ' + '--os-project-domain-name mydomain ' + '--os-project-domain-id myid user-list') + bilean_shell = openstack_shell.BileanShell() + bilean_shell.main(args.split()) + ((args), kwargs) = session.call_args + self.assertEqual('myname', kwargs['project_name']) + self.assertEqual('mydomain', kwargs['project_domain_name']) + self.assertEqual('myid', kwargs['project_domain_id']) + + @mock.patch.object(openstack_shell.BileanShell, 'main') + def test_shell_keyboard_interrupt(self, mock_bilean_shell): + # Ensure that exit code is 130 for KeyboardInterrupt + try: + mock_bilean_shell.side_effect = KeyboardInterrupt() + openstack_shell.main() + except SystemExit as ex: + self.assertEqual(130, ex.code) + + @mock.patch('bileanclient.common.utils.exit', side_effect=utils.exit) + def test_shell_illegal_version(self, mock_exit): + # Only int versions are allowed on cli + shell = openstack_shell.BileanShell() + argstr = '--os-bilean-api-version 1.1 user-list' + try: + shell.main(argstr.split()) + except SystemExit as ex: + self.assertEqual(1, ex.code) + msg = ("Invalid API version parameter. " + "Supported values are %s" % openstack_shell.SUPPORTED_VERSIONS) + mock_exit.assert_called_with(msg=msg) + + @mock.patch('bileanclient.common.utils.exit', side_effect=utils.exit) + def test_shell_unsupported_version(self, mock_exit): + # Test an integer version which is not supported (-1) + shell = openstack_shell.BileanShell() + argstr = '--os-bilean-api-version -1 user-list' + try: + shell.main(argstr.split()) + except SystemExit as ex: + self.assertEqual(1, ex.code) + msg = ("Invalid API version parameter. " + "Supported values are %s" % openstack_shell.SUPPORTED_VERSIONS) + mock_exit.assert_called_with(msg=msg) + + @mock.patch.object(openstack_shell.BileanShell, + 'get_subcommand_parser') + def test_shell_import_error_with_mesage(self, mock_parser): + msg = 'Unable to import module xxx' + mock_parser.side_effect = ImportError('%s' % msg) + shell = openstack_shell.BileanShell() + argstr = '--os-bilean-api-version 1 user-list' + try: + shell.main(argstr.split()) + self.fail('No import error returned') + except ImportError as e: + self.assertEqual(msg, str(e)) + + @mock.patch.object(openstack_shell.BileanShell, + 'get_subcommand_parser') + def test_shell_import_error_default_message(self, mock_parser): + mock_parser.side_effect = ImportError + shell = openstack_shell.BileanShell() + argstr = '--os-bilean-api-version 1 user-list' + try: + shell.main(argstr.split()) + self.fail('No import error returned') + except ImportError as e: + msg = 'Unable to import module. Re-run with --debug for more info.' + self.assertEqual(msg, str(e)) + + @mock.patch('bileanclient.v1.client.Client') + def test_auth_plugin_invocation_without_username(self, v1_client): + self.make_env(exclude='OS_USERNAME') + args = '--os-bilean-api-version 1 user-list' + bilean_shell = openstack_shell.BileanShell() + self.assertRaises(exc.CommandError, bilean_shell.main, args.split()) + + @mock.patch('bileanclient.v1.client.Client') + def test_auth_plugin_invocation_without_auth_url(self, v1_client): + self.make_env(exclude='OS_AUTH_URL') + args = '--os-bilean-api-version 1 user-list' + bilean_shell = openstack_shell.BileanShell() + self.assertRaises(exc.CommandError, bilean_shell.main, args.split()) + + @mock.patch('bileanclient.v1.client.Client') + def test_auth_plugin_invocation_without_tenant(self, v1_client): + if 'OS_TENANT_NAME' in os.environ: + self.make_env(exclude='OS_TENANT_NAME') + if 'OS_PROJECT_ID' in os.environ: + self.make_env(exclude='OS_PROJECT_ID') + args = '--os-bilean-api-version 1 user-list' + bilean_shell = openstack_shell.BileanShell() + self.assertRaises(exc.CommandError, bilean_shell.main, args.split()) + + @mock.patch('sys.argv', ['bilean']) + @mock.patch('sys.stdout', six.StringIO()) + @mock.patch('sys.stderr', six.StringIO()) + def test_main_noargs(self): + # Ensure that main works with no command-line arguments + try: + openstack_shell.main() + except SystemExit: + self.fail('Unexpected SystemExit') + + # We expect the normal usage as a result + expected = ['Command-line interface to the OpenStack Bilean API', + 'user-list', + 'rule-list'] + for output in expected: + self.assertIn(output, + sys.stdout.getvalue()) + + @mock.patch('bileanclient.v1.client.Client') + @mock.patch('bileanclient.v1.shell.do_user_list') + @mock.patch('bileanclient.shell.logging.basicConfig') + def test_setup_debug(self, conf, func, v1_client): + cli = mock.MagicMock() + v1_client.return_value = cli + cli.http_client.get.return_value = (None, {'versions': []}) + args = '--debug user-list' + bilean_shell = openstack_shell.BileanShell() + bilean_shell.main(args.split()) + bilean_logger = logging.getLogger('bileanclient') + self.assertEqual(bilean_logger.getEffectiveLevel(), logging.DEBUG) + conf.assert_called_with(level=logging.DEBUG) + + +class ShellTestWithKeystoneV3Auth(ShellTest): + # auth environment to use + auth_env = FAKE_V3_ENV.copy() + token_url = DEFAULT_V3_AUTH_URL + '/auth/tokens' + + def _assert_auth_plugin_args(self): + self.assertFalse(self.v2_auth.called) + + body = json.loads(self.v3_auth.last_request.body) + user = body['auth']['identity']['password']['user'] + + self.assertEqual(self.auth_env['OS_USERNAME'], user['name']) + self.assertEqual(self.auth_env['OS_PASSWORD'], user['password']) + self.assertEqual(self.auth_env['OS_USER_DOMAIN_NAME'], + user['domain']['name']) + self.assertEqual(self.auth_env['OS_PROJECT_ID'], + body['auth']['scope']['project']['id']) + + @mock.patch('bileanclient.v1.client.Client') + def test_auth_plugin_invocation(self, v1_client): + args = '--os-bilean-api-version 1 user-list' + bilean_shell = openstack_shell.BileanShell() + bilean_shell.main(args.split()) + self.assertEqual(0, self.v3_auth.call_count) + + @mock.patch('keystoneclient.discover.Discover', + side_effect=ks_exc.ClientException()) + def test_api_discovery_failed_with_unversioned_auth_url(self, + discover): + args = ('--os-bilean-api-version 1 --os-auth-url %s user-list' + % DEFAULT_UNVERSIONED_AUTH_URL) + bilean_shell = openstack_shell.BileanShell() + self.assertRaises(exc.CommandError, bilean_shell.main, args.split()) + + def test_bash_completion(self): + stdout, stderr = self.shell('--os-bilean-api-version 1 bash_completion') + # just check we have some output + required = [ + '--value', + 'rule-create', + 'help', + '--limit'] + for r in required: + self.assertIn(r, stdout.split()) + avoided = [ + 'bash_completion', + 'bash-completion'] + for r in avoided: + self.assertNotIn(r, stdout.split()) diff --git a/bileanclient/v1/policies.py b/bileanclient/v1/policies.py index 2a44e95..5124c9f 100644 --- a/bileanclient/v1/policies.py +++ b/bileanclient/v1/policies.py @@ -28,6 +28,16 @@ class Policy(base.Resource): class PolicyManager(base.BaseManager): resource_class = Policy + def _list(self, url, response_key, obj_class=None, body=None): + resp, body = self.client.get(url) + + if obj_class is None: + obj_class = self.resource_class + + data = body[response_key] + return ([obj_class(self, res, loaded=True) for res in data if res], + resp) + def list(self, **kwargs): """Retrieve a list of policies. @@ -37,7 +47,7 @@ class PolicyManager(base.BaseManager): '''Paginate policies, even if more than API limit.''' current_limit = int(params.get('limit') or 0) url = '/policies?%s' % parse.urlencode(params, True) - policies = self._list(url, 'policies') + policies, resq = self._list(url, 'policies') for policy in policies: yield policy @@ -61,18 +71,22 @@ class PolicyManager(base.BaseManager): return paginate(params) def create(self, **kwargs): - """Create a policy.""" - return self._post('/policies', json=kwargs, response_key='policy') + """Create a new policy.""" + resq, body = self.client.post(url, data=kwargs) + return self.resource_class(self, body.get('policy'), loaded=True) def get(self, policy_id): """Get a specific policy.""" - return self._get('/policies/%s' % policy_id, 'policy') + url = '/policies/%s' % parse.quote(str(policy_id)) + resq, body = self.client.get(url) + return self.resource_class(self, body.get('policy'), loaded=True) def action(self, policy_id, **kwargs): """Perform specified action on a policy.""" - url = '/policies/%s/action' % policy_id - return self._post(url, json=kwargs, response_key='policy') + url = '/policies/%s/action' % parse.quote(str(policy_id)) + resq, body = self.client.post(url, data=kwargs) + return self.resource_class(self, body.get('policy'), loaded=True) def delete(self, policy_id): """Delete a specific policy.""" - return self._delete('/policies/%s' % policy_id) + return self._delete('/policies/%s' % parse.quote(str(policy_id))) diff --git a/bileanclient/v1/resources.py b/bileanclient/v1/resources.py index 948a776..2e980c3 100644 --- a/bileanclient/v1/resources.py +++ b/bileanclient/v1/resources.py @@ -28,6 +28,16 @@ class BileanResource(base.Resource): class ResourceManager(base.BaseManager): resource_class = BileanResource + def _list(self, url, response_key, obj_class=None, body=None): + resp, body = self.client.get(url) + + if obj_class is None: + obj_class = self.resource_class + + data = body[response_key] + return ([obj_class(self, res, loaded=True) for res in data if res], + resp) + def list(self, **kwargs): """Retrieve a list of resources. @@ -37,7 +47,7 @@ class ResourceManager(base.BaseManager): '''Paginate resources, even if more than API limit.''' current_limit = int(params.get('limit') or 0) url = '/resources?%s' % parse.urlencode(params, True) - resources = self._list(url, 'resources') + resources, resp = self._list(url, 'resources') for resource in resources: yield resource @@ -65,4 +75,6 @@ class ResourceManager(base.BaseManager): :param resource_id: ID of the resource """ - return self._get('/resources/%s' % resource_id, 'resource') + url = '/resources/%s' % parse.quote(str(resource_id)) + resp, body = self.client.get(url) + return self.resource_class(self, body.get('resource'), loaded=True) diff --git a/bileanclient/v1/rules.py b/bileanclient/v1/rules.py index cc891de..23a47b3 100644 --- a/bileanclient/v1/rules.py +++ b/bileanclient/v1/rules.py @@ -28,6 +28,16 @@ class Rule(base.Resource): class RuleManager(base.BaseManager): resource_class = Rule + def _list(self, url, response_key, obj_class=None, body=None): + resp, body = self.client.get(url) + + if obj_class is None: + obj_class = self.resource_class + + data = body[response_key] + return ([obj_class(self, res, loaded=True) for res in data if res], + resp) + def list(self, **kwargs): """Retrieve a list of rules. @@ -37,7 +47,7 @@ class RuleManager(base.BaseManager): '''Paginate rules, even if more than API limit.''' current_limit = int(params.get('limit') or 0) url = '/rules?%s' % parse.urlencode(params, True) - rules = self._list(url, 'rules') + rules, resp = self._list(url, 'rules') for rule in rules: yield rule @@ -49,6 +59,7 @@ class RuleManager(base.BaseManager): for rule in paginate(params): yield rule + return_request_id = kwargs.get('return_req_id', None) params = {} if 'filters' in kwargs: filters = kwargs.pop('filters') @@ -61,13 +72,25 @@ class RuleManager(base.BaseManager): return paginate(params) def create(self, **kwargs): - """Create a rule.""" - return self._post('/rules', json=kwargs, response_key='rule') + """Create a rule by given data.""" + url = '/rules' + resp, body = self.client.post(url, data=kwargs) + rule = body.get('rule') + return self.resource_class(self, rule, loaded=True) def get(self, rule_id): - """Get a specific rule.""" - return self._get('/rules/%s' % rule_id, 'rule') + """Get a specific rule. + + :param rule_id: Id of the rule to get + """ + url = '/rules/%s' % parse.quote(str(rule_id)) + resp, body = self.client.get(url) + data = body.get('rule') + return self.resource_class(self, data, loaded=True) def delete(self, rule_id): - """Delete a specific rule.""" - return self._delete('/rules/%s' % rule_id) + """Delete a specific rule. + + :param rule_id: Id of the rule to delete + """ + return self._delete('/rules/%s' % parse.quote(str(rule_id))) diff --git a/bileanclient/v1/users.py b/bileanclient/v1/users.py index 90807c3..4ff9a5d 100644 --- a/bileanclient/v1/users.py +++ b/bileanclient/v1/users.py @@ -19,6 +19,8 @@ from six.moves.urllib import parse from bileanclient.openstack.common.apiclient import base +OS_REQ_ID_HDR = 'x-openstack-request-id' + class User(base.Resource): def __repr__(self): @@ -72,7 +74,7 @@ class UserManager(base.BaseManager): if value: params[key] = value - return paginate(params) + return paginate(params, return_request_id) def get(self, user_id, return_request_id=None): """Get the details for a specific user.