pbs_qstat_formats.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  1. # coding: utf - 8
  2. # Copyright (C) 1994-2018 Altair Engineering, Inc.
  3. # For more information, contact Altair at www.altair.com.
  4. #
  5. # This file is part of the PBS Professional ("PBS Pro") software.
  6. #
  7. # Open Source License Information:
  8. #
  9. # PBS Pro is free software. You can redistribute it and/or modify it under the
  10. # terms of the GNU Affero General Public License as published by the Free
  11. # Software Foundation, either version 3 of the License, or (at your option) any
  12. # later version.
  13. #
  14. # PBS Pro is distributed in the hope that it will be useful, but WITHOUT ANY
  15. # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  16. # FOR A PARTICULAR PURPOSE.
  17. # See the GNU Affero General Public License for more details.
  18. #
  19. # You should have received a copy of the GNU Affero General Public License
  20. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  21. #
  22. # Commercial License Information:
  23. #
  24. # For a copy of the commercial license terms and conditions,
  25. # go to: (http://www.pbspro.com/UserArea/agreement.html)
  26. # or contact the Altair Legal Department.
  27. #
  28. # Altair’s dual-license business model allows companies, individuals, and
  29. # organizations to create proprietary derivative works of PBS Pro and
  30. # distribute them - whether embedded or bundled with other software -
  31. # under a commercial license agreement.
  32. #
  33. # Use of Altair’s trademarks, including but not limited to "PBS™",
  34. # "PBS Professional®", and "PBS Pro™" and Altair’s logos is subject to Altair's
  35. # trademark licensing policies.
  36. from tests.functional import *
  37. import json
  38. @tags('commands')
  39. class TestQstatFormats(TestFunctional):
  40. """
  41. This test suite validates output of qstat for
  42. various formats
  43. """
  44. def parse_dsv(self, jid, qstat_type, delimiter=None):
  45. """
  46. Common function to parse qstat dsv output using delimiter
  47. """
  48. if delimiter:
  49. delim = "-D" + str(delimiter)
  50. else:
  51. delim = " "
  52. if qstat_type == "job":
  53. cmd = ' -f -F dsv ' + delim + " " + str(jid)
  54. qstat_cmd_dsv = os.path.join(self.server.pbs_conf['PBS_EXEC'],
  55. 'bin', 'qstat') + cmd
  56. qstat_cmd = os.path.join(self.server.pbs_conf['PBS_EXEC'],
  57. 'bin', 'qstat') + ' -f ' + str(jid)
  58. elif qstat_type == "server":
  59. qstat_cmd_dsv = os.path.join(self.server.pbs_conf[
  60. 'PBS_EXEC'], 'bin', 'qstat') + ' -Bf -F dsv ' + delim
  61. qstat_cmd = os.path.join(self.server.pbs_conf[
  62. 'PBS_EXEC'], 'bin', 'qstat') + ' -Bf '
  63. elif qstat_type == "queue":
  64. qstat_cmd_dsv = os.path.join(self.server.pbs_conf[
  65. 'PBS_EXEC'], 'bin', 'qstat') + ' -Qf -F dsv ' + delim
  66. qstat_cmd = os.path.join(self.server.pbs_conf[
  67. 'PBS_EXEC'], 'bin', 'qstat') + ' -Qf '
  68. rv = self.du.run_cmd(self.server.hostname, cmd=qstat_cmd)
  69. attrs_qstatf = []
  70. for line in rv['out']:
  71. attr = line.split("=")
  72. if not re.match(r'[\t]', attr[0]):
  73. attrs_qstatf.append(attr[0].strip())
  74. attrs_qstatf.pop()
  75. ret = self.du.run_cmd(self.server.hostname, cmd=qstat_cmd_dsv)
  76. qstat_attrs = []
  77. for line in ret['out']:
  78. if delimiter:
  79. attr_vals = line.split(str(delimiter))
  80. else:
  81. attr_vals = line.split("|")
  82. for item in attr_vals:
  83. qstat_attr = item.split("=")
  84. qstat_attrs.append(qstat_attr[0])
  85. for attr in attrs_qstatf:
  86. if attr not in qstat_attrs:
  87. self.assertFalse(attr + " is missing")
  88. def parse_json(self, dictitems, qstat_attr):
  89. """
  90. Common function for parsing all values in json output
  91. """
  92. for key, val in dictitems.items():
  93. qstat_attr.append(str(key))
  94. if isinstance(val, dict):
  95. for key, val in val.items():
  96. qstat_attr.append(str(key))
  97. if isinstance(val, dict):
  98. self.parse_json(val, qstat_attr)
  99. return qstat_attr
  100. def get_qstat_attribs(self, obj_type):
  101. """
  102. Common function to get the qstat attributes in default format.
  103. Attributes returned by this function are used to validate the
  104. '-F json' format output.
  105. The dictionary of attributes as returned by status() can not
  106. be used directly because some attributes are printed differently
  107. in '-F json' format. Hence this function returns a modified
  108. attributes list.
  109. obj_type: Can be SERVER, QUEUE or JOB for qstat -Bf, qstat -Qf
  110. and qstat -f respectively
  111. """
  112. attrs = self.server.status(obj_type)
  113. qstat_attrs = []
  114. for key, val in attrs[0].iteritems():
  115. # qstat -F json output does not
  116. # print the 'id' attribute. Its value
  117. # is printed instead.
  118. if key is 'id':
  119. qstat_attrs.append(str(val))
  120. else:
  121. # Extract keys coming after '.' in 'qstat -f' output so they
  122. # can be matched with 'qstat -f -F json' format.
  123. # This is because some attributes, like below, are represented
  124. # differently in 'qstat -f' output and 'qstat -f -F json'
  125. # outputs
  126. #
  127. # Example:
  128. # qstat -f output:
  129. # default_chunk.ncpus = 1
  130. # default_chunk.mem = 1gb
  131. # Resource_List.ncpus = 1
  132. # Resource_List.nodect = 1
  133. #
  134. # qstat -f -F json output:
  135. # "default_chunk":{
  136. # "ncpus":1
  137. # "mem":1gb
  138. # }
  139. # "Resource_List":{
  140. # "ncpus":1,
  141. # "nodect":1,
  142. # }
  143. k = key.split('.')
  144. if k[0] not in qstat_attrs:
  145. qstat_attrs.append(str(k[0]))
  146. if len(k) == 2:
  147. qstat_attrs.append(str(k[1]))
  148. # Extract individual variables under 'Variable_List' from
  149. # 'qstat -f' output so they can be matched with 'qstat -f -F json'
  150. # format.
  151. # Example:
  152. #
  153. # qstat -f output:
  154. # Variable_List = PBS_O_LANG=en_US.UTF-8,
  155. # PBS_O_PATH=/usr/lib64/qt-3.3/bin
  156. # PBS_O_SHELL=/bin/bash,
  157. # PBS_O_WORKDIR=/home/pbsuser,
  158. # PBS_O_SYSTEM=Linux,PBS_O_QUEUE=workq,
  159. #
  160. # qstat -f -F json output:
  161. # "Variable_List":{
  162. # "PBS_O_LANG":"en_US.UTF-8",
  163. # "PBS_O_PATH":"/usr/lib64/qt-3.3/bin:/usr/local/bin
  164. # "PBS_O_SHELL":"/bin/bash",
  165. # "PBS_O_WORKDIR":"/home/pbsuser,
  166. # "PBS_O_SYSTEM":"Linux",
  167. # "PBS_O_QUEUE":"workq",
  168. # },
  169. if ',' in val:
  170. for v in val.split(','):
  171. qstat_attrs.append(str(v).split('=')[0])
  172. return qstat_attrs
  173. def test_qstat_dsv(self):
  174. """
  175. test qstat outputs job info in dsv format with default delimiter pipe
  176. """
  177. j = Job(TEST_USER)
  178. j.set_sleep_time(10)
  179. jid = self.server.submit(j)
  180. self.server.expect(JOB, {'job_state': "R"}, id=jid)
  181. self.parse_dsv(jid, "job")
  182. def test_qstat_bf_dsv(self):
  183. """
  184. test qstat outputs server info in dsv format with default
  185. delimiter pipe
  186. """
  187. self.parse_dsv(None, "server")
  188. def test_qstat_qf_dsv(self):
  189. """
  190. test qstat outputs queue info in dsv format with default delimiter pipe
  191. """
  192. self.parse_dsv(None, "queue")
  193. def test_qstat_dsv_semicolon(self):
  194. """
  195. test qstat outputs job info in dsv format with semicolon as delimiter
  196. """
  197. j = Job(TEST_USER)
  198. j.set_sleep_time(10)
  199. jid = self.server.submit(j)
  200. self.server.expect(JOB, {'job_state': "R"}, id=jid)
  201. self.parse_dsv(jid, "job", ";")
  202. def test_qstat_bf_dsv_semicolon(self):
  203. """
  204. test qstat outputs server info in dsv format with semicolon as
  205. delimiter
  206. """
  207. self.parse_dsv(None, "server", ";")
  208. def test_qstat_qf_dsv_semicolon(self):
  209. """
  210. test qstat outputs queue info in dsv format with semicolon as delimiter
  211. """
  212. self.parse_dsv(None, "queue", ";")
  213. def test_qstat_dsv_comma_ja(self):
  214. """
  215. test qstat outputs job array info in dsv format with comma as delimiter
  216. """
  217. j = Job(TEST_USER)
  218. j.set_sleep_time(10)
  219. j.set_attributes({ATTR_J: '1-3'})
  220. jid = self.server.submit(j)
  221. self.server.expect(JOB, {'job_state': "B"}, id=jid)
  222. self.parse_dsv(jid, "job", ",")
  223. def test_qstat_bf_dsv_comma(self):
  224. """
  225. test qstat outputs server info in dsv format with comma as delimiter
  226. """
  227. self.parse_dsv(None, "server", ",")
  228. def test_qstat_qf_dsv_comma(self):
  229. """
  230. test qstat outputs queue info in dsv format with comma as delimiter
  231. """
  232. self.parse_dsv(None, "queue", ",")
  233. def test_qstat_dsv_string(self):
  234. """
  235. test qstat outputs job info in dsv format with string as delimiter
  236. """
  237. j = Job(TEST_USER)
  238. j.set_sleep_time(10)
  239. jid = self.server.submit(j)
  240. self.server.expect(JOB, {'job_state': "R"}, id=jid)
  241. self.parse_dsv(jid, "job", "QWERTY")
  242. def test_qstat_bf_dsv_string(self):
  243. """
  244. test qstat outputs server info in dsv format with string as delimiter
  245. """
  246. self.parse_dsv(None, "server", "QWERTY")
  247. def test_qstat_qf_dsv_string(self):
  248. """
  249. test qstat outputs queue info in dsv format with string as delimiter
  250. """
  251. self.parse_dsv(None, "queue", "QWERTY")
  252. def test_oneline_dsv(self):
  253. """
  254. submit a single job and check the no of attributes parsed from dsv
  255. is equal to the one parsed from one line output.
  256. """
  257. j = Job(TEST_USER)
  258. j.set_sleep_time(10)
  259. jid = self.server.submit(j)
  260. time.sleep(1)
  261. qstat_cmd = os.path.join(
  262. self.server.pbs_conf['PBS_EXEC'], 'bin', 'qstat')
  263. [qstat_dsv_script, qstat_dsv_out, qstat_oneline_script,
  264. qstat_oneline_out] = [DshUtils().create_temp_file() for _ in range(4)]
  265. f = open(qstat_dsv_script, 'w')
  266. f.write(qstat_cmd + ' -f -F dsv ' + str(jid) + ' > ' + qstat_dsv_out)
  267. f.close()
  268. run_script = "sh " + qstat_dsv_script
  269. dsv_ret = self.du.run_cmd(
  270. self.server.hostname,
  271. cmd=run_script)
  272. f = open(qstat_dsv_out, 'r')
  273. dsv_out = f.read()
  274. f.close()
  275. dsv_attr_count = len(dsv_out.replace("\|", "").split("|"))
  276. f = open(qstat_oneline_script, 'w')
  277. f.write(qstat_cmd + ' -f -w ' + str(jid) + ' > ' + qstat_oneline_out)
  278. f.close()
  279. run_script = 'sh ' + qstat_oneline_script
  280. oneline_ret = self.du.run_cmd(
  281. self.server.hostname, cmd=run_script)
  282. oneline_attr_count = sum(1 for line in open(
  283. qstat_oneline_out) if not line.isspace())
  284. map(os.remove, [qstat_dsv_script, qstat_dsv_out,
  285. qstat_oneline_script, qstat_oneline_out])
  286. self.assertEqual(dsv_attr_count, oneline_attr_count)
  287. def test_json(self):
  288. """
  289. Check whether the qstat json output can be parsed using
  290. python json module
  291. """
  292. j = Job(TEST_USER)
  293. j.set_sleep_time(10)
  294. jid = self.server.submit(j)
  295. [qstat_json_script, qstat_json_out] = [DshUtils().create_temp_file()
  296. for _ in range(2)]
  297. qstat_cmd = os.path.join(
  298. self.server.pbs_conf['PBS_EXEC'], 'bin', 'qstat')
  299. f = open(qstat_json_script, 'w')
  300. f.write(qstat_cmd + ' -f -F json ' + str(jid) + ' > ' + qstat_json_out)
  301. f.close()
  302. self.du.chmod(path=qstat_json_script, mode=0o755)
  303. run_script = 'sh ' + qstat_json_script
  304. json_ret = self.du.run_cmd(
  305. self.server.hostname, cmd=run_script)
  306. data = open(qstat_json_out, 'r').read()
  307. map(os.remove, [qstat_json_script, qstat_json_out])
  308. try:
  309. json_data = json.loads(data)
  310. except:
  311. self.assertTrue(False)
  312. def test_qstat_tag(self):
  313. """
  314. Test <jsdl-hpcpa:Executable> tag is dispalyed with "Executable"
  315. while doing qstat -f
  316. """
  317. ret = True
  318. j = Job(TEST_USER)
  319. j.set_sleep_time(10)
  320. jid = self.server.submit(j)
  321. qstat_cmd = os.path.join(self.server.pbs_conf['PBS_EXEC'], 'bin',
  322. 'qstat') + ' -f ' + str(jid)
  323. ret = self.du.run_cmd(self.server.hostname, cmd=qstat_cmd, sudo=True)
  324. if -1 != str(ret).find('Executable'):
  325. if -1 == str(ret).find('<jsdl-hpcpa:Executable>'):
  326. ret = False
  327. self.assertTrue(ret)
  328. @tags('smoke')
  329. def test_qstat_json_valid(self):
  330. """
  331. Test json output of qstat -f is in valid format when querired as a
  332. super user and all attributes displayed in qstat are present in output
  333. """
  334. j = Job(TEST_USER)
  335. j.set_sleep_time(40)
  336. jid = self.server.submit(j)
  337. self.server.expect(JOB, {'job_state': "R"}, id=jid)
  338. qstat_cmd_json = os.path.join(self.server.pbs_conf[
  339. 'PBS_EXEC'], 'bin', 'qstat') + ' -f -F json ' + str(jid)
  340. ret = self.du.run_cmd(self.server.hostname, cmd=qstat_cmd_json)
  341. qstat_out = "\n".join(ret['out'])
  342. try:
  343. json_object = json.loads(qstat_out)
  344. except ValueError, e:
  345. self.assertTrue(False)
  346. json_only_attrs = ['Jobs', 'timestamp', 'pbs_version', 'pbs_server']
  347. attrs_qstatf = self.get_qstat_attribs(JOB)
  348. qstat_json_attr = []
  349. for key, val in json_object.iteritems():
  350. qstat_json_attr.append(str(key))
  351. if isinstance(val, dict):
  352. self.parse_json(val, qstat_json_attr)
  353. for attr in attrs_qstatf:
  354. if attr not in qstat_json_attr:
  355. self.assertFalse(attr + " is missing")
  356. for attr in json_only_attrs:
  357. if attr not in qstat_json_attr:
  358. self.assertFalse(attr + " is missing")
  359. def test_qstat_json_valid_multiple_jobs(self):
  360. """
  361. Test json output of qstat -f is in valid format when multiple jobs are
  362. queried and make sure that all attributes are displayed in qstat are
  363. present in the output
  364. """
  365. j = Job(TEST_USER)
  366. j.set_sleep_time(10)
  367. jid1 = self.server.submit(j)
  368. jid2 = self.server.submit(j)
  369. qstat_cmd_json = os.path.join(self.server.pbs_conf['PBS_EXEC'], 'bin',
  370. 'qstat') + \
  371. ' -f -F json ' + str(jid1) + ' ' + str(jid2)
  372. ret = self.du.run_cmd(self.server.hostname, cmd=qstat_cmd_json)
  373. qstat_out = "\n".join(ret['out'])
  374. try:
  375. json.loads(qstat_out)
  376. except ValueError:
  377. self.assertTrue(False)
  378. def test_qstat_json_valid_user(self):
  379. """
  380. Test json output of qstat -f is in valid format when queried as
  381. normal user
  382. """
  383. j = Job(TEST_USER)
  384. j.set_sleep_time(10)
  385. jid = self.server.submit(j)
  386. self.server.expect(JOB, {'job_state': "R"}, id=jid)
  387. qstat_cmd_json = os.path.join(self.server.pbs_conf[
  388. 'PBS_EXEC'], 'bin', 'qstat') + ' -f -F json ' + str(jid)
  389. ret = self.du.run_cmd(self.server.hostname,
  390. cmd=qstat_cmd_json, runas=TEST_USER)
  391. qstat_out = "\n".join(ret['out'])
  392. try:
  393. json_object = json.loads(qstat_out)
  394. except ValueError, e:
  395. self.assertTrue(False)
  396. def test_qstat_json_valid_ja(self):
  397. """
  398. Test json output of qstat -f of Job arrays is in valid format
  399. """
  400. j = Job(TEST_USER)
  401. j.set_sleep_time(10)
  402. j.set_attributes({ATTR_J: '1-3'})
  403. jid = self.server.submit(j)
  404. self.server.expect(JOB, {'job_state': "B"}, id=jid)
  405. qstat_cmd_json = os.path.join(self.server.pbs_conf[
  406. 'PBS_EXEC'], 'bin', 'qstat') + ' -f -F json ' + str(jid)
  407. ret = self.du.run_cmd(self.server.hostname, cmd=qstat_cmd_json)
  408. qstat_out = "\n".join(ret['out'])
  409. try:
  410. json_object = json.loads(qstat_out)
  411. except ValueError, e:
  412. self.assertTrue(False)
  413. @tags('smoke')
  414. def test_qstat_bf_json_valid(self):
  415. """
  416. Test json output of qstat -Bf is in valid format and all
  417. attributes displayed in qstat are present in output
  418. """
  419. qstat_cmd_json = os.path.join(self.server.pbs_conf[
  420. 'PBS_EXEC'], 'bin', 'qstat') + ' -Bf -F json'
  421. ret = self.du.run_cmd(self.server.hostname, cmd=qstat_cmd_json)
  422. qstat_out = "\n".join(ret['out'])
  423. try:
  424. json_object = json.loads(qstat_out)
  425. except ValueError, e:
  426. self.assertTrue(False)
  427. json_only_attrs = ['Server', 'timestamp', 'pbs_version', 'pbs_server']
  428. attrs_qstatbf = self.get_qstat_attribs(SERVER)
  429. qstat_json_attr = []
  430. for key, val in json_object.iteritems():
  431. qstat_json_attr.append(str(key))
  432. if isinstance(val, dict):
  433. self.parse_json(val, qstat_json_attr)
  434. for attr in attrs_qstatbf:
  435. if attr not in qstat_json_attr:
  436. self.assertFalse(attr + " is missing")
  437. for attr in json_only_attrs:
  438. if attr not in qstat_json_attr:
  439. self.assertFalse(attr + " is missing")
  440. @tags('smoke')
  441. def test_qstat_qf_json_valid(self):
  442. """
  443. Test json output of qstat -Qf is in valid format and all
  444. attributes displayed in qstat are present in output
  445. """
  446. qstat_cmd_json = os.path.join(self.server.pbs_conf[
  447. 'PBS_EXEC'], 'bin', 'qstat') + ' -Qf -F json'
  448. ret = self.du.run_cmd(self.server.hostname, cmd=qstat_cmd_json)
  449. qstat_out = "\n".join(ret['out'])
  450. try:
  451. json_object = json.loads(qstat_out)
  452. except ValueError, e:
  453. self.assertTrue(False)
  454. json_only_attrs = ['Queue', 'timestamp', 'pbs_version', 'pbs_server']
  455. attrs_qstatqf = self.get_qstat_attribs(QUEUE)
  456. qstat_json_attr = []
  457. for key, val in json_object.iteritems():
  458. qstat_json_attr.append(str(key))
  459. if isinstance(val, dict):
  460. self.parse_json(val, qstat_json_attr)
  461. for attr in attrs_qstatqf:
  462. if attr not in qstat_json_attr:
  463. self.assertFalse(attr + " is missing")
  464. for attr in json_only_attrs:
  465. if attr not in qstat_json_attr:
  466. self.assertFalse(attr + " is missing")
  467. def test_qstat_qf_json_valid_multiple_queues(self):
  468. """
  469. Test json output of qstat -Qf is in valid format when
  470. we query multiple queues
  471. """
  472. q_attr = {'queue_type': 'execution', 'enabled': 'True'}
  473. self.server.manager(MGR_CMD_CREATE, QUEUE, q_attr, id='workq1')
  474. self.server.manager(MGR_CMD_CREATE, QUEUE, q_attr, id='workq2')
  475. qstat_cmd_json = os.path.join(self.server.pbs_conf[
  476. 'PBS_EXEC'], 'bin', 'qstat') + ' -Qf -F json workq1 workq2'
  477. ret = self.du.run_cmd(self.server.hostname, cmd=qstat_cmd_json)
  478. qstat_out = "\n".join(ret['out'])
  479. try:
  480. json.loads(qstat_out)
  481. except ValueError, e:
  482. self.assertTrue(False)
  483. def test_qstat_json_valid_job_special_env(self):
  484. """
  485. Test json output of qstat -f is in valid format
  486. with special chars in env
  487. """
  488. os.environ["DOUBLEQUOTES"] = 'hi"ha'
  489. os.environ["REVERSESOLIDUS"] = 'hi\ha'
  490. self.server.manager(MGR_CMD_SET, SERVER,
  491. {'default_qsub_arguments': '-V'})
  492. j = Job(self.du.get_current_user())
  493. j.preserve_env = True
  494. j.set_sleep_time(10)
  495. jid = self.server.submit(j)
  496. qstat_cmd_json = os.path.join(self.server.pbs_conf['PBS_EXEC'], 'bin',
  497. 'qstat') + \
  498. ' -f -F json ' + str(jid)
  499. ret = self.du.run_cmd(self.server.hostname, cmd=qstat_cmd_json)
  500. qstat_out = "\n".join(ret['out'])
  501. try:
  502. json.loads(qstat_out)
  503. except ValueError:
  504. self.assertTrue(False)