def test_connect_fail(self, req): """Test Connect Failure handled correctly. If we get a connect failure, this is transient, and we expect that this will end up working correctly later. We don't want to disable the client. """ req.side_effect = ks_exc.ConnectFailure() self.client._get_resource_provider("fake") self.assertFalse(self.client._disabled) # reset the call count to demonstrate that future calls do # work req.reset_mock() self.client._get_resource_provider("fake") self.assertTrue(req.called)
def test_auth_ref_fails_debug_with_native_keystone_error( self, mock_is_debug, mock_log_exception): from keystoneauth1 import exceptions as ks_exc mock_is_debug.return_value = True keystone = osclients.Keystone(self.credential, {}, {}) session = mock.Mock() auth_plugin = mock.Mock() auth_plugin.get_access.side_effect = ks_exc.ConnectFailure("foo") keystone.get_session = mock.Mock(return_value=(session, auth_plugin)) self.assertRaises(osclients.AuthenticationFailed, lambda: keystone.auth_ref) self.assertFalse(mock_log_exception.called) mock_is_debug.assert_called_once_with() auth_plugin.get_access.assert_called_once_with(session)
class ShellTest(utils.TestCase): FAKE_ENV = { 'OS_USERNAME': '******', 'OS_PASSWORD': '******', 'OS_TENANT_NAME': 'tenant_name', 'OS_AUTH_URL': 'http://no.where/v2.0', } # Patch os.environ to avoid required auth info. def make_env(self, exclude=None, include=None): env = dict((k, v) for k, v in self.FAKE_ENV.items() if k != exclude) env.update(include or {}) self.useFixture(fixtures.MonkeyPatch('os.environ', env)) def setUp(self): super(ShellTest, self).setUp() for var in self.FAKE_ENV: self.useFixture( fixtures.EnvironmentVariable(var, self.FAKE_ENV[var])) def shell(self, argstr): orig = sys.stdout try: sys.stdout = moves.StringIO() _shell = shell.OpenStackCinderShell() _shell.main(argstr.split()) except SystemExit: exc_type, exc_value, exc_traceback = sys.exc_info() self.assertEqual(0, exc_value.code) finally: out = sys.stdout.getvalue() sys.stdout.close() sys.stdout = orig return out def test_help_unknown_command(self): self.assertRaises(exceptions.CommandError, self.shell, 'help foofoo') def test_help(self): required = [ '.*?^usage: ', '.*?(?m)^\s+create\s+Creates a volume.', '.*?(?m)^Run "cinder help SUBCOMMAND" for help on a subcommand.', ] help_text = self.shell('help') for r in required: self.assertThat(help_text, matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE)) def test_help_on_subcommand(self): required = [ '.*?^usage: cinder list', '.*?(?m)^Lists all volumes.', ] help_text = self.shell('help list') for r in required: self.assertThat(help_text, matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE)) def register_keystone_auth_fixture(self, mocker, url): mocker.register_uri('GET', url, text=keystone_client.keystone_request_callback) @requests_mock.Mocker() def test_version_discovery(self, mocker): _shell = shell.OpenStackCinderShell() sess = session.Session() os_auth_url = "https://wrongdiscoveryresponse.discovery.com:35357/v2.0" self.register_keystone_auth_fixture(mocker, os_auth_url) self.assertRaises(DiscoveryFailure, _shell._discover_auth_versions, sess, auth_url=os_auth_url) os_auth_url = "https://DiscoveryNotSupported.discovery.com:35357/v2.0" self.register_keystone_auth_fixture(mocker, os_auth_url) v2_url, v3_url = _shell._discover_auth_versions(sess, auth_url=os_auth_url) self.assertEqual(os_auth_url, v2_url, "Expected v2 url") self.assertIsNone(v3_url, "Expected no v3 url") os_auth_url = "https://DiscoveryNotSupported.discovery.com:35357/v3.0" self.register_keystone_auth_fixture(mocker, os_auth_url) v2_url, v3_url = _shell._discover_auth_versions(sess, auth_url=os_auth_url) self.assertEqual(os_auth_url, v3_url, "Expected v3 url") self.assertIsNone(v2_url, "Expected no v2 url") @requests_mock.Mocker() def list_volumes_on_service(self, count, mocker): os_auth_url = "http://multiple.service.names/v2.0" mocker.register_uri('POST', os_auth_url + "/tokens", text=keystone_client.keystone_request_callback) mocker.register_uri('GET', "http://cinder%i.api.com/v2/volumes/detail" % count, text='{"volumes": []}') self.make_env(include={ 'OS_AUTH_URL': os_auth_url, 'CINDER_SERVICE_NAME': 'cinder%i' % count }) _shell = shell.OpenStackCinderShell() _shell.main(['list']) def test_cinder_service_name(self): # Failing with 'No mock address' means we are not # choosing the correct endpoint for count in range(1, 4): self.list_volumes_on_service(count) @mock.patch('keystoneauth1.identity.v2.Password') @mock.patch('keystoneauth1.adapter.Adapter.get_token', side_effect=ks_exc.ConnectFailure()) @mock.patch('keystoneauth1.discover.Discover', side_effect=ks_exc.ConnectFailure()) @mock.patch('sys.stdin', side_effect=mock.Mock) @mock.patch('getpass.getpass', return_value='password') def test_password_prompted(self, mock_getpass, mock_stdin, mock_discover, mock_token, mock_password): self.make_env(exclude='OS_PASSWORD') _shell = shell.OpenStackCinderShell() self.assertRaises(ks_exc.ConnectFailure, _shell.main, ['list']) mock_getpass.assert_called_with('OS Password: '******'password' # equal to mock_getpass.return_value. mock_password.assert_called_with( self.FAKE_ENV['OS_AUTH_URL'], password=mock_getpass.return_value, tenant_id='', tenant_name=self.FAKE_ENV['OS_TENANT_NAME'], username=self.FAKE_ENV['OS_USERNAME']) @mock.patch.object(requests, "request") @mock.patch.object(pkg_resources, "iter_entry_points") def test_auth_system_not_keystone(self, mock_iter_entry_points, mock_request): """Test that we can authenticate using the auth plugin system.""" non_keystone_auth_url = "http://non-keystone-url.com/v2.0" class MockEntrypoint(pkg_resources.EntryPoint): def load(self): return FakePlugin class FakePlugin(auth_plugin.BaseAuthPlugin): def authenticate(self, cls, auth_url): cls._authenticate(auth_url, {"fake": "me"}) def get_auth_url(self): return non_keystone_auth_url mock_iter_entry_points.side_effect = lambda _t: [ MockEntrypoint("fake", "fake", ["FakePlugin"]) ] mock_request.side_effect = mock_http_request() # Tell the shell we wish to use our 'fake' auth instead of keystone # and the auth plugin will provide the auth url self.make_env(exclude="OS_AUTH_URL", include={'OS_AUTH_SYSTEM': 'fake'}) # This should fail as we have not setup a mock response for 'list', # however auth should have been called _shell = shell.OpenStackCinderShell() self.assertRaises(KeyError, _shell.main, ['list']) headers = requested_headers(_shell.cs) token_url = _shell.cs.client.auth_url + "/tokens" self.assertEqual(non_keystone_auth_url + "/tokens", token_url) mock_request.assert_any_call("POST", token_url, headers=headers, data='{"fake": "me"}', allow_redirects=True, **self.TEST_REQUEST_BASE) @mock.patch.object(cinderclient.client.HTTPClient, 'authenticate', side_effect=exceptions.Unauthorized('No')) # Easiest way to make cinderclient use httpclient is a None session @mock.patch.object(cinderclient.shell.OpenStackCinderShell, '_get_keystone_session', return_value=None) def test_http_client_insecure(self, mock_authenticate, mock_session): self.make_env(include={'CINDERCLIENT_INSECURE': True}) _shell = shell.OpenStackCinderShell() # This "fails" but instantiates the client. self.assertRaises(exceptions.CommandError, _shell.main, ['list']) self.assertEqual(False, _shell.cs.client.verify_cert) @mock.patch.object(cinderclient.client.SessionClient, 'authenticate', side_effect=exceptions.Unauthorized('No')) def test_session_client_debug_logger(self, mock_session): _shell = shell.OpenStackCinderShell() # This "fails" but instantiates the client. self.assertRaises(exceptions.CommandError, _shell.main, ['--debug', 'list']) # In case of SessionClient when --debug switch is specified # 'keystoneauth' logger should be initialized. self.assertEqual('keystoneauth', _shell.cs.client.logger.name) @mock.patch('keystoneauth1.session.Session.__init__', side_effect=RuntimeError()) def test_http_client_with_cert(self, mock_session): _shell = shell.OpenStackCinderShell() # We crash the command after Session instantiation because this test # focuses only on arguments provided to Session.__init__ args = '--os-cert', 'minnie', 'list' self.assertRaises(RuntimeError, _shell.main, args) mock_session.assert_called_once_with(cert='minnie', verify=mock.ANY) @mock.patch('keystoneauth1.session.Session.__init__', side_effect=RuntimeError()) def test_http_client_with_cert_and_key(self, mock_session): _shell = shell.OpenStackCinderShell() # We crash the command after Session instantiation because this test # focuses only on arguments provided to Session.__init__ args = '--os-cert', 'minnie', '--os-key', 'mickey', 'list' self.assertRaises(RuntimeError, _shell.main, args) mock_session.assert_called_once_with(cert=('minnie', 'mickey'), verify=mock.ANY)
def _send_request(self, url, method, redirect, log, logger, connect_retries, connect_retry_delay=0.5, **kwargs): # NOTE(jamielennox): We handle redirection manually because the # requests lib follows some browser patterns where it will redirect # POSTs as GETs for certain statuses which is not want we want for an # API. See: https://en.wikipedia.org/wiki/Post/Redirect/Get # NOTE(jamielennox): The interaction between retries and redirects are # handled naively. We will attempt only a maximum number of retries and # redirects rather than per request limits. Otherwise the extreme case # could be redirects * retries requests. This will be sufficient in # most cases and can be fixed properly if there's ever a need. try: try: resp = self.session.request(method, url, **kwargs) except requests.exceptions.SSLError as e: msg = 'SSL exception connecting to %(url)s: %(error)s' % { 'url': url, 'error': e } raise exceptions.SSLError(msg) except requests.exceptions.Timeout: msg = 'Request to %s timed out' % url raise exceptions.ConnectTimeout(msg) except requests.exceptions.ConnectionError: msg = 'Unable to establish connection to %s' % url raise exceptions.ConnectFailure(msg) except requests.exceptions.RequestException as e: msg = 'Unexpected exception for %(url)s: %(error)s' % { 'url': url, 'error': e } raise exceptions.UnknownConnectionError(msg, e) except exceptions.RetriableConnectionFailure as e: if connect_retries <= 0: raise logger.info('Failure: %(e)s. Retrying in %(delay).1fs.', { 'e': e, 'delay': connect_retry_delay }) time.sleep(connect_retry_delay) return self._send_request(url, method, redirect, log, logger, connect_retries=connect_retries - 1, connect_retry_delay=connect_retry_delay * 2, **kwargs) if log: self._http_log_response(response=resp, logger=logger) if resp.status_code in self._REDIRECT_STATUSES: # be careful here in python True == 1 and False == 0 if isinstance(redirect, bool): redirect_allowed = redirect else: redirect -= 1 redirect_allowed = redirect >= 0 if not redirect_allowed: return resp try: location = resp.headers['location'] except KeyError: logger.warning( "Failed to redirect request to %s as new " "location was not provided.", resp.url) else: # NOTE(jamielennox): We don't pass through connect_retry_delay. # This request actually worked so we can reset the delay count. new_resp = self._send_request(location, method, redirect, log, logger, connect_retries=connect_retries, **kwargs) if not isinstance(new_resp.history, list): new_resp.history = list(new_resp.history) new_resp.history.insert(0, resp) resp = new_resp return resp
class ShellTest(utils.TestCase): FAKE_ENV = { 'OS_USERNAME': '******', 'OS_PASSWORD': '******', 'OS_PROJECT_NAME': 'tenant_name', 'OS_AUTH_URL': 'http://no.where/v2.0', } # Patch os.environ to avoid required auth info. def make_env(self, exclude=None, include=None): env = dict((k, v) for k, v in self.FAKE_ENV.items() if k != exclude) env.update(include or {}) self.useFixture(fixtures.MonkeyPatch('os.environ', env)) def setUp(self): super(ShellTest, self).setUp() for var in self.FAKE_ENV: self.useFixture( fixtures.EnvironmentVariable(var, self.FAKE_ENV[var])) self.mock_completion() def shell(self, argstr): orig = sys.stdout try: sys.stdout = moves.StringIO() _shell = shell.OpenStackCinderShell() _shell.main(argstr.split()) except SystemExit: exc_type, exc_value, exc_traceback = sys.exc_info() self.assertEqual(0, exc_value.code) finally: out = sys.stdout.getvalue() sys.stdout.close() sys.stdout = orig return out def test_default_auth_env(self): _shell = shell.OpenStackCinderShell() args, __ = _shell.get_base_parser().parse_known_args([]) self.assertEqual('', args.os_auth_type) def test_auth_type_env(self): self.make_env(exclude='OS_PASSWORD', include={ 'OS_AUTH_SYSTEM': 'non existent auth', 'OS_AUTH_TYPE': 'noauth' }) _shell = shell.OpenStackCinderShell() args, __ = _shell.get_base_parser().parse_known_args([]) self.assertEqual('noauth', args.os_auth_type) def test_auth_system_env(self): self.make_env(exclude='OS_PASSWORD', include={'OS_AUTH_SYSTEM': 'noauth'}) _shell = shell.OpenStackCinderShell() args, __ = _shell.get_base_parser().parse_known_args([]) self.assertEqual('noauth', args.os_auth_type) @mock.patch.object(cinderclient.shell.OpenStackCinderShell, '_get_keystone_session') @mock.patch.object(cinderclient.client.SessionClient, 'authenticate', side_effect=RuntimeError()) def test_password_auth_type(self, mock_authenticate, mock_get_session): self.make_env(include={'OS_AUTH_TYPE': 'password'}) _shell = shell.OpenStackCinderShell() # We crash the command after Client instantiation because this test # focuses only keystoneauth1 indentity cli opts parsing. self.assertRaises(RuntimeError, _shell.main, ['list']) self.assertIsInstance(_shell.cs.client.session.auth, ks_password) def test_help_unknown_command(self): self.assertRaises(exceptions.CommandError, self.shell, 'help foofoo') def test_help(self): # Some expected help output, including microversioned commands required = [ '.*?^usage: ', '.*?(?m)^\s+create\s+Creates a volume.', '.*?(?m)^\s+summary\s+Get volumes summary.', '.*?(?m)^Run "cinder help SUBCOMMAND" for help on a subcommand.', ] help_text = self.shell('help') for r in required: self.assertThat(help_text, matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE)) def test_help_on_subcommand(self): required = [ '.*?^usage: cinder list', '.*?(?m)^Lists all volumes.', ] help_text = self.shell('help list') for r in required: self.assertThat(help_text, matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE)) def test_help_on_subcommand_mv(self): required = [ '.*?^usage: cinder summary', '.*?(?m)^Get volumes summary.', ] help_text = self.shell('help summary') for r in required: self.assertThat(help_text, matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE)) @ddt.data('backup-create --help', '--help backup-create') def test_dash_dash_help_on_subcommand(self, cmd): required = ['.*?^Creates a volume backup.'] help_text = self.shell(cmd) for r in required: self.assertThat(help_text, matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE)) def register_keystone_auth_fixture(self, mocker, url): mocker.register_uri('GET', url, text=keystone_client.keystone_request_callback) @requests_mock.Mocker() def test_version_discovery(self, mocker): _shell = shell.OpenStackCinderShell() sess = session.Session() os_auth_url = "https://wrongdiscoveryresponse.discovery.com:35357/v2.0" self.register_keystone_auth_fixture(mocker, os_auth_url) self.assertRaises(DiscoveryFailure, _shell._discover_auth_versions, sess, auth_url=os_auth_url) os_auth_url = "https://DiscoveryNotSupported.discovery.com:35357/v2.0" self.register_keystone_auth_fixture(mocker, os_auth_url) v2_url, v3_url = _shell._discover_auth_versions(sess, auth_url=os_auth_url) self.assertEqual(os_auth_url, v2_url, "Expected v2 url") self.assertIsNone(v3_url, "Expected no v3 url") os_auth_url = "https://DiscoveryNotSupported.discovery.com:35357/v3.0" self.register_keystone_auth_fixture(mocker, os_auth_url) v2_url, v3_url = _shell._discover_auth_versions(sess, auth_url=os_auth_url) self.assertEqual(os_auth_url, v3_url, "Expected v3 url") self.assertIsNone(v2_url, "Expected no v2 url") @requests_mock.Mocker() def list_volumes_on_service(self, count, mocker): os_auth_url = "http://multiple.service.names/v2.0" mocker.register_uri('POST', os_auth_url + "/tokens", text=keystone_client.keystone_request_callback) mocker.register_uri('GET', "http://cinder%i.api.com/v2/volumes/detail" % count, text='{"volumes": []}') self.make_env(include={ 'OS_AUTH_URL': os_auth_url, 'CINDER_SERVICE_NAME': 'cinder%i' % count }) _shell = shell.OpenStackCinderShell() _shell.main(['list']) def test_duplicate_filters(self): _shell = shell.OpenStackCinderShell() self.assertRaises(exceptions.CommandError, _shell.main, ['list', '--name', 'abc', '--filters', 'name=xyz']) @unittest.skip("Skip cuz I broke it") def test_cinder_service_name(self): # Failing with 'No mock address' means we are not # choosing the correct endpoint for count in range(1, 4): self.list_volumes_on_service(count) @mock.patch('keystoneauth1.identity.v2.Password') @mock.patch('keystoneauth1.adapter.Adapter.get_token', side_effect=ks_exc.ConnectFailure()) @mock.patch('keystoneauth1.discover.Discover', side_effect=ks_exc.ConnectFailure()) @mock.patch('sys.stdin', side_effect=mock.Mock) @mock.patch('getpass.getpass', return_value='password') def test_password_prompted(self, mock_getpass, mock_stdin, mock_discover, mock_token, mock_password): self.make_env(exclude='OS_PASSWORD') _shell = shell.OpenStackCinderShell() self.assertRaises(ks_exc.ConnectFailure, _shell.main, ['list']) mock_getpass.assert_called_with('OS Password: '******'password' # equal to mock_getpass.return_value. mock_password.assert_called_with( self.FAKE_ENV['OS_AUTH_URL'], password=mock_getpass.return_value, tenant_id='', tenant_name=self.FAKE_ENV['OS_PROJECT_NAME'], username=self.FAKE_ENV['OS_USERNAME']) @requests_mock.Mocker() def test_noauth_plugin(self, mocker): os_auth_url = "http://example.com/v2" mocker.register_uri('GET', "%s/admin/volumes/detail" % os_auth_url, text='{"volumes": []}') _shell = shell.OpenStackCinderShell() args = [ '--os-endpoint', os_auth_url, '--os-auth-type', 'noauth', '--os-user-id', 'admin', '--os-project-id', 'admin', 'list' ] _shell.main(args) self.assertIsInstance(_shell.cs.client.session.auth, noauth.CinderNoAuthPlugin) @mock.patch.object(cinderclient.client.HTTPClient, 'authenticate', side_effect=exceptions.Unauthorized('No')) # Easiest way to make cinderclient use httpclient is a None session @mock.patch.object(cinderclient.shell.OpenStackCinderShell, '_get_keystone_session', return_value=None) def test_http_client_insecure(self, mock_authenticate, mock_session): self.make_env(include={'CINDERCLIENT_INSECURE': True}) _shell = shell.OpenStackCinderShell() # This "fails" but instantiates the client. self.assertRaises(exceptions.CommandError, _shell.main, ['list']) self.assertEqual(False, _shell.cs.client.verify_cert) @mock.patch.object(cinderclient.client.SessionClient, 'authenticate', side_effect=exceptions.Unauthorized('No')) def test_session_client_debug_logger(self, mock_session): _shell = shell.OpenStackCinderShell() # This "fails" but instantiates the client. self.assertRaises(exceptions.CommandError, _shell.main, ['--debug', 'list']) # In case of SessionClient when --debug switch is specified # 'keystoneauth' logger should be initialized. self.assertEqual('keystoneauth', _shell.cs.client.logger.name) @mock.patch('keystoneauth1.session.Session.__init__', side_effect=RuntimeError()) def test_http_client_with_cert(self, mock_session): _shell = shell.OpenStackCinderShell() # We crash the command after Session instantiation because this test # focuses only on arguments provided to Session.__init__ args = '--os-cert', 'minnie', 'list' self.assertRaises(RuntimeError, _shell.main, args) mock_session.assert_called_once_with(cert='minnie', verify=mock.ANY) @mock.patch('keystoneauth1.session.Session.__init__', side_effect=RuntimeError()) def test_http_client_with_cert_and_key(self, mock_session): _shell = shell.OpenStackCinderShell() # We crash the command after Session instantiation because this test # focuses only on arguments provided to Session.__init__ args = '--os-cert', 'minnie', '--os-key', 'mickey', 'list' self.assertRaises(RuntimeError, _shell.main, args) mock_session.assert_called_once_with(cert=('minnie', 'mickey'), verify=mock.ANY)