# 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. import os from tests.functional import * from ptl.utils.pbs_logutils import PBSLogUtils class TestPbsHookSetJobEnv(TestFunctional): """ This test suite to make sure hooks properly handle environment variables with special characters, values, in particular newline (\n), commas (,), semicolons (;), single quotes ('), double quotes ("), and backaslashes (\). PRE: Set up currently executing user's environment to have variables whose values have the special characters. Job A: Submit a job using the -V option (pass current environment) where there are NO hooks in the system. Introduce execjob_begin and execjob_launch hooks in the system. Let the former update pbs.event().job.Variable_List while the latter update pbs.event().env. Job B: Submit a job using the -V option (pass current environment) where there are now mom hooks in the system. POST: Job A and Job B would see the same environment variables, with Job B also seeing the changes made to the job by the 2 mom hooks. """ # List of environment variables not to compare between # job ran without hooks, job ran with hooks. exclude_env = [] env_nohook = {} env_nohook_exclude = {} env_hook = {} env_hook_exclude = {} def setUp(self): """ Set environment variables """ TestFunctional.setUp(self) # Set environment variables with special characters os.environ['TEST_COMMA'] = '1,2,3,4' os.environ['TEST_RETURN'] = """'3, 4, 5'""" os.environ['TEST_SEMICOLON'] = ';' os.environ['TEST_ENCLOSED'] = '\',\'' os.environ['TEST_COLON'] = ':' os.environ['TEST_BACKSLASH'] = '\\' os.environ['TEST_DQUOTE'] = '"' os.environ['TEST_DQUOTE2'] = 'happy days"are"here to stay' os.environ['TEST_DQUOTE3'] = 'nothing compares" to you' os.environ['TEST_DQUOTE4'] = '"music makes the people"' os.environ['TEST_DQUOTE5'] = 'music "makes \'the\'"people' os.environ['TEST_DQUOTE6'] = 'lalaland"' os.environ['TEST_SQUOTE'] = '\'' os.environ['TEST_SQUOTE2'] = 'happy\'days' os.environ['TEST_SQUOTE3'] = 'the days\'are here now\'then' os.environ['TEST_SQUOTE4'] = '\'the way that was\'' os.environ['TEST_SQUOTE5'] = 'music \'makes "the\'"people' os.environ['TEST_SQUOTE6'] = 'loving\'' os.environ['TEST_SPECIAL'] = "{}[]()~@#$%^&*!" os.environ['TEST_SPECIAL2'] = "" # List of environment variables not to compare between # job ran without hooks, job ran with hooks. self.exclude_env = ['PBS_NODEFILE'] self.exclude_env += ['PBS_JOBID'] self.exclude_env += ['PBS_JOBCOOKIE'] # Each job submitted by default gets a unique jobname self.exclude_env += ['PBS_JOBNAME'] self.exclude_env += ['TMPDIR'] self.exclude_env += ['happy'] self.ATTR_V = 'Full_Variable_List' api_to_cli.setdefault(self.ATTR_V, 'V') # temporary files fn = self.du.create_temp_file(prefix="job_out1") self.job_out1_tempfile = fn fn = self.du.create_temp_file(prefix="job_out2") self.job_out2_tempfile = fn fn = self.du.create_temp_file(prefix="job_out3") self.job_out3_tempfile = fn def tearDown(self): TestFunctional.tearDown(self) try: os.remove(self.job_out1_tempfile) os.remove(self.job_out2_tempfile) os.remove(self.job_out3_tempfile) except OSError: pass def read_env(self, outputfile, ishook): """ Parse the output file and store the variable list in a dictionary """ with open(outputfile) as fd: pkey = "" tmpenv = {} penv = {} penv_exclude = {} for line in fd: l = line.split("=", 1) if (len(l) == 2): pkey = l[0] if pkey not in self.exclude_env: penv[pkey] = l[1] tmpenv = penv else: penv_exclude[pkey] = l[1] tmpenv = penv_exclude elif pkey != "": # append to previous dictionary entry tmpenv[pkey] += l[0] if (ishook == "hook"): self.env_hook = penv self.env_hook_exclude = penv_exclude else: self.env_nohook = penv self.env_nohook_exclude = penv_exclude def common_log_match(self, daemon): """ Validate the env variable output in daemon logs """ logutils = PBSLogUtils() logmsg = ["TEST_COMMA=1\,2\,3\,4", "TEST_SEMICOLON=;", "TEST_ENCLOSED=\\'\,\\'", "TEST_COLON=:", "TEST_BACKSLASH=\\\\", "TEST_DQUOTE=\\\"", "TEST_DQUOTE2=happy days\\\"are\\\"here to stay", "TEST_DQUOTE3=nothing compares\\\" to you", "TEST_DQUOTE4=\\\"music makes the people\\\"", "TEST_DQUOTE5=music \\\"makes \\'the\\'\\\"people", "TEST_DQUOTE6=lalaland\\\"", "TEST_SQUOTE=\\'", "TEST_SQUOTE2=happy\\'days", "TEST_SQUOTE3=the days\\'are here now\\'then", "TEST_SQUOTE4=\\'the way that was\\'", "TEST_SQUOTE5=music \\'makes \\\"the\\'\\\"people", "TEST_SQUOTE6=loving\\'", "TEST_SPECIAL={}[]()~@#$%^&*!", "TEST_SPECIAL2=", "TEST_RETURN=\\'3\,", # Cannot add '\n' here because '\n' is not included in # the items of the list returned by log_lines(), (though # lines are split by '\n') "4\,", "5\\',"] if (daemon == "mom"): self.logger.info("Matching in mom logs") logfile_type = self.mom elif (daemon == "server"): self.logger.info("Matching in server logs") logfile_type = self.server else: self.logger.info("Provide a valid daemon name; server or mom") return lines = None ret_linenum = 0 search_msg = 'log match: searching for ' nomatch_msg = ' No match for ' for msg in logmsg: for attempt in range(1, 61): lines = self.server.log_lines( logfile_type, starttime=self.server.ctime) match = logutils.match_msg(lines, msg=msg) if match: # Dont want the test to pass if there are # unwanted matched for "4\," and "5\\'. if msg == "TEST_RETURN=\\'3\,": ret_linenum = match[0] if (msg == "4\," and match[0] != (ret_linenum - 1)) or \ (msg == "5\\'" and match[0] != (ret_linenum - 2)): pass else: self.logger.info(search_msg + msg + ' ... OK') break else: self.logger.info(nomatch_msg + msg + ' attempt ' + str(attempt)) time.sleep(0.5) if match is None: _msg = nomatch_msg + msg raise PtlLogMatchError(rc=1, rv=False, msg=_msg) def common_validate(self): """ This is a common function to validate the environment values with and without hook """ self.assertEqual(self.env_nohook, self.env_hook) self.logger.info("Environment variables are same" " with and without hooks") match_str = self.env_hook['TEST_COMMA'].rstrip('\n') self.assertEqual(os.environ['TEST_COMMA'], match_str) self.logger.info( "TEST_COMMA matched - " + os.environ['TEST_COMMA'] + " == " + match_str) self.assertEqual(os.environ['TEST_RETURN'], self.env_hook['TEST_RETURN'].rstrip('\n')) self.logger.info( "TEST_RETURN matched - " + os.environ['TEST_RETURN'] + " == " + self.env_hook['TEST_RETURN'].rstrip('\n')) self.assertEqual(os.environ['TEST_SEMICOLON'], self.env_hook['TEST_SEMICOLON'].rstrip('\n')) self.logger.info( "TEST_SEMICOLON matched - " + os.environ['TEST_SEMICOLON'] + " == " + self.env_hook['TEST_SEMICOLON'].rstrip('\n')) self.assertEqual( os.environ['TEST_ENCLOSED'], self.env_hook['TEST_ENCLOSED'].rstrip('\n')) self.logger.info( "TEST_ENCLOSED matched - " + os.environ['TEST_ENCLOSED'] + " == " + self.env_hook['TEST_ENCLOSED'].rstrip('\n')) self.assertEqual(os.environ['TEST_COLON'], self.env_hook['TEST_COLON'].rstrip('\n')) self.logger.info("TEST_COLON matched - " + os.environ['TEST_COLON'] + " == " + self.env_hook['TEST_COLON'].rstrip('\n')) self.assertEqual( os.environ['TEST_BACKSLASH'], self.env_hook['TEST_BACKSLASH'].rstrip('\n')) self.logger.info( "TEST_BACKSLASH matched - " + os.environ['TEST_BACKSLASH'] + " == " + self.env_hook['TEST_BACKSLASH'].rstrip('\n')) self.assertEqual(os.environ['TEST_DQUOTE'], self.env_hook['TEST_DQUOTE'].rstrip('\n')) self.logger.info("TEST_DQUOTE matched - " + os.environ['TEST_DQUOTE'] + " == " + self.env_hook['TEST_DQUOTE'].rstrip('\n')) self.assertEqual(os.environ['TEST_DQUOTE2'], self.env_hook['TEST_DQUOTE2'].rstrip('\n')) self.logger.info("TEST_DQUOTE2 matched - " + os.environ['TEST_DQUOTE2'] + " == " + self.env_hook['TEST_DQUOTE2'].rstrip('\n')) self.assertEqual(os.environ['TEST_DQUOTE3'], self.env_hook['TEST_DQUOTE3'].rstrip('\n')) self.logger.info("TEST_DQUOTE3 matched - " + os.environ['TEST_DQUOTE3'] + " == " + self.env_hook['TEST_DQUOTE3'].rstrip('\n')) self.assertEqual(os.environ['TEST_DQUOTE4'], self.env_hook['TEST_DQUOTE4'].rstrip('\n')) self.logger.info("TEST_DQUOTE4 matched - " + os.environ['TEST_DQUOTE4'] + " == " + self.env_hook['TEST_DQUOTE4'].rstrip('\n')) self.assertEqual(os.environ['TEST_DQUOTE5'], self.env_hook['TEST_DQUOTE5'].rstrip('\n')) self.logger.info("TEST_DQUOTE5 matched - " + os.environ['TEST_DQUOTE5'] + " == " + self.env_hook['TEST_DQUOTE5'].rstrip('\n')) self.assertEqual(os.environ['TEST_DQUOTE6'], self.env_hook['TEST_DQUOTE6'].rstrip('\n')) self.logger.info("TEST_DQUOTE6 matched - " + os.environ['TEST_DQUOTE6'] + " == " + self.env_hook['TEST_DQUOTE6'].rstrip('\n')) self.assertEqual(os.environ['TEST_SQUOTE'], self.env_hook['TEST_SQUOTE'].rstrip('\n')) self.logger.info("TEST_SQUOTE matched - " + os.environ['TEST_SQUOTE'] + " == " + self.env_hook['TEST_SQUOTE'].rstrip('\n')) self.assertEqual(os.environ['TEST_SQUOTE2'], self.env_hook['TEST_SQUOTE2'].rstrip('\n')) self.logger.info("TEST_SQUOTE2 matched - " + os.environ['TEST_SQUOTE2'] + " == " + self.env_hook['TEST_SQUOTE2'].rstrip('\n')) self.assertEqual(os.environ['TEST_SQUOTE3'], self.env_hook['TEST_SQUOTE3'].rstrip('\n')) self.logger.info("TEST_SQUOTE3 matched - " + os.environ['TEST_SQUOTE3'] + " == " + self.env_hook['TEST_SQUOTE3'].rstrip('\n')) self.assertEqual(os.environ['TEST_SQUOTE4'], self.env_hook['TEST_SQUOTE4'].rstrip('\n')) self.logger.info("TEST_SQUOTE4 matched - " + os.environ['TEST_SQUOTE4'] + " == " + self.env_hook['TEST_SQUOTE4'].rstrip('\n')) self.assertEqual(os.environ['TEST_SQUOTE5'], self.env_hook['TEST_SQUOTE5'].rstrip('\n')) self.logger.info("TEST_SQUOTE5 matched - " + os.environ['TEST_SQUOTE5'] + " == " + self.env_hook['TEST_SQUOTE5'].rstrip('\n')) self.assertEqual(os.environ['TEST_SQUOTE6'], self.env_hook['TEST_SQUOTE6'].rstrip('\n')) self.logger.info("TEST_SQUOTE6 matched - " + os.environ['TEST_SQUOTE6'] + " == " + self.env_hook['TEST_SQUOTE6'].rstrip('\n')) self.assertEqual(os.environ['TEST_SPECIAL'], self.env_hook['TEST_SPECIAL'].rstrip('\n')) self.logger.info("TEST_SPECIAL matched - " + os.environ['TEST_SPECIAL'] + " == " + self.env_hook['TEST_SPECIAL'].rstrip('\n')) self.assertEqual(os.environ['TEST_SPECIAL2'], self.env_hook['TEST_SPECIAL2'].rstrip('\n')) self.logger.info("TEST_SPECIAL2 matched - " + os.environ['TEST_SPECIAL2'] + " == " + self.env_hook['TEST_SPECIAL2'].rstrip('\n')) def create_and_submit_job(self, user=None, attribs=None, content=None, content_interactive=None, preserve_env=False): """ create the job object and submit it to the server as 'user', attributes list 'attribs' script 'content' or 'content_interactive', and to 'preserve_env' if interactive job. """ # A user=None value means job will be executed by current user # where the environment is set up if attribs is None: use_attribs = {} else: use_attribs = attribs retjob = Job(username=user, attrs=use_attribs) if content is not None: retjob.create_script(body=content) elif content_interactive is not None: retjob.interactive_script = content_interactive retjob.preserve_env = preserve_env return self.server.submit(retjob) def test_begin_launch(self): """ Test to verify that job environment variables having special characters are not truncated with execjob_launch and execjob_begin hook """ self.exclude_env += ['HAPPY'] self.exclude_env += ['happy'] a = {'Resource_List.select': '1:ncpus=1', 'Resource_List.walltime': 10, self.ATTR_V: None} script = ['env\n'] script += ['sleep 5\n'] # Submit a job without hooks in the system jid = self.create_and_submit_job(attribs=a, content=script) qstat = self.server.status(JOB, ATTR_o, id=jid) job_outfile = qstat[0][ATTR_o].split(':')[1] self.server.expect(JOB, 'queue', op=UNSET, id=jid, offset=10) # Read the env variables from job output self.env_nohook = {} self.env_nohook_exclude = {} self.read_env(job_outfile, "nohook") # Now start introducing hooks hook_body = """ import pbs e=pbs.event() e.job.Variable_List["happy"] = "days" pbs.logmsg(pbs.LOG_DEBUG,"Variable List is %s" % (e.job.Variable_List,)) """ hook_name = "begin" a2 = {'event': "execjob_begin", 'enabled': 'True', 'debug': 'True'} rv = self.server.create_import_hook( hook_name, a2, hook_body, overwrite=True) self.assertTrue(rv) hook_body = """ import pbs e=pbs.event() e.env["HAPPY"] = "nights" """ hook_name = "launch" a2 = {'event': "execjob_launch", 'enabled': 'True', 'debug': 'True'} rv = self.server.create_import_hook( hook_name, a2, hook_body, overwrite=True) self.assertTrue(rv) # Submit a job with hooks in the system jid2 = self.create_and_submit_job(attribs=a, content=script) qstat = self.server.status(JOB, ATTR_o, id=jid2) job_outfile = qstat[0][ATTR_o].split(':')[1] self.server.expect(JOB, 'queue', op=UNSET, id=jid2, offset=10) self.env_hook = {} self.env_hook_exclude = {} self.read_env(job_outfile, "hook") # Validate the values printed in job output file self.assertTrue('HAPPY' not in self.env_nohook_exclude) self.assertTrue('happy' not in self.env_nohook_exclude) self.assertEqual(self.env_hook_exclude['HAPPY'], 'nights\n') self.assertEqual(self.env_hook_exclude['happy'], 'days\n') self.common_validate() # Check the values in mom logs as well self.common_log_match("mom") def test_que(self): """ Test that variable_list do not change with and without queuejob hook """ self.exclude_env += ['happy'] a = {'Resource_List.select': '1:ncpus=1', 'Resource_List.walltime': 10} script = ['#PBS -V'] script += ['env\n'] script += ['sleep 5\n'] # Submit a job without hooks in the system jid = self.create_and_submit_job(attribs=a, content=script) qstat = self.server.status(JOB, ATTR_o, id=jid) job_outfile = qstat[0][ATTR_o].split(':')[1] self.server.expect(JOB, 'queue', op=UNSET, id=jid, offset=10) # Read the env variable from job output file self.env_nohook = {} self.env_nohook_exclude = {} self.read_env(job_outfile, "nohook") # Now start introducing hooks hook_body = """ import pbs e=pbs.event() e.job.Variable_List["happy"] = "days" pbs.logmsg(pbs.LOG_DEBUG,"Variable List is %s" % (e.job.Variable_List,)) """ hook_name = "qjob" a2 = {'event': "queuejob", 'enabled': 'True', 'debug': 'True'} rv = self.server.create_import_hook( hook_name, a2, hook_body, overwrite=True) self.assertTrue(rv) # Submit a job with hooks in the system jid2 = self.create_and_submit_job(attribs=a, content=script) qstat = self.server.status(JOB, ATTR_o, id=jid2) job_outfile = qstat[0][ATTR_o].split(':')[1] self.server.expect(JOB, 'queue', op=UNSET, id=jid2, offset=10) self.env_hook = {} self.env_hook_exclude = {} self.read_env(job_outfile, "hook") # Validate the env values from job output file # with and without queuejob hook self.assertTrue('happy' not in self.env_nohook_exclude) self.assertEqual(self.env_hook_exclude['happy'], 'days\n') self.common_validate() self.common_log_match("server") def test_execjob_epi(self): """ Test that Variable_List will contain environment variable with commas, newline and all special characters even for other mom hooks """ self.exclude_env += ['happy'] a = {'Resource_List.select': '1:ncpus=1', 'Resource_List.walltime': 10} script = ['#PBS -V'] script += ['env\n'] script += ['sleep 5\n'] # Submit a job without hooks in the system jid = self.create_and_submit_job(attribs=a, content=script) qstat = self.server.status(JOB, ATTR_o, id=jid) job_outfile = qstat[0][ATTR_o].split(':')[1] self.server.expect(JOB, 'queue', op=UNSET, id=jid, offset=10) # Read the output file and parse the values self.env_nohook = {} self.env_nohook_exclude = {} self.read_env(job_outfile, "nohook") # Now start the hooks hook_name = "test_epi" hook_body = """ import pbs e = pbs.event() j = e.job j.Variable_List["happy"] = "days" pbs.logmsg(pbs.LOG_DEBUG,"Variable_List is %s" % (j.Variable_List,)) """ a2 = {'event': "execjob_epilogue", 'enabled': "true", 'debug': "true"} self.server.create_import_hook( hook_name, a2, hook_body, overwrite=True) # Submit a job with hooks in the system jid2 = self.create_and_submit_job(attribs=a, content=script) qstat = self.server.status(JOB, ATTR_o, id=jid2) job_outfile = qstat[0][ATTR_o].split(':')[1] self.server.expect(JOB, 'queue', op=UNSET, id=jid2, offset=10) # read the output file for env with hooks self.env_hook = {} self.env_hook_exclude = {} self.read_env(job_outfile, "hook") # Validate self.common_validate() # Verify the env variables in logs too self.common_log_match("mom") def test_execjob_pro(self): """ Test that environment variable not gets truncated for execjob_prologue hook """ a = {'Resource_List.select': '1:ncpus=1', 'Resource_List.walltime': 10} script = ['#PBS -V'] script += ['env\n'] script += ['sleep 5\n'] # Submit a job without hooks in the system jid = self.create_and_submit_job(attribs=a, content=script) qstat = self.server.status(JOB, ATTR_o, id=jid) job_outfile = qstat[0][ATTR_o].split(':')[1] self.server.expect(JOB, 'queue', op=UNSET, id=jid, offset=10) # read the output file for env without hook self.env_nohook = {} self.env_nohook_exclude = {} self.read_env(job_outfile, "nohook") # Now start the hooks hook_name = "test_pro" hook_body = """ import pbs e = pbs.event() j = e.job j.Variable_List["happy"] = "days" pbs.logmsg(pbs.LOG_DEBUG,"Variable_List is %s" % (j.Variable_List,)) """ a2 = {'event': "execjob_prologue", 'enabled': "true", 'debug': "true"} rv = self.server.create_import_hook( hook_name, a2, hook_body, overwrite=True) self.assertTrue(rv) # Submit a job with hooks in the system jid2 = self.create_and_submit_job(attribs=a, content=script) qstat = self.server.status(JOB, ATTR_o, id=jid2) job_outfile = qstat[0][ATTR_o].split(':')[1] self.server.expect(JOB, 'queue', op=UNSET, id=jid2, offset=10) # Read the job ouput file self.env_hook = {} self.env_hook_exclude = {} self.read_env(job_outfile, "hook") # Validate the env values with and without hook self.common_validate() # compare the values in mom_logs as well self.common_log_match("mom") @checkModule("pexpect") def test_interactive(self): """ Test that interactive jobs do not have truncated environment variable list with execjob_launch hook """ self.exclude_env += ['happy'] # submit an interactive job without hook cmd = 'env > ' + self.job_out1_tempfile a = {ATTR_inter: '', self.ATTR_V: None} interactive_script = [('hostname', '.*'), (cmd, '.*')] jid = self.create_and_submit_job( attribs=a, content_interactive=interactive_script, preserve_env=True) # Once all commands sent and matched, job exits self.server.expect(JOB, 'queue', op=UNSET, id=jid, offset=10) # read the environment list from the job without hook self.env_nohook = {} self.env_nohook_exclude = {} self.read_env(self.job_out1_tempfile, "nohook") # now do the same with the hook hook_name = "launch" hook_body = """ import pbs e = pbs.event() j = e.job j.Variable_List["happy"] = "days" pbs.logmsg(pbs.LOG_DEBUG, "Variable_List is %s" % (j.Variable_List,)) """ a2 = {'event': "execjob_launch", 'enabled': 'true', 'debug': 'true'} self.server.create_import_hook(hook_name, a2, hook_body) # submit an interactive job without hook cmd = 'env > ' + self.job_out2_tempfile interactive_script = [('hostname', '.*'), (cmd, '.*')] jid2 = self.create_and_submit_job( attribs=a, content_interactive=interactive_script, preserve_env=True) # Once all commands sent and matched, job exits self.server.expect(JOB, 'queue', op=UNSET, id=jid2, offset=10) # read the environment list from the job without hook self.env_hook = {} self.env_hook_exclude = {} self.read_env(self.job_out2_tempfile, "hook") # validate the environment values self.common_validate() # verify the env values in logs self.common_log_match("mom") def test_no_hook(self): """ Test to verify that environment variables are not truncated and also not modified by PBS when no hook is present """ os.environ['BROL'] = 'hii\\\haha' os.environ['BROL1'] = """'hii haa'""" a = {'Resource_List.select': '1:ncpus=1', 'Resource_List.walltime': 10} script = ['#PBS -V'] script += ['env\n'] script += ['sleep 5\n'] # Submit a job without hooks in the system jid = self.create_and_submit_job(attribs=a, content=script) qstat = self.server.status(JOB, id=jid) job_outfile = qstat[0]['Output_Path'].split(':')[1] job_var = qstat[0]['Variable_List'] self.logger.info("job variable list is %s" % job_var) self.server.expect(JOB, 'queue', op=UNSET, id=jid, offset=10) # Read the env variable from job output file self.env_nohook = {} self.env_nohook_exclude = {} self.read_env(job_outfile, "nohook") # Verify the output with and without job self.assertEqual(os.environ['TEST_COMMA'], self.env_nohook['TEST_COMMA'].rstrip('\n')) self.logger.info( "TEST_COMMA matched - " + os.environ['TEST_COMMA'] + " == " + self.env_nohook['TEST_COMMA'].rstrip('\n')) self.assertEqual(os.environ['TEST_RETURN'], self.env_nohook['TEST_RETURN'].rstrip('\n')) self.logger.info( "TEST_RETURN matched - " + os.environ['TEST_RETURN'] + " == " + self.env_nohook['TEST_RETURN'].rstrip('\n')) self.assertEqual(os.environ['TEST_SEMICOLON'], self.env_nohook['TEST_SEMICOLON'].rstrip('\n')) self.logger.info( "TEST_SEMICOLON macthed - " + os.environ['TEST_SEMICOLON'] + " == " + self.env_nohook['TEST_SEMICOLON'].rstrip('\n')) self.assertEqual( os.environ['TEST_ENCLOSED'], self.env_nohook['TEST_ENCLOSED'].rstrip('\n')) self.logger.info( "TEST_ENCLOSED matched - " + os.environ['TEST_ENCLOSED'] + " == " + self.env_nohook['TEST_ENCLOSED'].rstrip('\n')) self.assertEqual(os.environ['TEST_COLON'], self.env_nohook['TEST_COLON'].rstrip('\n')) self.logger.info("TEST_COLON macthed - " + os.environ['TEST_COLON'] + " == " + self.env_nohook['TEST_COLON'].rstrip('\n')) self.assertEqual( os.environ['TEST_BACKSLASH'], self.env_nohook['TEST_BACKSLASH'].rstrip('\n')) self.logger.info( "TEST_BACKSLASH matched - " + os.environ['TEST_BACKSLASH'] + " == " + self.env_nohook['TEST_BACKSLASH'].rstrip('\n')) self.assertEqual(os.environ['TEST_DQUOTE'], self.env_nohook['TEST_DQUOTE'].rstrip('\n')) self.logger.info("TEST_DQUOTE - " + os.environ['TEST_DQUOTE'] + " == " + self.env_nohook['TEST_DQUOTE'].rstrip('\n')) self.assertEqual(os.environ['TEST_DQUOTE2'], self.env_nohook['TEST_DQUOTE2'].rstrip('\n')) self.logger.info("TEST_DQUOTE2 - " + os.environ['TEST_DQUOTE2'] + " == " + self.env_nohook['TEST_DQUOTE2'].rstrip('\n')) self.assertEqual(os.environ['TEST_DQUOTE3'], self.env_nohook['TEST_DQUOTE3'].rstrip('\n')) self.logger.info("TEST_DQUOTE3 - " + os.environ['TEST_DQUOTE3'] + " == " + self.env_nohook['TEST_DQUOTE3'].rstrip('\n')) self.assertEqual(os.environ['TEST_DQUOTE4'], self.env_nohook['TEST_DQUOTE4'].rstrip('\n')) self.logger.info("TEST_DQUOTE4 - " + os.environ['TEST_DQUOTE4'] + " == " + self.env_nohook['TEST_DQUOTE4'].rstrip('\n')) self.assertEqual(os.environ['TEST_DQUOTE5'], self.env_nohook['TEST_DQUOTE5'].rstrip('\n')) self.logger.info("TEST_DQUOTE5 - " + os.environ['TEST_DQUOTE5'] + " == " + self.env_nohook['TEST_DQUOTE5'].rstrip('\n')) self.assertEqual(os.environ['TEST_DQUOTE6'], self.env_nohook['TEST_DQUOTE6'].rstrip('\n')) self.logger.info("TEST_DQUOTE6 - " + os.environ['TEST_DQUOTE6'] + " == " + self.env_nohook['TEST_DQUOTE6'].rstrip('\n')) self.assertEqual(os.environ['TEST_SQUOTE'], self.env_nohook['TEST_SQUOTE'].rstrip('\n')) self.logger.info("TEST_SQUOTE - " + os.environ['TEST_SQUOTE'] + " == " + self.env_nohook['TEST_SQUOTE'].rstrip('\n')) self.assertEqual(os.environ['TEST_SQUOTE2'], self.env_nohook['TEST_SQUOTE2'].rstrip('\n')) self.logger.info("TEST_SQUOTE2 - " + os.environ['TEST_SQUOTE2'] + " == " + self.env_nohook['TEST_SQUOTE2'].rstrip('\n')) self.assertEqual(os.environ['TEST_SQUOTE3'], self.env_nohook['TEST_SQUOTE3'].rstrip('\n')) self.logger.info("TEST_SQUOTE3 - " + os.environ['TEST_SQUOTE3'] + " == " + self.env_nohook['TEST_SQUOTE3'].rstrip('\n')) self.assertEqual(os.environ['TEST_SQUOTE4'], self.env_nohook['TEST_SQUOTE4'].rstrip('\n')) self.logger.info("TEST_SQUOTE4 - " + os.environ['TEST_SQUOTE4'] + " == " + self.env_nohook['TEST_SQUOTE4'].rstrip('\n')) self.assertEqual(os.environ['TEST_SQUOTE5'], self.env_nohook['TEST_SQUOTE5'].rstrip('\n')) self.logger.info("TEST_SQUOTE5 - " + os.environ['TEST_SQUOTE5'] + " == " + self.env_nohook['TEST_SQUOTE5'].rstrip('\n')) self.assertEqual(os.environ['TEST_SQUOTE6'], self.env_nohook['TEST_SQUOTE6'].rstrip('\n')) self.logger.info("TEST_SQUOTE6 - " + os.environ['TEST_SQUOTE6'] + " == " + self.env_nohook['TEST_SQUOTE6'].rstrip('\n')) self.assertEqual(os.environ['BROL'], self.env_nohook['BROL'].rstrip('\n')) self.logger.info("BROL - " + os.environ['BROL'] + " == " + self.env_nohook['BROL'].rstrip('\n')) self.assertEqual(os.environ['BROL1'], self.env_nohook['BROL1'].rstrip('\n')) self.logger.info("BROL - " + os.environ['BROL1'] + " == " + self.env_nohook['BROL1'].rstrip('\n')) # match the values in qstat -f Variable_List # Following is blocked on PTL bug PP-1008 # self.assertTrue("TEST_COMMA=1\,2\,3\,4" in job_var) # self.assertTrue("TEST_SEMICOLON=\;" in job_var) # self.assertTrue("TEST_COLON=:" in job_var) # self.assertTrue("TEST_DQUOTE=\"" in job_var) # self.assertTrue("TEST_SQUOTE=\'" in job_var) # self.assertTrue("TEST_BACKSLASH=\\" in job_var) # self.assertTrue("BROL=hii\\\\\\haha" in job_var) # self.assertTrue("TEST_ENCLOSED=\," in job_var) # self.assertTrue("BROL1=hii\nhaa" in job_var) # self.assertTrue("TEST_RETURN=3\,\n4\,\n5\," in job_var) @checkModule("pexpect") def test_interactive_no_hook(self): """ Test to verify that environment variable values are not truncated or escaped wrongly whithin a job even when there is no hook present """ os.environ['BROL'] = 'hii\\\haha' os.environ['BROL1'] = """'hii haa'""" # submit an interactive job without hook cmd = 'env > ' + self.job_out3_tempfile a = {ATTR_inter: '', self.ATTR_V: None} interactive_script = [('hostname', '.*'), (cmd, '.*')] jid = self.create_and_submit_job( attribs=a, content_interactive=interactive_script, preserve_env=True) # Once all commands sent and matched, job exits self.server.expect(JOB, 'queue', op=UNSET, id=jid, offset=10) # read the environment list from the job without hook self.env_nohook = {} self.env_nohook_exclude = {} self.read_env(self.job_out3_tempfile, "nohook") # Verify the output with and without job self.logger.info("job Variable list is ") self.assertEqual(os.environ['TEST_COMMA'], self.env_nohook['TEST_COMMA'].rstrip('\n')) self.logger.info( "TEST_COMMA matched - " + os.environ['TEST_COMMA'] + " == " + self.env_nohook['TEST_COMMA'].rstrip('\n')) self.assertEqual(os.environ['TEST_RETURN'], self.env_nohook['TEST_RETURN'].rstrip('\n')) self.logger.info( "TEST_RETURN matched - " + os.environ['TEST_RETURN'] + " == " + self.env_nohook['TEST_RETURN'].rstrip('\n')) self.assertEqual(os.environ['TEST_SEMICOLON'], self.env_nohook['TEST_SEMICOLON'].rstrip('\n')) self.logger.info( "TEST_SEMICOLON macthed - " + os.environ['TEST_SEMICOLON'] + " == " + self.env_nohook['TEST_SEMICOLON'].rstrip('\n')) self.assertEqual( os.environ['TEST_ENCLOSED'], self.env_nohook['TEST_ENCLOSED'].rstrip('\n')) self.logger.info( "TEST_ENCLOSED matched - " + os.environ['TEST_ENCLOSED'] + " == " + self.env_nohook['TEST_ENCLOSED'].rstrip('\n')) self.assertEqual(os.environ['TEST_COLON'], self.env_nohook['TEST_COLON'].rstrip('\n')) self.logger.info("TEST_COLON macthed - " + os.environ['TEST_COLON'] + " == " + self.env_nohook['TEST_COLON'].rstrip('\n')) self.assertEqual( os.environ['TEST_BACKSLASH'], self.env_nohook['TEST_BACKSLASH'].rstrip('\n')) self.logger.info( "TEST_BACKSLASH matched - " + os.environ['TEST_BACKSLASH'] + " == " + self.env_nohook['TEST_BACKSLASH'].rstrip('\n')) self.assertEqual(os.environ['TEST_DQUOTE'], self.env_nohook['TEST_DQUOTE'].rstrip('\n')) self.logger.info("TEST_DQUOTE - " + os.environ['TEST_DQUOTE'] + " == " + self.env_nohook['TEST_DQUOTE'].rstrip('\n')) self.logger.info("TEST_DQUOTE2 - " + os.environ['TEST_DQUOTE2'] + " == " + self.env_nohook['TEST_DQUOTE2'].rstrip('\n')) self.logger.info("TEST_DQUOTE3 - " + os.environ['TEST_DQUOTE3'] + " == " + self.env_nohook['TEST_DQUOTE3'].rstrip('\n')) self.logger.info("TEST_DQUOTE4 - " + os.environ['TEST_DQUOTE4'] + " == " + self.env_nohook['TEST_DQUOTE4'].rstrip('\n')) self.logger.info("TEST_DQUOTE5 - " + os.environ['TEST_DQUOTE5'] + " == " + self.env_nohook['TEST_DQUOTE5'].rstrip('\n')) self.logger.info("TEST_DQUOTE6 - " + os.environ['TEST_DQUOTE6'] + " == " + self.env_nohook['TEST_DQUOTE6'].rstrip('\n')) self.logger.info("TEST_SQUOTE - " + os.environ['TEST_SQUOTE'] + " == " + self.env_nohook['TEST_SQUOTE'].rstrip('\n')) self.logger.info("TEST_SQUOTE2 - " + os.environ['TEST_SQUOTE2'] + " == " + self.env_nohook['TEST_SQUOTE2'].rstrip('\n')) self.logger.info("TEST_SQUOTE3 - " + os.environ['TEST_SQUOTE3'] + " == " + self.env_nohook['TEST_SQUOTE3'].rstrip('\n')) self.logger.info("TEST_SQUOTE4 - " + os.environ['TEST_SQUOTE4'] + " == " + self.env_nohook['TEST_SQUOTE4'].rstrip('\n')) self.logger.info("TEST_SQUOTE5 - " + os.environ['TEST_SQUOTE5'] + " == " + self.env_nohook['TEST_SQUOTE5'].rstrip('\n')) self.logger.info("TEST_SQUOTE6 - " + os.environ['TEST_SQUOTE6'] + " == " + self.env_nohook['TEST_SQUOTE6'].rstrip('\n')) self.assertEqual(os.environ['BROL'], self.env_nohook['BROL'].rstrip('\n')) self.logger.info("BROL - " + os.environ['BROL'] + " == " + self.env_nohook['BROL'].rstrip('\n')) self.assertEqual(os.environ['BROL1'], self.env_nohook['BROL1'].rstrip('\n')) self.logger.info("BROL - " + os.environ['BROL1'] + " == " + self.env_nohook['BROL1'].rstrip('\n')) def test_execjob_epi2(self): """ Test that Variable_List will contain environment variable with commas, newline and all special characters for a job that has been recovered from a prematurely killed mom. This is a test from an execjob_epilogue hook's view. PRE: Set up currently executing user's environment to have variables whose values have the special characters. Submit a job using the -V option (pass current environment) where there is an execjob_epilogue hook that references Variable_List value. Now kill -9 pbs_mom and then restart it. This causes pbs_mom to read in job data from the *.JB file on disk, and pbs_mom immediately kills the job causing execjob_epilogue hook to execute. POST: The epilogue hook should see the proper value to the Variable_List. """ a = {'Resource_List.select': '1:ncpus=1', 'Resource_List.walltime': 60} j = Job(attrs=a) script = ['#PBS -V'] script += ['env\n'] script += ['sleep 30\n'] j.create_script(body=script) # Now create/start the hook hook_name = "test_epi" hook_body = """ import pbs import time e = pbs.event() j = e.job pbs.logmsg(pbs.LOG_DEBUG,"Variable_List is %s" % (j.Variable_List,)) pbs.logmsg(pbs.LOG_DEBUG, "PBS_O_LOGNAME is %s" % j.Variable_List["PBS_O_LOGNAME"]) """ a = {'event': "execjob_epilogue", 'enabled': "true", 'debug': "true"} self.server.create_import_hook( hook_name, a, hook_body, overwrite=True) # Submit a job with hooks in the system jid = self.server.submit(j) # Wait for the job to start running. self.server.expect(JOB, {ATTR_state: 'R'}, id=jid) # kill -9 mom self.mom.signal('-KILL') # now restart mom self.mom.start() self.mom.log_match("Restart sent to server") # Verify the env variables are seen in logs self.common_log_match("mom") self.mom.log_match( "PBS_O_LOGNAME is %s" % (self.du.get_current_user()))