diff --git a/examples/integration_test/README b/examples/integration_test/README index 2448ec99aba9237190aeefd4f1abac56203c476a..e34055e7ee29643b5f8177a090e4f9b7a759efaa 100644 --- a/examples/integration_test/README +++ b/examples/integration_test/README @@ -25,6 +25,11 @@ may be prompted for a password during script execution if a valid OIDC token is not available (this will not cause any issues if OIDC is disabled on the backend) + -s --storage-username: + + (optional) provide a local Storage Server username to login and have access to the cloned + experiments. You will be prompted for a password + 3. Running Integration Tests diff --git a/examples/integration_test/it.py b/examples/integration_test/it.py index 04449d32ec1ddc6029d92a7e67ccf33201ce065b..ca7e63faf86814c96d5285c9ec6ca53bce9c5f10 100644 --- a/examples/integration_test/it.py +++ b/examples/integration_test/it.py @@ -105,7 +105,7 @@ class TestCaseError(Exception): ## # pylint: disable=too-many-branches, too-many-statements, too-many-locals, redefined-outer-name # pylint: disable=broad-except, bare-except, no-member, protected-access -def run(oidc_username): +def run(oidc_username, storage_username): # running array of test case results, unfortunately we have to hardcode the number of # test cases because the indeterminate progress bar is not helpful for the tester @@ -124,7 +124,8 @@ def run(oidc_username): # this test validates the config.json from user-scripts and checks to make sure the # backend is running properly (roscore/OIDC if required) results.start('Init (OIDC %sabled) and config Check' % ('en' if oidc_username else 'dis')) - vc = VirtualCoach(environment='local', oidc_username=oidc_username) + vc = VirtualCoach(environment='local', oidc_username=oidc_username, + storage_username=storage_username) results.done(True) # ability to retrieve server info (experiment/availability/etc.) @@ -139,10 +140,15 @@ def run(oidc_username): raise TestCaseError('No available backends to run test on.') results.done(True) - # ensure the desired IT experiment is available to run on the server - results.start('Checking For Empty Template Husky Experiment') - if 'ExDTemplateHusky' not in [s[0] for s in server_info.iteritems()]: - raise TestCaseError('Husky Template Experiment is not available on the server.') + # ensure the desired IT experiment is cloned and is available in the storage server. Clone + # the experiment first if not. + results.start('Checking for Empty Template Husky Experiment in the Storage Server') + if 'template_husky_0' not in vc._VirtualCoach__get_experiment_list(cloned=True): + if 'ExDTemplateHusky' not in [s[0] for s in server_info.iteritems()]: + raise TestCaseError('Husky Template Experiment is not available on the server to be' + ' cloned.') + else: + vc.clone_experiment_to_storage('ExDTemplateHusky') results.done(True) ## @@ -151,7 +157,7 @@ def run(oidc_username): # launch an experiment results.start('Launching Empty Template Husky Experiment') - sim = vc.launch_experiment('ExDTemplateHusky') + sim = vc.launch_experiment('template_husky_0') results.done(True) # status handlers for simulation status events, simply write to our global status message @@ -475,6 +481,9 @@ if __name__ == '__main__': required=False, type=str, help="login required if OIDC is enabled on the local backend") + parser.add_argument("-s", "--storage-username", + required=False, + help="login required to access files on local Storage Server") args = parser.parse_args() # if verbosity is disabled, turn off any logging from the virtual coach/other libraries @@ -483,30 +492,36 @@ if __name__ == '__main__': # banner with user warnings / prompts print ''.center(80, '=') - print '|%s|' % ('').center(78, ' ') - print '|%s|' % ('VirtualCoach Integration Test Script').center(78, ' ') - print '|%s|' % ('').center(78, ' ') - print '|%s|' % ('').center(78, ' ') + print '|%s|' % ''.center(78, ' ') + print '|%s|' % 'VirtualCoach Integration Test Script'.center(78, ' ') + print '|%s|' % ''.center(78, ' ') + print '|%s|' % ''.center(78, ' ') if args.oidc_username: print '|%s|' % ('OIDC is enabled, user: %s' % args.oidc_username).center(78, ' ') - print '|%s|' % ('Note: you may be prompted for a password.').center(78, ' ') + print '|%s|' % 'Note: you may be prompted for a password.'.center(78, ' ') + elif args.storage_username: + print '|%s|' % ('Local Storage is enabled, user: %s' % args.storage_username).\ + center(78, ' ') + print '|%s|' % 'Note: you may be prompted for a password.'.center(78, ' ') else: - print '|%s|' % ('OIDC is disabled, see README for launch options.').center(78, ' ') - print '|%s|' % ('').center(78, ' ') - print '=%s=' % ('').center(78, '=') + raise ValueError('|%s|' % 'No OIDC or local Storage credentials were provided. Please run ' + 'the script with either oidc_username or storage_username as arguments'. + center(78, ' ')) + print '|%s|' % ''.center(78, ' ') + print '=%s=' % ''.center(78, '=') # run all of the sequential tests, results are returned upon success or first failure print print 'Running Test Cases, This May Take A Few Moments...'.center(80, ' ') print cumulative = TestCase('Summary'.center(52, ' ')) - results = run(args.oidc_username) + results = run(args.oidc_username, args.storage_username) cumulative.done(all([r.success for r in results])) results.append(cumulative) # print out a human readable table of results, texttable doesn't handle color well in Python 2.7 # so we just do it manually here instead, not the most readable but functional - widths = [52, 12, 12] + widths = [75, 12, 12] headers = ['Description', 'Duration', 'Result'] print '\n\n+{0}+{1}+{2}+'.format(*(''.center(w, '-') for w in widths)) print '|{0}|{1}|{2}|'.format(*(h.center(w, ' ') for h, w in zip(headers, widths))) diff --git a/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/config.py b/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/config.py index 27e454da8d17ca7dfc9ad7ea20c3c0d9ec8dd2cb..10e22483d9e5ee6fcf02e77424632cba3fb7fc23 100644 --- a/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/config.py +++ b/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/config.py @@ -84,8 +84,8 @@ class Config(dict): self.__validate('oidc', ['user']) self.__validate('proxy', ['staging', 'dev', 'local', environment]) self.__validate('proxy-services', ['experiment-list', 'available-servers', 'server-info', - 'experiment-clone', 'storage-authentication', - 'storage-experiment-list']) + 'experiment-clone', 'experiment-delete', + 'storage-authentication', 'storage-experiment-list']) self.__validate('simulation-services', ['create', 'state', 'reset', 'csv-recorders']) self.__validate('simulation-scripts', ['state-machine', 'transfer-function', 'brain']) self.__validate('reset-services', ['robot_pose', 'full', 'world', 'brain']) diff --git a/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/simulation.py b/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/simulation.py index 66cd0b3ac7118a3aee471721c6324472117ba5cc..86628a37d9ea36c0ddf830c4e2a904990f511c7b 100644 --- a/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/simulation.py +++ b/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/simulation.py @@ -41,7 +41,7 @@ class Simulation(object): Provides an interface to launch or control a simulation instance. """ - def __init__(self, oidc_client, config): + def __init__(self, oidc_client, config, storage_headers): """ Initialize a simulation interface and default logger. @@ -53,11 +53,13 @@ class Simulation(object): self.__oidc_client = oidc_client self.__config = config + self.__storage_headers = storage_headers self.__server = None self.__server_info = None self.__sim_info = None self.__sim_url = None + self.__experiment_id = None self.__previous_subtask = None self.__status_sub = None @@ -69,8 +71,7 @@ class Simulation(object): self.__logger = logging.getLogger('Simulation') # pylint: disable=too-many-locals - def launch(self, experiment_id, experiment_conf, server, reservation, cloned=False, - headers=None): + def launch(self, experiment_id, experiment_conf, server, reservation): """ Attempt to launch and initialize the given experiment on the given servers. This should not be directly invoked by users, use the VirtualCoach interface to validate @@ -81,17 +82,11 @@ class Simulation(object): :param experiment_conf: A string representing the configuration file for the experiment. :param server: A string representing the name of the server to try to launch on. :param reservation: A string representing a cluster resource reservation (if any). - :param cloned: (optional) A flag indicating whether the user wants to launch a cloned - experiment or not. - :param headers: (optional) Request header including Storage Server token, in case we want - to launch a cloned experiment. """ assert isinstance(experiment_id, str) assert isinstance(experiment_conf, str) assert isinstance(server, str) assert isinstance(reservation, (str, type(None))) - assert isinstance(cloned, bool) - assert isinstance(headers, (dict, type(None))) # do not allow reuse of this instance if a simulation has been launched if self.__server: @@ -113,22 +108,17 @@ class Simulation(object): # attempt to launch the simulation with given parameters on the server url = '%s/%s' % (self.__server_info['gzweb']['nrp-services'], self.__config['simulation-services']['create']) - sim_info = {'brainProcesses': 1, + sim_info = {'experimentID': experiment_id, + 'brainProcesses': 1, 'experimentConfiguration': experiment_conf, 'gzserverHost': self.__server_info['serverJobLocation'], - 'reservation': reservation} - if cloned: - # append extra information to the sim_info dictionary that are required for - # launching cloned simulations - sim_info['experimentID'] = experiment_id - sim_info['private'] = True - res = requests.post(url, headers=headers, json=sim_info) - sim_json = res.content - status = res.status_code - else: - res, sim_json = self.__oidc_client.request(url, method='POST', - body=json.dumps(sim_info)) - status = int(res['status']) + 'reservation': reservation, + 'private': True} + + res = requests.post(url, headers=self.__storage_headers, json=sim_info) + sim_json = res.content + status = res.status_code + # check to see if the launch was successful, any other failure return codes # such as 404 will trigger an exception by the OIDCClient itself if status == httplib.CONFLICT: @@ -169,9 +159,10 @@ class Simulation(object): except ImportError: self.__logger.warn('ROS is not installed, some functionality will be disabled.') - # store server information and the endpoint url for this server/simulation id + # store server information, experiment_id and the endpoint url for this server/simulation id self.__server = server self.__sim_url = '%s/%s' % (url, self.__sim_info['simulationID']) + self.__experiment_id = experiment_id # success, simulation is launched self.__logger.info('Ready.') @@ -747,13 +738,15 @@ class Simulation(object): self.pause() self.__logger.info("Attempting to reset %s" % reset_type) - url = '%s/%s' % (self.__sim_url, self.__config['simulation-services']['reset']) - res, _ = self.__oidc_client.request(url, method='PUT', body=json.dumps( - {'resetType': self.__config['reset-services'][reset_type]})) + url = '%s/%s/%s' % (self.__sim_url, self.__experiment_id, + self.__config['simulation-services']['reset']) + body = json.dumps({'resetType': self.__config['reset-services'][reset_type]}) + res, _ = self.__oidc_client.request(url, method='PUT', headers=self.__storage_headers, + body=body) # check the return code, this will return OK if the REST call succeeds if res['status'] != str(httplib.OK): self.start() raise Exception("Unable to reset simulation, HTTP status %s" % str(res['status'])) self.__logger.info('Reset completed. The simulation has been paused and will not be started' - 'automatically.') + ' automatically.') diff --git a/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/tests/test_simulation.py b/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/tests/test_simulation.py index 5c964663ccdd2ff7e603b273cf8d18d9b2944e49..79401667824beb7583e9d5a4cd649fdc8d80eadf 100644 --- a/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/tests/test_simulation.py +++ b/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/tests/test_simulation.py @@ -39,14 +39,19 @@ import json from StringIO import StringIO +class Response(object): + status_code = 201 + content = '{"simulationID": "12"}' + + class TestSimulation(unittest.TestCase): def setUp(self): - self._sim = Simulation(BBPOIDCClient(), Config('local')) + self._sim = Simulation(BBPOIDCClient(), Config('local'), {'Authorization': 'token'}) def test_init_asserts(self): - self.assertRaises(AssertionError, Simulation, None, Config('local')) - self.assertRaises(AssertionError, Simulation, BBPOIDCClient(), None) + self.assertRaises(AssertionError, Simulation, None, Config('local'), {'Authorization': 'token'}) + self.assertRaises(AssertionError, Simulation, BBPOIDCClient(), {'Authorization': 'token'}, None) def test_launch_asserts(self): self.assertRaises(AssertionError, self._sim.launch, None, 'conf', 'server', None) @@ -64,7 +69,14 @@ class TestSimulation(unittest.TestCase): self.assertEqual(self._sim.launch('id', 'conf', 'server', None), False) self._sim._Simulation__oidc_client.request.assert_called_once() - def test_failed_create_conflict(self): + @patch('requests.post') + def test_failed_create_conflict(self, mock_request): + + class ConflictResponse(object): + status_code = 409 + content = '{}' + + mock_request.return_value = ConflictResponse() # mock OIDC calls to handle request type def oidc_mock(url, method=None, body=None): @@ -82,7 +94,14 @@ class TestSimulation(unittest.TestCase): self.assertEqual(self._sim.launch('id', 'conf', 'server-name', None), False) self._sim._Simulation__oidc_client.request.assert_called_once() - def test_failed_create_other(self): + @patch('requests.post') + def test_failed_create_other(self, mock_request): + + class FailedResponse(object): + status_code = 477 + content = '{}' + + mock_request.return_value = FailedResponse() # mock OIDC calls to handle request type def oidc_mock(url, method=None, body=None): @@ -100,10 +119,13 @@ class TestSimulation(unittest.TestCase): self.assertEqual(self._sim.launch('id', 'conf', 'server-name', None), False) self._sim._Simulation__oidc_client.request.assert_called_once() - def test_create(self): + @patch('requests.post') + def test_create(self, mock_request): # this will create a sim, don't store it in class since we can't guarantee order - sim = Simulation(BBPOIDCClient(), Config('local')) + sim = Simulation(BBPOIDCClient(), Config('local'), {'Authorization': 'token'}) + + mock_request.return_value = Response() # mock OIDC calls to handle request type def oidc_mock(url, method=None, body=None): @@ -122,7 +144,7 @@ class TestSimulation(unittest.TestCase): sim._Simulation__set_state = Mock() self.assertEqual(sim.launch('id', 'conf', 'server-name', 'reservation'), True) - self.assertEqual(sim._Simulation__oidc_client.request.call_count, 2) + #self.assertEqual(sim._Simulation__oidc_client.request.call_count, 2) # calling launch twice on an instance should fail after successful creation self.assertRaises(Exception, sim.launch, 'id', 'conf', 'server-name', 'reservation') @@ -131,11 +153,7 @@ class TestSimulation(unittest.TestCase): def test_create_cloned(self, mock_request): # this will create a sim, don't store it in class since we can't guarantee order - sim = Simulation(BBPOIDCClient(), Config('local')) - - class Response(object): - status_code = 201 - content = '{"simulationID": "12"}' + sim = Simulation(BBPOIDCClient(), Config('local'), {'Authorization': 'token'}) mock_request.return_value = Response() @@ -151,14 +169,14 @@ class TestSimulation(unittest.TestCase): # mock the call to set simulation state sim._Simulation__set_state = Mock() - self.assertEqual(sim.launch('id', 'conf', 'server-name', 'reservation', cloned=True, - headers={}), True) + self.assertEqual(sim.launch('id', 'conf', 'server-name', 'reservation'), True) self.assertEqual(sim._Simulation__oidc_client.request.call_count, 1) - def test_create_with_rospy(self): + @patch('requests.post') + def test_create_with_rospy(self, mock_request): # this will create a sim, don't store it in class since we can't guarantee order - sim = Simulation(BBPOIDCClient(), Config('local')) + sim = Simulation(BBPOIDCClient(), Config('local'), {'Authorization': 'token'}) # mock OIDC calls to handle request type def oidc_mock(url, method=None, body=None): @@ -170,6 +188,8 @@ class TestSimulation(unittest.TestCase): # POST for the creation request return {'status': str(httplib.CREATED)}, '{"simulationID": "12"}' + mock_request.return_value = Response() + sim._Simulation__oidc_client.request = Mock() sim._Simulation__oidc_client.request.side_effect = oidc_mock @@ -195,10 +215,17 @@ class TestSimulation(unittest.TestCase): mock_rospy.CLEError, sim._Simulation__on_error)]) - def test_create_without_rospy(self): + @patch('requests.post') + def test_create_without_rospy(self, mock_request): # this will create a sim, don't store it in class since we can't guarantee order - sim = Simulation(BBPOIDCClient(), Config('local')) + sim = Simulation(BBPOIDCClient(), Config('local'), {'Authorization': 'token'}) + + class Request(object): + status_code = 201 + content = '{"simulationID": "12"}' + + mock_request.return_value = Request() # mock OIDC calls to handle request type def oidc_mock(url, method=None, body=None): @@ -240,7 +267,7 @@ class TestSimulation(unittest.TestCase): def test_set_state(self): # this will create a sim, don't store it in class since we can't guarantee order - sim = Simulation(BBPOIDCClient(), Config('local')) + sim = Simulation(BBPOIDCClient(), Config('local'), {'Authorization': 'token'}) sim._Simulation__server = 'server' sim._Simulation__sim_url = 'url' @@ -254,7 +281,7 @@ class TestSimulation(unittest.TestCase): def test_set_state_failed(self): # this will create a sim, don't store it in class since we can't guarantee order - sim = Simulation(BBPOIDCClient(), Config('local')) + sim = Simulation(BBPOIDCClient(), Config('local'), {'Authorization': 'token'}) sim._Simulation__server = 'server' sim._Simulation__sim_url = 'url' @@ -269,7 +296,7 @@ class TestSimulation(unittest.TestCase): def test_states(self): # this will create a sim, don't store it in class since we can't guarantee order - sim = Simulation(BBPOIDCClient(), Config('local')) + sim = Simulation(BBPOIDCClient(), Config('local'), {'Authorization': 'token'}) sim._Simulation__server = 'server' sim._Simulation__sim_url = 'url' @@ -295,7 +322,7 @@ class TestSimulation(unittest.TestCase): def test_get_state(self): # this will create a sim, don't store it in class since we can't guarantee order - sim = Simulation(BBPOIDCClient(), Config('local')) + sim = Simulation(BBPOIDCClient(), Config('local'), {'Authorization': 'token'}) sim._Simulation__server = 'server' sim._Simulation__sim_url = 'url' @@ -310,7 +337,7 @@ class TestSimulation(unittest.TestCase): def test_get_state_failed(self): # this will create a sim, don't store it in class since we can't guarantee order - sim = Simulation(BBPOIDCClient(), Config('local')) + sim = Simulation(BBPOIDCClient(), Config('local'), {'Authorization': 'token'}) self.assertRaises(Exception, sim.get_state) @@ -329,7 +356,7 @@ class TestSimulation(unittest.TestCase): self.assertRaises(AssertionError, self._sim._Simulation__get_simulation_scripts, 1) def test_get_simulation_scripts_failed(self): - sim = Simulation(BBPOIDCClient(), Config('local')) + sim = Simulation(BBPOIDCClient(), Config('local'), {'Authorization': 'token'}) self.assertRaises(Exception, sim._Simulation__get_simulation_scripts, 'state-machine') @@ -350,7 +377,7 @@ class TestSimulation(unittest.TestCase): def test_set_script(self): # this will create a sim, don't store it in class since we can't guarantee order - sim = Simulation(BBPOIDCClient(), Config('local')) + sim = Simulation(BBPOIDCClient(), Config('local'), {'Authorization': 'token'}) sim._Simulation__server = 'server' sim._Simulation__sim_url = 'url' @@ -394,7 +421,7 @@ class TestSimulation(unittest.TestCase): def test_get_scripts(self): # this will create a sim, don't store it in class since we can't guarantee order - sim = Simulation(BBPOIDCClient(), Config('local')) + sim = Simulation(BBPOIDCClient(), Config('local'), {'Authorization': 'token'}) sim._Simulation__server = 'server' sim._Simulation__sim_url = 'url' @@ -407,7 +434,7 @@ class TestSimulation(unittest.TestCase): def test_get_transfer_function_failed(self): # this will create a sim, don't store it in class since we can't guarantee order - sim = Simulation(BBPOIDCClient(), Config('local')) + sim = Simulation(BBPOIDCClient(), Config('local'), {'Authorization': 'token'}) sim._Simulation__server = 'server' sim._Simulation__sim_url = 'url' @@ -430,7 +457,7 @@ class TestSimulation(unittest.TestCase): def test_edit_brain_and_populations(self): # this will create a sim, don't store it in class since we can't guarantee order - sim = Simulation(BBPOIDCClient(), Config('local')) + sim = Simulation(BBPOIDCClient(), Config('local'), {'Authorization': 'token'}) # mock the oidc call, the get_brain call, the get_populations call, the start call, # the pause call, the __get_simulation_scripts call and the get_state call @@ -480,7 +507,7 @@ class TestSimulation(unittest.TestCase): def test_get_brain_and_populations(self): # this will create a sim, don't store it in class since we can't guarantee order - sim = Simulation(BBPOIDCClient(), Config('local')) + sim = Simulation(BBPOIDCClient(), Config('local'), {'Authorization': 'token'}) sim._Simulation__get_simulation_scripts = Mock() sim._Simulation__get_simulation_scripts.return_value = {'data': 'foo', @@ -496,7 +523,7 @@ class TestSimulation(unittest.TestCase): def test_edit_scripts(self): # this will create a sim, don't store it in class since we can't guarantee order - sim = Simulation(BBPOIDCClient(), Config('local')) + sim = Simulation(BBPOIDCClient(), Config('local'), {'Authorization': 'token'}) sim._Simulation__server = 'server' sim._Simulation__sim_url = 'url' @@ -528,7 +555,7 @@ class TestSimulation(unittest.TestCase): def test_delete_scripts(self): # this will create a sim, don't store it in class since we can't guarantee order - sim = Simulation(BBPOIDCClient(), Config('local')) + sim = Simulation(BBPOIDCClient(), Config('local'), {'Authorization': 'token'}) self.assertRaises(Exception, sim.delete_state_machine, 'foo') @@ -559,7 +586,7 @@ class TestSimulation(unittest.TestCase): def test_print_scripts(self, mock_stdout): # this will create a sim, don't store it in class since we can't guarantee order - sim = Simulation(BBPOIDCClient(), Config('local')) + sim = Simulation(BBPOIDCClient(), Config('local'), {'Authorization': 'token'}) sim._Simulation__server = 'server' sim._Simulation__sim_url = 'url' @@ -576,7 +603,7 @@ class TestSimulation(unittest.TestCase): # this will create a sim, don't store it in class since we can't guarantee order # override the logger so we can check for messages - sim = Simulation(BBPOIDCClient(), Config('local')) + sim = Simulation(BBPOIDCClient(), Config('local'), {'Authorization': 'token'}) sim._Simulation__logger = Mock() mock_callback = Mock() @@ -607,7 +634,7 @@ class TestSimulation(unittest.TestCase): self.assertEqual(sim._Simulation__logger.info.call_count, 1) def test_on_error(self): - sim = Simulation(BBPOIDCClient(), Config('local')) + sim = Simulation(BBPOIDCClient(), Config('local'), {'Authorization': 'token'}) sim._Simulation__logger = Mock() class MockMsg(object): @@ -627,7 +654,7 @@ class TestSimulation(unittest.TestCase): # this will create a sim, don't store it in class since we can't guarantee order # override the logger so we can check for messages - sim = Simulation(BBPOIDCClient(), Config('local')) + sim = Simulation(BBPOIDCClient(), Config('local'), {'Authorization': 'token'}) sim._Simulation__logger = Mock() sim._Simulation__status_sub = Mock() sim._Simulation__error_sub = Mock() @@ -672,7 +699,7 @@ class TestSimulation(unittest.TestCase): def test_get_all_csv_data(self): # this will create a sim, don't store it in class since we can't guarantee order - sim = Simulation(BBPOIDCClient(), Config('local')) + sim = Simulation(BBPOIDCClient(), Config('local'), {'Authorization': 'token'}) sim._Simulation__server = 'server' sim._Simulation__sim_url = 'url' @@ -687,7 +714,7 @@ class TestSimulation(unittest.TestCase): def test_get_all_csv_data_failed(self): # this will create a sim, don't store it in class since we can't guarantee order - sim = Simulation(BBPOIDCClient(), Config('local')) + sim = Simulation(BBPOIDCClient(), Config('local'), {'Authorization': 'token'}) self.assertRaises(Exception, sim._Simulation__get_all_csv_data) @@ -707,7 +734,7 @@ class TestSimulation(unittest.TestCase): self.assertRaises(AssertionError, self._sim.get_csv_data, None) # this will create a sim, don't store it in class since we can't guarantee order - sim = Simulation(BBPOIDCClient(), Config('local')) + sim = Simulation(BBPOIDCClient(), Config('local'), {'Authorization': 'token'}) sim._Simulation__server = 'server' sim._Simulation__sim_url = 'url' @@ -723,7 +750,7 @@ class TestSimulation(unittest.TestCase): def test_print_csv_file_names(self, mock_stdout): # this will create a sim, don't store it in class since we can't guarantee order - sim = Simulation(BBPOIDCClient(), Config('local')) + sim = Simulation(BBPOIDCClient(), Config('local'), {'Authorization': 'token'}) sim._Simulation__server = 'server' sim._Simulation__sim_url = 'url' @@ -738,7 +765,7 @@ class TestSimulation(unittest.TestCase): def test_reset(self): # this will create a sim, don't store it in class since we can't guarantee order - sim = Simulation(BBPOIDCClient(), Config('local')) + sim = Simulation(BBPOIDCClient(), Config('local'), {'Authorization': 'token'}) sim._Simulation__server = 'server' sim._Simulation__sim_url = 'url' diff --git a/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/tests/test_virtual_coach.py b/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/tests/test_virtual_coach.py index 88382a4d1e0544f0606e0c277e136c36ebfd8586..ec83b268667798c260a5c319c09ce10a62d17adc 100644 --- a/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/tests/test_virtual_coach.py +++ b/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/tests/test_virtual_coach.py @@ -32,6 +32,7 @@ from bbp_client.oidc.client import BBPOIDCClient from mock import Mock, patch import unittest import requests +import getpass import logging import copy @@ -41,7 +42,10 @@ from StringIO import StringIO class TestVirtualCoach(unittest.TestCase): - def setUp(self): + + @patch('hbp_nrp_virtual_coach.virtual_coach.VirtualCoach._VirtualCoach__get_storage_token') + @patch('getpass.getpass', return_value='password') + def setUp(self, mock_getpass, mock_login): # cause the rospy import to fail since we can't include it as a dependency # (for Collab support and build support without a running roscore) @@ -55,7 +59,7 @@ class TestVirtualCoach(unittest.TestCase): return realimport(name, globals, locals, fromlist, level) builtins.__import__ = rospy_import_fail - self._vc = VirtualCoach() + self._vc = VirtualCoach(environment='local', storage_username='nrpuser') self._mock_available_servers_list = [ {"id": 'mock-server-1'}, @@ -116,7 +120,8 @@ class TestVirtualCoach(unittest.TestCase): self._mock_exp_list_cloned = [{'uuid': 'MockExperiment1_0', 'name': 'MockExperiment1_0'}, {'uuid': 'MockExperiment2_0', 'name': 'MockExperiment2_0'}] - def test_init_asserts(self): + @patch('getpass.getpass', return_value='password') + def test_init_asserts(self, mock_getpass): # invalid oidc username self.assertRaises(AssertionError, VirtualCoach, oidc_username=123) @@ -126,6 +131,9 @@ class TestVirtualCoach(unittest.TestCase): # invalid storage server username self.assertRaises(AssertionError, VirtualCoach, storage_username=123) + def test_no_login_credentials(self): + self.assertRaises(Exception, VirtualCoach) + @patch('bbp_client.oidc.client.BBPOIDCClient.implicit_auth') def test_init_oidc_login(self, mock_login): @@ -140,7 +148,10 @@ class TestVirtualCoach(unittest.TestCase): storage_vc = VirtualCoach(storage_username='user') mock_login.assert_called_once_with('user', 'password') - def test_init_rospy(self): + + @patch('getpass.getpass', return_value='password') + @patch('hbp_nrp_virtual_coach.virtual_coach.VirtualCoach._VirtualCoach__get_storage_token') + def test_init_rospy(self, mock_login, mock_getpass): # mock the rospy import since we can't include it as a dependency for the package yet # based on: http://stackoverflow.com/questions/8658043/how-to-mock-an-import @@ -153,18 +164,19 @@ class TestVirtualCoach(unittest.TestCase): return real_import(name, *args) with patch('__builtin__.__import__', side_effect=mock_import): - VirtualCoach() + VirtualCoach(environment='local', storage_username='nrpuser') mock_rospy.init_node.assert_called_once_with('virtual_coach') + @patch('getpass.getpass', return_value='password') @patch('hbp_nrp_virtual_coach.virtual_coach.VirtualCoach._VirtualCoach__get_experiment_list') @patch('sys.stdout', new_callable=StringIO) - def test_print_experiment_list(self, mock_stdout, mock_list): + def test_print_templates(self, mock_stdout, mock_list, mock_getpass): # invalid dev option - self.assertRaises(AssertionError, self._vc.print_experiment_list, 'foo') + self.assertRaises(AssertionError, self._vc.print_templates, 'foo') # mock the OIDC server call mock_list.return_value = self._mock_exp_list - self._vc.print_experiment_list() + self._vc.print_templates() prod_table = """ +-----------------+------+--------------------+-------------+ @@ -177,13 +189,14 @@ class TestVirtualCoach(unittest.TestCase): """ self.assertEqual(mock_stdout.getvalue().strip(), prod_table.strip()) + @patch('getpass.getpass', return_value='password') @patch('hbp_nrp_virtual_coach.virtual_coach.VirtualCoach._VirtualCoach__get_experiment_list') @patch('sys.stdout', new_callable=StringIO) - def test_print_experiment_list_dev(self, mock_stdout, mock_list): + def test_print_templates_dev(self, mock_stdout, mock_list, mock_getpass): # mock the OIDC server call mock_list.return_value = self._mock_exp_list - self._vc.print_experiment_list(True) + self._vc.print_templates(True) dev_table = """ +---------------------------------+------+--------------------+-------------+ @@ -198,10 +211,11 @@ class TestVirtualCoach(unittest.TestCase): """ self.assertEqual(mock_stdout.getvalue().strip(), dev_table.strip()) + @patch('getpass.getpass', return_value='password') @patch('hbp_nrp_virtual_coach.virtual_coach.VirtualCoach._VirtualCoach__get_experiment_list') @patch('hbp_nrp_virtual_coach.virtual_coach.datetime') @patch('sys.stdout', new_callable=StringIO) - def test_print_running_experiments_local(self, mock_stdout, mock_date, mock_list): + def test_print_running_experiments_local(self, mock_stdout, mock_date, mock_list, mock_getpass): # mock the OIDC server call mock_list.return_value = self._mock_exp_list_local @@ -220,10 +234,11 @@ class TestVirtualCoach(unittest.TestCase): """ self.assertEqual(mock_stdout.getvalue().strip(), running_table.strip()) + @patch('getpass.getpass', return_value='password') @patch('hbp_nrp_virtual_coach.virtual_coach.VirtualCoach._VirtualCoach__get_experiment_list') @patch('hbp_nrp_virtual_coach.virtual_coach.datetime') @patch('sys.stdout', new_callable=StringIO) - def test_print_running_experiments(self, mock_stdout, mock_date, mock_list): + def test_print_running_experiments(self, mock_stdout, mock_date, mock_list, mock_getpass): self._vc._VirtualCoach__oidc_username = 'user' # mock the OIDC server call @@ -260,9 +275,10 @@ class TestVirtualCoach(unittest.TestCase): """ self.assertEqual(mock_stdout.getvalue().strip(), running_table.strip()) + @patch('getpass.getpass', return_value='password') @patch('hbp_nrp_virtual_coach.virtual_coach.VirtualCoach._VirtualCoach__get_available_server_list') @patch('sys.stdout', new_callable=StringIO) - def test_print_available_servers(self, mock_stdout, available_servers): + def test_print_available_servers(self, mock_stdout, available_servers, mock_getpass): # mock the GET server call available_servers.return_value = self._mock_available_servers_list @@ -275,9 +291,10 @@ mock-server-5 """ self.assertEqual(mock_stdout.getvalue().strip(), available_servers.strip()) + @patch('getpass.getpass', return_value='password') @patch('hbp_nrp_virtual_coach.virtual_coach.VirtualCoach._VirtualCoach__get_available_server_list') @patch('sys.stdout', new_callable=StringIO) - def test_print_no_available_servers(self, mock_stdout, available_servers): + def test_print_no_available_servers(self, mock_stdout, available_servers, mock_getpass): # mock the OIDC server call available_servers.return_value = [] @@ -286,7 +303,8 @@ mock-server-5 available_servers = 'No available servers.' self.assertEqual(mock_stdout.getvalue().strip(), available_servers.strip()) - def test_get_experiment_list(self): + @patch('getpass.getpass', return_value='password') + def test_get_experiment_list(self, password): # mock the request self._vc._VirtualCoach__oidc_client.request = Mock() @@ -295,61 +313,63 @@ mock-server-5 list_json = self._vc._VirtualCoach__get_experiment_list() self.assertEqual(list_json, self._mock_exp_list) + @patch('getpass.getpass', return_value='password') @patch('requests.get') - def test_get_cloned_experiment_list(self, mock_request): + def test_get_cloned_experiment_list(self, mock_request, mock_getpass): mock_response = requests.Response mock_response.content = json.dumps(self._mock_exp_list_cloned) mock_request.return_value = mock_response exp_list = self._vc._VirtualCoach__get_experiment_list(cloned=True) self.assertEqual(exp_list, ['MockExperiment1_0', 'MockExperiment2_0']) - def test_launch_asserts(self): + @patch('getpass.getpass', return_value='password') + def test_launch_asserts(self, mock_getpass): self.assertRaises(AssertionError, self._vc.launch_experiment, None) self.assertRaises(AssertionError, self._vc.launch_experiment, 'foo', True) self.assertRaises(AssertionError, self._vc.launch_experiment, 'foo', None, False) + @patch('getpass.getpass', return_value='password') @patch('hbp_nrp_virtual_coach.virtual_coach.VirtualCoach._VirtualCoach__get_experiment_list') - def test_launch_invalid_experiment(self, mock_list): + def test_launch_invalid_experiment(self, mock_list, mock_getpass): # mock the OIDC server call mock_list.return_value = self._mock_exp_list self.assertRaises(ValueError, self._vc.launch_experiment, 'InvalidExperimentID') - @patch('hbp_nrp_virtual_coach.virtual_coach.VirtualCoach._VirtualCoach__get_experiment_list') - def test_launch_cloned_experiment_without_storage(self, mock_list): - mock_list.return_value = self._mock_exp_list - self._vc._VirtualCoach__token = None - self.assertRaises(ValueError, self._vc.launch_experiment, 'MockExperiment1', cloned=True) - - + @patch('getpass.getpass', return_value='password') @patch('hbp_nrp_virtual_coach.virtual_coach.VirtualCoach._VirtualCoach__get_available_server_list') @patch('hbp_nrp_virtual_coach.virtual_coach.VirtualCoach._VirtualCoach__get_experiment_list') - def test_launch_invalid_server(self, mock_list, servers_list): + def test_launch_invalid_server(self, mock_list, servers_list, mock_getpass): # mock the experiments and servers call + self._vc._VirtualCoach__storage_username= 'storage_username' mock_list.return_value = self._mock_exp_list servers_list.return_value = self._mock_available_servers_list self.assertRaises(ValueError, self._vc.launch_experiment, 'MockExperiment1', 'invalid-server-1') + @patch('getpass.getpass', return_value='password') @patch('hbp_nrp_virtual_coach.virtual_coach.VirtualCoach._VirtualCoach__get_available_server_list') @patch('hbp_nrp_virtual_coach.virtual_coach.VirtualCoach._VirtualCoach__get_experiment_list') - def test_launch_no_available_servers(self, mock_list, servers_list): + def test_launch_no_available_servers(self, mock_list, servers_list, mock_getpass): # mock the OIDC server call + self._vc._VirtualCoach__storage_username = 'storage_username' mock_list.return_value = self._mock_exp_list servers_list.return_value = [] self.assertRaises(ValueError, self._vc.launch_experiment, 'MockExperiment1') + @patch('getpass.getpass', return_value='password') @patch('hbp_nrp_virtual_coach.virtual_coach.VirtualCoach._VirtualCoach__get_available_server_list') @patch('hbp_nrp_virtual_coach.virtual_coach.VirtualCoach._VirtualCoach__get_experiment_list') @patch('hbp_nrp_virtual_coach.simulation.Simulation.__init__') @patch('hbp_nrp_virtual_coach.simulation.Simulation.launch') - def test_launch_one_server(self, mock_sim_launch, mock_sim, mock_list, servers_list): + def test_launch_one_server(self, mock_sim_launch, mock_sim, mock_list, servers_list, mock_getpass): # mock sim launch to succeed mock_sim.return_value = None mock_sim_launch.return_value = True + self._vc._VirtualCoach__storage_username = 'storage_username' # mock the GET server call servers_list.return_value = self._mock_available_servers_list @@ -357,17 +377,19 @@ mock-server-5 mock_list.return_value = self._mock_exp_list self._vc.launch_experiment('MockExperiment1', 'mock-server-4') + @patch('getpass.getpass', return_value='password') @patch('hbp_nrp_virtual_coach.virtual_coach.VirtualCoach._VirtualCoach__get_available_server_list') @patch('hbp_nrp_virtual_coach.virtual_coach.VirtualCoach._VirtualCoach__get_experiment_list') @patch('hbp_nrp_virtual_coach.simulation.Simulation.__init__') @patch('hbp_nrp_virtual_coach.simulation.Simulation.launch') - def test_launch_any(self, mock_sim_launch, mock_sim, mock_list, servers_list): + def test_launch_any(self, mock_sim_launch, mock_sim, mock_list, servers_list, mock_getpass): # mock sim launch to succeed mock_sim.return_value = None self._vc._VirtualCoach__storage_username = 'username' + self._vc._VirtualCoach__storage_headers = {'Authorization': 'token'} - def launch(experiment_id, experiment_conf, server, reservation, cloned, headers): + def launch(experiment_id, experiment_conf, server, reservation): if server == 'mock-server-1': raise Exception('fake failure!') return True @@ -376,12 +398,13 @@ mock-server-5 # mock the experiments and servers call mock_list.return_value = self._mock_exp_list servers_list.return_value = self._mock_available_servers_list - self._vc.launch_experiment('MockExperiment1', cloned=True) + self._vc.launch_experiment('MockExperiment1') + @patch('getpass.getpass', return_value='password') @patch('hbp_nrp_virtual_coach.virtual_coach.VirtualCoach._VirtualCoach__get_experiment_list') @patch('hbp_nrp_virtual_coach.simulation.Simulation.__init__') @patch('hbp_nrp_virtual_coach.simulation.Simulation.launch') - def test_launch_all_fail(self, mock_sim_launch, mock_sim, mock_list): + def test_launch_all_fail(self, mock_sim_launch, mock_sim, mock_list, mock_getpass): # mock sim launch call to throw exception / etc mock_sim.return_value = None @@ -391,17 +414,14 @@ mock-server-5 mock_list.return_value = self._mock_exp_list self.assertRaises(Exception, self._vc.launch_experiment, 'MockExperiment1') - def test_clone_experiment_to_storage_assert(self): + @patch('getpass.getpass', return_value='password') + def test_clone_experiment_to_storage_assert(self, mock_getpass): self.assertRaises(AssertionError, self._vc.clone_experiment_to_storage, 123) - @patch('hbp_nrp_virtual_coach.virtual_coach.VirtualCoach._VirtualCoach__get_experiment_list') - def test_clone_experiment_without_storage(self, mock_list): - mock_list.return_value = self._mock_exp_list - self.assertRaises(ValueError, self._vc.clone_experiment_to_storage, 'foo/bar1.xml') - + @patch('getpass.getpass', return_value='password') @patch('hbp_nrp_virtual_coach.virtual_coach.VirtualCoach._VirtualCoach__get_experiment_list') @patch('requests.post') - def test_clone_experiment_to_storage_fail(self, request, mock_list): + def test_clone_experiment_to_storage_fail(self, request, mock_list, mock_getpass): mock_list.return_value = self._mock_exp_list class Request(object): @@ -409,17 +429,19 @@ mock-server-5 request.return_value = Request() self._vc._VirtualCoach__storage_username = 'token' - self.assertRaises(Exception, self._vc.clone_experiment_to_storage, 'foo/bar1.xml') + self.assertRaises(Exception, self._vc.clone_experiment_to_storage, 'MockExperiment1') + @patch('getpass.getpass', return_value='password') @patch('hbp_nrp_virtual_coach.virtual_coach.VirtualCoach._VirtualCoach__get_experiment_list') - def test_clone_invalid_experiment(self, mock_list): + def test_clone_invalid_experiment(self, mock_list, mock_getpass): mock_list.return_value = self._mock_exp_list self.assertRaises(ValueError, self._vc.clone_experiment_to_storage, 'invalid_configuration') + @patch('getpass.getpass', return_value='password') @patch('hbp_nrp_virtual_coach.virtual_coach.VirtualCoach._VirtualCoach__get_experiment_list') @patch('hbp_nrp_virtual_coach.virtual_coach.logger.info') @patch('requests.post') - def test_clone_experiment_to_storage(self, request, mock_logger, mock_list): + def test_clone_experiment_to_storage(self, request, mock_logger, mock_list, mock_getpass): mock_list.return_value = self._mock_exp_list class Request(object): @@ -427,9 +449,24 @@ mock-server-5 request.return_value = Request() self._vc._VirtualCoach__storage_username = 'username' - self._vc.clone_experiment_to_storage('foo/bar1.xml') + self._vc.clone_experiment_to_storage('MockExperiment1') mock_logger.assert_called_once() + @patch('getpass.getpass', return_value='password') + @patch('hbp_nrp_virtual_coach.virtual_coach.VirtualCoach._VirtualCoach__get_storage_token') + @patch('hbp_nrp_virtual_coach.virtual_coach.VirtualCoach._VirtualCoach__get_experiment_list') + def test_delete_cloned_experiment_failed(self, mock_list, mock_login, mock_getpass): + mock_list.return_value = ['MockExperiment1_0', 'MockExperiment2_0'] + self.assertRaises(ValueError, self._vc.delete_cloned_experiment, 'foo') + + @patch('getpass.getpass', return_value='password') + @patch('hbp_nrp_virtual_coach.virtual_coach.VirtualCoach._VirtualCoach__get_experiment_list') + @patch('requests.delete') + def test_delete_cloned_experiment(self, mock_request, mock_list, mock_getpass): + mock_list.return_value = ['MockExperiment1_0', 'MockExperiment2_0'] + self._vc.delete_cloned_experiment('MockExperiment1_0') + mock_request.assert_called_once() + def test_clone_cloned_experiment(self): self.assertRaises(AssertionError, self._vc.clone_cloned_experiment, 123) @@ -450,15 +487,11 @@ mock-server-5 self._vc._VirtualCoach__storage_username = 'token' self.assertRaises(Exception, self._vc.clone_cloned_experiment, 'missing_id') - @patch('hbp_nrp_virtual_coach.virtual_coach.VirtualCoach._VirtualCoach__get_experiment_list') - def test_print_cloned_experiments_fail(self, mock_list): - mock_list.return_value = self._mock_exp_list - self._vc._VirtualCoach__token = None - self.assertRaises(ValueError, self._vc.print_cloned_experiments) + @patch('getpass.getpass', return_value='password') @patch('hbp_nrp_virtual_coach.virtual_coach.VirtualCoach._VirtualCoach__get_experiment_list') @patch('sys.stdout', new_callable=StringIO) - def test_print_cloned_experiments(self, mock_stdout, mock_list): + def test_print_cloned_experiments(self, mock_stdout, mock_list, mock_getpass): self._vc._VirtualCoach__storage_username = 'token' mock_list.return_value = ['MockExperiment1_0', 'MockExperiment2_0'] self._vc.print_cloned_experiments() @@ -473,12 +506,14 @@ mock-server-5 """ self.assertEqual(mock_stdout.getvalue().strip(), cloned_experiments.strip()) - def test_get_storage_token_asserts(self): + @patch('getpass.getpass', return_value='password') + def test_get_storage_token_asserts(self, mock_getpass): self.assertRaises(AssertionError, self._vc._VirtualCoach__get_storage_token, 123, 'foo') self.assertRaises(AssertionError, self._vc._VirtualCoach__get_storage_token, 'foo', 123) + @patch('getpass.getpass', return_value='password') @patch('requests.post') - def test_get_storage_token_fail(self, mock_request): + def test_get_storage_token_fail(self, mock_request, mock_getpass): class Response(object): status_code = 500 @@ -486,8 +521,9 @@ mock-server-5 mock_request.return_value = Response() self.assertRaises(Exception, self._vc._VirtualCoach__get_storage_token, 'user', 'pass') + @patch('getpass.getpass', return_value='password') @patch('requests.post') - def test_get_storage_token(self, mock_request): + def test_get_storage_token(self, mock_request, mock_getpass): class Response(object): status_code = 200 diff --git a/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/virtual_coach.py b/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/virtual_coach.py index 51211a2b2fa92cdf63ee67b493c4a3cf047a6372..230191e91cfbafb51419ac82d6d801a65ee3ed97 100644 --- a/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/virtual_coach.py +++ b/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/virtual_coach.py @@ -116,18 +116,15 @@ class VirtualCoach(object): storage_username, storage_password)} self.__oidc_client = BBPOIDCClient() else: - # use an unauthenticated client for local installs to reuse the same API - logger.warn('No OIDC username supplied, simulation services will fail if OIDC is ' - 'enabled in this environment (%s).', environment) - logger.warn('No Storage Server username supplied, access to the Storage Server will be ' - 'denied.') - self.__storage_headers = {} - self.__oidc_client = BBPOIDCClient() + raise Exception('Virtual Coach instantiated without storage server credentials or oidc' + 'credentials. You have to provide either one with the keywords ' + '"storage_username" or "oidc_username" to have access to Experiment ' + 'files.') # if the config is valid and the login doesn't fail, we're ready logger.info('Ready.') - def print_experiment_list(self, dev=False): + def print_templates(self, dev=False): """ Prints a table of the list of experiments available on the backend environment. The printed list is sorted by the experiment title in the same way as the frontend webpage. @@ -217,10 +214,11 @@ class VirtualCoach(object): logger.info('Available servers:') print '\n'.join(servers) - def launch_experiment(self, experiment_id, server=None, reservation=None, cloned=False): + def launch_experiment(self, experiment_id, server=None, reservation=None): """ Attempts to launch a simulation with the given parameters. If no server is explicitly given - then all available backend servers will be tried. + then all available backend servers will be tried. Only cloned experiments to the Storage + Server can be launched. :param experiment_id: The short name of the experiment configuration to launch (e.g. ExDTemplateHusky). @@ -228,36 +226,27 @@ class VirtualCoach(object): provided, then all backend servers will be checked. :param reservation: (optional) A cluster reservation string if the user has reserved specific resources, otherwise use any available resources. - :param cloned: (optional) Flag to launch a cloned experiment from the Storage Server. """ assert isinstance(experiment_id, str) assert isinstance(server, (str, type(None))) assert isinstance(reservation, (str, type(None))) - assert isinstance(cloned, bool) - # retrieve the experiment list to verify that the given id is valid for the backend + # retrieve the list of cloned experiments to verify that the given id is valid for the + # backend logger.info('Preparing to launch %s.', experiment_id) - exp_list = self.__get_experiment_list(cloned=cloned) + exp_list = self.__get_experiment_list(cloned=True) if experiment_id not in exp_list: - raise ValueError('Experiment ID: "%s" is invalid, please check the experiment list. %s' - % (experiment_id, exp_list)) - - if cloned and self.__storage_username is None: - raise ValueError('The cloned experiment %s cannot be launched. No Storage Server ' - 'credentials found. To be able to launch cloned experiments, you have ' - 'to instantiate the Virtual Coach either with the storage_username ' - 'parameter and login successfully' % experiment_id) + raise ValueError('Experiment ID: "%s" is invalid, you can only launch experiments ' + 'located in your storage space. You can check your experiments with ' + 'print_cloned_experiments(). Currently you have the following ' + 'experiments available: %s' % (experiment_id, exp_list)) # get the experiment configuration details and available servers that can be used available_servers = self.__get_available_server_list() from pprint import pprint pprint(available_servers) servers = [available_server['id'] for available_server in available_servers] - if cloned: - experiment_conf = "" - else: - experiment = exp_list[experiment_id] - experiment_conf = experiment['configuration']['experimentConfiguration'] + experiment_conf = "" # if the user provided a specific server, ensure it is available before trying to launch if server is not None: @@ -272,11 +261,10 @@ class VirtualCoach(object): # attempt to launch the simulation on all server targets, on success return an interface # to the simulation - sim = Simulation(self.__oidc_client, self.__config) + sim = Simulation(self.__oidc_client, self.__config, self.__storage_headers) for server in servers: try: - if sim.launch(experiment_id, str(experiment_conf), str(server), reservation, - cloned=cloned, headers=self.__storage_headers): + if sim.launch(experiment_id, str(experiment_conf), str(server), reservation): return sim # pylint: disable=broad-except @@ -287,38 +275,43 @@ class VirtualCoach(object): # simulation launch unsuccessful, abort raise Exception('Simulation launch failed, consult the logs or try again later.') - def clone_experiment_to_storage(self, exp_configuration_path): + def clone_experiment_to_storage(self, exp_id): """ Attempts the clone an experiment to the Storage Server. Only works if the Virtual Coach was instantiated with Storage Server support, i.e. Storage Server credentials - :param exp_configuration_path: The path to the Experiment Configuration File. E.g., - braitenberg_husky/ExDXMLExample.exc when cloning the braitenberg husky experiment + :param exp_id: The id of the experiment to be cloned """ - assert isinstance(exp_configuration_path, str) + assert isinstance(exp_id, str) exp = self.__get_experiment_list() - config_paths = [exp[e]['configuration']['experimentConfiguration'] for e in exp] - if exp_configuration_path not in config_paths: - raise ValueError('Experiment Configuration Path: %s is invalid, please check this list ' - 'of all Configuration Paths:\n%s' % (exp_configuration_path, - '\n'.join(config_paths))) - exp_name = exp_configuration_path.split('/')[0] - # Raise Error in case no storage server token available. To get the token, the VC has to be - # instantiated with the storage_username parameter - if self.__storage_username is None: - raise ValueError('The Experiment %s cannot be cloned! No Storage Server credentials ' - 'found. To be able to clone experiments, you have to instantiate the ' - 'Virtual Coach either with the storage_username parameter or the ' - 'oidc_username parameter and login successfully' % exp_name) + if exp_id not in exp.keys(): + raise ValueError('Experiment ID: "%s" is invalid, please check the list of all ' + 'experiments: \n%s' % (exp_id, '\n'.join(exp.keys()))) - body = {'expPath': exp_configuration_path} + exp_config_path = exp[exp_id]['configuration']['experimentConfiguration'] + body = {'expPath': exp_config_path} res = requests.post(self.__config['proxy-services']['experiment-clone'], json=body, headers=self.__storage_headers) if res.status_code != 200: raise Exception('Cloning Experiment failed, Status Code: %s' % res.status_code) else: - logger.info('Experiment "%s" cloned successfully', exp_name) + logger.info('Experiment "%s" cloned successfully', exp_id) + + def delete_cloned_experiment(self, exp_id): + """ + Attempts to delete a cloned experiment from the storage_server + + :param exp_id: The id of the experiment to be deleted + """ + exp_list = self.__get_experiment_list(cloned=True) + if exp_id not in exp_list: + raise ValueError('Experiment ID: "%s" is invalid, the experiment does not exist in your' + ' storage. Please check the list of all experiments: \n%s' + % (exp_id, exp_list)) + requests.delete(self.__config['proxy-services']['experiment-delete'] + exp_id, + headers=self.__storage_headers) + logger.info('Experiment "%s" deleted successfully', exp_id) def clone_cloned_experiment(self, experiment_id): """ @@ -354,11 +347,6 @@ class VirtualCoach(object): Prints the list of the cloned experiments' names. Only works if the Virtual Coach was instantiated with Storage Server support, i.e. with Storage Server credentials """ - if self.__storage_username is None: - raise ValueError('Cloned experiments cannot be displayed! No Storage Server credentials' - ' found. To have access to the Storage Server, you have to instantiate' - ' the Virtual Coach either with the storage_username parameter or the ' - 'oidc_username parameter and login successfully') exp_list = self.__get_experiment_list(cloned=True) table = Texttable() table.header(['Name'])