diff --git a/client/config/bash_completion/vos_job b/client/config/bash_completion/vos_job index b2719f9b3b10e75a324f84af949db15b8343d295..deee9bd18cf574b243fe60ddf7b613cca4260d5c 100644 --- a/client/config/bash_completion/vos_job +++ b/client/config/bash_completion/vos_job @@ -10,9 +10,9 @@ _vos_job() COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" - opts="info list results" + opts="info list search results" - if [[ ${cur} == info || ${cur} == results || ${COMP_CWORD} -eq 1 ]] ; then + if [[ ${cur} == info || ${cur} == search || ${cur} == results || ${COMP_CWORD} -eq 1 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 fi diff --git a/client/vos_job b/client/vos_job index ba0777e0cc7492e4e3199bc0651e3fd08fdcc656..e83198be7b16433d7762d7667a8953fb0e4e2ac0 100644 --- a/client/vos_job +++ b/client/vos_job @@ -71,6 +71,24 @@ class VOSJob(RedisRPCClient): sys.exit(f"\nError code: {errorCode}\nError message: {errorMsg}\n") else: sys.exit("\nFATAL: Unknown response type.\n") + + def search(self, searchStr): + jobRequest = { "requestType": "JOB_SEARCH", "searchStr": searchStr } + jobResponse = self.call(jobRequest) + if "responseType" not in jobResponse: + sys.exit("FATAL: Malformed response.\n") + elif jobResponse["responseType"] == "SEARCH_DONE": + jobSearch = jobResponse["jobSearch"] + if jobSearch: + print("\n" + tabulate(jobResponse["jobSearch"], headers = "keys", tablefmt = "pretty") + "\n") + else: + sys.exit(f"\nThe search did not return any results.\n") + elif jobResponse["responseType"] == "ERROR": + errorCode = jobResponse["errorCode"] + errorMsg = jobResponse["errorMsg"] + sys.exit(f"\nError code: {errorCode}\nError message: {errorMsg}\n") + else: + sys.exit("\nFATAL: Unknown response type.\n") def results(self, jobId): jobRequest = { "requestType": "JOB_RESULTS", "jobId": jobId } @@ -116,6 +134,12 @@ DESCRIPTION info prints a JSON object containing the job info according to the UWS specification. A job ID is required as argument. + + search + performs a search on jobs and returns those having a match between the search string + passed by the user and one of the following fields: + + 'job_id', 'job_type', 'ownder_id', 'user_name' results prints a JSON object containing the job results according to the UWS specification. @@ -141,6 +165,8 @@ elif len(sys.argv) == 3: vosJobCli.help() elif cmd == "info": vosJobCli.info(arg) + elif cmd == "search": + vosJobCli.search(arg) elif cmd == "results": vosJobCli.results(arg) else: diff --git a/transfer_service/db_connector.py b/transfer_service/db_connector.py index c4b373e8468de06bc9838ad33d66023d5e4345c1..b63e7e1b353d3463b7666343016f1661e8f77a77 100644 --- a/transfer_service/db_connector.py +++ b/transfer_service/db_connector.py @@ -508,6 +508,53 @@ class DbConnector(object): return result finally: self.connPool.putconn(conn, close = False) + + def searchJobs(self, searchStr): + "Performs a search on jobs." + conn = self.getConnection() + try: + cursor = conn.cursor(cursor_factory = RealDictCursor) + cursor.execute(""" + SELECT job_id, + job_type, + phase, + start_time, + end_time, + owner_id + FROM job + JOIN users + ON job.owner_id = users.user_id + WHERE job_type IN ('pullFromVoSpace', + 'pullToVoSpace', + 'pushToVoSpace', + 'vos_data', + 'vos_group', + 'vos_import') + AND (job_id ~ %s + OR job_type ~ %s + OR owner_id ~ %s + OR user_name ~ %s) + ORDER BY creation_time DESC; + """, + (searchStr, + searchStr, + searchStr, + searchStr,)) + result = cursor.fetchall() + cursor.close() + except Exception: + if not conn.closed: + conn.rollback() + raise + else: + for row in result: + for idx in row: + el = row[idx] + if isinstance(el, datetime.datetime): + row[idx] = el.isoformat() + return result + finally: + self.connPool.putconn(conn, close = False) ##### User ##### diff --git a/transfer_service/job_rpc_server.py b/transfer_service/job_rpc_server.py index 124d2efe7fc19e9fb65c7187947ecd36f05f07f1..2352338f90c04bb11fde2a1035e1c56195279a5c 100644 --- a/transfer_service/job_rpc_server.py +++ b/transfer_service/job_rpc_server.py @@ -113,6 +113,20 @@ class JobRPCServer(RedisRPCServer): "errorCode": 2, "errorMsg": errorMsg } return response + elif requestBody["requestType"] == "JOB_SEARCH": + searchStr = requestBody["searchStr"] + try: + result = self.dbConn.searchJobs(searchStr) + except Exception: + errorMsg = "Database error." + self.logger.exception(errorMsg) + response = { "responseType": "ERROR", + "errorCode": 2, + "errorMsg": errorMsg } + return response + else: + response = { "responseType": "SEARCH_DONE", + "jobSearch": result } else: errorMsg = "Unkown request type." self.logger.error(errorMsg)