diff --git a/refstack_client/refstack_client.py b/refstack_client/refstack_client.py index 65dc6d6..fdee030 100755 --- a/refstack_client/refstack_client.py +++ b/refstack_client/refstack_client.py @@ -27,10 +27,10 @@ Tempest configuration file. import argparse import binascii import ConfigParser +import itertools import json import logging import os -import requests import subprocess import time @@ -38,6 +38,9 @@ from Crypto.Hash import SHA256 from Crypto.PublicKey import RSA from Crypto.Signature import PKCS1_v1_5 from keystoneclient.v2_0 import client as ksclient +import requests +import requests.exceptions +import six.moves from subunit_processor import SubunitProcessor @@ -197,6 +200,10 @@ class RefstackClient: self.logger.exception(e) return + if response.status_code == 201: + resp = response.json() + print 'Test results uploaded!\nURL: %s' % resp.get('url', '') + def test(self): '''Execute Tempest test against the cloud.''' self._prep_test() @@ -274,6 +281,41 @@ class RefstackClient: self.post_results(self.args.url, json_data, sign_with=self.args.priv_key) + def yield_results(self, url, start_page=1, + start_date='', end_date='', cpid=''): + endpoint = '%s/v1/results/' % url + headers = {'Content-type': 'application/json'} + for page in itertools.count(start_page): + params = {'page': page} + for param in ('start_date', 'end_date', 'cpid'): + if locals()[param]: + params.update({param: locals()[param]}) + try: + resp = requests.get(endpoint, headers=headers, params=params) + resp.raise_for_status() + except requests.exceptions.HTTPError as e: + self.logger.info('Failed to list %s - %s ' % (endpoint, e)) + raise StopIteration + else: + resp = resp.json() + results = resp.get('results', []) + yield results + if resp['pagination']['total_pages'] == page: + raise StopIteration + + def list(self): + """Retrieve list with last test results from Refstack.""" + results = self.yield_results(self.args.url, + start_date=self.args.start_date, + end_date=self.args.end_date) + for page_of_results in results: + for r in page_of_results: + print('%s - %s' % (r['created_at'], r['url'])) + try: + six.moves.input('Press Enter to go to next page...') + except KeyboardInterrupt: + return + def parse_cli_args(args=None): @@ -359,4 +401,24 @@ def parse_cli_args(args=None): 'or the server specified by --url.') parser_test.set_defaults(func="test") + # List command + parser_list = subparsers.add_parser( + 'list', parents=[shared_args], + help='List last results from Refstack') + parser_list.add_argument('--start-date', + required=False, + dest='start_date', + type=str, + help='Specify a date for start listing of ' + 'test results ' + '(e.g. --start-date "2015-04-24 01:23:56").') + parser_list.add_argument('--end-date', + required=False, + dest='end_date', + type=str, + help='Specify a date for end listing of ' + 'test results ' + '(e.g. --end-date "2015-04-24 01:23:56").') + parser_list.set_defaults(func='list') + return parser.parse_args(args=args) diff --git a/refstack_client/tests/unit/test_client.py b/refstack_client/tests/unit/test_client.py index 427c0ab..8aad303 100755 --- a/refstack_client/tests/unit/test_client.py +++ b/refstack_client/tests/unit/test_client.py @@ -14,11 +14,10 @@ # under the License. # -import logging import json +import logging import os import tempfile -import subprocess import httmock import mock @@ -45,23 +44,25 @@ class TestRefstackClient(unittest.TestCase): self.addCleanup(patcher.stop) return thing - def mock_argv(self, conf_file_name=None, verbose=None, priv_key=None): + def mock_argv(self, command='test', **kwargs): """ Build argv for test. :param conf_file_name: Configuration file name :param verbose: verbosity level :return: argv """ - if conf_file_name is None: - conf_file_name = self.conf_file_name - argv = ['test', - '-c', conf_file_name, - '--test-cases', 'tempest.api.compute', + argv = [command, '--url', 'http://127.0.0.1'] - if priv_key: - argv.extend(('-i', priv_key)) - if verbose: - argv.append(verbose) + if command == 'test': + argv.extend( + ('-c', kwargs.get('conf_file_name', self.conf_file_name))) + if kwargs.get('test_cases', None): + argv.extend(('--test-cases', kwargs.get('test_cases', None))) + + if kwargs.get('priv_key', None): + argv.extend(('-i', kwargs.get('priv_key', None))) + if kwargs.get('verbose', None): + argv.append(kwargs.get('verbose', None)) return argv def mock_keystone(self): @@ -280,7 +281,8 @@ class TestRefstackClient(unittest.TestCase): Test that the test command will run the tempest script using the default configuration. """ - args = rc.parse_cli_args(self.mock_argv(verbose='-vv')) + args = rc.parse_cli_args( + self.mock_argv(verbose='-vv', test_cases='tempest.api.compute')) client = rc.RefstackClient(args) client.tempest_dir = self.test_path mock_popen = self.patch( @@ -307,7 +309,8 @@ class TestRefstackClient(unittest.TestCase): Test that the test command will run the tempest script and call post_results when the --upload argument is passed in. """ - argv = self.mock_argv(verbose='-vv') + argv = self.mock_argv(verbose='-vv', + test_cases='tempest.api.compute') argv.append('--upload') args = rc.parse_cli_args(argv) client = rc.RefstackClient(args) @@ -334,7 +337,8 @@ class TestRefstackClient(unittest.TestCase): Test that the test command will run the tempest script and call post_results when the --upload argument is passed in. """ - argv = self.mock_argv(verbose='-vv', priv_key='rsa_key') + argv = self.mock_argv(verbose='-vv', priv_key='rsa_key', + test_cases='tempest.api.compute') argv.append('--upload') args = rc.parse_cli_args(argv) client = rc.RefstackClient(args) @@ -385,7 +389,8 @@ class TestRefstackClient(unittest.TestCase): Check that the result JSON file is renamed with the result file tag when the --result-file-tag argument is passed in. """ - argv = self.mock_argv(verbose='-vv') + argv = self.mock_argv(verbose='-vv', + test_cases='tempest.api.compute') argv.extend(['--result-file-tag', 'my-test']) args = rc.parse_cli_args(argv) client = rc.RefstackClient(args) @@ -459,62 +464,55 @@ class TestRefstackClient(unittest.TestCase): client = rc.RefstackClient(args) self.assertRaises(SystemExit, client.upload) - def _set_mocks_for_setup(self): + def test_yeild_results(self): """ - Setup mocks for testing setup command in positive case + Test the post_results method, ensuring a requests call is made. """ - env = dict() - env['args'] = rc.parse_cli_args(['setup', '-r', 'havana-eol', - '--tempest-dir', '/tmp/tempest']) - env['raw_input'] = self.patch( - 'refstack_client.refstack_client.get_input', - return_value='yes' - ) - env['exists'] = self.patch( - 'refstack_client.refstack_client.os.path.exists', - return_value=True - ) - env['rmtree'] = self.patch( - 'refstack_client.refstack_client.shutil.rmtree', - return_value=True - ) - env['test_commit_sha'] = '42' - env['tag'] = MagicMock( - **{'commit.hexsha': env['test_commit_sha']} - ) - env['tag'].configure_mock(name='havana-eol') - env['git.reset'] = MagicMock() - env['repo'] = MagicMock( - tags=[env['tag']], - **{'git.reset': env['git.reset']} - ) - self.patch( - 'refstack_client.refstack_client.git.Repo.clone_from', - return_value=env['repo'] - ) - env['os.chdir'] = self.patch( - 'refstack_client.refstack_client.os.chdir' - ) - env['subprocess.check_output'] = self.patch( - 'refstack_client.refstack_client.subprocess.check_output', - return_value='Ok!' - ) - return env + args = rc.parse_cli_args(self.mock_argv(command='list')) + client = rc.RefstackClient(args) + expected_response = { + "pagination": { + "current_page": 1, + "total_pages": 1 + }, + "results": [ + { + "cpid": "42", + "created_at": "2015-04-28 13:57:05", + "test_id": "1", + "url": "http://127.0.0.1:8000/output.html?test_id=1" + }, + { + "cpid": "42", + "created_at": "2015-04-28 13:57:05", + "test_id": "2", + "url": "http://127.0.0.1:8000/output.html?test_id=2" + }]} - def _check_mocks_for_setup(self, env): - """ - Check mocks after successful run 'setup' command - """ - env['exists'].assert_called_once_with('/tmp/tempest') - env['rmtree'].assert_called_once_with('/tmp/tempest') - env['git.reset'].assert_called_once_with( - env['test_commit_sha'], hard=True - ) - env['os.chdir'].assert_has_calls([mock.call('/tmp/tempest'), - mock.call(os.getcwd())]) - env['subprocess.check_output'].assert_has_calls([ - mock.call(['virtualenv', '.venv'], - stderr=subprocess.STDOUT), - mock.call(['.venv//bin//pip', 'install', '-r', 'requirements.txt'], - stderr=subprocess.STDOUT) - ]) + @httmock.urlmatch(netloc=r'(.*\.)?127.0.0.1$', path='/v1/results/') + def refstack_api_mock(url, request): + return json.dumps(expected_response) + + with httmock.HTTMock(refstack_api_mock): + results = client.yield_results("http://127.0.0.1") + self.assertEqual(expected_response['results'], next(results)) + self.assertRaises(StopIteration, next, results) + + @mock.patch('six.moves.input', side_effect=KeyboardInterrupt) + @mock.patch('sys.stdout', new_callable=MagicMock) + def test_list(self, mock_stdout, mock_input): + args = rc.parse_cli_args(self.mock_argv(command='list')) + client = rc.RefstackClient(args) + results = [[{"cpid": "42", + "created_at": "2015-04-28 13:57:05", + "test_id": "1", + "url": "http://127.0.0.1:8000/output.html?test_id=1"}, + {"cpid": "42", + "created_at": "2015-04-28 13:57:05", + "test_id": "2", + "url": "http://127.0.0.1:8000/output.html?test_id=2"}]] + mock_results = MagicMock() + mock_results.__iter__.return_value = results + client.yield_results = MagicMock(return_value=mock_results) + client.list() + self.assertTrue(mock_stdout.write.called)