def test_can_modify(self): with self.__app.test_request_context('/test', headers={'X-User-Name': 'Test'}): self.assertTrue( UserAuthentication.can_modify(FakeSimulation('Test'))) self.assertFalse( UserAuthentication.can_modify(FakeSimulation('default-owner'))) with self.__app.test_request_context('/test'): self.assertFalse( UserAuthentication.can_modify(FakeSimulation('Test'))) self.assertTrue( UserAuthentication.can_modify(FakeSimulation('default-owner')))
def post(self, sim_id, command): """ Issue user commands to the simulator recorder. See parameter description for supported query commands. :param sim_id: The simulation ID to command. :param command: The command to issue, supported: [start, stop, cancel, reset, save] :status 500: {0} :status 404: {1} :status 401: {2} :status 400: The command is invalid/refused by recorder - see message returned. :status 200: Success. The command was issued. """ # validate simulation id sim = _get_simulation_or_abort(sim_id) # only the simulation owner can command the recorder if not UserAuthentication.can_modify(sim): raise NRPServicesWrongUserException() # pure local command to save file to storage if command == 'save': try: file_name = sim.lifecycle.save_record_to_user_storage() return {'filename': file_name}, 200 except Exception as e: raise NRPServicesClientErrorException( 'Cannot copy record to client storage', error_code=404) else: # validate the remote command type valid_commands = { 'start': SimulationRecorderRequest.START, 'stop': SimulationRecorderRequest.STOP, 'cancel': SimulationRecorderRequest.CANCEL, 'reset': SimulationRecorderRequest.RESET } if command not in valid_commands: raise NRPServicesClientErrorException( 'Invalid recorder command: %s' % command, error_code=404) # command the recorder, if unsuccessful return the error message try: resp = sim.cle.command_simulation_recorder( valid_commands[command]) # check the command success, on failure return status 400 + error if not resp.value: raise NRPServicesClientErrorException(resp.message) # successful, return status 200 return 'success', 200 # internal CLE ROS error if service call fails, notify frontend except ROSCLEClientException as e: raise NRPServicesGeneralException(str(e), 'CLE error', 500)
def put(self, sim_id): """ Change the material of a given visual. Currently, only the change of screen materials is implemented :param sim_id: The simulation ID :< json string visual_path: The path to the visual for which a change in material is requested :< json string material_name: The name of the material that will be applied :status 404: {0} :status 401: {1} :status 400: The parameters are invalid :status 200: Material applied successfully """ simulation = _get_simulation_or_abort(sim_id) if not UserAuthentication.can_modify(simulation): raise NRPServicesWrongUserException() body = request.get_json(force=True) if 'visual_path' not in body: return "No visual_path given", 400 if 'material' not in body: return "No material given", 400 return self.__set_material(body['visual_path'], body['material'])
def delete(self, sim_id, state_machine_name): """ Delete a state machine :param sim_id: The simulation ID :param state_machine_name: The name of the state machine to be deleted :status 500: {0} :status 404: {1} :status 401: {2} :status 200: The delete operation was successfully called. This does not imply that the state machine function was correctly deleted though. """ simulation = _get_simulation_or_abort(sim_id) if not UserAuthentication.can_modify(simulation): raise NRPServicesWrongUserException() failure_message = "State machine destruction failed: " try: ok, response_message = simulation.delete_state_machine( state_machine_name) if ok: return "Success. The state machine was successfully deleted.", 200 except Exception as e: info = exc_info() raise NRPServicesGeneralException( failure_message + " {0}: {1}".format(e.__class__.__name__, e.message), "State machine error", ), None, info[2] raise NRPServicesStateMachineException( failure_message + "\n" + response_message, 404)
def put(self, sim_id, transfer_function_name, activate): """ Sets the activation state of the transfer function :param sim_id: The simulation ID :param transfer_function_name: The name of the transfer function to be modified :param activate A boolean denoting the new desired activation status :status 404: {0} :status 401: {1} :status 400: {2} :status 200: Success. The activation state of the TF has been successfully changed """ simulation = _get_simulation_or_abort(sim_id) if not UserAuthentication.can_modify(simulation): raise NRPServicesWrongUserException() activate_bool = activate.lower( ) == "true" # convert unicode to boolean error_message = simulation.cle.activate_simulation_transfer_function( transfer_function_name, activate_bool) if error_message: raise NRPServicesTransferFunctionException( "Transfer function (de-)activation failed: {}\n".format( error_message)) return 200
def put(self, sim_id, transfer_function_name): """ Applies user changes to transfer function code :param sim_id: The simulation ID :param transfer_function_name: The name of the transfer function to be modified :< string data: The source code of the transfer function :status 404: {0} :status 403: {1} :status 401: {2} :status 400: {3} :status 200: Success. The transfer function was successfully patched """ simulation = _get_simulation_or_abort(sim_id) if not UserAuthentication.can_modify(simulation): raise NRPServicesWrongUserException() transfer_function_source = request.data error_message = simulation.cle.edit_simulation_transfer_function( transfer_function_name, transfer_function_source) if error_message: ex_msg = ("Transfer Function patch failed: {error_msg}\n" "Updated source:\n" "{tf_src}").format(error_msg=error_message, tf_src=str(transfer_function_source)) raise NRPServicesDuplicateNameException(ex_msg) if "duplicate" in error_message \ else NRPServicesTransferFunctionException(ex_msg) return 200
def put(self, sim_id): """ Sets the simulation with the given name into a new state. Allowed values are: created, initialized, started, paused, stopped :param sim_id: The simulation id :< json string state: The state of the simulation to set :> json string state: The state of the simulation :status 404: {0} :status 401: {1} :status 400: The state transition is invalid :status 200: The state of the simulation with the given ID was successfully set """ simulation = _get_simulation_or_abort(sim_id) if not UserAuthentication.can_modify(simulation): raise NRPServicesWrongUserException() body = request.get_json(force=True) try: simulation.state = body['state'] except MachineError: raise NRPServicesStateException("Invalid transition (" + simulation.state + "->" + body['state'] + ")") return {'state': str(simulation.state)}, 200
def post(self, sim_id): """ Adds a new transfer function :param sim_id: The simulation ID :< string data: The source code of the transfer function :status 404: {0} :status 403: {1} :status 401: {2} :status 400: {3} :status 200: Success. The transfer function was successfully added """ simulation = _get_simulation_or_abort(sim_id) if not UserAuthentication.can_modify(simulation): raise NRPServicesWrongUserException() transfer_function_source = request.data error_message = simulation.cle.add_simulation_transfer_function( transfer_function_source) if error_message: ex_msg = ("Adding a new Transfer Function failed: {error_msg}\n" "Updated source:\n" "{tf_src}").format(error_msg=error_message, tf_src=str(transfer_function_source)) raise NRPServicesDuplicateNameException(ex_msg) if "duplicate" in error_message \ else NRPServicesTransferFunctionException(ex_msg) return 200
def post(self, sim_id): """ Extends the simulation timeout :param sim_id: The simulation id :status 404: {0} :status 402: Failed to extend the timeout :status 412: Failed due to simulation timeout type being 'simulation' :status 401: {1} :status 200: Success. The simulation timeout has been extended """ simulation = _get_simulation_or_abort(sim_id) if not UserAuthentication.can_modify(simulation): raise NRPServicesWrongUserException() if simulation.timeout_type == TimeoutType.SIMULATION: return {}, 412 new_timeout = simulation.kill_datetime + \ datetime.timedelta(minutes=SIMULATION_TIMEOUT_EXTEND) if not simulation.cle.extend_simulation_timeout(new_timeout): return {}, 402 simulation.kill_datetime = new_timeout return {}, 200
def put(self, sim_id): """ Set the populations of the brain in a simulation with the specified simulation id. :param sim_id: The simulation ID :< json dict brain_populations: A dictionary indexed by population names and containing neuron indices :< json string brain_type: Type of the brain file ('h5' or 'py') :< param brain_populations: Contents of the brain file. Encoding given in field data_type :< json string data_type: type of the data field ('text' or 'base64') :< json string change_population: indicates if it has to be changes in the transfer functions :> json string message: Error Message if there is a syntax error in the code :status 404: {0} :status 200: Success. The populations of the brain where successfully set """ simulation = _get_simulation_or_abort(sim_id) if not UserAuthentication.can_modify(simulation): raise NRPServicesWrongUserException() body = request.get_json(force=True) result = simulation.cle.set_simulation_populations( brain_type=body['brain_type'], brain_populations=json.dumps(body['brain_populations']), data_type=body['data_type'], change_population=body['change_population']) if result.message: return {'error_message': result.message}, 400 return {'message': 'Success'}, 200
def delete(self, sim_id, robot_id): """ Delete a robot from the simulation. :param sim_id: The simulation ID. :param robot_id: The robot ID. :status 500: {0} :status 404: {1} :status 401: {2} :status 400: Invalid request, the JSON parameters are incorrect :status 200: The requested robot was deleted successfully """ # pylint: disable=no-self-use sim = _get_simulation_or_abort(sim_id) if not UserAuthentication.can_modify(sim): raise NRPServicesWrongUserException() try: res, err = SimulationRobot.__delete_robot(sim, robot_id) except ROSCLEClientException as e: raise NRPServicesGeneralException(str(e), 'CLE error', 500) if not res: return {'res': err}, 404 return {'res': 'success'}, 200
def put(self, sim_id): """ Set brain file of the simulation specified with simulation ID. Depending on the type of brain file, it has to be transmitted as text or as base64 :param sim_id: The simulation ID :< json string brain_type: Type of the brain file ('h5' or 'py') :< json string data_type: type of the data field ('text' or 'base64') :< json string data: Contents of the brain file. Encoding given in field data_type :< json dict brain_populations: A dictionary indexed by population names and containing neuron indices :> json string error_message: Error Message if there is a syntax error in the code :> json int error_line: Line of code, where error occurred :> json int error_column: Column, where error occurred (if available) :> json bool handle_population_change: a flag indicating if user wants to change transfer functions according to population changes. :status 500: {0} :status 404: {1} :status 401: {2} :status 400: {3} :status 200: Success. The experiment brain file was replaced """ simulation = _get_simulation_or_abort(sim_id) storage_client = StorageClient() if not UserAuthentication.can_modify(simulation): raise NRPServicesWrongUserException() body = request.get_json(force=True) file_url = body.get('urls', {}).get('fileUrl') if file_url: # TODO: fix server certificate and remove verify with requests.get(file_url, verify=False) as h5: filename = os.path.basename(file_url) with open(os.path.join(simulation.lifecycle.sim_dir, filename), 'w') as f: f.write(h5.content) storage_client.create_or_update(simulation.token, simulation.experiment_id, filename, h5.content, "text/plain") result = simulation.cle.set_simulation_brain( brain_type=body['brain_type'], data=body['data'], data_type=body['data_type'], brain_populations=json.dumps(body['brain_populations'])) if result.error_message: # Error in given brain return { 'error_message': result.error_message, 'error_line': result.error_line, 'error_column': result.error_column }, 400 # Success return {'message': "Success"}, 200
def put(self, sim_id, experiment_id): """ Calls the CLE for resetting a given simulation to the last saved state in the storage. :param sim_id: The simulation ID. :param experiment_id: The experiment ID :> json resetType: the reset type the user wants to be performed, details about possible values are given in GazeboRosPackages/src/cle_ros_msgs/srv/ResetSimulation.srv :status 500: {0} :status 404: {1} :status 401: {2} :status 400: Invalid request, the JSON parameters are incorrect :status 200: The requested reset was performed successfully """ sim = _get_simulation_or_abort(sim_id) if not UserAuthentication.can_modify(sim): raise NRPServicesWrongUserException() req_body = request.get_json(force=True) context_id = req_body.get('contextId', None) for missing_par in (par for par in self.ResetRequest.required if par not in req_body): raise NRPServicesClientErrorException('Missing parameter {}'.format(missing_par)) for invalid_p in (par for par in req_body if par not in self.ResetRequest.resource_fields): raise NRPServicesClientErrorException('Invalid parameter {}'.format(invalid_p)) reset_type = req_body.get('resetType') rsr = srv.ResetSimulationRequest try: if reset_type == rsr.RESET_ROBOT_POSE: sim.cle.reset(reset_type) elif reset_type == rsr.RESET_WORLD: sim.cle.reset(reset_type, world_sdf=self._get_sdf_world_from_storage(experiment_id, context_id)) elif reset_type == rsr.RESET_FULL: brain_path, populations, _ = \ self._get_brain_info_from_storage(experiment_id, context_id) if sim.playback_path is None: self.reset_from_storage_all(sim, experiment_id, context_id) sim.cle.reset(reset_type, world_sdf=self._get_sdf_world_from_storage(experiment_id, context_id), brain_path=brain_path, populations=populations) else: return {}, 400 # Other reset modes are unsupported except ROSCLEClientException as e: raise NRPServicesGeneralException(str(e), 'CLE error', 500) return {}, 200
def put(self, sim_id, state_machine_name): """ Applies user changes to state machine code. If the simulation is paused or started, it will be paused. A stopped, created or failed simulation will fail the request with error code 403 :param sim_id: The simulation ID :param state_machine_name: The name of the state machine to be modified :< json string data: The source code of the state machine :status 404: {0} :status 401: {1} :status 400: {2} :status 200: Success. The code was successfully patched """ simulation = _get_simulation_or_abort(sim_id) assert simulation, Simulation if not UserAuthentication.can_modify(simulation): raise NRPServicesWrongUserException() state_machine_source = request.data try: simulation.set_state_machine_code(state_machine_name, state_machine_source) return "Success. The code was successfully patched.", 200 except (AttributeError, NameError) as e: info = exc_info() raise NRPServicesStateMachineException(e.message, 400), None, info[2] except SyntaxError as e: info = exc_info() args_txt = "" for text in e.args: args_txt += " {0}".format(text) raise NRPServicesStateMachineException( "The source code is invalid: " "SyntaxError in line {0}{1}.".format(e.lineno, args_txt), 400), None, info[2] except Exception as e: info = exc_info() raise NRPServicesGeneralException( "Update of state machine code failed. " "{0}: {1}".format(e.__class__.__name__, e.message), "State machine error"), None, info[2]
def post(self, sim_id, robot_id): """ Add a new robot to the simulation. :param sim_id: The simulation ID. :param robot_id: The robot string ID. :status 500: {0} :status 404: {1} :status 401: {2} :status 400: Invalid request, the JSON parameters are incorrect :status 200: The requested robot was created successfully """ # pylint: disable=no-self-use sim = _get_simulation_or_abort(sim_id) if not UserAuthentication.can_modify(sim): raise NRPServicesWrongUserException() body = request.get_json(force=True) missing_parameters = [ item for item in SimulationRobot.RobotPostRequest.required if item not in body ] if missing_parameters: raise NRPServicesClientErrorException('Missing parameter(s): ' + ' '.join(missing_parameters)) try: res, err = SimulationRobot.__add_new_robot( sim=sim, robot_id=robot_id, robot_model=body.get(ParamNames.ROBOT_MODEL), is_custom=(body.get(ParamNames.IS_CUSTOM, None) == 'True'), initial_pose=body.get(ParamNames.ROBOT_POSE, None)) except ROSCLEClientException as e: raise NRPServicesGeneralException(str(e), 'CLE error', 500) if not res: return {'res': err}, 404 return {'res': 'success'}, 200
def delete(self, sim_id, transfer_function_name): """ Delete a transfer function :param sim_id: The simulation ID :param transfer_function_name: The name of the transfer function to be deleted :status 404: {0} :status 401: {1} :status 200: Success. The code was successfully patched """ simulation = _get_simulation_or_abort(sim_id) if not UserAuthentication.can_modify(simulation): raise NRPServicesWrongUserException() response = simulation.cle.delete_simulation_transfer_function( transfer_function_name) if response is False: raise NRPServicesTransferFunctionException( "Transfer function delete failed: {}".format( str(transfer_function_name))) return 200
def put(self, sim_id): """ Raises a light event :param sim_id: The simulation ID :< json string name: The light to change :< json RGBADescription diffuse: the diffuse color :< json float attenuation_constant: the attenuation constant :< json float attenuation_linear: the attenuation linear :< json float attenuation_quadratic: the attenuation quadratic :status 404: {0} :status 401: {1} :status 400: The parameters are invalid :status 200: Successfully raised event """ simulation = _get_simulation_or_abort(sim_id) if not UserAuthentication.can_modify(simulation): raise NRPServicesClientErrorException( "You need to be the simulation owner to trigger interactions", error_code=401) body = request.get_json(force=True) if 'name' not in body: raise NRPServicesClientErrorException("No name given") in_name = LightControl.as_ascii(body['name']) in_diffuse = body.get('diffuse') in_attenuation_constant = LightControl.as_float(body.get('attenuation_constant')) in_attenuation_linear = LightControl.as_float(body.get('attenuation_linear')) in_attenuation_quadratic = LightControl.as_float(body.get('attenuation_quadratic')) diffuse = None if in_diffuse is not None: diffuse = ColorRGBA(float(in_diffuse['r']), float(in_diffuse['g']), float(in_diffuse['b']), float(in_diffuse['a'])) if in_diffuse is None or in_attenuation_constant is None \ or in_attenuation_linear is None or in_attenuation_quadratic is None: try: rospy.wait_for_service('/gazebo/get_light_properties', 3) except rospy.ROSException as exc: raise NRPServicesUnavailableROSService(str(exc)) get_light_properties = rospy.ServiceProxy('/gazebo/get_light_properties', GetLightProperties) try: light_properties = get_light_properties(light_name=in_name) if in_attenuation_constant is None: in_attenuation_constant = light_properties.attenuation_constant if in_attenuation_linear is None: in_attenuation_linear = light_properties.attenuation_linear if in_attenuation_quadratic is None: in_attenuation_quadratic = light_properties.attenuation_quadratic if in_diffuse is None: diffuse = light_properties.diffuse except rospy.ServiceException as exc: raise NRPServicesClientErrorException( "Service did not process request:" + str(exc)) try: rospy.wait_for_service('/gazebo/set_light_properties', 3) except rospy.ROSException as exc: raise NRPServicesUnavailableROSService(str(exc)) set_light_properties = rospy.ServiceProxy('/gazebo/set_light_properties', SetLightProperties) try: set_light_properties(light_name=in_name, diffuse=diffuse, attenuation_constant=in_attenuation_constant, attenuation_linear=in_attenuation_linear, attenuation_quadratic=in_attenuation_quadratic) except rospy.ServiceException as exc: raise NRPServicesClientErrorException("Service did not process request: " + str(exc)) return "Changed light intensity", 200