pbs_dshutils.py 74 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983
  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 subprocess import PIPE, Popen
  37. import os
  38. import sys
  39. import re
  40. import stat
  41. import socket
  42. import logging
  43. import traceback
  44. import copy
  45. import tempfile
  46. import pwd
  47. import grp
  48. import platform
  49. DFLT_RSYNC_CMD = ['rsync', '-e', 'ssh', '--progress', '--partial', '-ravz']
  50. DFLT_COPY_CMD = ['scp', '-p']
  51. DFLT_RSH_CMD = ['ssh']
  52. DFLT_SUDO_CMD = ['sudo', '-H']
  53. logging.DEBUG2 = logging.DEBUG - 1
  54. logging.INFOCLI = logging.INFO - 1
  55. logging.INFOCLI2 = logging.INFOCLI - 1
  56. class PbsConfigError(Exception):
  57. """
  58. Initialize PBS configuration error
  59. """
  60. def __init__(self, message=None, rv=None, rc=None, msg=None):
  61. self.message = message
  62. self.rv = rv
  63. self.rc = rc
  64. self.msg = msg
  65. def __str__(self):
  66. return ('rc=' + str(self.rc) + ', rv=' + str(self.rv) +
  67. ',msg=' + str(self.msg))
  68. def __repr__(self):
  69. return (self.__class__.__name__ + '(rc=' + str(self.rc) + ', rv=' +
  70. str(self.rv) + ', msg=' + str(self.msg) + ')')
  71. class PtlUtilError(Exception):
  72. """
  73. Initialize PTL Util error
  74. """
  75. def __init__(self, message=None, rv=None, rc=None, msg=None):
  76. self.message = message
  77. self.rv = rv
  78. self.rc = rc
  79. self.msg = msg
  80. def __str__(self):
  81. return ('rc=' + str(self.rc) + ', rv=' + str(self.rv) +
  82. ',msg=' + str(self.msg))
  83. def __repr__(self):
  84. return (self.__class__.__name__ + '(rc=' + str(self.rc) + ', rv=' +
  85. str(self.rv) + ', msg=' + str(self.msg) + ')')
  86. class DshUtils(object):
  87. """
  88. PBS shell utilities
  89. A set of tools to run commands, copy files, get process
  90. information and parse a PBS configuration on an arbitrary host
  91. """
  92. logger = logging.getLogger(__name__)
  93. _h2osinfo = {} # host to OS info cache
  94. _h2p = {} # host to platform cache
  95. _h2pu = {} # host to uname cache
  96. _h2c = {} # host to pbs_conf file cache
  97. _h2l = {} # host to islocal cache
  98. _h2which = {} # host to which cache
  99. rsh_cmd = DFLT_RSH_CMD
  100. sudo_cmd = DFLT_SUDO_CMD
  101. copy_cmd = DFLT_COPY_CMD
  102. def __init__(self):
  103. self._current_user = None
  104. logging.addLevelName('INFOCLI', logging.INFOCLI)
  105. setattr(self.logger, 'infocli',
  106. lambda *args: self.logger.log(logging.INFOCLI, *args))
  107. logging.addLevelName('DEBUG2', logging.DEBUG2)
  108. setattr(self.logger, 'debug2',
  109. lambda *args: self.logger.log(logging.DEBUG2, *args))
  110. logging.addLevelName('INFOCLI2', logging.INFOCLI2)
  111. setattr(self.logger, 'infocli2',
  112. lambda *args: self.logger.log(logging.INFOCLI2, *args))
  113. self.mom_conf_map = {'PBS_MOM_SERVICE_PORT': '-M',
  114. 'PBS_MANAGER_SERVICE_PORT': '-R',
  115. 'PBS_HOME': '-d',
  116. 'PBS_BATCH_SERVICE_PORT': '-S',
  117. }
  118. self.server_conf_map = {'PBS_MOM_SERVICE_PORT': '-M',
  119. 'PBS_MANAGER_SERVICE_PORT': '-R',
  120. 'PBS_HOME': '-d',
  121. 'PBS_BATCH_SERVICE_PORT': '-p',
  122. 'PBS_SCHEDULER_SERVICE_PORT': '-S',
  123. }
  124. self.sched_conf_map = {'PBS_HOME': '-d',
  125. 'PBS_BATCH_SERVICE_PORT': '-p',
  126. 'PBS_SCHEDULER_SERVICE_PORT': '-S',
  127. }
  128. self._tempdir = {}
  129. self.platform = self.get_platform()
  130. def get_platform(self, hostname=None, pyexec=None):
  131. """
  132. Get a local or remote platform info, essentially the value of
  133. Python's sys.platform, in case of Cray it will return a string
  134. as "cray" for actual Cray cluster and "craysim" for Cray ALPS
  135. simulator
  136. :param hostname: The hostname to query for platform info
  137. :type hostname: str or None
  138. :param pyexec: A path to a Python interpreter to use to query
  139. a remote host for platform info
  140. :type pyexec: str or None
  141. For efficiency the value is cached and retrieved from the
  142. cache upon subsequent request
  143. """
  144. splatform = sys.platform
  145. found_already = False
  146. if hostname is None:
  147. hostname = socket.gethostname()
  148. if hostname in self._h2p:
  149. return self._h2p[hostname]
  150. if self.isfile(hostname=hostname, path='/etc/xthostname',
  151. level=logging.DEBUG2):
  152. if self.isfile(hostname=hostname, path='/proc/cray_xt/cname',
  153. level=logging.DEBUG2):
  154. splatform = 'cray'
  155. else:
  156. splatform = 'craysim'
  157. found_already = True
  158. if not self.is_localhost(hostname) and not found_already:
  159. if pyexec is None:
  160. pyexec = self.which(hostname, 'python', level=logging.DEBUG2)
  161. cmd = [pyexec, '-c', '"import sys; print sys.platform"']
  162. ret = self.run_cmd(hostname, cmd=cmd)
  163. if ret['rc'] != 0 or len(ret['out']) == 0:
  164. _msg = 'Unable to retrieve platform info,'
  165. _msg += 'defaulting to local platform'
  166. self.logger.warning(_msg)
  167. splatform = sys.platform
  168. else:
  169. splatform = ret['out'][0]
  170. self._h2p[hostname] = splatform
  171. return splatform
  172. def get_uname(self, hostname=None, pyexec=None):
  173. """
  174. Get a local or remote platform info in uname format, essentially
  175. the value of Python's platform.uname
  176. :param hostname: The hostname to query for platform info
  177. :type hostname: str or None
  178. :param pyexec: A path to a Python interpreter to use to query
  179. a remote host for platform info
  180. :type pyexec: str or None
  181. For efficiency the value is cached and retrieved from the
  182. cache upon subsequent request
  183. """
  184. uplatform = ' '.join(platform.uname())
  185. if hostname is None:
  186. hostname = socket.gethostname()
  187. if hostname in self._h2pu:
  188. return self._h2pu[hostname]
  189. if not self.is_localhost(hostname):
  190. if pyexec is None:
  191. pyexec = self.which(hostname, 'python', level=logging.DEBUG2)
  192. _cmdstr = '"import platform;'
  193. _cmdstr += 'print \' \'.join(platform.uname())"'
  194. cmd = [pyexec, '-c', _cmdstr]
  195. ret = self.run_cmd(hostname, cmd=cmd)
  196. if ret['rc'] != 0 or len(ret['out']) == 0:
  197. _msg = 'Unable to retrieve platform info,'
  198. _msg += 'defaulting to local platform'
  199. self.logger.warning(_msg)
  200. else:
  201. uplatform = ret['out'][0]
  202. self._h2pu[hostname] = uplatform
  203. return uplatform
  204. def get_os_info(self, hostname=None, pyexec=None):
  205. """
  206. Get a local or remote OS info
  207. :param hostname: The hostname to query for platform info
  208. :type hostname: str or None
  209. :param pyexec: A path to a Python interpreter to use to query
  210. a remote host for platform info
  211. :type pyexec: str or None
  212. :returns: a 'str' object containing os info
  213. """
  214. local_info = platform.platform()
  215. if hostname is None or self.is_localhost(hostname):
  216. return local_info
  217. if hostname in self._h2osinfo:
  218. return self._h2osinfo[hostname]
  219. if pyexec is None:
  220. pyexec = self.which(hostname, 'python', level=logging.DEBUG2)
  221. cmd = [pyexec, '-c',
  222. '"import platform; print platform.platform()"']
  223. ret = self.run_cmd(hostname, cmd=cmd)
  224. if ret['rc'] != 0 or len(ret['out']) == 0:
  225. self.logger.warning("Unable to retrieve OS info, defaulting "
  226. "to local")
  227. ret_info = local_info
  228. else:
  229. ret_info = ret['out'][0]
  230. self._h2osinfo[hostname] = ret_info
  231. return ret_info
  232. def _parse_file(self, hostname, file):
  233. """
  234. helper function to parse a file containing entries of the
  235. form ``<key>=<value>`` into a Python dictionary format
  236. """
  237. if hostname is None:
  238. hostname = socket.gethostname()
  239. try:
  240. rv = self.cat(hostname, file, level=logging.DEBUG2, logerr=False)
  241. if rv['rc'] != 0:
  242. return {}
  243. props = {}
  244. for l in rv['out']:
  245. if l.find('=') != -1 and l[0] != '#':
  246. c = l.split('=')
  247. props[c[0]] = c[1].strip()
  248. except:
  249. self.logger.error('error parsing file ' + str(file))
  250. self.logger.error(traceback.print_exc())
  251. return {}
  252. return props
  253. def _set_file(self, hostname, fin, fout, append, variables, sudo=False):
  254. """
  255. Create a file out of a set of dictionaries, possibly parsed
  256. from an input file. @see _parse_file.
  257. :param hostname: the name of the host on which to operate.
  258. Defaults to localhost
  259. :type hostname: str
  260. :param fin: the input file to read from
  261. :type fin: str
  262. :param fout: the output file to write to
  263. :type fout: str
  264. :param append: If true, append to the output file.
  265. :type append: bool
  266. :param variables: The ``key/value`` pairs to write to fout
  267. :type variables: dictionary
  268. :param sudo: copy file to destination through sudo
  269. :type sudo: boolean
  270. :return dictionary of items set
  271. :raises PbsConfigError:
  272. """
  273. if hostname is None:
  274. hostname = socket.gethostname()
  275. if append:
  276. conf = self._parse_file(hostname, fin)
  277. else:
  278. conf = {}
  279. conf = dict(conf.items() + variables.items())
  280. if os.path.isfile(fout):
  281. fout_stat = os.stat(fout)
  282. user = fout_stat.st_uid
  283. group = fout_stat.st_gid
  284. else:
  285. user = None
  286. group = None
  287. try:
  288. fn = self.create_temp_file()
  289. self.chmod(path=fn, mode=0644)
  290. with open(fn, 'w') as fd:
  291. for k, v in conf.items():
  292. fd.write(str(k) + '=' + str(v) + '\n')
  293. rv = self.run_copy(hostname, fn, fout, uid=user, gid=group,
  294. level=logging.DEBUG2, sudo=sudo)
  295. if rv['rc'] != 0:
  296. raise PbsConfigError
  297. except:
  298. raise PbsConfigError(rc=1, rv=None,
  299. msg='error writing to file ' + str(fout))
  300. finally:
  301. if os.path.isfile(fn):
  302. self.rm(path=fn)
  303. return conf
  304. def get_pbs_conf_file(self, hostname=None):
  305. """
  306. Get the path of the pbs conf file. Defaults back to
  307. ``/etc/pbs.conf`` if unsuccessful
  308. :param hostname: Hostname of the machine
  309. :type hostname: str or None
  310. :returns: Path to pbs conf file
  311. """
  312. dflt_conf = '/etc/pbs.conf'
  313. if hostname is None:
  314. hostname = socket.gethostname()
  315. if hostname in self._h2c:
  316. return self._h2c[hostname]
  317. if self.is_localhost(hostname):
  318. if 'PBS_CONF_FILE' in os.environ:
  319. dflt_conf = os.environ['PBS_CONF_FILE']
  320. else:
  321. pc = ('"import os;print [False, os.environ[\'PBS_CONF_FILE\']]'
  322. '[\'PBS_CONF_FILE\' in os.environ]"')
  323. cmd = ['python', '-c', pc]
  324. ret = self.run_cmd(hostname, cmd, logerr=False)
  325. if ((ret['rc'] != 0) and (len(ret['out']) > 0) and
  326. (ret['out'][0] != 'False')):
  327. dflt_conf = ret['out'][0]
  328. self._h2c[hostname] = dflt_conf
  329. return dflt_conf
  330. def parse_pbs_config(self, hostname=None, file=None):
  331. """
  332. initialize ``pbs_conf`` dictionary by parsing pbs config file
  333. :param file: PBS conf file
  334. :type file: str or None
  335. """
  336. if file is None:
  337. file = self.get_pbs_conf_file(hostname)
  338. return self._parse_file(hostname, file)
  339. def set_pbs_config(self, hostname=None, fin=None, fout=None,
  340. append=True, confs=None):
  341. """
  342. Set ``environment/configuration`` variables in a
  343. ``pbs.conf`` file
  344. :param hostname: the name of the host on which to operate
  345. :type hostname: str or None
  346. :param fin: the input pbs.conf file
  347. :type fin: str or None
  348. :param fout: the name of the output pbs.conf file, defaults
  349. to ``/etc/pbs.conf``
  350. :type fout: str or None
  351. :param append: whether to append to fout or not, defaults
  352. to True
  353. :type append: boolean
  354. :param confs: The ``key/value`` pairs to create
  355. :type confs: Dictionary or None
  356. """
  357. if fin is None:
  358. fin = self.get_pbs_conf_file(hostname)
  359. if fout is None and fin is not None:
  360. fout = fin
  361. if confs is not None:
  362. self.logger.info('Set ' + str(confs) + ' in ' + fout)
  363. else:
  364. confs = {}
  365. return self._set_file(hostname, fin, fout, append, confs, sudo=True)
  366. def unset_pbs_config(self, hostname=None, fin=None, fout=None,
  367. confs=None):
  368. """
  369. Unset ``environment/configuration`` variables in a pbs.conf
  370. file
  371. :param hostname: the name of the host on which to operate
  372. :type hostname: str or None
  373. :param fin: the input pbs.conf file
  374. :type fin: str or None
  375. :param fout: the name of the output pbs.conf file, defaults
  376. to ``/etc/pbs.conf``
  377. :type fout: str or None
  378. :param confs: The configuration keys to unset
  379. :type confs: List or str or dict or None
  380. """
  381. if fin is None:
  382. fin = self.get_pbs_conf_file(hostname)
  383. if fout is None and fin is not None:
  384. fout = fin
  385. if confs is None:
  386. confs = []
  387. elif isinstance(confs, str):
  388. confs = confs.split(',')
  389. elif isinstance(confs, dict):
  390. confs = confs.keys()
  391. tounset = []
  392. cur_confs = self.parse_pbs_config(hostname, fin)
  393. for k in confs:
  394. if k in cur_confs:
  395. tounset.append(k)
  396. del cur_confs[k]
  397. if tounset:
  398. self.logger.info('Unset ' + ",".join(tounset) + ' from ' + fout)
  399. return self._set_file(hostname, fin, fout, append=False,
  400. variables=cur_confs, sudo=True)
  401. def get_pbs_server_name(self, pbs_conf=None):
  402. """
  403. Return the name of the server which may be different than
  404. ``PBS_SERVER``,in order, this method looks at
  405. ``PBS_PRIMARY``, ``PBS_SERVER_HOST_NAME``, and
  406. ``PBS_LEAF_NAME``, and ``PBS_SERVER``
  407. """
  408. if pbs_conf is None:
  409. pbs_conf = self.parse_pbs_config()
  410. if 'PBS_PRIMARY' in pbs_conf:
  411. return pbs_conf['PBS_PRIMARY']
  412. elif 'PBS_SERVER_HOST_NAME' in pbs_conf:
  413. return pbs_conf['PBS_SERVER_HOST_NAME']
  414. elif 'PBS_LEAF_NAME' in pbs_conf:
  415. return pbs_conf['PBS_LEAF_NAME']
  416. return pbs_conf['PBS_SERVER']
  417. def parse_pbs_environment(self, hostname=None,
  418. file='/var/spool/pbs/pbs_environment'):
  419. """
  420. Initialize pbs_conf dictionary by parsing pbs config file
  421. """
  422. return self._parse_file(hostname, file)
  423. def set_pbs_environment(self, hostname=None,
  424. fin='/var/spool/pbs/pbs_environment', fout=None,
  425. append=True, environ=None):
  426. """
  427. Set the PBS environment
  428. :param environ: variables to set
  429. :type environ: dict or None
  430. :param hostname: Hostname of the machine
  431. :type hostname: str or None
  432. :param fin: pbs_environment input file
  433. :type fin: str
  434. :param fout: pbs_environment output file
  435. :type fout: str or None
  436. :param append: whether to append to fout or not, defaults
  437. defaults to true
  438. :type append: bool
  439. """
  440. if fout is None and fin is not None:
  441. fout = fin
  442. if environ is None:
  443. environ = {}
  444. return self._set_file(hostname, fin, fout, append, environ, sudo=True)
  445. def unset_pbs_environment(self, hostname=None,
  446. fin='/var/spool/pbs/pbs_environment', fout=None,
  447. environ=None):
  448. """
  449. Unset environment variables in a pbs_environment file
  450. :param hostname: the name of the host on which to operate
  451. :type hostname: str or None
  452. :param fin: the input pbs_environment file
  453. :type fin: str
  454. :param fout: the name of the output pbs.conf file, defaults
  455. to ``/var/spool/pbs/pbs_environment``
  456. :type fout: str or None
  457. :param environ: The environment keys to unset
  458. :type environ: List or str or dict or None
  459. """
  460. if fout is None and fin is not None:
  461. fout = fin
  462. if environ is None:
  463. environ = []
  464. elif isinstance(environ, str):
  465. environ = environ.split(',')
  466. elif isinstance(environ, dict):
  467. environ = environ.keys()
  468. tounset = []
  469. cur_environ = self.parse_pbs_environment(hostname, fin)
  470. for k in environ:
  471. if k in cur_environ:
  472. tounset.append(k)
  473. del cur_environ[k]
  474. if tounset:
  475. self.logger.info('Unset ' + ",".join(tounset) + ' from ' + fout)
  476. return self._set_file(hostname, fin, fout, append=False,
  477. variables=cur_environ, sudo=True)
  478. def parse_rhosts(self, hostname=None, user=None):
  479. """
  480. Parse remote host
  481. :param hostname: Hostname of the machine
  482. :type hostname: str or None
  483. :param user: User name
  484. :type user: str or None
  485. """
  486. if hostname is None:
  487. hostname = socket.gethostname()
  488. if user is None:
  489. user = os.getuid()
  490. try:
  491. # currently assumes identical file system layout on every host
  492. if isinstance(user, int):
  493. home = pwd.getpwuid(user).pw_dir
  494. else:
  495. home = pwd.getpwnam(user).pw_dir
  496. rhost = os.path.join(home, '.rhosts')
  497. rv = self.cat(hostname, rhost, level=logging.DEBUG2, runas=user,
  498. logerr=False)
  499. if rv['rc'] != 0:
  500. return {}
  501. props = {}
  502. for l in rv['out']:
  503. if l[0] != '#':
  504. k, v = l.split()
  505. v = v.strip()
  506. if k in self.props:
  507. if isinstance(self.props[k], list):
  508. self.props[k].append(v)
  509. else:
  510. self.props[k] = [self.props[k], v]
  511. else:
  512. self.props[k] = v
  513. except:
  514. self.logger.error('error parsing .rhost')
  515. self.logger.error(traceback.print_exc())
  516. return {}
  517. return props
  518. def set_rhosts(self, hostname=None, user=None, entry={}, append=True):
  519. """
  520. Set the remote host attributes
  521. :param entry: remote hostname user dictionary
  522. :type entry: Dictionary
  523. :param append: If true append key value else not
  524. :type append: boolean
  525. """
  526. if hostname is None:
  527. hostname = socket.gethostname()
  528. if user is None:
  529. user = os.getuid()
  530. if append:
  531. conf = self.parse_rhosts(hostname, user)
  532. for k, v in entry.items():
  533. if k in conf:
  534. if isinstance(conf[k], list):
  535. if isinstance(v, list):
  536. conf[k].extend(v)
  537. else:
  538. conf[k].append(v)
  539. else:
  540. if isinstance(v, list):
  541. conf[k] = [conf[k]] + v
  542. else:
  543. conf[k] = [conf[k], v]
  544. else:
  545. conf[k] = v
  546. else:
  547. conf = entry
  548. try:
  549. # currently assumes identical file system layout on every host
  550. if isinstance(user, int):
  551. _user = pwd.getpwuid(user)
  552. home = _user.pw_dir
  553. uid = _user.pw_uid
  554. else:
  555. # user might be PbsUser object
  556. _user = pwd.getpwnam(str(user))
  557. home = _user.pw_dir
  558. uid = _user.pw_uid
  559. rhost = os.path.join(home, '.rhosts')
  560. fn = self.create_temp_file(hostname)
  561. self.chmod(hostname, fn, mode=0755)
  562. with open(fn, 'w') as fd:
  563. fd.write('#!/bin/bash\n')
  564. fd.write('cd %s\n' % (home))
  565. fd.write('%s -rf %s\n' % (self.which(hostname, 'rm',
  566. level=logging.DEBUG2),
  567. rhost))
  568. fd.write('touch %s\n' % (rhost))
  569. for k, v in conf.items():
  570. if isinstance(v, list):
  571. for eachprop in v:
  572. l = 'echo "%s %s" >> %s\n' % (str(k),
  573. str(eachprop),
  574. rhost)
  575. fd.write(l)
  576. else:
  577. l = 'echo "%s %s" >> %s\n' % (str(k), str(v), rhost)
  578. fd.write(l)
  579. fd.write('%s 0600 %s\n' % (self.which(hostname, 'chmod',
  580. level=logging.DEBUG2),
  581. rhost))
  582. ret = self.run_cmd(hostname, cmd=fn, runas=uid)
  583. self.rm(hostname, path=fn)
  584. if ret['rc'] != 0:
  585. raise Exception(ret['out'] + ret['err'])
  586. except Exception, e:
  587. raise PbsConfigError(rc=1, rv=None, msg='error writing .rhosts ' +
  588. str(e))
  589. return conf
  590. def map_pbs_conf_to_cmd(self, cmd_map={}, pconf={}):
  591. """
  592. Map PBS configuration parameter to command
  593. :param cmd_map: command mapping
  594. :type cmd_map: Dictionary
  595. :param pconf: PBS conf parameter dictionary
  596. :type pconf: Dictionary
  597. """
  598. cmd = []
  599. for k, v in pconf.items():
  600. if k in cmd_map:
  601. cmd += [cmd_map[k], str(v)]
  602. return cmd
  603. def get_current_user(self):
  604. """
  605. helper function to return the name of the current user
  606. """
  607. if self._current_user is not None:
  608. return self._current_user
  609. self._current_user = pwd.getpwuid(os.getuid())[0]
  610. return self._current_user
  611. def check_user_exists(self, username=None, hostname=None):
  612. """
  613. Check if user exist or not
  614. :param username: Username to check
  615. :type username: str or None
  616. :param hostname: Machine hostname
  617. :type hostname: str or None
  618. :returns: True if exist else return False
  619. """
  620. if hostname is None:
  621. hostname = socket.gethostname()
  622. ret = self.run_cmd(hostname, ['id', username])
  623. if ret['rc'] == 0:
  624. return True
  625. return False
  626. def check_group_membership(self, username=None, uid=None, grpname=None,
  627. gid=None):
  628. """
  629. Checks whether a user, passed in as username or uid, is a
  630. member of a group, passed in as group name or group id.
  631. :param username: The username to inquire about
  632. :type username: str or None
  633. :param uid: The uid of the user to inquire about (alternative
  634. to username)
  635. :param grpname: The groupname to check for user membership
  636. :type grpname: str or None
  637. :param gid: The group id to check for user membership
  638. (alternative to grpname)
  639. """
  640. if username is None and uid is None:
  641. self.logger.warning('A username or uid was expected')
  642. return True
  643. if grpname is None and gid is None:
  644. self.logger.warning('A grpname or gid was expected')
  645. return True
  646. if grpname:
  647. try:
  648. _g = grp.getgrnam(grpname)
  649. if username and username in _g.gr_mem:
  650. return True
  651. elif uid is not None:
  652. _u = pwd.getpwuid(uid)
  653. if _u.pwname in _g.gr_mem:
  654. return True
  655. except:
  656. self.logger.error('Unknown user')
  657. return False
  658. def group_memberships(self, group_list=[]):
  659. """
  660. Returns all group memberships as a dictionary of group names
  661. and associated memberships
  662. """
  663. groups = {}
  664. if not group_list:
  665. return groups
  666. users_list = [u.pw_name for u in pwd.getpwall()]
  667. glist = {}
  668. for u in users_list:
  669. info = self.get_id_info(u)
  670. if not info['pgroup'] in glist.keys():
  671. glist[info['pgroup']] = [info['name']]
  672. else:
  673. glist[info['pgroup']].append(info['name'])
  674. for g in info['groups']:
  675. if g not in glist.keys():
  676. glist[g] = []
  677. if not info['name'] in glist[g]:
  678. glist[g].append(info['name'])
  679. for g in group_list:
  680. if g in glist.keys():
  681. groups[g] = glist[g]
  682. else:
  683. try:
  684. i = grp.getgrnam(g)
  685. groups[g] = i.gr_mem
  686. except KeyError:
  687. pass
  688. return groups
  689. def get_id_info(self, user):
  690. """
  691. Return user info in dic format
  692. obtained by ``"id -a <user>"`` command for given user
  693. :param user: The username to inquire about
  694. :type user: str
  695. :returns: dic format:
  696. {
  697. "uid": <uid of given user>,
  698. "gid": <gid of given user's primary group>,
  699. "name": <name of given user>,
  700. "pgroup": <name of primary group of given user>,
  701. "groups": <list of names of groups of given user>
  702. }
  703. """
  704. info = {'uid': None, 'gid': None, 'name': None, 'pgroup': None,
  705. 'groups': None}
  706. ret = self.run_cmd(cmd=['id', '-a', str(user)], logerr=True)
  707. if ret['rc'] == 0:
  708. p = re.compile(r'(?P<uid>\d+)\((?P<name>[\w\s."\'-]+)\)')
  709. map_list = re.findall(p, ret['out'][0])
  710. info['uid'] = int(map_list[0][0])
  711. info['name'] = map_list[0][1].strip()
  712. info['gid'] = int(map_list[1][0])
  713. info['pgroup'] = map_list[1][1].strip()
  714. groups = []
  715. if len(map_list) > 2:
  716. for g in map_list[2:]:
  717. groups.append(g[1].strip().strip('"').strip("'"))
  718. info['groups'] = groups
  719. return info
  720. def get_tempdir(self, hostname=None):
  721. """
  722. :returns: The temporary directory on the given host
  723. Default host is localhost.
  724. """
  725. # return the cached value whenever possible
  726. if hostname is None:
  727. hostname = socket.gethostname()
  728. if hostname in self._tempdir:
  729. return self._tempdir[hostname]
  730. if self.is_localhost(hostname):
  731. self._tempdir[hostname] = tempfile.gettempdir()
  732. else:
  733. cmd = ['python', '-c',
  734. '"import tempfile;print tempfile.gettempdir()"']
  735. ret = self.run_cmd(hostname, cmd, level=logging.DEBUG)
  736. if ret['rc'] == 0:
  737. self._tempdir[hostname] = ret['out'][0].strip()
  738. else:
  739. # Optimistically fall back to /tmp.
  740. self._tempdir[hostname] = '/tmp'
  741. return self._tempdir[hostname]
  742. def run_cmd(self, hosts=None, cmd=None, sudo=False, stdin=None,
  743. stdout=PIPE, stderr=PIPE, input=None, cwd=None, env=None,
  744. runas=None, logerr=True, as_script=False, wait_on_script=True,
  745. level=logging.INFOCLI2):
  746. """
  747. Run a command on a host or list of hosts.
  748. :param hosts: the name of hosts on which to run the command,
  749. can be a comma-separated string or a list.
  750. Defaults to localhost
  751. :type hosts: str or None
  752. :param cmd: the command to run
  753. :type cmd: str or None
  754. :param sudo: whether to run the command as root or not.
  755. Defaults to False.
  756. :type sudo: boolean
  757. :param stdin: custom stdin. Defaults to PIPE
  758. :param stdout: custom stdout. Defaults to PIPE
  759. :param stderr: custom stderr. Defaults to PIPE
  760. :param input: input to pass to the pipe on target host,
  761. e.g. PBS answer file
  762. :param cwd: working directory on local host from which
  763. command is run
  764. :param env: environment variables to set on local host
  765. :param runas: run command as given user. Defaults to calling
  766. user
  767. :param logerr: whether to log error messages or not. Defaults
  768. to True
  769. :type logerr: boolean
  770. :param as_script: if True, run the command in a script
  771. created as a temporary file that gets
  772. deleted after being run. This is used
  773. mainly to circumvent some implementations
  774. of sudo that prevent passing environment
  775. variables through sudo.
  776. :type as_script: boolean
  777. :param wait_on_script: If True (default) waits on process
  778. launched as script to return.
  779. :type wait_on_script: boolean
  780. :returns: error, output, return code as a dictionary:
  781. ``{'out':...,'err':...,'rc':...}``
  782. """
  783. rshcmd = []
  784. sudocmd = []
  785. if level is None:
  786. level = self.logger.level
  787. _user = self.get_current_user()
  788. # runas may be a PbsUser object, ensure it is a string for the
  789. # remainder of the function
  790. if runas is not None:
  791. if isinstance(runas, int):
  792. runas = pwd.getpwuid(runas).pw_name
  793. elif not isinstance(runas, str):
  794. # must be as PbsUser object
  795. runas = str(runas)
  796. if isinstance(cmd, str):
  797. cmd = cmd.split()
  798. if hosts is None:
  799. hosts = socket.gethostname()
  800. if isinstance(hosts, str):
  801. hosts = hosts.split(',')
  802. if not isinstance(hosts, list):
  803. err_msg = 'target hostnames must be a comma-separated ' + \
  804. 'string or list'
  805. self.logger.error(err_msg)
  806. return {'out': '', 'err': err_msg, 'rc': 1}
  807. ret = {'out': '', 'err': '', 'rc': 0}
  808. for hostname in hosts:
  809. islocal = self.is_localhost(hostname)
  810. if islocal is None:
  811. # an error occurred processing that name, move on
  812. # the error is logged in is_localhost.
  813. ret['err'] = 'error getting host by name in run_cmd'
  814. ret['rc'] = 1
  815. continue
  816. if not islocal:
  817. rshcmd = self.rsh_cmd + [hostname]
  818. if sudo or ((runas is not None) and (runas != _user)):
  819. sudocmd = copy.copy(self.sudo_cmd)
  820. if runas is not None:
  821. sudocmd += ['-u', runas]
  822. # Initialize information to return
  823. ret = {'out': None, 'err': None, 'rc': None}
  824. rc = rshcmd + sudocmd + cmd
  825. if as_script:
  826. _script = self.create_temp_file()
  827. script_body = ['#!/bin/bash']
  828. if cwd is not None:
  829. script_body += ['cd "%s"' % (cwd)]
  830. cwd = None
  831. if isinstance(cmd, str):
  832. script_body += [cmd]
  833. elif isinstance(cmd, list):
  834. script_body += [" ".join(cmd)]
  835. with open(_script, 'w') as f:
  836. f.write('\n'.join(script_body))
  837. os.chmod(_script, 0755)
  838. if not islocal:
  839. # TODO: get a valid remote temporary file rather than
  840. # assume that the remote host has a similar file
  841. # system layout
  842. self.run_copy(hostname, _script, _script, level=level)
  843. os.remove(_script)
  844. runcmd = rshcmd + sudocmd + [_script]
  845. else:
  846. runcmd = rc
  847. _msg = hostname.split('.')[0] + ': '
  848. _runcmd = map(lambda x: '\'\'' if x == '' else str(x), runcmd)
  849. _msg += ' '.join(_runcmd)
  850. _msg = [_msg]
  851. if as_script:
  852. _msg += ['Contents of ' + _script + ':']
  853. _msg += ['-' * 40, '\n'.join(script_body), '-' * 40]
  854. self.logger.log(level, '\n'.join(_msg))
  855. if input:
  856. self.logger.log(level, input)
  857. try:
  858. p = Popen(runcmd, bufsize=-1, stdin=stdin, stdout=stdout,
  859. stderr=stderr, cwd=cwd, env=env)
  860. except Exception, e:
  861. self.logger.error("Error running command " + str(runcmd))
  862. if as_script:
  863. self.logger.error('Script contents: \n' +
  864. '\n'.join(script_body))
  865. self.logger.debug(str(e))
  866. raise
  867. if as_script and not wait_on_script:
  868. o = p.stdout.readline()
  869. e = p.stderr.readline()
  870. ret['rc'] = 0
  871. else:
  872. (o, e) = p.communicate(input)
  873. ret['rc'] = p.returncode
  874. if as_script:
  875. # must pass as_script=False otherwise it will loop infinite
  876. self.rm(hostname, path=_script, as_script=False,
  877. level=level)
  878. # handle the case where stdout is not a PIPE
  879. if o is not None:
  880. ret['out'] = o.splitlines()
  881. else:
  882. ret['out'] = []
  883. # Some output can be very verbose, for example listing many lines
  884. # of a log file, those messages are typically channeled through
  885. # at level DEBUG2, since we don't to pollute the output with too
  886. # verbose an information, we log at most at level DEBUG
  887. if level < logging.DEBUG:
  888. self.logger.log(level, 'out: ' + str(ret['out']))
  889. else:
  890. self.logger.debug('out: ' + str(ret['out']))
  891. if e is not None:
  892. ret['err'] = e.splitlines()
  893. else:
  894. ret['err'] = []
  895. if ret['err'] and logerr:
  896. self.logger.error('err: ' + str(ret['err']))
  897. else:
  898. self.logger.debug('err: ' + str(ret['err']))
  899. self.logger.debug('rc: ' + str(ret['rc']))
  900. return ret
  901. def run_copy(self, hosts=None, src=None, dest=None, sudo=False, uid=None,
  902. gid=None, mode=None, env=None, logerr=True,
  903. recursive=False, runas=None, preserve_permission=True,
  904. level=logging.INFOCLI2):
  905. """
  906. copy a file or directory to specified target hosts.
  907. :param hosts: the host(s) to which to copy the data. Can be
  908. a comma-separated string or a list
  909. :type hosts: str or None
  910. :param src: the path to the file or directory to copy. If
  911. src is remote,it must be prefixed by the
  912. hostname. ``e.g. remote1:/path,remote2:/path``
  913. :type src: str or None
  914. :param dest: the destination path.
  915. :type dest: str or None
  916. :param sudo: whether to copy as root or not. Defaults to
  917. False
  918. :type sudo: boolean
  919. :param uid: optionally change ownership of dest to the
  920. specified user id,referenced by uid number or
  921. username
  922. :param gid: optionally change ownership of dest to the
  923. specified group ``name/id``
  924. :param mode: optinoally set mode bits of dest
  925. :param env: environment variables to set on the calling host
  926. :param logerr: whether to log error messages or not.
  927. Defaults to True.
  928. :param recursive: whether to copy a directory (when true) or
  929. a file.Defaults to False.
  930. :type recursive: boolean
  931. :param runas: run command as user
  932. :type runas: str or None
  933. :param preserve_permission: Preserve file permission while
  934. copying file (cp cmd with -p flag)
  935. Defaults to True
  936. :type preserve_permission:boolean
  937. :param level: logging level, defaults to DEBUG
  938. :type level: int
  939. :returns: {'out':<outdata>, 'err': <errdata>, 'rc':<retcode>}
  940. upon and None if no source file specified
  941. """
  942. if src is None:
  943. self.logger.warning('no source file specified')
  944. return None
  945. if hosts is None:
  946. hosts = socket.gethostname()
  947. if isinstance(hosts, str):
  948. hosts = hosts.split(',')
  949. if not isinstance(hosts, list):
  950. self.logger.error('destination must be a string or a list')
  951. return 1
  952. if dest is None:
  953. dest = src
  954. # If PTL_SUDO_CMD were to be unset we should assume no sudo
  955. if sudo is True and not self.sudo_cmd:
  956. sudo = False
  957. for targethost in hosts:
  958. islocal = self.is_localhost(targethost)
  959. if sudo and not islocal:
  960. # to avoid a file copy as root, we copy it as current user
  961. # and move it remotely to the desired path/name.
  962. # First, get a remote temporary filename
  963. cmd = ['python', '-c',
  964. '"import tempfile;print ' +
  965. 'tempfile.mkstemp(\'PtlPbstmpcopy\')[1]"']
  966. # save original destination
  967. sudo_save_dest = dest
  968. # Make the target of the copy the temporary file
  969. dest = self.run_cmd(targethost, cmd,
  970. level=level,
  971. logerr=logerr)['out'][0]
  972. cmd = []
  973. else:
  974. # if not using sudo or target is local, initialize the
  975. # command to run accordingly
  976. sudo_save_dest = None
  977. if sudo:
  978. cmd = copy.copy(self.sudo_cmd)
  979. else:
  980. cmd = []
  981. # Remote copy if target host is remote or if source file/dir is
  982. # remote.
  983. if ((not islocal) or (':' in src)):
  984. copy_cmd = copy.deepcopy(self.copy_cmd)
  985. if not preserve_permission:
  986. copy_cmd.remove('-p')
  987. if copy_cmd[0][0] != '/':
  988. copy_cmd[0] = self.which(targethost, copy_cmd[0],
  989. level=level)
  990. cmd += copy_cmd
  991. if recursive:
  992. cmd += ['-r']
  993. cmd += [src]
  994. if islocal:
  995. cmd += [dest]
  996. else:
  997. cmd += [targethost + ':' + dest]
  998. else:
  999. cmd += [self.which(targethost, 'cp', level=level)]
  1000. if preserve_permission:
  1001. cmd += ['-p']
  1002. if recursive:
  1003. cmd += ['-r']
  1004. cmd += [src]
  1005. cmd = cmd + [dest]
  1006. ret = self.run_cmd(socket.gethostname(), cmd, env=env, runas=runas,
  1007. logerr=logerr, level=level)
  1008. if ret['rc'] != 0:
  1009. self.logger.error(ret['err'])
  1010. elif sudo_save_dest:
  1011. cmd = [self.which(targethost, 'cp', level=level)]
  1012. cmd += [dest, sudo_save_dest]
  1013. ret = self.run_cmd(targethost, cmd=cmd, sudo=True, level=level)
  1014. self.rm(targethost, path=dest, level=level)
  1015. dest = sudo_save_dest
  1016. if ret['rc'] != 0:
  1017. self.logger.error(ret['err'])
  1018. if mode is not None:
  1019. self.chmod(targethost, path=dest, mode=mode, sudo=sudo,
  1020. recursive=recursive, runas=runas)
  1021. if ((uid is not None and uid != self.get_current_user()) or
  1022. gid is not None):
  1023. self.chown(targethost, path=dest, uid=uid, gid=gid, sudo=True,
  1024. recursive=False)
  1025. return ret
  1026. def run_ptl_cmd(self, hostname, cmd, sudo=False, stdin=None, stdout=PIPE,
  1027. stderr=PIPE, input=None, cwd=None, env=None, runas=None,
  1028. logerr=True, as_script=False, wait_on_script=True,
  1029. level=logging.INFOCLI2):
  1030. """
  1031. Wrapper method of run_cmd to run PTL command
  1032. """
  1033. # Add absolute path of command also add log level to command
  1034. self.logger.infocli('running command "%s" on %s' % (' '.join(cmd),
  1035. hostname))
  1036. _cmd = [self.which(exe=cmd[0], level=level)]
  1037. _cmd += ['-l', logging.getLevelName(self.logger.parent.level)]
  1038. _cmd += cmd[1:]
  1039. cmd = _cmd
  1040. self.logger.debug(' '.join(cmd))
  1041. dest = None
  1042. if ('PYTHONPATH' in os.environ.keys() and
  1043. not self.is_localhost(hostname)):
  1044. body = ['#!/bin/bash']
  1045. body += ['PYTHONPATH=%s exec %s' % (os.environ['PYTHONPATH'],
  1046. ' '.join(cmd))]
  1047. fn = self.create_temp_file(body='\n'.join(body))
  1048. tmpdir = self.get_tempdir(hostname)
  1049. dest = os.path.join(tmpdir, os.path.basename(fn))
  1050. oldc = self.copy_cmd[:]
  1051. self.set_copy_cmd('scp -p')
  1052. self.run_copy(hostname, fn, dest, mode=0755, level=level)
  1053. self.set_copy_cmd(' '.join(oldc))
  1054. self.rm(None, path=fn, force=True, logerr=False)
  1055. cmd = dest
  1056. ret = self.run_cmd(hostname, cmd, sudo, stdin, stdout, stderr, input,
  1057. cwd, env, runas, logerr, as_script, wait_on_script,
  1058. level)
  1059. if dest is not None:
  1060. self.rm(hostname, path=dest, force=True, logerr=False)
  1061. # TODO: check why output is coming to ret['err']
  1062. if ret['rc'] == 0:
  1063. ret['out'] = ret['err']
  1064. ret['err'] = []
  1065. return ret
  1066. @classmethod
  1067. def set_sudo_cmd(cls, cmd):
  1068. """
  1069. set the sudo command
  1070. """
  1071. cls.logger.infocli('setting sudo command to ' + cmd)
  1072. cls.sudo_cmd = cmd.split()
  1073. @classmethod
  1074. def set_copy_cmd(cls, cmd):
  1075. """
  1076. set the copy command
  1077. """
  1078. cls.logger.infocli('setting copy command to ' + cmd)
  1079. cls.copy_cmd = cmd.split()
  1080. @classmethod
  1081. def set_rsh_cmd(cls, cmd):
  1082. """
  1083. set the remote shell command
  1084. """
  1085. cls.logger.infocli('setting remote shell command to ' + cmd)
  1086. cls.rsh_cmd = cmd.split()
  1087. def is_localhost(self, host=None):
  1088. """
  1089. :param host: Hostname of machine
  1090. :type host: str or None
  1091. :returns: true if specified host (by name) is the localhost
  1092. all aliases matching the hostname are searched
  1093. """
  1094. if host is None:
  1095. return True
  1096. if host in self._h2l:
  1097. return self._h2l[host]
  1098. try:
  1099. (hostname, aliaslist, iplist) = socket.gethostbyname_ex(host)
  1100. except:
  1101. self.logger.error('error getting host by name: ' + host)
  1102. print traceback.print_stack()
  1103. return None
  1104. localhost = socket.gethostname()
  1105. if localhost == hostname or localhost in aliaslist:
  1106. self._h2l[host] = True
  1107. try:
  1108. ipaddr = socket.gethostbyname(localhost)
  1109. except:
  1110. self.logger.error('could not resolve local host name')
  1111. return False
  1112. if ipaddr in iplist:
  1113. self._h2l[host] = True
  1114. return True
  1115. self._h2l[host] = False
  1116. return False
  1117. def isdir(self, hostname=None, path=None, sudo=False, runas=None,
  1118. level=logging.INFOCLI2):
  1119. """
  1120. :param hostname: The name of the host on which to check for
  1121. directory
  1122. :type hostname: str or None
  1123. :param path: The path to the directory to check
  1124. :type path: str or None
  1125. :param sudo: Whether to run the command as a privileged user
  1126. :type sudo: boolean
  1127. :param runas: run command as user
  1128. :type runas: str or None
  1129. :param level: Logging level
  1130. :returns: True if directory pointed to by path exists and
  1131. False otherwise
  1132. """
  1133. if path is None:
  1134. return False
  1135. if (self.is_localhost(hostname) and (not sudo) and (runas is None)):
  1136. return os.path.isdir(path)
  1137. else:
  1138. # Constraints on the build system prevent running commands as
  1139. # a privileged user through python, fall back to ls
  1140. dirname = os.path.dirname(path)
  1141. basename = os.path.basename(path)
  1142. cmd = ['ls', '-l', dirname]
  1143. self.logger.log(level, "grep'ing for " + basename + " in " +
  1144. dirname)
  1145. ret = self.run_cmd(hostname, cmd=cmd, sudo=sudo, runas=runas,
  1146. logerr=False, level=level)
  1147. if ret['rc'] != 0:
  1148. return False
  1149. else:
  1150. for l in ret['out']:
  1151. if basename == l[-len(basename):] and l.startswith('d'):
  1152. return True
  1153. return False
  1154. def isfile(self, hostname=None, path=None, sudo=False, runas=None,
  1155. level=logging.INFOCLI2):
  1156. """
  1157. :param hostname: The name of the host on which to check for
  1158. file
  1159. :type hostname: str or None
  1160. :param path: The path to the file to check
  1161. :type path: str or None
  1162. :param sudo: Whether to run the command as a privileged user
  1163. :type sudo: boolean
  1164. :param runas: run command as user
  1165. :type runas: str or None
  1166. :param level: Logging level
  1167. :returns: True if file pointed to by path exists, and False
  1168. otherwise
  1169. """
  1170. if path is None:
  1171. return False
  1172. if (self.is_localhost(hostname) and (not sudo) and (runas is None)):
  1173. return os.path.isfile(path)
  1174. else:
  1175. # Constraints on the build system prevent running commands as
  1176. # a privileged user through python, fall back to ls
  1177. cmd = ['ls', '-l', path]
  1178. ret = self.run_cmd(hostname, cmd=cmd, sudo=sudo, runas=runas,
  1179. logerr=False, level=level)
  1180. if ret['rc'] != 0:
  1181. return False
  1182. elif ret['out']:
  1183. if not ret['out'][0].startswith('d'):
  1184. return True
  1185. return False
  1186. def getmtime(self, hostname=None, path=None, sudo=False, runas=None,
  1187. level=logging.INFOCLI2):
  1188. """
  1189. :param hostname: The name of the host on which file exists
  1190. :type hostname: str or None
  1191. :param path: The path to the file to get mtime
  1192. :type path: str or None
  1193. :param sudo: Whether to run the command as a privileged user
  1194. :type sudo: boolean
  1195. :param runas: run command as user
  1196. :type runas: str or None
  1197. :param level: Logging level
  1198. :returns: Modified time of given file
  1199. """
  1200. if path is None:
  1201. return None
  1202. if (self.is_localhost(hostname) and (not sudo) and (runas is None)):
  1203. return os.path.getmtime(path)
  1204. else:
  1205. py_cmd = 'import os; print os.path.getmtime(\'%s\')' % (path)
  1206. if not self.is_localhost(hostname):
  1207. py_cmd = '\"' + py_cmd + '\"'
  1208. cmd = [self.which(hostname, 'python', level=level), '-c', py_cmd]
  1209. ret = self.run_cmd(hostname, cmd=cmd, sudo=sudo, runas=runas,
  1210. logerr=False, level=level)
  1211. if ((ret['rc'] == 0) and (len(ret['out']) == 1) and
  1212. (isinstance(eval(ret['out'][0].strip()), (int, float)))):
  1213. return eval(ret['out'][0].strip())
  1214. return None
  1215. def listdir(self, hostname=None, path=None, sudo=False, runas=None,
  1216. fullpath=True, level=logging.INFOCLI2):
  1217. """
  1218. :param hostname: The name of the host on which to list for
  1219. directory
  1220. :type hostname: str or None
  1221. :param path: The path to directory to list
  1222. :type path: str or None
  1223. :param sudo: Whether to chmod as root or not. Defaults to
  1224. False
  1225. :type sudo: bool
  1226. :param runas: run command as user
  1227. :type runas: str or None
  1228. :param fullpath: Return full paths?
  1229. :type fullpath: bool
  1230. :param level: Logging level.
  1231. :type level: int
  1232. :returns: A list containing the names of the entries in
  1233. the directory
  1234. """
  1235. if path is None:
  1236. return None
  1237. if (self.is_localhost(hostname) and (not sudo) and (runas is None)):
  1238. try:
  1239. files = os.listdir(path)
  1240. except OSError:
  1241. return None
  1242. else:
  1243. ret = self.run_cmd(hostname, cmd=['ls', path], sudo=sudo,
  1244. runas=runas, logerr=False, level=level)
  1245. if ret['rc'] == 0:
  1246. files = ret['out']
  1247. else:
  1248. return None
  1249. if fullpath is True:
  1250. return map(lambda p: os.path.join(path, p.strip()), files)
  1251. else:
  1252. return map(lambda p: p.strip(), files)
  1253. def chmod(self, hostname=None, path=None, mode=None, sudo=False,
  1254. runas=None, recursive=False, logerr=True,
  1255. level=logging.INFOCLI2):
  1256. """
  1257. Generic function of chmod with remote host support
  1258. :param hostname: hostname (default current host)
  1259. :type hostname: str or None
  1260. :param path: the path to the file or directory to chmod
  1261. :type path: str or None
  1262. :param mode: mode to apply as octal number like 0777,
  1263. 0666 etc.
  1264. :param sudo: whether to chmod as root or not. Defaults
  1265. to False
  1266. :type sudo: boolean
  1267. :param runas: run command as user
  1268. :type runas: str or None
  1269. :param recursive: whether to chmod a directory (when true)
  1270. or a file.Defaults to False.
  1271. :type recursive: boolean
  1272. :param logerr: whether to log error messages or not. Defaults
  1273. to True.
  1274. :type logerr: boolean
  1275. :param level: logging level, defaults to INFOCLI2
  1276. :returns: True on success otherwise False
  1277. """
  1278. if (path is None) or (mode is None):
  1279. return False
  1280. cmd = [self.which(hostname, 'chmod', level=level)]
  1281. if recursive:
  1282. cmd += ['-R']
  1283. cmd += [oct(mode), path]
  1284. ret = self.run_cmd(hostname, cmd=cmd, sudo=sudo, logerr=logerr,
  1285. runas=runas, level=level)
  1286. if ret['rc'] == 0:
  1287. return True
  1288. return False
  1289. def chown(self, hostname=None, path=None, uid=None, gid=None, sudo=False,
  1290. recursive=False, runas=None, logerr=True,
  1291. level=logging.INFOCLI2):
  1292. """
  1293. Generic function of chown with remote host support
  1294. :param hostname: hostname (default current host)
  1295. :type hostname: str or None
  1296. :param path: the path to the file or directory to chown
  1297. :type path: str or None
  1298. :param uid: uid to apply (must be either user name or
  1299. uid or -1)
  1300. :param gid: gid to apply (must be either group name or
  1301. gid or -1)
  1302. :param sudo: whether to chown as root or not. Defaults
  1303. to False
  1304. :type sudo: boolean
  1305. :param recursive: whether to chmod a directory (when true)
  1306. or a file.Defaults to False.
  1307. :type recursive: boolean
  1308. :param runas: run command as user
  1309. :type runas: str or None
  1310. :param logerr: whether to log error messages or not. Defaults
  1311. to True.
  1312. :type logerr: boolean
  1313. :param level: logging level, defaults to INFOCLI2
  1314. :returns: True on success otherwise False
  1315. """
  1316. if path is None or (uid is None and gid is None):
  1317. return False
  1318. _u = ''
  1319. if isinstance(uid, int)and uid != -1:
  1320. _u = pwd.getpwuid(uid).pw_name
  1321. elif (isinstance(uid, str) and (uid != '-1')):
  1322. _u = uid
  1323. else:
  1324. # must be as PbsUser object
  1325. if str(uid) != '-1':
  1326. _u = str(uid)
  1327. if _u == '':
  1328. return False
  1329. cmd = [self.which(hostname, 'chown', level=level)]
  1330. if recursive:
  1331. cmd += ['-R']
  1332. cmd += [_u, path]
  1333. ret = self.run_cmd(hostname, cmd=cmd, sudo=sudo, logerr=logerr,
  1334. runas=runas, level=level)
  1335. if ret['rc'] == 0:
  1336. if gid is not None:
  1337. rv = self.chgrp(hostname, path, gid=gid, sudo=sudo,
  1338. level=level, recursive=recursive, runas=runas,
  1339. logerr=logerr)
  1340. if not rv:
  1341. return False
  1342. return True
  1343. return False
  1344. def chgrp(self, hostname=None, path=None, gid=None, sudo=False,
  1345. recursive=False, runas=None, logerr=True,
  1346. level=logging.INFOCLI2):
  1347. """
  1348. Generic function of chgrp with remote host support
  1349. :param hostname: hostname (default current host)
  1350. :type hostname: str or None
  1351. :param path: the path to the file or directory to chown
  1352. :type path: str or None
  1353. :param gid: gid to apply (must be either group name or
  1354. gid or -1)
  1355. :param sudo: whether to chgrp as root or not. Defaults
  1356. to False
  1357. :type sudo: boolean
  1358. :param recursive: whether to chmod a directory (when true)
  1359. or a file.Defaults to False.
  1360. :type recursive: boolean
  1361. :param runas: run command as user
  1362. :type runas: str or None
  1363. :param logerr: whether to log error messages or not. Defaults
  1364. to True.
  1365. :type logerr: boolean
  1366. :param level: logging level, defaults to INFOCLI2
  1367. :returns: True on success otherwise False
  1368. """
  1369. if path is None or gid is None:
  1370. return False
  1371. _g = ''
  1372. if isinstance(gid, int) and gid != -1:
  1373. _g = grp.getgrgid(gid).gr_name
  1374. elif (isinstance(gid, str) and (gid != '-1')):
  1375. _g = gid
  1376. else:
  1377. # must be as PbsGroup object
  1378. if str(gid) != '-1':
  1379. _g = str(gid)
  1380. if _g == '':
  1381. return False
  1382. cmd = [self.which(hostname, 'chgrp', level=level)]
  1383. if recursive:
  1384. cmd += ['-R']
  1385. cmd += [_g, path]
  1386. ret = self.run_cmd(hostname, cmd=cmd, sudo=sudo, logerr=logerr,
  1387. runas=runas, level=level)
  1388. if ret['rc'] == 0:
  1389. return True
  1390. return False
  1391. def which(self, hostname=None, exe=None, level=logging.INFOCLI2):
  1392. """
  1393. Generic function of which with remote host support
  1394. :param hostname: hostname (default current host)
  1395. :type hostname: str or None
  1396. :param exe: executable to locate (can be full path also)
  1397. (if exe is full path then only basename will
  1398. be used to locate)
  1399. :type exe: str or None
  1400. :param level: logging level, defaults to INFOCLI2
  1401. """
  1402. if exe is None:
  1403. return None
  1404. if hostname is None:
  1405. hostname = socket.gethostname()
  1406. oexe = exe
  1407. exe = os.path.basename(exe)
  1408. if hostname in self._h2which.keys():
  1409. if exe in self._h2which[hostname]:
  1410. return self._h2which[hostname][exe]
  1411. sudo_wrappers_dir = '/opt/tools/wrappers'
  1412. _exe = os.path.join(sudo_wrappers_dir, exe)
  1413. if os.path.isfile(_exe) and os.access(_exe, os.X_OK):
  1414. if hostname not in self._h2which.keys():
  1415. self._h2which.setdefault(hostname, {exe: _exe})
  1416. else:
  1417. self._h2which[hostname].setdefault(exe, _exe)
  1418. return _exe
  1419. cmd = ['which', exe]
  1420. ret = self.run_cmd(hostname, cmd=cmd, logerr=False,
  1421. level=level)
  1422. if ((ret['rc'] == 0) and (len(ret['out']) == 1) and
  1423. os.path.isabs(ret['out'][0].strip())):
  1424. path = ret['out'][0].strip()
  1425. if hostname not in self._h2which.keys():
  1426. self._h2which.setdefault(hostname, {exe: path})
  1427. else:
  1428. self._h2which[hostname].setdefault(exe, path)
  1429. return path
  1430. else:
  1431. return oexe
  1432. def rm(self, hostname=None, path=None, sudo=False, runas=None,
  1433. recursive=False, force=False, cwd=None, logerr=True,
  1434. as_script=False, level=logging.INFOCLI2):
  1435. """
  1436. Generic function of rm with remote host support
  1437. :param hostname: hostname (default current host)
  1438. :type hostname: str or None
  1439. :param path: the path to the files or directories to remove
  1440. for more than one files or directories pass as
  1441. list
  1442. :type path: str or None
  1443. :param sudo: whether to remove files or directories as root
  1444. or not.Defaults to False
  1445. :type sudo: boolean
  1446. :param runas: remove files or directories as given user
  1447. Defaults to calling user
  1448. :param recursive: remove files or directories and their
  1449. contents recursively
  1450. :type recursive: boolean
  1451. :param force: force remove files or directories
  1452. :type force: boolean
  1453. :param cwd: working directory on local host from which
  1454. command is run
  1455. :param logerr: whether to log error messages or not.
  1456. Defaults to True.
  1457. :type logerr: boolean
  1458. :param as_script: if True, run the rm in a script created
  1459. as a temporary file that gets deleted after
  1460. being run. This is used mainly to handle
  1461. wildcard in path list. Defaults to False.
  1462. :type as_script: boolean
  1463. :param level: logging level, defaults to INFOCLI2
  1464. :returns: True on success otherwise False
  1465. """
  1466. if (path is None) or (len(path) == 0):
  1467. return True
  1468. cmd = [self.which(hostname, 'rm', level=level)]
  1469. if recursive and force:
  1470. cmd += ['-rf']
  1471. else:
  1472. if recursive:
  1473. cmd += ['-r']
  1474. if force:
  1475. cmd += ['-f']
  1476. if isinstance(path, list):
  1477. for p in path:
  1478. if p == '/':
  1479. msg = 'encountered a dangerous package path ' + p
  1480. self.logger.error(msg)
  1481. return False
  1482. cmd += path
  1483. else:
  1484. if path == '/':
  1485. msg = 'encountered a dangerous package path ' + path
  1486. self.logger.error(msg)
  1487. return False
  1488. cmd += [path]
  1489. ret = self.run_cmd(hostname, cmd=cmd, sudo=sudo, logerr=logerr,
  1490. runas=runas, cwd=cwd, level=level,
  1491. as_script=as_script)
  1492. if ret['rc'] != 0:
  1493. return False
  1494. return True
  1495. def mkdir(self, hostname=None, path=None, mode=None, sudo=False,
  1496. runas=None, parents=True, cwd=None, logerr=True,
  1497. as_script=False, level=logging.INFOCLI2):
  1498. """
  1499. Generic function of mkdir with remote host support
  1500. :param hostname: hostname (default current host)
  1501. :type hostname: str or None
  1502. :param path: the path to the directories to create
  1503. for more than one directories pass as list
  1504. :type path: str or None
  1505. :param mode: mode to use while creating directories
  1506. (must be octal like 0777)
  1507. :param sudo: whether to create directories as root or not.
  1508. Defaults to False
  1509. :type sudo: boolean
  1510. :param runas: create directories as given user. Defaults to
  1511. calling user
  1512. :param parents: create parent directories as needed. Defaults
  1513. to True
  1514. :type parents: boolean
  1515. :param cwd: working directory on local host from which
  1516. command is run
  1517. :type cwd: str or None
  1518. :param logerr: whether to log error messages or not. Defaults
  1519. to True.
  1520. :type logerr: boolean
  1521. :param as_script: if True, run the command in a script
  1522. created as a temporary file that gets
  1523. deleted after being run. This is used
  1524. mainly to handle wildcard in path list.
  1525. Defaults to False.
  1526. :type as_script: boolean
  1527. :param level: logging level, defaults to INFOCLI2
  1528. :returns: True on success otherwise False
  1529. """
  1530. if (path is None) or (len(path) == 0):
  1531. return True
  1532. cmd = [self.which(hostname, 'mkdir', level=level)]
  1533. if parents:
  1534. cmd += ['-p']
  1535. if mode is not None:
  1536. cmd += ['-m', oct(mode)]
  1537. if isinstance(path, list):
  1538. cmd += path
  1539. else:
  1540. cmd += [path]
  1541. ret = self.run_cmd(hostname, cmd=cmd, sudo=sudo, logerr=logerr,
  1542. runas=runas, cwd=cwd, level=level,
  1543. as_script=as_script)
  1544. if ret['rc'] != 0:
  1545. return False
  1546. return True
  1547. def cat(self, hostname=None, filename=None, sudo=False, runas=None,
  1548. logerr=True, level=logging.INFOCLI2):
  1549. """
  1550. Generic function of cat with remote host support
  1551. :param hostname: hostname (default current host)
  1552. :type hostname: str or None
  1553. :param filename: the path to the filename to cat
  1554. :type filename: str or None
  1555. :param sudo: whether to create directories as root or not.
  1556. Defaults to False
  1557. :type sudo: boolean
  1558. :param runas: create directories as given user. Defaults
  1559. to calling user
  1560. :type runas: str or None
  1561. :param logerr: whether to log error messages or not. Defaults
  1562. to True.
  1563. :type logerr: boolean
  1564. :returns: output of run_cmd
  1565. """
  1566. cmd = [self.which(hostname, 'cat', level=level), filename]
  1567. return self.run_cmd(hostname, cmd=cmd, sudo=sudo,
  1568. runas=runas, logerr=logerr, level=level)
  1569. def cmp(self, hostname=None, fileA=None, fileB=None, sudo=False,
  1570. runas=None, logerr=True):
  1571. """
  1572. Compare two files and return 0 if they are identical or
  1573. non-zero if not
  1574. :param hostname: the name of the host to operate on
  1575. :type hostname: str or None
  1576. :param fileA: the first file to compare
  1577. :type fileA: str or None
  1578. :param fileB: the file to compare fileA to
  1579. :type fileB: str or None
  1580. :param sudo: run the command as a privileged user
  1581. :type sudo: boolean
  1582. :param runas: run the cmp command as given user
  1583. :type runas: str or None
  1584. :param logerr: whether to log error messages or not.
  1585. Defaults to True.
  1586. :type logerr: boolean
  1587. """
  1588. if fileA is None and fileB is None:
  1589. return 0
  1590. if fileA is None or fileB is None:
  1591. return 1
  1592. cmd = ['cmp', fileA, fileB]
  1593. ret = self.run_cmd(hostname, cmd=cmd, sudo=sudo, runas=runas,
  1594. logerr=logerr)
  1595. return ret['rc']
  1596. def useradd(self, name, uid=None, gid=None, shell='/bin/bash',
  1597. create_home_dir=True, home_dir=None, groups=None, logerr=True,
  1598. level=logging.INFOCLI2):
  1599. """
  1600. Add the user
  1601. :param name: User name
  1602. :type name: str
  1603. :param shell: shell to use
  1604. :param create_home_dir: If true then create home directory
  1605. :type create_home_dir: boolean
  1606. :param home_dir: path to home directory
  1607. :type home_dir: str or None
  1608. :param groups: User groups
  1609. """
  1610. self.logger.info('adding user ' + str(name))
  1611. cmd = ['useradd']
  1612. cmd += ['-K', 'UMASK=0022']
  1613. if uid is not None:
  1614. cmd += ['-u', str(uid)]
  1615. if shell is not None:
  1616. cmd += ['-s', shell]
  1617. if gid is not None:
  1618. cmd += ['-g', str(gid)]
  1619. if create_home_dir:
  1620. cmd += ['-m']
  1621. if home_dir is not None:
  1622. cmd += ['-d', home_dir]
  1623. if ((groups is not None) and (len(groups) > 0)):
  1624. cmd += ['-G', ','.join(map(lambda g: str(g), groups))]
  1625. cmd += [str(name)]
  1626. ret = self.run_cmd(cmd=cmd, logerr=logerr, sudo=True, level=level)
  1627. if ((ret['rc'] != 0) and logerr):
  1628. raise PtlUtilError(rc=ret['rc'], rv=False, msg=ret['err'])
  1629. def userdel(self, name, del_home=True, force=True, logerr=True,
  1630. level=logging.INFOCLI2):
  1631. """
  1632. Delete the user
  1633. :param del_home: If true then delete user home
  1634. :type del_home: boolean
  1635. :param force: If true then delete forcefully
  1636. :type force: boolean
  1637. """
  1638. cmd = ['userdel']
  1639. if del_home:
  1640. cmd += ['-r']
  1641. if force:
  1642. cmd += ['-f']
  1643. cmd += [str(name)]
  1644. self.logger.info('deleting user ' + str(name))
  1645. ret = self.run_cmd(cmd=cmd, sudo=True, logerr=False, level=level)
  1646. if ((ret['rc'] != 0) and logerr):
  1647. raise PtlUtilError(rc=ret['rc'], rv=False, msg=ret['err'])
  1648. def groupadd(self, name, gid=None, logerr=True, level=logging.INFOCLI2):
  1649. """
  1650. Add a group
  1651. """
  1652. self.logger.info('adding group ' + str(name))
  1653. cmd = ['groupadd']
  1654. if gid is not None:
  1655. cmd += ['-g', str(gid)]
  1656. cmd += [str(name)]
  1657. ret = self.run_cmd(cmd=cmd, sudo=True, logerr=False, level=level)
  1658. if ((ret['rc'] != 0) and logerr):
  1659. raise PtlUtilError(rc=ret['rc'], rv=False, msg=ret['err'])
  1660. def groupdel(self, name, logerr=True, level=logging.INFOCLI2):
  1661. self.logger.info('deleting group ' + str(name))
  1662. cmd = ['groupdel', str(name)]
  1663. ret = self.run_cmd(cmd=cmd, sudo=True, logerr=logerr, level=level)
  1664. if ((ret['rc'] != 0) and logerr):
  1665. raise PtlUtilError(rc=ret['rc'], rv=False, msg=ret['err'])
  1666. def create_temp_file(self, hostname=None, suffix='', prefix='PtlPbs',
  1667. dirname=None, text=False, asuser=None, body=None,
  1668. level=logging.INFOCLI2):
  1669. """
  1670. Create a temp file by calling tempfile.mkstemp
  1671. :param hostname: the hostname on which to query tempdir from
  1672. :type hostname: str or None
  1673. :param suffix: the file name will end with this suffix
  1674. :type suffix: str
  1675. :param prefix: the file name will begin with this prefix
  1676. :type prefix: str
  1677. :param dirname: the file will be created in this directory
  1678. :type dirname: str or None
  1679. :param text: the file is opened in text mode is this is true
  1680. else in binary mode
  1681. :type text: boolean
  1682. :param asuser: Optional username or uid of temp file owner
  1683. :type asuser: str or None
  1684. :param body: Optional content to write to the temporary file
  1685. :type body: str or None
  1686. :param level: logging level, defaults to INFOCLI2
  1687. :type level: int
  1688. """
  1689. # create a temp file as current user
  1690. (fd, tmpfile) = tempfile.mkstemp(suffix, prefix, dirname, text)
  1691. # write user provided contents to file
  1692. if body is not None:
  1693. if isinstance(body, list):
  1694. os.write(fd, "\n".join(body))
  1695. else:
  1696. os.write(fd, body)
  1697. os.close(fd)
  1698. # if temp file to be created on remote host
  1699. if not self.is_localhost(hostname):
  1700. if asuser is not None:
  1701. # by default mkstemp creates file with 0600 permission
  1702. # to create file as different user first change the file
  1703. # permission to 0644 so that other user has read permission
  1704. self.chmod(hostname, tmpfile, mode=0644)
  1705. # copy temp file created on local host to remote host
  1706. # as different user
  1707. self.run_copy(hostname, tmpfile, tmpfile, runas=asuser,
  1708. preserve_permission=False, level=level)
  1709. else:
  1710. # copy temp file created on localhost to remote as current user
  1711. self.run_copy(hostname, tmpfile, tmpfile,
  1712. preserve_permission=False, level=level)
  1713. # remove local temp file
  1714. os.unlink(tmpfile)
  1715. if asuser is not None:
  1716. # by default mkstemp creates file with 0600 permission
  1717. # to create file as different user first change the file
  1718. # permission to 0644 so that other user has read permission
  1719. self.chmod(hostname, tmpfile, mode=0644)
  1720. # since we need to create as differnt user than current user
  1721. # create a temp file just to get temp file name with absolute path
  1722. (_, tmpfile2) = tempfile.mkstemp(suffix, prefix, dirname, text)
  1723. # remove the newly created temp file
  1724. os.unlink(tmpfile2)
  1725. # copy the orginal temp as new temp file
  1726. self.run_copy(hostname, tmpfile, tmpfile2, runas=asuser,
  1727. preserve_permission=False, level=level)
  1728. # remove original temp file
  1729. os.unlink(tmpfile)
  1730. return tmpfile2
  1731. return tmpfile
  1732. def mkdtemp(self, hostname=None, suffix='', prefix='PtlPbs', dir=None,
  1733. uid=None, gid=None, mode=None, level=logging.INFOCLI2):
  1734. """
  1735. Create a temp dir by calling ``tempfile.mkdtemp``
  1736. :param hostname: the hostname on which to query tempdir from
  1737. :type hostname: str or None
  1738. :param suffix: the directory name will end with this suffix
  1739. :type suffix: str
  1740. :param prefix: the directory name will begin with this prefix
  1741. :type prefix: str
  1742. :param dir: the directory will be created in this directory
  1743. :type dir: str or None
  1744. :param uid: Optional username or uid of temp directory owner
  1745. :param gid: Optional group name or gid of temp directory
  1746. group owner
  1747. :param mode: Optional mode bits to assign to the temporary
  1748. directory
  1749. :param level: logging level, defaults to INFOCLI2
  1750. """
  1751. if not self.is_localhost(hostname):
  1752. tmp_args = []
  1753. if suffix:
  1754. tmp_args += ['suffix=\'' + suffix + '\'']
  1755. if prefix:
  1756. tmp_args += ['prefix=\'' + prefix + '\'']
  1757. if dir is not None:
  1758. tmp_args += ['dir=\'' + str(dir) + '\'']
  1759. args = ",".join(tmp_args)
  1760. ret = self.run_cmd(hostname,
  1761. ['python', '-c', '"import tempfile; ' +
  1762. 'print tempfile.mkdtemp(' + args + ')"'],
  1763. level=level)
  1764. if ret['rc'] == 0 and ret['out']:
  1765. fn = ret['out'][0]
  1766. else:
  1767. fn = tempfile.mkdtemp(suffix, prefix, dir)
  1768. if mode is not None:
  1769. self.chmod(hostname, fn, mode=mode, recursive=True, level=level,
  1770. sudo=True)
  1771. if ((uid is not None) or (gid is not None)):
  1772. self.chown(hostname, fn, uid=uid, gid=gid, recursive=True,
  1773. sudo=True)
  1774. return fn
  1775. def parse_strace(self, lines):
  1776. """
  1777. strace parsing. Just the regular expressions for now
  1778. """
  1779. timestamp_pat = r'(^(\d{2}:\d{2}:\d{2})(.\d+){0,1} |^(\d+.\d+) ){0,1}'
  1780. exec_pat = r'execve\(("[^"]+"), \[([^]]+)\], [^,]+ = (\d+)$'
  1781. timestamp_exec_re = re.compile(timestamp_pat + exec_pat)
  1782. for line in lines:
  1783. m = timestamp_exec_re.match(line)
  1784. if m:
  1785. print line