# coding: utf - 8 # Copyright (C) 1994-2018 Altair Engineering, Inc. # For more information, contact Altair at www.altair.com. # # This file is part of the PBS Professional ("PBS Pro") software. # # Open Source License Information: # # PBS Pro is free software. You can redistribute it and/or modify it under the # terms of the GNU Affero General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) any # later version. # # PBS Pro is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. # See the GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # # Commercial License Information: # # For a copy of the commercial license terms and conditions, # go to: (http://www.pbspro.com/UserArea/agreement.html) # or contact the Altair Legal Department. # # Altair’s dual-license business model allows companies, individuals, and # organizations to create proprietary derivative works of PBS Pro and # distribute them - whether embedded or bundled with other software - # under a commercial license agreement. # # Use of Altair’s trademarks, including but not limited to "PBS™", # "PBS Professional®", and "PBS Pro™" and Altair’s logos is subject to Altair's # trademark licensing policies. from tests.functional import * import json @tags('commands') class TestQstatFormats(TestFunctional): """ This test suite validates output of qstat for various formats """ def parse_dsv(self, jid, qstat_type, delimiter=None): """ Common function to parse qstat dsv output using delimiter """ if delimiter: delim = "-D" + str(delimiter) else: delim = " " if qstat_type == "job": cmd = ' -f -F dsv ' + delim + " " + str(jid) qstat_cmd_dsv = os.path.join(self.server.pbs_conf['PBS_EXEC'], 'bin', 'qstat') + cmd qstat_cmd = os.path.join(self.server.pbs_conf['PBS_EXEC'], 'bin', 'qstat') + ' -f ' + str(jid) elif qstat_type == "server": qstat_cmd_dsv = os.path.join(self.server.pbs_conf[ 'PBS_EXEC'], 'bin', 'qstat') + ' -Bf -F dsv ' + delim qstat_cmd = os.path.join(self.server.pbs_conf[ 'PBS_EXEC'], 'bin', 'qstat') + ' -Bf ' elif qstat_type == "queue": qstat_cmd_dsv = os.path.join(self.server.pbs_conf[ 'PBS_EXEC'], 'bin', 'qstat') + ' -Qf -F dsv ' + delim qstat_cmd = os.path.join(self.server.pbs_conf[ 'PBS_EXEC'], 'bin', 'qstat') + ' -Qf ' rv = self.du.run_cmd(self.server.hostname, cmd=qstat_cmd) attrs_qstatf = [] for line in rv['out']: attr = line.split("=") if not re.match(r'[\t]', attr[0]): attrs_qstatf.append(attr[0].strip()) attrs_qstatf.pop() ret = self.du.run_cmd(self.server.hostname, cmd=qstat_cmd_dsv) qstat_attrs = [] for line in ret['out']: if delimiter: attr_vals = line.split(str(delimiter)) else: attr_vals = line.split("|") for item in attr_vals: qstat_attr = item.split("=") qstat_attrs.append(qstat_attr[0]) for attr in attrs_qstatf: if attr not in qstat_attrs: self.assertFalse(attr + " is missing") def parse_json(self, dictitems, qstat_attr): """ Common function for parsing all values in json output """ for key, val in dictitems.items(): qstat_attr.append(str(key)) if isinstance(val, dict): for key, val in val.items(): qstat_attr.append(str(key)) if isinstance(val, dict): self.parse_json(val, qstat_attr) return qstat_attr def get_qstat_attribs(self, obj_type): """ Common function to get the qstat attributes in default format. Attributes returned by this function are used to validate the '-F json' format output. The dictionary of attributes as returned by status() can not be used directly because some attributes are printed differently in '-F json' format. Hence this function returns a modified attributes list. obj_type: Can be SERVER, QUEUE or JOB for qstat -Bf, qstat -Qf and qstat -f respectively """ attrs = self.server.status(obj_type) qstat_attrs = [] for key, val in attrs[0].iteritems(): # qstat -F json output does not # print the 'id' attribute. Its value # is printed instead. if key is 'id': qstat_attrs.append(str(val)) else: # Extract keys coming after '.' in 'qstat -f' output so they # can be matched with 'qstat -f -F json' format. # This is because some attributes, like below, are represented # differently in 'qstat -f' output and 'qstat -f -F json' # outputs # # Example: # qstat -f output: # default_chunk.ncpus = 1 # default_chunk.mem = 1gb # Resource_List.ncpus = 1 # Resource_List.nodect = 1 # # qstat -f -F json output: # "default_chunk":{ # "ncpus":1 # "mem":1gb # } # "Resource_List":{ # "ncpus":1, # "nodect":1, # } k = key.split('.') if k[0] not in qstat_attrs: qstat_attrs.append(str(k[0])) if len(k) == 2: qstat_attrs.append(str(k[1])) # Extract individual variables under 'Variable_List' from # 'qstat -f' output so they can be matched with 'qstat -f -F json' # format. # Example: # # qstat -f output: # Variable_List = PBS_O_LANG=en_US.UTF-8, # PBS_O_PATH=/usr/lib64/qt-3.3/bin # PBS_O_SHELL=/bin/bash, # PBS_O_WORKDIR=/home/pbsuser, # PBS_O_SYSTEM=Linux,PBS_O_QUEUE=workq, # # qstat -f -F json output: # "Variable_List":{ # "PBS_O_LANG":"en_US.UTF-8", # "PBS_O_PATH":"/usr/lib64/qt-3.3/bin:/usr/local/bin # "PBS_O_SHELL":"/bin/bash", # "PBS_O_WORKDIR":"/home/pbsuser, # "PBS_O_SYSTEM":"Linux", # "PBS_O_QUEUE":"workq", # }, if ',' in val: for v in val.split(','): qstat_attrs.append(str(v).split('=')[0]) return qstat_attrs def test_qstat_dsv(self): """ test qstat outputs job info in dsv format with default delimiter pipe """ j = Job(TEST_USER) j.set_sleep_time(10) jid = self.server.submit(j) self.server.expect(JOB, {'job_state': "R"}, id=jid) self.parse_dsv(jid, "job") def test_qstat_bf_dsv(self): """ test qstat outputs server info in dsv format with default delimiter pipe """ self.parse_dsv(None, "server") def test_qstat_qf_dsv(self): """ test qstat outputs queue info in dsv format with default delimiter pipe """ self.parse_dsv(None, "queue") def test_qstat_dsv_semicolon(self): """ test qstat outputs job info in dsv format with semicolon as delimiter """ j = Job(TEST_USER) j.set_sleep_time(10) jid = self.server.submit(j) self.server.expect(JOB, {'job_state': "R"}, id=jid) self.parse_dsv(jid, "job", ";") def test_qstat_bf_dsv_semicolon(self): """ test qstat outputs server info in dsv format with semicolon as delimiter """ self.parse_dsv(None, "server", ";") def test_qstat_qf_dsv_semicolon(self): """ test qstat outputs queue info in dsv format with semicolon as delimiter """ self.parse_dsv(None, "queue", ";") def test_qstat_dsv_comma_ja(self): """ test qstat outputs job array info in dsv format with comma as delimiter """ j = Job(TEST_USER) j.set_sleep_time(10) j.set_attributes({ATTR_J: '1-3'}) jid = self.server.submit(j) self.server.expect(JOB, {'job_state': "B"}, id=jid) self.parse_dsv(jid, "job", ",") def test_qstat_bf_dsv_comma(self): """ test qstat outputs server info in dsv format with comma as delimiter """ self.parse_dsv(None, "server", ",") def test_qstat_qf_dsv_comma(self): """ test qstat outputs queue info in dsv format with comma as delimiter """ self.parse_dsv(None, "queue", ",") def test_qstat_dsv_string(self): """ test qstat outputs job info in dsv format with string as delimiter """ j = Job(TEST_USER) j.set_sleep_time(10) jid = self.server.submit(j) self.server.expect(JOB, {'job_state': "R"}, id=jid) self.parse_dsv(jid, "job", "QWERTY") def test_qstat_bf_dsv_string(self): """ test qstat outputs server info in dsv format with string as delimiter """ self.parse_dsv(None, "server", "QWERTY") def test_qstat_qf_dsv_string(self): """ test qstat outputs queue info in dsv format with string as delimiter """ self.parse_dsv(None, "queue", "QWERTY") def test_oneline_dsv(self): """ submit a single job and check the no of attributes parsed from dsv is equal to the one parsed from one line output. """ j = Job(TEST_USER) j.set_sleep_time(10) jid = self.server.submit(j) time.sleep(1) qstat_cmd = os.path.join( self.server.pbs_conf['PBS_EXEC'], 'bin', 'qstat') [qstat_dsv_script, qstat_dsv_out, qstat_oneline_script, qstat_oneline_out] = [DshUtils().create_temp_file() for _ in range(4)] f = open(qstat_dsv_script, 'w') f.write(qstat_cmd + ' -f -F dsv ' + str(jid) + ' > ' + qstat_dsv_out) f.close() run_script = "sh " + qstat_dsv_script dsv_ret = self.du.run_cmd( self.server.hostname, cmd=run_script) f = open(qstat_dsv_out, 'r') dsv_out = f.read() f.close() dsv_attr_count = len(dsv_out.replace("\|", "").split("|")) f = open(qstat_oneline_script, 'w') f.write(qstat_cmd + ' -f -w ' + str(jid) + ' > ' + qstat_oneline_out) f.close() run_script = 'sh ' + qstat_oneline_script oneline_ret = self.du.run_cmd( self.server.hostname, cmd=run_script) oneline_attr_count = sum(1 for line in open( qstat_oneline_out) if not line.isspace()) map(os.remove, [qstat_dsv_script, qstat_dsv_out, qstat_oneline_script, qstat_oneline_out]) self.assertEqual(dsv_attr_count, oneline_attr_count) def test_json(self): """ Check whether the qstat json output can be parsed using python json module """ j = Job(TEST_USER) j.set_sleep_time(10) jid = self.server.submit(j) [qstat_json_script, qstat_json_out] = [DshUtils().create_temp_file() for _ in range(2)] qstat_cmd = os.path.join( self.server.pbs_conf['PBS_EXEC'], 'bin', 'qstat') f = open(qstat_json_script, 'w') f.write(qstat_cmd + ' -f -F json ' + str(jid) + ' > ' + qstat_json_out) f.close() self.du.chmod(path=qstat_json_script, mode=0o755) run_script = 'sh ' + qstat_json_script json_ret = self.du.run_cmd( self.server.hostname, cmd=run_script) data = open(qstat_json_out, 'r').read() map(os.remove, [qstat_json_script, qstat_json_out]) try: json_data = json.loads(data) except: self.assertTrue(False) def test_qstat_tag(self): """ Test tag is dispalyed with "Executable" while doing qstat -f """ ret = True j = Job(TEST_USER) j.set_sleep_time(10) jid = self.server.submit(j) qstat_cmd = os.path.join(self.server.pbs_conf['PBS_EXEC'], 'bin', 'qstat') + ' -f ' + str(jid) ret = self.du.run_cmd(self.server.hostname, cmd=qstat_cmd, sudo=True) if -1 != str(ret).find('Executable'): if -1 == str(ret).find(''): ret = False self.assertTrue(ret) @tags('smoke') def test_qstat_json_valid(self): """ Test json output of qstat -f is in valid format when querired as a super user and all attributes displayed in qstat are present in output """ j = Job(TEST_USER) j.set_sleep_time(40) jid = self.server.submit(j) self.server.expect(JOB, {'job_state': "R"}, id=jid) qstat_cmd_json = os.path.join(self.server.pbs_conf[ 'PBS_EXEC'], 'bin', 'qstat') + ' -f -F json ' + str(jid) ret = self.du.run_cmd(self.server.hostname, cmd=qstat_cmd_json) qstat_out = "\n".join(ret['out']) try: json_object = json.loads(qstat_out) except ValueError, e: self.assertTrue(False) json_only_attrs = ['Jobs', 'timestamp', 'pbs_version', 'pbs_server'] attrs_qstatf = self.get_qstat_attribs(JOB) qstat_json_attr = [] for key, val in json_object.iteritems(): qstat_json_attr.append(str(key)) if isinstance(val, dict): self.parse_json(val, qstat_json_attr) for attr in attrs_qstatf: if attr not in qstat_json_attr: self.assertFalse(attr + " is missing") for attr in json_only_attrs: if attr not in qstat_json_attr: self.assertFalse(attr + " is missing") def test_qstat_json_valid_multiple_jobs(self): """ Test json output of qstat -f is in valid format when multiple jobs are queried and make sure that all attributes are displayed in qstat are present in the output """ j = Job(TEST_USER) j.set_sleep_time(10) jid1 = self.server.submit(j) jid2 = self.server.submit(j) qstat_cmd_json = os.path.join(self.server.pbs_conf['PBS_EXEC'], 'bin', 'qstat') + \ ' -f -F json ' + str(jid1) + ' ' + str(jid2) ret = self.du.run_cmd(self.server.hostname, cmd=qstat_cmd_json) qstat_out = "\n".join(ret['out']) try: json.loads(qstat_out) except ValueError: self.assertTrue(False) def test_qstat_json_valid_user(self): """ Test json output of qstat -f is in valid format when queried as normal user """ j = Job(TEST_USER) j.set_sleep_time(10) jid = self.server.submit(j) self.server.expect(JOB, {'job_state': "R"}, id=jid) qstat_cmd_json = os.path.join(self.server.pbs_conf[ 'PBS_EXEC'], 'bin', 'qstat') + ' -f -F json ' + str(jid) ret = self.du.run_cmd(self.server.hostname, cmd=qstat_cmd_json, runas=TEST_USER) qstat_out = "\n".join(ret['out']) try: json_object = json.loads(qstat_out) except ValueError, e: self.assertTrue(False) def test_qstat_json_valid_ja(self): """ Test json output of qstat -f of Job arrays is in valid format """ j = Job(TEST_USER) j.set_sleep_time(10) j.set_attributes({ATTR_J: '1-3'}) jid = self.server.submit(j) self.server.expect(JOB, {'job_state': "B"}, id=jid) qstat_cmd_json = os.path.join(self.server.pbs_conf[ 'PBS_EXEC'], 'bin', 'qstat') + ' -f -F json ' + str(jid) ret = self.du.run_cmd(self.server.hostname, cmd=qstat_cmd_json) qstat_out = "\n".join(ret['out']) try: json_object = json.loads(qstat_out) except ValueError, e: self.assertTrue(False) @tags('smoke') def test_qstat_bf_json_valid(self): """ Test json output of qstat -Bf is in valid format and all attributes displayed in qstat are present in output """ qstat_cmd_json = os.path.join(self.server.pbs_conf[ 'PBS_EXEC'], 'bin', 'qstat') + ' -Bf -F json' ret = self.du.run_cmd(self.server.hostname, cmd=qstat_cmd_json) qstat_out = "\n".join(ret['out']) try: json_object = json.loads(qstat_out) except ValueError, e: self.assertTrue(False) json_only_attrs = ['Server', 'timestamp', 'pbs_version', 'pbs_server'] attrs_qstatbf = self.get_qstat_attribs(SERVER) qstat_json_attr = [] for key, val in json_object.iteritems(): qstat_json_attr.append(str(key)) if isinstance(val, dict): self.parse_json(val, qstat_json_attr) for attr in attrs_qstatbf: if attr not in qstat_json_attr: self.assertFalse(attr + " is missing") for attr in json_only_attrs: if attr not in qstat_json_attr: self.assertFalse(attr + " is missing") @tags('smoke') def test_qstat_qf_json_valid(self): """ Test json output of qstat -Qf is in valid format and all attributes displayed in qstat are present in output """ qstat_cmd_json = os.path.join(self.server.pbs_conf[ 'PBS_EXEC'], 'bin', 'qstat') + ' -Qf -F json' ret = self.du.run_cmd(self.server.hostname, cmd=qstat_cmd_json) qstat_out = "\n".join(ret['out']) try: json_object = json.loads(qstat_out) except ValueError, e: self.assertTrue(False) json_only_attrs = ['Queue', 'timestamp', 'pbs_version', 'pbs_server'] attrs_qstatqf = self.get_qstat_attribs(QUEUE) qstat_json_attr = [] for key, val in json_object.iteritems(): qstat_json_attr.append(str(key)) if isinstance(val, dict): self.parse_json(val, qstat_json_attr) for attr in attrs_qstatqf: if attr not in qstat_json_attr: self.assertFalse(attr + " is missing") for attr in json_only_attrs: if attr not in qstat_json_attr: self.assertFalse(attr + " is missing") def test_qstat_qf_json_valid_multiple_queues(self): """ Test json output of qstat -Qf is in valid format when we query multiple queues """ q_attr = {'queue_type': 'execution', 'enabled': 'True'} self.server.manager(MGR_CMD_CREATE, QUEUE, q_attr, id='workq1') self.server.manager(MGR_CMD_CREATE, QUEUE, q_attr, id='workq2') qstat_cmd_json = os.path.join(self.server.pbs_conf[ 'PBS_EXEC'], 'bin', 'qstat') + ' -Qf -F json workq1 workq2' ret = self.du.run_cmd(self.server.hostname, cmd=qstat_cmd_json) qstat_out = "\n".join(ret['out']) try: json.loads(qstat_out) except ValueError, e: self.assertTrue(False) def test_qstat_json_valid_job_special_env(self): """ Test json output of qstat -f is in valid format with special chars in env """ os.environ["DOUBLEQUOTES"] = 'hi"ha' os.environ["REVERSESOLIDUS"] = 'hi\ha' self.server.manager(MGR_CMD_SET, SERVER, {'default_qsub_arguments': '-V'}) j = Job(self.du.get_current_user()) j.preserve_env = True j.set_sleep_time(10) jid = self.server.submit(j) qstat_cmd_json = os.path.join(self.server.pbs_conf['PBS_EXEC'], 'bin', 'qstat') + \ ' -f -F json ' + str(jid) ret = self.du.run_cmd(self.server.hostname, cmd=qstat_cmd_json) qstat_out = "\n".join(ret['out']) try: json.loads(qstat_out) except ValueError: self.assertTrue(False)