pbs_hook_set_jobenv.py 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974
  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. import os
  37. from tests.functional import *
  38. from ptl.utils.pbs_logutils import PBSLogUtils
  39. class TestPbsHookSetJobEnv(TestFunctional):
  40. """
  41. This test suite to make sure hooks properly
  42. handle environment variables with special characters,
  43. values, in particular newline (\n), commas (,), semicolons (;),
  44. single quotes ('), double quotes ("), and backaslashes (\).
  45. PRE: Set up currently executing user's environment to have variables
  46. whose values have the special characters.
  47. Job A: Submit a job using the -V option (pass current environment)
  48. where there are NO hooks in the system.
  49. Introduce execjob_begin and execjob_launch hooks in the system.
  50. Let the former update pbs.event().job.Variable_List while the latter
  51. update pbs.event().env.
  52. Job B: Submit a job using the -V option (pass current environment)
  53. where there are now mom hooks in the system.
  54. POST: Job A and Job B would see the same environment variables, with
  55. Job B also seeing the changes made to the job by the 2 mom hooks.
  56. """
  57. # List of environment variables not to compare between
  58. # job ran without hooks, job ran with hooks.
  59. exclude_env = []
  60. env_nohook = {}
  61. env_nohook_exclude = {}
  62. env_hook = {}
  63. env_hook_exclude = {}
  64. def setUp(self):
  65. """
  66. Set environment variables
  67. """
  68. TestFunctional.setUp(self)
  69. # Set environment variables with special characters
  70. os.environ['TEST_COMMA'] = '1,2,3,4'
  71. os.environ['TEST_RETURN'] = """'3,
  72. 4,
  73. 5'"""
  74. os.environ['TEST_SEMICOLON'] = ';'
  75. os.environ['TEST_ENCLOSED'] = '\',\''
  76. os.environ['TEST_COLON'] = ':'
  77. os.environ['TEST_BACKSLASH'] = '\\'
  78. os.environ['TEST_DQUOTE'] = '"'
  79. os.environ['TEST_DQUOTE2'] = 'happy days"are"here to stay'
  80. os.environ['TEST_DQUOTE3'] = 'nothing compares" to you'
  81. os.environ['TEST_DQUOTE4'] = '"music makes the people"'
  82. os.environ['TEST_DQUOTE5'] = 'music "makes \'the\'"people'
  83. os.environ['TEST_DQUOTE6'] = 'lalaland"'
  84. os.environ['TEST_SQUOTE'] = '\''
  85. os.environ['TEST_SQUOTE2'] = 'happy\'days'
  86. os.environ['TEST_SQUOTE3'] = 'the days\'are here now\'then'
  87. os.environ['TEST_SQUOTE4'] = '\'the way that was\''
  88. os.environ['TEST_SQUOTE5'] = 'music \'makes "the\'"people'
  89. os.environ['TEST_SQUOTE6'] = 'loving\''
  90. os.environ['TEST_SPECIAL'] = "{}[]()~@#$%^&*!"
  91. os.environ['TEST_SPECIAL2'] = "<dumb-test_text>"
  92. # List of environment variables not to compare between
  93. # job ran without hooks, job ran with hooks.
  94. self.exclude_env = ['PBS_NODEFILE']
  95. self.exclude_env += ['PBS_JOBID']
  96. self.exclude_env += ['PBS_JOBCOOKIE']
  97. # Each job submitted by default gets a unique jobname
  98. self.exclude_env += ['PBS_JOBNAME']
  99. self.exclude_env += ['TMPDIR']
  100. self.exclude_env += ['happy']
  101. self.ATTR_V = 'Full_Variable_List'
  102. api_to_cli.setdefault(self.ATTR_V, 'V')
  103. # temporary files
  104. fn = self.du.create_temp_file(prefix="job_out1")
  105. self.job_out1_tempfile = fn
  106. fn = self.du.create_temp_file(prefix="job_out2")
  107. self.job_out2_tempfile = fn
  108. fn = self.du.create_temp_file(prefix="job_out3")
  109. self.job_out3_tempfile = fn
  110. def tearDown(self):
  111. TestFunctional.tearDown(self)
  112. try:
  113. os.remove(self.job_out1_tempfile)
  114. os.remove(self.job_out2_tempfile)
  115. os.remove(self.job_out3_tempfile)
  116. except OSError:
  117. pass
  118. def read_env(self, outputfile, ishook):
  119. """
  120. Parse the output file and store the
  121. variable list in a dictionary
  122. """
  123. with open(outputfile) as fd:
  124. pkey = ""
  125. tmpenv = {}
  126. penv = {}
  127. penv_exclude = {}
  128. for line in fd:
  129. l = line.split("=", 1)
  130. if (len(l) == 2):
  131. pkey = l[0]
  132. if pkey not in self.exclude_env:
  133. penv[pkey] = l[1]
  134. tmpenv = penv
  135. else:
  136. penv_exclude[pkey] = l[1]
  137. tmpenv = penv_exclude
  138. elif pkey != "":
  139. # append to previous dictionary entry
  140. tmpenv[pkey] += l[0]
  141. if (ishook == "hook"):
  142. self.env_hook = penv
  143. self.env_hook_exclude = penv_exclude
  144. else:
  145. self.env_nohook = penv
  146. self.env_nohook_exclude = penv_exclude
  147. def common_log_match(self, daemon):
  148. """
  149. Validate the env variable output in daemon logs
  150. """
  151. logutils = PBSLogUtils()
  152. logmsg = ["TEST_COMMA=1\,2\,3\,4",
  153. "TEST_SEMICOLON=;",
  154. "TEST_ENCLOSED=\\'\,\\'",
  155. "TEST_COLON=:",
  156. "TEST_BACKSLASH=\\\\",
  157. "TEST_DQUOTE=\\\"",
  158. "TEST_DQUOTE2=happy days\\\"are\\\"here to stay",
  159. "TEST_DQUOTE3=nothing compares\\\" to you",
  160. "TEST_DQUOTE4=\\\"music makes the people\\\"",
  161. "TEST_DQUOTE5=music \\\"makes \\'the\\'\\\"people",
  162. "TEST_DQUOTE6=lalaland\\\"",
  163. "TEST_SQUOTE=\\'",
  164. "TEST_SQUOTE2=happy\\'days",
  165. "TEST_SQUOTE3=the days\\'are here now\\'then",
  166. "TEST_SQUOTE4=\\'the way that was\\'",
  167. "TEST_SQUOTE5=music \\'makes \\\"the\\'\\\"people",
  168. "TEST_SQUOTE6=loving\\'",
  169. "TEST_SPECIAL={}[]()~@#$%^&*!",
  170. "TEST_SPECIAL2=<dumb-test_text>",
  171. "TEST_RETURN=\\'3\,",
  172. # Cannot add '\n' here because '\n' is not included in
  173. # the items of the list returned by log_lines(), (though
  174. # lines are split by '\n')
  175. "4\,",
  176. "5\\',"]
  177. if (daemon == "mom"):
  178. self.logger.info("Matching in mom logs")
  179. logfile_type = self.mom
  180. elif (daemon == "server"):
  181. self.logger.info("Matching in server logs")
  182. logfile_type = self.server
  183. else:
  184. self.logger.info("Provide a valid daemon name; server or mom")
  185. return
  186. lines = None
  187. ret_linenum = 0
  188. search_msg = 'log match: searching for '
  189. nomatch_msg = ' No match for '
  190. for msg in logmsg:
  191. for attempt in range(1, 61):
  192. lines = self.server.log_lines(
  193. logfile_type, starttime=self.server.ctime)
  194. match = logutils.match_msg(lines, msg=msg)
  195. if match:
  196. # Dont want the test to pass if there are
  197. # unwanted matched for "4\," and "5\\'.
  198. if msg == "TEST_RETURN=\\'3\,":
  199. ret_linenum = match[0]
  200. if (msg == "4\," and match[0] != (ret_linenum - 1)) or \
  201. (msg == "5\\'" and match[0] != (ret_linenum - 2)):
  202. pass
  203. else:
  204. self.logger.info(search_msg + msg + ' ... OK')
  205. break
  206. else:
  207. self.logger.info(nomatch_msg + msg +
  208. ' attempt ' + str(attempt))
  209. time.sleep(0.5)
  210. if match is None:
  211. _msg = nomatch_msg + msg
  212. raise PtlLogMatchError(rc=1, rv=False, msg=_msg)
  213. def common_validate(self):
  214. """
  215. This is a common function to validate the
  216. environment values with and without hook
  217. """
  218. self.assertEqual(self.env_nohook, self.env_hook)
  219. self.logger.info("Environment variables are same"
  220. " with and without hooks")
  221. match_str = self.env_hook['TEST_COMMA'].rstrip('\n')
  222. self.assertEqual(os.environ['TEST_COMMA'], match_str)
  223. self.logger.info(
  224. "TEST_COMMA matched - " + os.environ['TEST_COMMA'] +
  225. " == " + match_str)
  226. self.assertEqual(os.environ['TEST_RETURN'],
  227. self.env_hook['TEST_RETURN'].rstrip('\n'))
  228. self.logger.info(
  229. "TEST_RETURN matched - " + os.environ['TEST_RETURN'] +
  230. " == " + self.env_hook['TEST_RETURN'].rstrip('\n'))
  231. self.assertEqual(os.environ['TEST_SEMICOLON'],
  232. self.env_hook['TEST_SEMICOLON'].rstrip('\n'))
  233. self.logger.info(
  234. "TEST_SEMICOLON matched - " + os.environ['TEST_SEMICOLON'] +
  235. " == " + self.env_hook['TEST_SEMICOLON'].rstrip('\n'))
  236. self.assertEqual(
  237. os.environ['TEST_ENCLOSED'],
  238. self.env_hook['TEST_ENCLOSED'].rstrip('\n'))
  239. self.logger.info(
  240. "TEST_ENCLOSED matched - " + os.environ['TEST_ENCLOSED'] +
  241. " == " + self.env_hook['TEST_ENCLOSED'].rstrip('\n'))
  242. self.assertEqual(os.environ['TEST_COLON'],
  243. self.env_hook['TEST_COLON'].rstrip('\n'))
  244. self.logger.info("TEST_COLON matched - " + os.environ['TEST_COLON'] +
  245. " == " + self.env_hook['TEST_COLON'].rstrip('\n'))
  246. self.assertEqual(
  247. os.environ['TEST_BACKSLASH'],
  248. self.env_hook['TEST_BACKSLASH'].rstrip('\n'))
  249. self.logger.info(
  250. "TEST_BACKSLASH matched - " + os.environ['TEST_BACKSLASH'] +
  251. " == " + self.env_hook['TEST_BACKSLASH'].rstrip('\n'))
  252. self.assertEqual(os.environ['TEST_DQUOTE'],
  253. self.env_hook['TEST_DQUOTE'].rstrip('\n'))
  254. self.logger.info("TEST_DQUOTE matched - " +
  255. os.environ['TEST_DQUOTE'] +
  256. " == " + self.env_hook['TEST_DQUOTE'].rstrip('\n'))
  257. self.assertEqual(os.environ['TEST_DQUOTE2'],
  258. self.env_hook['TEST_DQUOTE2'].rstrip('\n'))
  259. self.logger.info("TEST_DQUOTE2 matched - " +
  260. os.environ['TEST_DQUOTE2'] +
  261. " == " + self.env_hook['TEST_DQUOTE2'].rstrip('\n'))
  262. self.assertEqual(os.environ['TEST_DQUOTE3'],
  263. self.env_hook['TEST_DQUOTE3'].rstrip('\n'))
  264. self.logger.info("TEST_DQUOTE3 matched - " +
  265. os.environ['TEST_DQUOTE3'] +
  266. " == " + self.env_hook['TEST_DQUOTE3'].rstrip('\n'))
  267. self.assertEqual(os.environ['TEST_DQUOTE4'],
  268. self.env_hook['TEST_DQUOTE4'].rstrip('\n'))
  269. self.logger.info("TEST_DQUOTE4 matched - " +
  270. os.environ['TEST_DQUOTE4'] +
  271. " == " + self.env_hook['TEST_DQUOTE4'].rstrip('\n'))
  272. self.assertEqual(os.environ['TEST_DQUOTE5'],
  273. self.env_hook['TEST_DQUOTE5'].rstrip('\n'))
  274. self.logger.info("TEST_DQUOTE5 matched - " +
  275. os.environ['TEST_DQUOTE5'] +
  276. " == " + self.env_hook['TEST_DQUOTE5'].rstrip('\n'))
  277. self.assertEqual(os.environ['TEST_DQUOTE6'],
  278. self.env_hook['TEST_DQUOTE6'].rstrip('\n'))
  279. self.logger.info("TEST_DQUOTE6 matched - " +
  280. os.environ['TEST_DQUOTE6'] +
  281. " == " + self.env_hook['TEST_DQUOTE6'].rstrip('\n'))
  282. self.assertEqual(os.environ['TEST_SQUOTE'],
  283. self.env_hook['TEST_SQUOTE'].rstrip('\n'))
  284. self.logger.info("TEST_SQUOTE matched - " + os.environ['TEST_SQUOTE'] +
  285. " == " + self.env_hook['TEST_SQUOTE'].rstrip('\n'))
  286. self.assertEqual(os.environ['TEST_SQUOTE2'],
  287. self.env_hook['TEST_SQUOTE2'].rstrip('\n'))
  288. self.logger.info("TEST_SQUOTE2 matched - " +
  289. os.environ['TEST_SQUOTE2'] +
  290. " == " + self.env_hook['TEST_SQUOTE2'].rstrip('\n'))
  291. self.assertEqual(os.environ['TEST_SQUOTE3'],
  292. self.env_hook['TEST_SQUOTE3'].rstrip('\n'))
  293. self.logger.info("TEST_SQUOTE3 matched - " +
  294. os.environ['TEST_SQUOTE3'] +
  295. " == " + self.env_hook['TEST_SQUOTE3'].rstrip('\n'))
  296. self.assertEqual(os.environ['TEST_SQUOTE4'],
  297. self.env_hook['TEST_SQUOTE4'].rstrip('\n'))
  298. self.logger.info("TEST_SQUOTE4 matched - " +
  299. os.environ['TEST_SQUOTE4'] +
  300. " == " + self.env_hook['TEST_SQUOTE4'].rstrip('\n'))
  301. self.assertEqual(os.environ['TEST_SQUOTE5'],
  302. self.env_hook['TEST_SQUOTE5'].rstrip('\n'))
  303. self.logger.info("TEST_SQUOTE5 matched - " +
  304. os.environ['TEST_SQUOTE5'] +
  305. " == " + self.env_hook['TEST_SQUOTE5'].rstrip('\n'))
  306. self.assertEqual(os.environ['TEST_SQUOTE6'],
  307. self.env_hook['TEST_SQUOTE6'].rstrip('\n'))
  308. self.logger.info("TEST_SQUOTE6 matched - " +
  309. os.environ['TEST_SQUOTE6'] +
  310. " == " + self.env_hook['TEST_SQUOTE6'].rstrip('\n'))
  311. self.assertEqual(os.environ['TEST_SPECIAL'],
  312. self.env_hook['TEST_SPECIAL'].rstrip('\n'))
  313. self.logger.info("TEST_SPECIAL matched - " +
  314. os.environ['TEST_SPECIAL'] +
  315. " == " + self.env_hook['TEST_SPECIAL'].rstrip('\n'))
  316. self.assertEqual(os.environ['TEST_SPECIAL2'],
  317. self.env_hook['TEST_SPECIAL2'].rstrip('\n'))
  318. self.logger.info("TEST_SPECIAL2 matched - " +
  319. os.environ['TEST_SPECIAL2'] +
  320. " == " + self.env_hook['TEST_SPECIAL2'].rstrip('\n'))
  321. def create_and_submit_job(self, user=None, attribs=None, content=None,
  322. content_interactive=None, preserve_env=False):
  323. """
  324. create the job object and submit it to the server
  325. as 'user', attributes list 'attribs' script
  326. 'content' or 'content_interactive', and to
  327. 'preserve_env' if interactive job.
  328. """
  329. # A user=None value means job will be executed by current user
  330. # where the environment is set up
  331. if attribs is None:
  332. use_attribs = {}
  333. else:
  334. use_attribs = attribs
  335. retjob = Job(username=user, attrs=use_attribs)
  336. if content is not None:
  337. retjob.create_script(body=content)
  338. elif content_interactive is not None:
  339. retjob.interactive_script = content_interactive
  340. retjob.preserve_env = preserve_env
  341. return self.server.submit(retjob)
  342. def test_begin_launch(self):
  343. """
  344. Test to verify that job environment variables having special
  345. characters are not truncated with execjob_launch and
  346. execjob_begin hook
  347. """
  348. self.exclude_env += ['HAPPY']
  349. self.exclude_env += ['happy']
  350. a = {'Resource_List.select': '1:ncpus=1',
  351. 'Resource_List.walltime': 10,
  352. self.ATTR_V: None}
  353. script = ['env\n']
  354. script += ['sleep 5\n']
  355. # Submit a job without hooks in the system
  356. jid = self.create_and_submit_job(attribs=a, content=script)
  357. qstat = self.server.status(JOB, ATTR_o, id=jid)
  358. job_outfile = qstat[0][ATTR_o].split(':')[1]
  359. self.server.expect(JOB, 'queue', op=UNSET, id=jid, offset=10)
  360. # Read the env variables from job output
  361. self.env_nohook = {}
  362. self.env_nohook_exclude = {}
  363. self.read_env(job_outfile, "nohook")
  364. # Now start introducing hooks
  365. hook_body = """
  366. import pbs
  367. e=pbs.event()
  368. e.job.Variable_List["happy"] = "days"
  369. pbs.logmsg(pbs.LOG_DEBUG,"Variable List is %s" % (e.job.Variable_List,))
  370. """
  371. hook_name = "begin"
  372. a2 = {'event': "execjob_begin", 'enabled': 'True', 'debug': 'True'}
  373. rv = self.server.create_import_hook(
  374. hook_name,
  375. a2,
  376. hook_body,
  377. overwrite=True)
  378. self.assertTrue(rv)
  379. hook_body = """
  380. import pbs
  381. e=pbs.event()
  382. e.env["HAPPY"] = "nights"
  383. """
  384. hook_name = "launch"
  385. a2 = {'event': "execjob_launch", 'enabled': 'True', 'debug': 'True'}
  386. rv = self.server.create_import_hook(
  387. hook_name,
  388. a2,
  389. hook_body,
  390. overwrite=True)
  391. self.assertTrue(rv)
  392. # Submit a job with hooks in the system
  393. jid2 = self.create_and_submit_job(attribs=a, content=script)
  394. qstat = self.server.status(JOB, ATTR_o, id=jid2)
  395. job_outfile = qstat[0][ATTR_o].split(':')[1]
  396. self.server.expect(JOB, 'queue', op=UNSET, id=jid2, offset=10)
  397. self.env_hook = {}
  398. self.env_hook_exclude = {}
  399. self.read_env(job_outfile, "hook")
  400. # Validate the values printed in job output file
  401. self.assertTrue('HAPPY' not in self.env_nohook_exclude)
  402. self.assertTrue('happy' not in self.env_nohook_exclude)
  403. self.assertEqual(self.env_hook_exclude['HAPPY'], 'nights\n')
  404. self.assertEqual(self.env_hook_exclude['happy'], 'days\n')
  405. self.common_validate()
  406. # Check the values in mom logs as well
  407. self.common_log_match("mom")
  408. def test_que(self):
  409. """
  410. Test that variable_list do not change with and without
  411. queuejob hook
  412. """
  413. self.exclude_env += ['happy']
  414. a = {'Resource_List.select': '1:ncpus=1',
  415. 'Resource_List.walltime': 10}
  416. script = ['#PBS -V']
  417. script += ['env\n']
  418. script += ['sleep 5\n']
  419. # Submit a job without hooks in the system
  420. jid = self.create_and_submit_job(attribs=a, content=script)
  421. qstat = self.server.status(JOB, ATTR_o, id=jid)
  422. job_outfile = qstat[0][ATTR_o].split(':')[1]
  423. self.server.expect(JOB, 'queue', op=UNSET, id=jid, offset=10)
  424. # Read the env variable from job output file
  425. self.env_nohook = {}
  426. self.env_nohook_exclude = {}
  427. self.read_env(job_outfile, "nohook")
  428. # Now start introducing hooks
  429. hook_body = """
  430. import pbs
  431. e=pbs.event()
  432. e.job.Variable_List["happy"] = "days"
  433. pbs.logmsg(pbs.LOG_DEBUG,"Variable List is %s" % (e.job.Variable_List,))
  434. """
  435. hook_name = "qjob"
  436. a2 = {'event': "queuejob", 'enabled': 'True', 'debug': 'True'}
  437. rv = self.server.create_import_hook(
  438. hook_name,
  439. a2,
  440. hook_body,
  441. overwrite=True)
  442. self.assertTrue(rv)
  443. # Submit a job with hooks in the system
  444. jid2 = self.create_and_submit_job(attribs=a, content=script)
  445. qstat = self.server.status(JOB, ATTR_o, id=jid2)
  446. job_outfile = qstat[0][ATTR_o].split(':')[1]
  447. self.server.expect(JOB, 'queue', op=UNSET, id=jid2, offset=10)
  448. self.env_hook = {}
  449. self.env_hook_exclude = {}
  450. self.read_env(job_outfile, "hook")
  451. # Validate the env values from job output file
  452. # with and without queuejob hook
  453. self.assertTrue('happy' not in self.env_nohook_exclude)
  454. self.assertEqual(self.env_hook_exclude['happy'], 'days\n')
  455. self.common_validate()
  456. self.common_log_match("server")
  457. def test_execjob_epi(self):
  458. """
  459. Test that Variable_List will contain environment variable
  460. with commas, newline and all special characters even for
  461. other mom hooks
  462. """
  463. self.exclude_env += ['happy']
  464. a = {'Resource_List.select': '1:ncpus=1',
  465. 'Resource_List.walltime': 10}
  466. script = ['#PBS -V']
  467. script += ['env\n']
  468. script += ['sleep 5\n']
  469. # Submit a job without hooks in the system
  470. jid = self.create_and_submit_job(attribs=a, content=script)
  471. qstat = self.server.status(JOB, ATTR_o, id=jid)
  472. job_outfile = qstat[0][ATTR_o].split(':')[1]
  473. self.server.expect(JOB, 'queue', op=UNSET, id=jid, offset=10)
  474. # Read the output file and parse the values
  475. self.env_nohook = {}
  476. self.env_nohook_exclude = {}
  477. self.read_env(job_outfile, "nohook")
  478. # Now start the hooks
  479. hook_name = "test_epi"
  480. hook_body = """
  481. import pbs
  482. e = pbs.event()
  483. j = e.job
  484. j.Variable_List["happy"] = "days"
  485. pbs.logmsg(pbs.LOG_DEBUG,"Variable_List is %s" % (j.Variable_List,))
  486. """
  487. a2 = {'event': "execjob_epilogue", 'enabled': "true", 'debug': "true"}
  488. self.server.create_import_hook(
  489. hook_name,
  490. a2,
  491. hook_body,
  492. overwrite=True)
  493. # Submit a job with hooks in the system
  494. jid2 = self.create_and_submit_job(attribs=a, content=script)
  495. qstat = self.server.status(JOB, ATTR_o, id=jid2)
  496. job_outfile = qstat[0][ATTR_o].split(':')[1]
  497. self.server.expect(JOB, 'queue', op=UNSET, id=jid2, offset=10)
  498. # read the output file for env with hooks
  499. self.env_hook = {}
  500. self.env_hook_exclude = {}
  501. self.read_env(job_outfile, "hook")
  502. # Validate
  503. self.common_validate()
  504. # Verify the env variables in logs too
  505. self.common_log_match("mom")
  506. def test_execjob_pro(self):
  507. """
  508. Test that environment variable not gets truncated
  509. for execjob_prologue hook
  510. """
  511. a = {'Resource_List.select': '1:ncpus=1',
  512. 'Resource_List.walltime': 10}
  513. script = ['#PBS -V']
  514. script += ['env\n']
  515. script += ['sleep 5\n']
  516. # Submit a job without hooks in the system
  517. jid = self.create_and_submit_job(attribs=a, content=script)
  518. qstat = self.server.status(JOB, ATTR_o, id=jid)
  519. job_outfile = qstat[0][ATTR_o].split(':')[1]
  520. self.server.expect(JOB, 'queue', op=UNSET, id=jid, offset=10)
  521. # read the output file for env without hook
  522. self.env_nohook = {}
  523. self.env_nohook_exclude = {}
  524. self.read_env(job_outfile, "nohook")
  525. # Now start the hooks
  526. hook_name = "test_pro"
  527. hook_body = """
  528. import pbs
  529. e = pbs.event()
  530. j = e.job
  531. j.Variable_List["happy"] = "days"
  532. pbs.logmsg(pbs.LOG_DEBUG,"Variable_List is %s" % (j.Variable_List,))
  533. """
  534. a2 = {'event': "execjob_prologue", 'enabled': "true", 'debug': "true"}
  535. rv = self.server.create_import_hook(
  536. hook_name,
  537. a2,
  538. hook_body,
  539. overwrite=True)
  540. self.assertTrue(rv)
  541. # Submit a job with hooks in the system
  542. jid2 = self.create_and_submit_job(attribs=a, content=script)
  543. qstat = self.server.status(JOB, ATTR_o, id=jid2)
  544. job_outfile = qstat[0][ATTR_o].split(':')[1]
  545. self.server.expect(JOB, 'queue', op=UNSET, id=jid2, offset=10)
  546. # Read the job ouput file
  547. self.env_hook = {}
  548. self.env_hook_exclude = {}
  549. self.read_env(job_outfile, "hook")
  550. # Validate the env values with and without hook
  551. self.common_validate()
  552. # compare the values in mom_logs as well
  553. self.common_log_match("mom")
  554. @checkModule("pexpect")
  555. def test_interactive(self):
  556. """
  557. Test that interactive jobs do not have truncated environment
  558. variable list with execjob_launch hook
  559. """
  560. self.exclude_env += ['happy']
  561. # submit an interactive job without hook
  562. cmd = 'env > ' + self.job_out1_tempfile
  563. a = {ATTR_inter: '', self.ATTR_V: None}
  564. interactive_script = [('hostname', '.*'), (cmd, '.*')]
  565. jid = self.create_and_submit_job(
  566. attribs=a,
  567. content_interactive=interactive_script,
  568. preserve_env=True)
  569. # Once all commands sent and matched, job exits
  570. self.server.expect(JOB, 'queue', op=UNSET, id=jid, offset=10)
  571. # read the environment list from the job without hook
  572. self.env_nohook = {}
  573. self.env_nohook_exclude = {}
  574. self.read_env(self.job_out1_tempfile, "nohook")
  575. # now do the same with the hook
  576. hook_name = "launch"
  577. hook_body = """
  578. import pbs
  579. e = pbs.event()
  580. j = e.job
  581. j.Variable_List["happy"] = "days"
  582. pbs.logmsg(pbs.LOG_DEBUG, "Variable_List is %s" % (j.Variable_List,))
  583. """
  584. a2 = {'event': "execjob_launch", 'enabled': 'true', 'debug': 'true'}
  585. self.server.create_import_hook(hook_name, a2, hook_body)
  586. # submit an interactive job without hook
  587. cmd = 'env > ' + self.job_out2_tempfile
  588. interactive_script = [('hostname', '.*'), (cmd, '.*')]
  589. jid2 = self.create_and_submit_job(
  590. attribs=a,
  591. content_interactive=interactive_script,
  592. preserve_env=True)
  593. # Once all commands sent and matched, job exits
  594. self.server.expect(JOB, 'queue', op=UNSET, id=jid2, offset=10)
  595. # read the environment list from the job without hook
  596. self.env_hook = {}
  597. self.env_hook_exclude = {}
  598. self.read_env(self.job_out2_tempfile, "hook")
  599. # validate the environment values
  600. self.common_validate()
  601. # verify the env values in logs
  602. self.common_log_match("mom")
  603. def test_no_hook(self):
  604. """
  605. Test to verify that environment variables are
  606. not truncated and also not modified by PBS when
  607. no hook is present
  608. """
  609. os.environ['BROL'] = 'hii\\\haha'
  610. os.environ['BROL1'] = """'hii
  611. haa'"""
  612. a = {'Resource_List.select': '1:ncpus=1',
  613. 'Resource_List.walltime': 10}
  614. script = ['#PBS -V']
  615. script += ['env\n']
  616. script += ['sleep 5\n']
  617. # Submit a job without hooks in the system
  618. jid = self.create_and_submit_job(attribs=a, content=script)
  619. qstat = self.server.status(JOB, id=jid)
  620. job_outfile = qstat[0]['Output_Path'].split(':')[1]
  621. job_var = qstat[0]['Variable_List']
  622. self.logger.info("job variable list is %s" % job_var)
  623. self.server.expect(JOB, 'queue', op=UNSET, id=jid, offset=10)
  624. # Read the env variable from job output file
  625. self.env_nohook = {}
  626. self.env_nohook_exclude = {}
  627. self.read_env(job_outfile, "nohook")
  628. # Verify the output with and without job
  629. self.assertEqual(os.environ['TEST_COMMA'],
  630. self.env_nohook['TEST_COMMA'].rstrip('\n'))
  631. self.logger.info(
  632. "TEST_COMMA matched - " + os.environ['TEST_COMMA'] +
  633. " == " + self.env_nohook['TEST_COMMA'].rstrip('\n'))
  634. self.assertEqual(os.environ['TEST_RETURN'],
  635. self.env_nohook['TEST_RETURN'].rstrip('\n'))
  636. self.logger.info(
  637. "TEST_RETURN matched - " + os.environ['TEST_RETURN'] +
  638. " == " + self.env_nohook['TEST_RETURN'].rstrip('\n'))
  639. self.assertEqual(os.environ['TEST_SEMICOLON'],
  640. self.env_nohook['TEST_SEMICOLON'].rstrip('\n'))
  641. self.logger.info(
  642. "TEST_SEMICOLON macthed - " + os.environ['TEST_SEMICOLON'] +
  643. " == " + self.env_nohook['TEST_SEMICOLON'].rstrip('\n'))
  644. self.assertEqual(
  645. os.environ['TEST_ENCLOSED'],
  646. self.env_nohook['TEST_ENCLOSED'].rstrip('\n'))
  647. self.logger.info(
  648. "TEST_ENCLOSED matched - " + os.environ['TEST_ENCLOSED'] +
  649. " == " + self.env_nohook['TEST_ENCLOSED'].rstrip('\n'))
  650. self.assertEqual(os.environ['TEST_COLON'],
  651. self.env_nohook['TEST_COLON'].rstrip('\n'))
  652. self.logger.info("TEST_COLON macthed - " + os.environ['TEST_COLON'] +
  653. " == " + self.env_nohook['TEST_COLON'].rstrip('\n'))
  654. self.assertEqual(
  655. os.environ['TEST_BACKSLASH'],
  656. self.env_nohook['TEST_BACKSLASH'].rstrip('\n'))
  657. self.logger.info(
  658. "TEST_BACKSLASH matched - " + os.environ['TEST_BACKSLASH'] +
  659. " == " + self.env_nohook['TEST_BACKSLASH'].rstrip('\n'))
  660. self.assertEqual(os.environ['TEST_DQUOTE'],
  661. self.env_nohook['TEST_DQUOTE'].rstrip('\n'))
  662. self.logger.info("TEST_DQUOTE - " + os.environ['TEST_DQUOTE'] +
  663. " == " + self.env_nohook['TEST_DQUOTE'].rstrip('\n'))
  664. self.assertEqual(os.environ['TEST_DQUOTE2'],
  665. self.env_nohook['TEST_DQUOTE2'].rstrip('\n'))
  666. self.logger.info("TEST_DQUOTE2 - " + os.environ['TEST_DQUOTE2'] +
  667. " == " + self.env_nohook['TEST_DQUOTE2'].rstrip('\n'))
  668. self.assertEqual(os.environ['TEST_DQUOTE3'],
  669. self.env_nohook['TEST_DQUOTE3'].rstrip('\n'))
  670. self.logger.info("TEST_DQUOTE3 - " + os.environ['TEST_DQUOTE3'] +
  671. " == " + self.env_nohook['TEST_DQUOTE3'].rstrip('\n'))
  672. self.assertEqual(os.environ['TEST_DQUOTE4'],
  673. self.env_nohook['TEST_DQUOTE4'].rstrip('\n'))
  674. self.logger.info("TEST_DQUOTE4 - " + os.environ['TEST_DQUOTE4'] +
  675. " == " + self.env_nohook['TEST_DQUOTE4'].rstrip('\n'))
  676. self.assertEqual(os.environ['TEST_DQUOTE5'],
  677. self.env_nohook['TEST_DQUOTE5'].rstrip('\n'))
  678. self.logger.info("TEST_DQUOTE5 - " + os.environ['TEST_DQUOTE5'] +
  679. " == " + self.env_nohook['TEST_DQUOTE5'].rstrip('\n'))
  680. self.assertEqual(os.environ['TEST_DQUOTE6'],
  681. self.env_nohook['TEST_DQUOTE6'].rstrip('\n'))
  682. self.logger.info("TEST_DQUOTE6 - " + os.environ['TEST_DQUOTE6'] +
  683. " == " + self.env_nohook['TEST_DQUOTE6'].rstrip('\n'))
  684. self.assertEqual(os.environ['TEST_SQUOTE'],
  685. self.env_nohook['TEST_SQUOTE'].rstrip('\n'))
  686. self.logger.info("TEST_SQUOTE - " + os.environ['TEST_SQUOTE'] +
  687. " == " + self.env_nohook['TEST_SQUOTE'].rstrip('\n'))
  688. self.assertEqual(os.environ['TEST_SQUOTE2'],
  689. self.env_nohook['TEST_SQUOTE2'].rstrip('\n'))
  690. self.logger.info("TEST_SQUOTE2 - " + os.environ['TEST_SQUOTE2'] +
  691. " == " + self.env_nohook['TEST_SQUOTE2'].rstrip('\n'))
  692. self.assertEqual(os.environ['TEST_SQUOTE3'],
  693. self.env_nohook['TEST_SQUOTE3'].rstrip('\n'))
  694. self.logger.info("TEST_SQUOTE3 - " + os.environ['TEST_SQUOTE3'] +
  695. " == " + self.env_nohook['TEST_SQUOTE3'].rstrip('\n'))
  696. self.assertEqual(os.environ['TEST_SQUOTE4'],
  697. self.env_nohook['TEST_SQUOTE4'].rstrip('\n'))
  698. self.logger.info("TEST_SQUOTE4 - " + os.environ['TEST_SQUOTE4'] +
  699. " == " + self.env_nohook['TEST_SQUOTE4'].rstrip('\n'))
  700. self.assertEqual(os.environ['TEST_SQUOTE5'],
  701. self.env_nohook['TEST_SQUOTE5'].rstrip('\n'))
  702. self.logger.info("TEST_SQUOTE5 - " + os.environ['TEST_SQUOTE5'] +
  703. " == " + self.env_nohook['TEST_SQUOTE5'].rstrip('\n'))
  704. self.assertEqual(os.environ['TEST_SQUOTE6'],
  705. self.env_nohook['TEST_SQUOTE6'].rstrip('\n'))
  706. self.logger.info("TEST_SQUOTE6 - " + os.environ['TEST_SQUOTE6'] +
  707. " == " + self.env_nohook['TEST_SQUOTE6'].rstrip('\n'))
  708. self.assertEqual(os.environ['BROL'],
  709. self.env_nohook['BROL'].rstrip('\n'))
  710. self.logger.info("BROL - " + os.environ['BROL'] + " == " +
  711. self.env_nohook['BROL'].rstrip('\n'))
  712. self.assertEqual(os.environ['BROL1'],
  713. self.env_nohook['BROL1'].rstrip('\n'))
  714. self.logger.info("BROL - " + os.environ['BROL1'] + " == " +
  715. self.env_nohook['BROL1'].rstrip('\n'))
  716. # match the values in qstat -f Variable_List
  717. # Following is blocked on PTL bug PP-1008
  718. # self.assertTrue("TEST_COMMA=1\,2\,3\,4" in job_var)
  719. # self.assertTrue("TEST_SEMICOLON=\;" in job_var)
  720. # self.assertTrue("TEST_COLON=:" in job_var)
  721. # self.assertTrue("TEST_DQUOTE=\"" in job_var)
  722. # self.assertTrue("TEST_SQUOTE=\'" in job_var)
  723. # self.assertTrue("TEST_BACKSLASH=\\" in job_var)
  724. # self.assertTrue("BROL=hii\\\\\\haha" in job_var)
  725. # self.assertTrue("TEST_ENCLOSED=\," in job_var)
  726. # self.assertTrue("BROL1=hii\nhaa" in job_var)
  727. # self.assertTrue("TEST_RETURN=3\,\n4\,\n5\," in job_var)
  728. @checkModule("pexpect")
  729. def test_interactive_no_hook(self):
  730. """
  731. Test to verify that environment variable values
  732. are not truncated or escaped wrongly whithin a
  733. job even when there is no hook present
  734. """
  735. os.environ['BROL'] = 'hii\\\haha'
  736. os.environ['BROL1'] = """'hii
  737. haa'"""
  738. # submit an interactive job without hook
  739. cmd = 'env > ' + self.job_out3_tempfile
  740. a = {ATTR_inter: '', self.ATTR_V: None}
  741. interactive_script = [('hostname', '.*'), (cmd, '.*')]
  742. jid = self.create_and_submit_job(
  743. attribs=a,
  744. content_interactive=interactive_script,
  745. preserve_env=True)
  746. # Once all commands sent and matched, job exits
  747. self.server.expect(JOB, 'queue', op=UNSET, id=jid, offset=10)
  748. # read the environment list from the job without hook
  749. self.env_nohook = {}
  750. self.env_nohook_exclude = {}
  751. self.read_env(self.job_out3_tempfile, "nohook")
  752. # Verify the output with and without job
  753. self.logger.info("job Variable list is ")
  754. self.assertEqual(os.environ['TEST_COMMA'],
  755. self.env_nohook['TEST_COMMA'].rstrip('\n'))
  756. self.logger.info(
  757. "TEST_COMMA matched - " + os.environ['TEST_COMMA'] +
  758. " == " + self.env_nohook['TEST_COMMA'].rstrip('\n'))
  759. self.assertEqual(os.environ['TEST_RETURN'],
  760. self.env_nohook['TEST_RETURN'].rstrip('\n'))
  761. self.logger.info(
  762. "TEST_RETURN matched - " + os.environ['TEST_RETURN'] +
  763. " == " + self.env_nohook['TEST_RETURN'].rstrip('\n'))
  764. self.assertEqual(os.environ['TEST_SEMICOLON'],
  765. self.env_nohook['TEST_SEMICOLON'].rstrip('\n'))
  766. self.logger.info(
  767. "TEST_SEMICOLON macthed - " + os.environ['TEST_SEMICOLON'] +
  768. " == " + self.env_nohook['TEST_SEMICOLON'].rstrip('\n'))
  769. self.assertEqual(
  770. os.environ['TEST_ENCLOSED'],
  771. self.env_nohook['TEST_ENCLOSED'].rstrip('\n'))
  772. self.logger.info(
  773. "TEST_ENCLOSED matched - " + os.environ['TEST_ENCLOSED'] +
  774. " == " + self.env_nohook['TEST_ENCLOSED'].rstrip('\n'))
  775. self.assertEqual(os.environ['TEST_COLON'],
  776. self.env_nohook['TEST_COLON'].rstrip('\n'))
  777. self.logger.info("TEST_COLON macthed - " + os.environ['TEST_COLON'] +
  778. " == " + self.env_nohook['TEST_COLON'].rstrip('\n'))
  779. self.assertEqual(
  780. os.environ['TEST_BACKSLASH'],
  781. self.env_nohook['TEST_BACKSLASH'].rstrip('\n'))
  782. self.logger.info(
  783. "TEST_BACKSLASH matched - " + os.environ['TEST_BACKSLASH'] +
  784. " == " + self.env_nohook['TEST_BACKSLASH'].rstrip('\n'))
  785. self.assertEqual(os.environ['TEST_DQUOTE'],
  786. self.env_nohook['TEST_DQUOTE'].rstrip('\n'))
  787. self.logger.info("TEST_DQUOTE - " + os.environ['TEST_DQUOTE'] +
  788. " == " + self.env_nohook['TEST_DQUOTE'].rstrip('\n'))
  789. self.logger.info("TEST_DQUOTE2 - " + os.environ['TEST_DQUOTE2'] +
  790. " == " + self.env_nohook['TEST_DQUOTE2'].rstrip('\n'))
  791. self.logger.info("TEST_DQUOTE3 - " + os.environ['TEST_DQUOTE3'] +
  792. " == " + self.env_nohook['TEST_DQUOTE3'].rstrip('\n'))
  793. self.logger.info("TEST_DQUOTE4 - " + os.environ['TEST_DQUOTE4'] +
  794. " == " + self.env_nohook['TEST_DQUOTE4'].rstrip('\n'))
  795. self.logger.info("TEST_DQUOTE5 - " + os.environ['TEST_DQUOTE5'] +
  796. " == " + self.env_nohook['TEST_DQUOTE5'].rstrip('\n'))
  797. self.logger.info("TEST_DQUOTE6 - " + os.environ['TEST_DQUOTE6'] +
  798. " == " + self.env_nohook['TEST_DQUOTE6'].rstrip('\n'))
  799. self.logger.info("TEST_SQUOTE - " + os.environ['TEST_SQUOTE'] +
  800. " == " + self.env_nohook['TEST_SQUOTE'].rstrip('\n'))
  801. self.logger.info("TEST_SQUOTE2 - " + os.environ['TEST_SQUOTE2'] +
  802. " == " + self.env_nohook['TEST_SQUOTE2'].rstrip('\n'))
  803. self.logger.info("TEST_SQUOTE3 - " + os.environ['TEST_SQUOTE3'] +
  804. " == " + self.env_nohook['TEST_SQUOTE3'].rstrip('\n'))
  805. self.logger.info("TEST_SQUOTE4 - " + os.environ['TEST_SQUOTE4'] +
  806. " == " + self.env_nohook['TEST_SQUOTE4'].rstrip('\n'))
  807. self.logger.info("TEST_SQUOTE5 - " + os.environ['TEST_SQUOTE5'] +
  808. " == " + self.env_nohook['TEST_SQUOTE5'].rstrip('\n'))
  809. self.logger.info("TEST_SQUOTE6 - " + os.environ['TEST_SQUOTE6'] +
  810. " == " + self.env_nohook['TEST_SQUOTE6'].rstrip('\n'))
  811. self.assertEqual(os.environ['BROL'],
  812. self.env_nohook['BROL'].rstrip('\n'))
  813. self.logger.info("BROL - " + os.environ['BROL'] + " == " +
  814. self.env_nohook['BROL'].rstrip('\n'))
  815. self.assertEqual(os.environ['BROL1'],
  816. self.env_nohook['BROL1'].rstrip('\n'))
  817. self.logger.info("BROL - " + os.environ['BROL1'] + " == " +
  818. self.env_nohook['BROL1'].rstrip('\n'))
  819. def test_execjob_epi2(self):
  820. """
  821. Test that Variable_List will contain environment variable
  822. with commas, newline and all special characters for a job
  823. that has been recovered from a prematurely killed mom. This
  824. is a test from an execjob_epilogue hook's view.
  825. PRE: Set up currently executing user's environment to have variables
  826. whose values have the special characters.
  827. Submit a job using the -V option (pass current environment)
  828. where there is an execjob_epilogue hook that references
  829. Variable_List value.
  830. Now kill -9 pbs_mom and then restart it.
  831. This causes pbs_mom to read in job data from the *.JB file on
  832. disk, and pbs_mom immediately kills the job causing
  833. execjob_epilogue hook to execute.
  834. POST: The epilogue hook should see the proper value to the
  835. Variable_List.
  836. """
  837. a = {'Resource_List.select': '1:ncpus=1',
  838. 'Resource_List.walltime': 60}
  839. j = Job(attrs=a)
  840. script = ['#PBS -V']
  841. script += ['env\n']
  842. script += ['sleep 30\n']
  843. j.create_script(body=script)
  844. # Now create/start the hook
  845. hook_name = "test_epi"
  846. hook_body = """
  847. import pbs
  848. import time
  849. e = pbs.event()
  850. j = e.job
  851. pbs.logmsg(pbs.LOG_DEBUG,"Variable_List is %s" % (j.Variable_List,))
  852. pbs.logmsg(pbs.LOG_DEBUG,
  853. "PBS_O_LOGNAME is %s" % j.Variable_List["PBS_O_LOGNAME"])
  854. """
  855. a = {'event': "execjob_epilogue", 'enabled': "true", 'debug': "true"}
  856. self.server.create_import_hook(
  857. hook_name,
  858. a,
  859. hook_body,
  860. overwrite=True)
  861. # Submit a job with hooks in the system
  862. jid = self.server.submit(j)
  863. # Wait for the job to start running.
  864. self.server.expect(JOB, {ATTR_state: 'R'}, id=jid)
  865. # kill -9 mom
  866. self.mom.signal('-KILL')
  867. # now restart mom
  868. self.mom.start()
  869. self.mom.log_match("Restart sent to server")
  870. # Verify the env variables are seen in logs
  871. self.common_log_match("mom")
  872. self.mom.log_match(
  873. "PBS_O_LOGNAME is %s" % (self.du.get_current_user()))