def get_variable(self, name): if name not in self.__variables: logging.error( "Impossible to get value of variable '%s' because it does not exist.", name) return None return self.__variables[name]
def __run(self): logging.debug("(Q Service session '%s') session '%s' is started at '%s'.", self.__context.get_id(), self.__context.get_id(), time.ctime()) with self.__code_block_locker: command_container = self.__context.get_next_command() while (self.__active is True) and (command_container is not None): (command, arguments) = command_container if command == command_type.COMMAND_SEND: self.__process_send_command(arguments[0], arguments[1], arguments[2], arguments[3]) elif command == command_type.COMMAND_TIMEOUT: self.__process_timeout_command(arguments[0]) elif command == command_type.COMMAND_WAIT: self.__process_wait_command(arguments[0]) elif command == command_type.COMMAND_REPLY: self.__process_reply_command(arguments[0], arguments[1], arguments[2], arguments[3]) elif command == command_type.COMMAND_IGNORE: self.__process_ignore_command() elif command == command_type.COMMAND_ASK: self.__process_ask_command(arguments[0]) elif command == command_type.COMMAND_ASSIGN: self.__process_assign_command(arguments[0], arguments[1]) elif command == command_type.COMMAND_PRINT: self.__process_print_comand(arguments[0]) elif command == command_type.COMMAND_EXIT: logging.info("(QSim Service task '%s') exit command is detected - termination...", self.__context.get_id()) self.__active = False elif command == command_type.COMMAND_MOVE_JSON: self.__process_move_json(arguments[0]) elif command == command_type.COMMAND_IF: self.__process_conditional_block(arguments[0], arguments[1]) else: logging.error("(QSim Service task '%s') unexpected command is detected...", self.__context.get_id()) self.__previous_command = command with self.__code_block_locker: command_container = self.__context.get_next_command() self.__manager.delete(self.__context.get_id()) logging.info("(QSim Service task '%s') session is terminated at '%s'.", self.__context.get_id(), time.ctime())
def __assign_variable_value(self, variable_name, variable_value): if variable_name not in {"SESSION_ID", "PARTY_ID", "RCACCOUNT_ID", "RCEXTENSION_ID"}: logging.error("(QSim Service task '%s') Variable '%s' is not supported.", self.__context.get_id(), variable_name) return if variable_name == "SESSION_ID": self.__context.set_session_id(variable_value) elif variable_name == "PARTY_ID": self.__context.set_party_id(variable_value) elif variable_name == "RCACCOUNT_ID": self.__context.set_rcaccount_id(variable_value) elif variable_name == "RCEXTENSION_ID": self.__context.set_rcextension_id(variable_value)
def __process_command_content(self): if self.__content_flag is not True: logging.error("Incorrect script-file format in line '%s' (%d).", self.__line_counter, self.__line) sys.exit(error_code.ERROR_SCRIPT_INCORRECT_FORMAT) reg_object = re.match("HEADER (.*) (.*)", self.__line) if reg_object: key = reg_object.group(1).rstrip().lstrip() value = reg_object.group(2).rstrip().lstrip() self.__command_headers[key] = value else: self.__command_content += self.__line
def load(script_id): script_filepath = os.path.join(script.__DEFAULT_FOLDER, script_id) if script_filepath is False: logging.error( "Impossible to load script because it does not exist.") sys.exit(error_code.ERROR_SCRIPT_NOT_FOUND) with open(script_filepath) as file_descriptor: content = file_descriptor.readlines() block = code_block(0, content) block.load() return block
def __process_command_else(self, command): if self.__command_sequence[len(self.__command_sequence) - 1][0] != command_type.COMMAND_IF: if self.__subfunction == code_block_type.CODE_BLOCK_MAIN: logging.error("Unexpected keyword '%s' (else branch of conditional block) without conditional block in line '%s' (%d).", command, self.__line, self.__line_counter) sys.exit(error_code.ERROR_SCRIPT_INCORRECT_FORMAT) # go to upper level to process it without shifting line (we still need to process else keyword) self.__line_counter = self.__line_counter - 1 return False else: block = code_block(self.__line_counter, self.__content, code_block_type.CODE_BLOCK_CONDITION) block.load() self.__line_counter = block.get_cursor() self.__command_sequence.append((command, [block.get_commands()])) return True
def send_request(self, http_method, http_link, json_request, general_headers): response_body = None connection = http.client.HTTPConnection(self.__address, self.__port, timeout=1) try: logging.vip("Send HTTP request to TAS (%s:%s): '%s' '%s'.", self.__address, self.__port, http_method, http_link) logging.debug("JSON payload of the HTTP request to TAS: '%s'.", json_request) headers = {"Content-Type": "application/json", "Accept": "*/*"} body = None if json_request is not None: body = json_request.encode("utf-8") headers['Content-Length'] = len(body) headers.update(general_headers) statistical.inc_qsim_requests() connection.request(http_method, http_link, body, headers) logging.debug("Check for HTTP response from TAS.") response = connection.getresponse() response_status = response.status logging.debug("Read body of the HTTP response from TAS.") response_body = response.read() statistical.inc_qsim_responses() logging.vip("HTTP response (code: %d) from TAS:", response_status) logging.debug(response_body.decode("utf-8")) except Exception as expection_object: response_status = 600 logging.error("Impossible to communicate correctly with TAS using HTTP (reason: '%s')." % expection_object) finally: connection.close() return response_status, response_body
def __process_command_trigger(self, command, argument): if self.__subfunction != code_block_type.CODE_BLOCK_MAIN: logging.error("Unexpected keyword '%s' (start trigger) in sub-block in line '%s' (%d).", command, self.__line, self.__line_counter) sys.exit(error_code.ERROR_SCRIPT_INCORRECT_FORMAT) if argument in self.__triggers: logging.error("Trigger redefinition (trigger for '%s' is already defined) in line '%s' (%d).", argument, self.__line, self.__line_counter) sys.exit(error_code.ERROR_SCRIPT_INCORRECT_FORMAT) # extract code block block = code_block(self.__line_counter, self.__content, code_block_type.CODE_BLOCK_TRIGGER) block.load() self.__line_counter = block.get_cursor() # extract arguments command_line_arguments = argument.split() if len(command_line_arguments) == 1: trigger_message_id, trigger_reply_code = command_line_arguments[0], None elif len(command_line_arguments) == 2: trigger_message_id, trigger_reply_code = command_line_arguments[0], command_line_arguments[1] else: logging.error("Invalid number of arguments of '%d' in line '%s' (%d).", len(command_line_arguments), self.__line, self.__line_counter) sys.exit(error_code.ERROR_SCRIPT_AMOUNT_ARGUMENTS) if trigger_reply_code is not None: trigger_reply_code = int(trigger_reply_code) # create and store trigger self.__triggers[trigger_message_id] = trigger(trigger_message_id, block.get_commands(), trigger_reply_code) return True
def __process_reply_command(self, code, message, json_body, headers): logging.debug("(QSim Service task '%s') command REPLY is executing...", self.__context.get_id()) previous_message = self.__context.get_last_input_message()[0] if previous_message is None: logging.error("(QSim Service task '%s') impossible to reply, nothing was received.", self.__context.get_id()) return if previous_message == tas_command_type.TASK_START: if (json_body is None) or (len(json_body) == 0): json_body = json_builder.start_qsim_response(self.__context.get_id()) event = event_start_response(code, message, headers, json_body) else: event = event_response(code, message, headers, json_body) queue.put(event) logging.debug("(QSim Service task '%s') Send event response to '%s' (code: '%d') to HTTP handler queue.", self.__context.get_id(), previous_message, code)
def evaluate(self): (variables_names, variables_values) = self.__extract_variables() for index_variable in range(len(variables_names)): if variables_names[index_variable] == "$(QSIM.CONTEXT)": internal_variable_name = "expression_analyser.current_context.get_tas_info()" else: internal_variable_name = "variables_values[" + str( index_variable) + "]" self.__expression = self.__expression.replace( variables_names[index_variable], internal_variable_name) result = None try: with expression_analyser.__evaluation_guard: expression_analyser.current_context = self.__context result = eval(self.__expression) except: logging.error("Impossible to evaluate expression '%s'.", self.__expression) return result
def __send_response(self, http_code, json_data=None, http_message=None, headers=None): time.sleep(configuration.get_response_delay() / 1000.0) self.send_response(http_code, http_message) self.send_header('Content-Type', 'application/json') body = None if json_data is not None: body = json_data.encode("utf-8") self.send_header('Content-Length', len(body)) if headers is not None: for key, value in headers.items(): self.send_header(key, value) try: self.end_headers() if body is not None: self.wfile.write(body) statistical.inc_qsim_responses() logging.info("Send response to TAS (code '%d (%s)', body '%s')", http_code, str(http_message), body) except ConnectionResetError: logging.error( "Impossible to send request to TAS due to reset connection.") except Exception as expection_object: logging.error( "Impossible to send request to TAS due to unknown reason ('%s')." % expection_object)
def parse(method, path): if method == http_method.HTTP_POST: reg_object = re.match( "/telephony/v1/account/(\d+)/services/queue/(\d+)/voice/tasks$", path) if (reg_object): return http_parser.extract_qsim_start_request(reg_object) if re.match(".*/on-command-error", path): return http_parser.extract_tas_error_callback() if re.match(".*/on-command-update", path): return http_parser.extract_tas_update_callback() elif method == http_method.HTTP_DELETE: reg_object = re.match( "/telephony/v1/account/(\d+)/services/queue/(\d+)/voice/tasks/(\d+)", path) if reg_object: return http_parser.extract_qsim_stop_request(reg_object) elif method == http_method.HTTP_GET: reg_object = re.match( "/telephony/v1/account/(\d+)/services/queue/(\d+)/voice/tasks/(\d+)", path) if reg_object: return http_parser.extract_qsim_execution_status(reg_object) reg_object = re.match( "/telephony/v1/account/(\d+)/services/status(.*)", path) if reg_object: return http_parser.extract_qsim_service_status(reg_object) else: logging.error("HTTP method '%s' is not supported.", method) return parser_failure.UNKNOWN_METHOD return parser_failure.UNKNOWN_PATH
def __extract_variables(self): variables_names = [] variables_values = [] start_variable = self.__expression.find("$(", 0) while start_variable >= 0: stop_variable = self.__expression.find(")", start_variable + 1) if stop_variable < 0: logging.error( "Impossible to parse expression with variable - incorrect format of variables (expression: '%s').", self.__expression) break # extract variable name variable_name = self.__expression[start_variable:stop_variable + 1] # check if it is a system variable if variable_name == "$(QSIM.CONTEXT)": variables_names.append(variable_name) variables_values.append(self.__context) else: if variable_name not in self.__context.get_variables(): logging.error( "Impossible to find variable '%s' because of not declaration (expression: '%s').", variable_name, self.__expression) break if variable_name not in variables_names: variables_names.append(variable_name) variables_values.append( self.__context.get_variable(variable_name)) start_variable = self.__expression.find("$(", stop_variable) return variables_names, variables_values
def do_POST(self): tas_host, tas_port = self.client_address[:2] logging.vip( "Receive HTTP POST request from TAS: '%s' (address: '%s:%s').", self.path, tas_host, tas_port) request = http_parser.parse(http_method.HTTP_POST, self.path) if isinstance(request, parser_failure): self.__send_response(self.__parser_failure_to_http_code(request), "\"Impossible to process POST request.\"") return # # TASK_START # if request[struct_field.id] == tas_command_type.TASK_START: if configuration.get_failure_start_qsim_code() is not None: self.__send_response( configuration.get_failure_start_qsim_code(), None, configuration.get_failure_start_qsim_message()) return # extract TAS request for session and attach additional information tas_request = {} json_request = self.rfile.read(int( self.headers['Content-Length'])).decode('utf-8') if (json_request is not None) and (len(json_request) > 0): tas_request = json.loads(json_request) tas_request["tas_address"] = {"ip": tas_host, "port": tas_port} tas_request["account_id"] = request[struct_field.account_id] # fill by default values tas_request["session_id"] = tas_request["sessionId"] tas_request["party_id"] = tas_request["inPartyId"] tas_request["q_party_id"] = tas_request["qPartyId"] tas_request["rcaccount_id"] = tas_request["account_id"] tas_request["rcextension_id"] = tas_request["account_id"] tas_request["rcbrand_id"] = "1210" logging.debug( "QueueId (%s): Store default party ID (%s) and session ID (%s)." % (request[struct_field.script_id], tas_request["party_id"], tas_request["session_id"])) if self.__get_session_manager().create( request[struct_field.script_id], tas_request) is False: message = "Impossible to create session object due to lack of scenario file." logging.error(message) self.__send_response(http_code.HTTP_NOT_FOUND, "\"%s\"" % message) return try: response = queue.get(True, 2) except: response = None if response is None: message = "Event with response is not received." logging.error(message) self.__send_response(http_code.HTTP_INTERNAL_SERVER_ERROR, "\"%s\"" % message) return event_type = type(response) if event_type == event_start_response: self.__send_response(response.code, response.body, response.message, response.headers) elif event_type == event_ignore: pass else: message = "Unexpected event is received." logging.error(message) # # RESULT_ACTION # elif (request[struct_field.id] == tas_command_type.ON_COMMAND_UPDATE or request[struct_field.id] == tas_command_type.ON_COMMAND_ERROR): if configuration.get_failure_action_result_code() is not None: self.__send_response( configuration.get_failure_action_result_code(), None, configuration.get_failure_action_result_message()) return message_size = int(self.headers['Content-Length']) json_result = self.rfile.read(message_size).decode('utf-8') # it is represented by map because most probably other staff may be conveyed to session. json_instance = None if message_size > 0: try: json_instance = json.loads(json_result) except: logging.error( "Impossible to parse JSON - corrupted JSON payload is received." ) self.__send_response( http_code.HTTP_BAD_REQUEST, "\"Corrupted JSON payload in POST request.\"") return message_playload = {'json': json_instance} action_type = request[struct_field.id] command_id = json_instance.get("commandId", None) if command_id is None: logging.error( "Incorrect action request - commandId is not found in JSON." ) self.__send_response( http_code.HTTP_BAD_REQUEST, "\"JSON body does not contain 'commandId' field.\"") return logging.info("Received callback id: '%s':", command_id) logging.debug(json_result) session_instance = self.__get_session_manager( ).get_session_by_command_id(command_id) if session_instance is not None: # check if code is returned by trigger reply_code = session_instance.notify(action_type, message_playload) #reply_code = self.__get_session_manager().notify(session_id, command_id, message_playload) if reply_code is None: # if there is no trigger then let's take it from the queue, if there is user-specific code try: response = queue.get(True, 1) event_type = type(response) if event_type == event_ignore: logging.debug( "Ignore incoming request (do not sent response)." ) return reply_code = response.code reply_message = response.message logging.debug( "Specific reply to incoming request '%s' (code: '%s', command ID: '%s')." % (command_id, reply_code, reply_message)) except: # otherwise send default code reply_code = http_code.HTTP_OK reply_message = "\"Success.\"" logging.debug( "Default reply is used for incoming request '%s'." % action_type) else: reply_message = "\"Specified reply code is used.\"" logging.debug( "Specific reply is used for incoming request '%s' via trigger " "(code: '%s', message: '%s')." % (command_id, reply_code, reply_message)) self.__send_response(reply_code, reply_message) return self.__send_response( http_code.HTTP_NOT_FOUND, "\"Session for action '" + str(command_id) + "' is not found.\"") self.__get_session_manager().get_session_by_command_id(command_id) else: self.__send_response( http_code.HTTP_BAD_REQUEST, "\"Unknown POST type command '" + str(request[struct_field.id]) + "'.\"") return
def do_DELETE(self): tas_host, tas_port = self.client_address[:2] logging.info( "Receive HTTP DELETE request from TAS: '%s' (address: '%s:%s').", self.path, tas_host, tas_port) request = http_parser.parse(http_method.HTTP_DELETE, self.path) if isinstance(request, parser_failure): self.__send_response(self.__parser_failure_to_http_code(request), "\"Impossible to process DELETE request.\"") return # # TASK_STOP # if request[struct_field.id] != tas_command_type.TASK_STOP: self.__send_response( http_code.HTTP_BAD_REQUEST, "\"Unknown DELETE type command '" + str(request[struct_field.id]) + "'.\"") return if configuration.get_failure_stop_qsim_code() is not None: self.__send_response(configuration.get_failure_stop_qsim_code(), None, configuration.get_failure_stop_qsim_message()) return session_id = request[struct_field.session_id] if self.__get_session_manager().exist(session_id) is True: # notify to check whether scenario contains something specific about termination self.__get_session_manager().notify(session_id, tas_command_type.TASK_STOP, None) try: response = queue.get(True, 1) except: response = None if response is None: # scenario does not have anything specific about termination reply_code = self.__get_session_manager().delete(session_id) if reply_code is None: reply_code = http_code.HTTP_OK_NO_CONTENT logging.info( "Reply is not provided for TASK_STOP, use simulator response code (%d).", reply_code) self.__send_response(reply_code) else: event_type = type(response) if event_type == event_ignore: pass elif event_type == event_response: logging.info( "Reply to TASK_STOP is provided by scenario (code: %d).", response.code) self.__send_response(response.code, response.body, response.message, response.headers) else: message = "Unexpected event is received." logging.error(message) else: self.__send_response( http_code.HTTP_NOT_FOUND, "\"Session '" + str(session_id) + "' is not found.\"")
def __process_command_end_trigger(self, command): if self.__subfunction != code_block_type.CODE_BLOCK_TRIGGER: logging.error("Unexpected keyword '%s' is appeard in non-subbody in line '%s' (%d).", command, self.__line, self.__line_counter) sys.exit(error_code.ERROR_SCRIPT_INCORRECT_FORMAT) return True
def __process_command_end_if(self, command): if self.__subfunction != code_block_type.CODE_BLOCK_CONDITION: logging.error("Unexpected keyword '%s' (end of conditional block) without conditional block in line '%s' (%d).", command, self.__line, self.__line_counter) sys.exit(error_code.ERROR_SCRIPT_INCORRECT_FORMAT) return True
def __process_command(self, command, argument): if self.__content_flag is True: if self.__command_sequence[-1][0] == command_type.COMMAND_REPLY: self.__command_sequence[-1][1][2] = self.__command_content.lstrip().rstrip() self.__command_sequence[-1][1][3] = self.__command_headers elif self.__command_sequence[-1][0] == command_type.COMMAND_SEND: self.__command_sequence[-1][1][1] = self.__command_content.lstrip().rstrip() self.__command_sequence[-1][1][2] = self.__command_headers self.__content_flag = False self.__command_content = "" self.__command_headers = {} if command == command_type.COMMAND_SEND: self.__process_command_send(command, argument) elif command == command_type.COMMAND_TIMEOUT: self.__process_command_timeout(command, argument) elif command == command_type.COMMAND_WAIT: self.__process_command_wait(command, argument) elif command == command_type.COMMAND_REPLY: self.__process_command_reply(command, argument) elif command == command_type.COMMAND_IGNORE: self.__process_command_ignore(command, argument) elif command == command_type.COMMAND_ASK: self.__process_command_ask(command, argument) elif command == command_type.COMMAND_ASSIGN: self.__process_command_assign(command, argument) elif command == command_type.COMMAND_EXIT: self.__process_command_exit(command) elif command == command_type.COMMAND_PRINT: self.__process_command_print(command, argument) elif command == command_type.COMMAND_MOVE_JSON: self.__process_command_move_json(command, argument) elif command == command_type.COMMAND_IF: self.__process_command_if(command, argument) elif command == command_type.COMMAND_ELSE: if self.__process_command_else(command) is False: return False elif command == command_type.COMMAND_END_IF: if self.__process_command_end_if(command) is False: return False elif command == command_type.COMMAND_TRIGGER: self.__process_command_trigger(command, argument) elif command == command_type.COMMAND_END_TRIGGER: if self.__process_command_end_trigger(command) is False: return False else: logging.error("Unknown command is detected '%s' in line '%s' (%d).", command, self.__line, self.__line_counter) sys.exit(error_code.ERROR_SCRIPT_UNKNOWN_COMMAND)
def __process_send_command(self, action, tas_content, headers, arguments): logging.debug("(QSim Service task '%s') command SEND is executing...", self.__context.get_id()) if action == "PLAY": tas_link = self.__context.get_tas_link_start_play() tas_method = "POST" elif action == "STOP_PLAY": tas_link = self.__context.get_tas_link_stop_play() tas_method = "DELETE" elif action == "GET_PLAY": tas_link = self.__context.get_tas_link_stop_play() tas_method = "GET" elif action == "COLLECT": tas_link = self.__context.get_tas_link_start_collect() tas_method = "POST" elif action == "GET_COLLECT": tas_link = self.__context.get_tas_link_get_collect() tas_method = "GET" elif action == "STOP_COLLECT": tas_link = self.__context.get_tas_link_stop_collect() tas_method = "DELETE" elif action == "FORWARD": party_id = None if len(arguments) > 0: expression = arguments[0] analyser = expression_analyser(expression, self.__context) party_id = analyser.evaluate() arguments = [] tas_link = self.__context.get_tas_link_forward(party_id) tas_method = "POST" elif action == "FORWARD_GROUP": tas_link = self.__context.get_tas_link_add_forward_group() tas_method = "POST" elif action == "PATCH_FORWARD_GROUP": tas_link = self.__context.get_tas_link_forward_group() tas_method = "PATCH" elif action == "GET_FORWARD_GROUP": tas_link = self.__context.get_tas_link_forward_group() tas_method = "GET" elif action == "DELETE_FORWARD_GROUP": tas_link = self.__context.get_tas_link_forward_group() tas_method = "DELETE" elif action == "GET_SESSION": tas_link = self.__context.get_tas_link_session() tas_method = "GET" else: logging.error("Unknown send command '%s'.", action) return if len(arguments) > 0: tas_link_pattern = arguments[0] tas_link = self.__change_tas_link(tas_link, tas_link_pattern) tas_content = self.__translate_content(tas_content) logging.info("(QSim Service task '%s') send command request to TAS ('%s', '%s').", self.__context.get_id(), tas_method, action) custom_headers = {"rcaccountid": self.__context.get_rcaccount_id(), "rcextensionid": self.__context.get_rcextension_id(), "rcbrandid": self.__context.get_brand_id(), "origin": "127.0.0.1:" + str(configuration.get_qsim_port())} # add new and overwrite existed default custom headers in line with scenario for key, value in headers.items(): custom_headers[key.lower()] = value (status, json_response) = self.__send(tas_method, tas_link, tas_content, custom_headers) if json_response is not None: self.__context.set_last_input_message(json_response) if (status >= 200) and (status <= 299): if (tas_method == "POST") and (json_response is not None) and (len(json_response) > 0): response = json.loads(json_response.decode('utf-8')) if action == "PLAY": action_id = response['id'] self.__context.set_play_id(action_id) elif action == "COLLECT": action_id = response['id'] self.__context.set_collect_id(action_id) elif action == "FORWARD_GROUP": action_id = response['id'] self.__context.set_forward_group_id(action_id) else: action_id = None logging.info("(QSim Service task '%s') TAS accepts '%s' command '%s' and return action id: '%s'.", self.__context.get_id(), tas_method, action, action_id) else: logging.vip("(QSim Service task '%s') TAS reply to '%s' command '%s' by success status (code: '%d').", self.__context.get_id(), tas_method, action, status) else: logging.warning("(QSim Service task '%s') TAS reply to '%s' command '%s' by failure status (code: '%d', reason: '%s').", self.__context.get_id(), tas_method, action, status, json_response)