CMS 3D CMS Logo

eostools.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 """
3 A module to manipulate files on EOS or on the local file system. Intended to have the same interface as castortools.py.
4 """
5 import sys
6 import os
7 import re
8 import pprint
9 import shutil
10 
11 eos_select = '/afs/cern.ch/project/eos/installation/cms/bin/eos.select'
12 
13 def setCAFPath():
14  """Hack to get the CAF scripts on the PYTHONPATH"""
15  caf = '/afs/cern.ch/cms/caf/python'
16 
17  if caf not in sys.path:
18  sys.path.append(caf)
19 setCAFPath()
20 import cmsIO
21 
22 def runXRDCommand(path, cmd, *args):
23  """Run an xrd command.
24 
25  !!! Will, what is happening in case of problem?
26  ??? At some point, should return a list of lines instead of a string."""
27 
28  lfn = eosToLFN(path)
29  #print "lfn:", lfn, cmd
30  tokens = cmsIO.splitPFN(lfnToPFN(lfn))
31 
32  command = ['xrd', tokens[1], cmd, tokens[2]]
33  command.extend(args)
34  runner = cmsIO.cmsFileManip()
35  # print ' '.join(command)
36  return runner.runCommand(command)
37 
38 def runEOSCommand(path, cmd, *args):
39  """Run an eos command.
40 
41  !!! Will, when the EOS command fails, it passes silently...
42  I think we should really try and raise an exception in case of problems.
43  should be possible as the return code is provided in the tuple returned by runner."""
44 
45  lfn = eosToLFN(path)
46  pfn = lfnToPFN(lfn)
47  tokens = cmsIO.splitPFN(pfn)
48 
49  #obviously, this is not nice
50  command = [eos_select, cmd]
51  command.extend(args)
52  command.append(tokens[2])
53  runner = cmsIO.cmsFileManip()
54  return runner.runCommand(command)
55 
56 def isLFN( path ):
57  """Tests whether this path is a CMS LFN (name starts with /store...)"""
58  # return re.match('^/store.*', path ) is not None
59  return path.startswith('/store')
60 
61 def isEOS( path ):
62  """Tests whether this path is a CMS EOS (name starts with /eos...)"""
63  return path.startswith('/eos') or path.startswith('root://eoscms.cern.ch//eos/cms')
64 
65 def eosToLFN( path ):
66  """Converts a EOS PFN to an LFN.
67 
68  Just strip out /eos/cms from path.
69  If this string is not found, return path.
70  ??? Shouldn't we raise an exception instead?"""
71  return path.replace('root://eoscms.cern.ch/', '').replace('/eos/cms','')
72 
73 #also define an alias for backwards compatibility
74 castorToLFN = eosToLFN
75 
76 def lfnToPFN( path, tfcProt = 'rfio'):
77  """Converts an LFN to a PFN. For example:
78  /store/cmst3/user/cbern/CMG/TauPlusX/Run2011A-03Oct2011-v1/AOD/V2/PAT_CMG_V2_4_0/H2TAUTAU_Nov21
79  ->
80  root://eoscms//eos/cms/store/cmst3/user/cbern/CMG/TauPlusX/Run2011A-03Oct2011-v1/AOD/V2/PAT_CMG_V2_4_0/H2TAUTAU_Nov21?svcClass=cmst3&stageHost=castorcms
81 
82  This function only checks path, and does not access the storage system.
83  If the path is in /store/cmst3, it assumes that the CMST3 svcClass is to be used.
84  Otherwise, is uses the default one.
85 
86  ??? what is tfcprot? """
87 
88  if path.startswith("/store/"):
89  path = path.replace("/store/","root://eoscms.cern.ch//eos/cms/store/")
90  if path.startswith("/pnfs/psi.ch/cms/trivcat/"):
91  path = path.replace("/pnfs/psi.ch/cms/trivcat/","root://t3se01.psi.ch//")
92  #print "path to cmsFile():", path
93  entity = cmsIO.cmsFile( path, tfcProt )
94 # tokens = cmsIO.splitPFN(entity.pfn)
95  pfn = '%s://%s//%s/' % (entity.protocol,entity.host,entity.path)
96 
97  pfn = entity.pfn
98  if tfcProt == 'rfio' and \
99  entity.path.startswith("/eos/cms/") and \
100  str(entity.stat()).startswith("Error 3011: Unable to stat"):
101 
102  pfn.replace("/eos/cms","/castor/cern.ch/cms")
103  pfn.replace("eoscms","castorcms")
104  return pfn
105 
106 
107 def lfnToEOS( path ):
108  """Converts LFN to EOS.
109 
110  If path is not an LFN in the first place, return path.
111  ??? shouldn't we raise an exception?"""
112  if isLFN(path):
113  pfn = 'root://eoscms.cern.ch//eos/cms/' + path
114  return pfn.replace('//store','/store')
115  else:
116  return path
117 
118 #also define an alias for backwards compatibility
119 lfnToCastor = lfnToEOS
120 
121 def isEOSDir( path ):
122  """Returns True if path is either:
123  /store/...
124  or
125  /eos/cms/store/...
126  or
127  root://eoscms.cern.ch//eos/cms/
128 
129  Otherwise, returns False.
130 
131  WARNING!! This function does not check for path existence,
132  and returns true also for plain files.
133  !!! Will, is my summary correct?
134  """
135  if os.path.exists( path ):
136  # path does not exist
137  # COLIN: I think this condition could be removed,
138  # as it duplicates the following one.
139  return False
140  if not path.startswith('/eos') and not path.startswith('/store') and not path.startswith('root://eoscms.cern.ch//eos/cms/'):
141  # neither an EOS PFN or a LFN.
142  return False
143  # at this stage, we must have an EOS PFN or an LFN
144  pfn = lfnToPFN(eosToLFN(path))
145  tokens = cmsIO.splitPFN(pfn)
146  return tokens and tokens[1].lower().startswith('eos')
147 
148 #also define an alias for backwards compatibility
149 isCastorDir = isEOSDir
150 
151 
152 def isEOSFile( path, tfcProt = 'rfio'):
153  """Returns True if path is a file or directory stored on EOS (checks for path existence)
154  ??? This function does not behave well if passed a non EOS path...
155  returns lots of error messages like:
156 >>> eostools.isEOSFile('/store/asdfasfd')
157 Command (['ls', '/', 's', 't', 'o', 'r', 'e', '/', 'a', 's', 'd', 'f', 'a', 's', 'f', 'd', '/store']) failed with return code: 2
158 ls: s: No such file or directory
159 ls: t: No such file or directory
160 ls: o: No such file or directory
161 ls: r: No such file or directory
162 ls: e: No such file or directory
163 ls: a: No such file or directory
164 ls: s: No such file or directory
165 ls: d: No such file or directory
166 ls: f: No such file or directory
167 ls: a: No such file or directory
168 ls: s: No such file or directory
169 ls: f: No such file or directory
170 ls: d: No such file or directory
171 ls: /store: No such file or directory
172 
173 ls: s: No such file or directory
174 ls: t: No such file or directory
175 ls: o: No such file or directory
176 ls: r: No such file or directory
177 ls: e: No such file or directory
178 ls: a: No such file or directory
179 ls: s: No such file or directory
180 ls: d: No such file or directory
181 ls: f: No such file or directory
182 ls: a: No such file or directory
183 ls: s: No such file or directory
184 ls: f: No such file or directory
185 ls: d: No such file or directory
186 ls: /store: No such file or directory
187 
188 False
189  """
190  _, _, ret = runEOSCommand( path, 'ls')
191  return ret == 0
192 
193 #also define an alias for backwards compatibility
194 isCastorFile = isEOSFile
195 
196 
197 def fileExists( path ):
198  """Returns true if path is a file or directory stored locally, or on EOS.
199 
200  This function checks for the file or directory existence."""
201 
202  eos = isEOSDir(path)
203  result = False
204  if eos:
205  # print 'eos', path
206  result = isEOSFile(path)
207  else:
208  # print 'not eos', path
209  #check locally
210  result = os.path.exists(path)
211  # print result
212  return result
213 
214 
215 def eosDirSize(path):
216  '''Returns the size of a directory on EOS in GB.'''
217  lfn = eosToLFN(path)
218  res = runEOSCommand(lfn, 'find', '--size')
219  output = res[0].split('\n')
220  size = 0
221  for file in output:
222  try:
223  size += float(file.split('=')[2])
224  except IndexError:
225  pass
226  return size/1024/1024/1024
227 
228 
229 def createEOSDir( path ):
230  """Makes a directory in EOS
231 
232  ???Will, I'm quite worried by the fact that if this path already exists, and is
233  a file, everything will 'work'. But then we have a file, and not a directory,
234  while we expect a dir..."""
235  lfn = eosToLFN(path)
236  if not isEOSFile(lfn):
237  # if not isDirectory(lfn):
238  runEOSCommand(lfn,'mkdir','-p')
239  # entity = cmsIO.cmsFile( lfn,"stageout")
240  # entity.mkdir([])
241  # # print 'created ', path
242  if isDirectory(path):
243  return path
244  else:
245  raise OSError('cannot create directory '+ path)
246 
247 #also define an alias for backwards compatibility
248 createCastorDir = createEOSDir
249 
250 def mkdir(path):
251  """Create a directory, either on EOS or locally"""
252  # print 'mkdir', path
253  if isEOS( path ) or isLFN(path):
254  createEOSDir(path)
255  else:
256  # recursive directory creation (like mkdir -p)
257  os.makedirs(path)
258  return path
259 
260 def isDirectory(path):
261  """Returns True if path is a directory on EOS.
262 
263  Tests for file existence.
264  This function returns False for EOS files, and crashes with local paths
265 
266  ???Will, this function also seems to work for paths like:
267  /eos/cms/...
268  ??? I think that it should work also for local files, see isFile."""
269 
270  out, _, _ = runXRDCommand(path,'existdir')
271  return 'The directory exists' in out
272 
273 def isFile(path):
274  """Returns True if a path is a file.
275 
276  Tests for file existence.
277  Returns False for directories.
278  Works on EOS and local paths.
279 
280  ???This function works with local files, so not the same as isDirectory...
281  isFile and isDirectory should behave the same.
282  """
283 
284  if not path.startswith('/eos') and not path.startswith('/store'):
285  if( os.path.isfile(path) ):
286  return True
287  else:
288  return False
289  else:
290  out, _, _ = runXRDCommand(path,'existfile')
291  return 'The file exists' in out
292 
293 def chmod(path, mode):
294  """Does chmod on a file or directory"""
295  #
296  return runEOSCommand(path, 'chmod', '-r', str(mode))
297 
298 
299 def listFiles(path, rec = False, full_info = False):
300  """Provides a list of the specified directory
301  """
302  # -- listing on the local filesystem --
303  if os.path.isdir( path ):
304  if not rec:
305  # not recursive
306  return [ '/'.join([path,file]) for file in os.listdir( path )]
307  else:
308  # recursive, directories are put in the list first,
309  # followed by the list of all files in the directory tree
310  result = []
311  allFiles = []
312  for root,dirs,files in os.walk(path):
313  result.extend( [ '/'.join([root,dir]) for dir in dirs] )
314  allFiles.extend( [ '/'.join([root,file]) for file in files] )
315  result.extend(allFiles)
316  return result
317  # -- listing on EOS --
318  cmd = 'dirlist'
319  if rec:
320  cmd = 'dirlistrec'
321  files, _, _ = runXRDCommand(path, cmd)
322  result = []
323  for line in files.split('\n'):
324  tokens = [t for t in line.split() if t]
325  if tokens:
326  #convert to an LFN
327  # result.append(tuple(tokens))
328  #COLIN need same interface for eos and local fs
329  if full_info:
330  result.append( tokens)
331  else:
332  result.append( tokens[4] )
333  return result
334 
335 def which(cmd):
336  command = ['which', cmd]
337  runner = cmsIO.cmsFileManip()
338  out, _, _ = runner.runCommand(command)
339 
340  lines = [line for line in out.split('\n') if line]
341  if len(lines) == 1:
342  return lines[0]
343  elif len(lines) == 2:
344  return lines[1]
345  else:
346  return lines
347 
348 def ls(path, rec = False):
349  """Provides a simple list of the specified directory, works on EOS and locally"""
350  return [eosToLFN(t) for t in listFiles(path, rec)]
351 
352 def ls_EOS(path, rec = False):
353  """Provides a simple list of the specified directory, works on EOS only, but is faster than the xrd version"""
354  if rec:
355  stdout, _, ret = runEOSCommand(path,'find','-f')
356  return [eosToLFN(line) for line in stdout.split('\n') if line]
357  else:
358  stdout, _, ret = runEOSCommand(path,'ls')
359  lfn = eosToLFN(path)
360  return [os.path.join(lfn,line) for line in stdout.split('\n') if line]
361 
362 def rm(path, rec=False):
363  """rm, works on EOS and locally.
364 
365  Colin: should implement a -f mode and a confirmation when deleting dirs recursively."""
366  # print 'rm ', path
367  path = lfnToEOS(path)
368  if isEOS(path):
369  if rec:
370  runEOSCommand(path, 'rm', '-r')
371  else:
372  runEOSCommand(path,'rm')
373  elif os.path.exists(path):
374  if not rec:
375  os.remove( path )
376  else:
377  shutil.rmtree(path)
378  else:
379  raise ValueError(path + ' is not EOS and not local... should not happen!')
380 
381 def remove( files, rec = False):
382  """Remove a list of files and directories, possibly recursively
383 
384  Colin: Is that obsolete? why not use rm?"""
385  for path in files:
386  lfn = eosToLFN(path)
387  if not rec:
388  rm(path)
389  else:
390  #this should be used with care
391  file_list = ls(path, rec = True)
392  file_list.append(lfn)
393 
394  #order the files in depth order - i.e. remove the deepest files first
395  files_rec = sorted([(len([ff for ff in f.split('/') if ff]), f) for f in file_list if f and f.startswith(lfn)], reverse = True)
396 
397  for f in files_rec:
398  rm(f[1])
399 
400 def cat(path):
401  """cat, works on EOS and locally"""
402  path = lfnToEOS(path)
403  if isEOS(path):
404  #print "the file to cat is:", path
405  out, err, _ = runXRDCommand(path,'cat')
406  lines = []
407  if out:
408  pattern = re.compile('cat returned [0-9]+')
409  for line in out.split('\n'):
410  match = pattern.search(line)
411  if line and match is not None:
412  lines.append(line.replace(match.group(0),''))
413  break
414  else:
415  lines.append(line)
416  if err:
417  print >> sys.stderr, out
418  print >> sys.stderr, err
419  allLines = '\n'.join(lines)
420  if allLines and not allLines.endswith('\n'):
421  allLines += '\n'
422  return allLines
423  else:
424  content = file(path).read()
425  if content and not content.endswith('\n'):
426  content += '\n'
427  return content
428 
429 def xrdcp(src, dest):
430  """Does a copy of files using xrd.
431 
432  Colin: implement a generic cp interface as done for rm, ls, etc?"""
433 
434  recursive = False
435 
436  #first the src file
437  pfn_src = src
438  if os.path.exists(src):
439  #local
440  pfn_src = src
441  if os.path.isdir(src):
442  recursive = True
443  elif fileExists(src):
444  src = eosToLFN(src)
445  pfn_src = lfnToPFN(src)
446  if isDirectory(src):
447  recursive = True
448  else:
449  raise ValueError(src + ' does not exist.')
450 
451  #now the dest
452  pfn_dest = dest
453  if isEOSDir(dest):
454  dest = eosToLFN(dest)
455  pfn_dest = lfnToPFN(dest)
456  if isDirectory(dest):
457  tokens = cmsIO.splitPFN(pfn_dest)
458  pfn_dest = '%s://%s//%s/' % (tokens[0],tokens[1],tokens[2])
459  elif os.path.exists(dest):
460  pfn_dest = dest
461 
462  command = ['xrdcp']
463  if recursive:
464  # print 'recursive'
465  topDir = src.rstrip('/').split('/')[-1]
466  if topDir != '.':
467  dest = '/'.join([dest, topDir])
468  # print 'mkdir ' + dest
469  mkdir( dest )
470  files = listFiles(src, rec=True)
471  # pprint.pprint( [file[4] for file in files] )
472  for srcFile in files:
473  # srcFile = file[4]
474  pfnSrcFile = srcFile
475  if isEOSDir(srcFile):
476  srcFile = eosToLFN(srcFile)
477  pfnSrcFile = lfnToPFN(srcFile)
478  destFile = srcFile.replace( src, '' )
479  destFile = '/'.join([dest,destFile])
480  pfnDestFile = destFile
481  if isEOSDir(destFile):
482  lfnDestFile = eosToLFN(destFile)
483  pfnDestFile = lfnToPFN(lfnDestFile)
484  # print 'srcFile', pfnSrcFile
485  # print 'destFile', pfnDestFile
486  if isFile(srcFile):
487  _xrdcpSingleFile( pfnSrcFile, pfnDestFile )
488  else:
489  mkdir(destFile)
490  else:
491  _xrdcpSingleFile( pfn_src, pfn_dest )
492 
493 
494 def _xrdcpSingleFile( pfn_src, pfn_dest):
495  """Copies a single file using xrd."""
496 
497  command = ['xrdcp']
498  command.append(pfn_src)
499  command.append(pfn_dest)
500  # print ' '.join(command)
501  run = True
502  if run:
503  runner = cmsIO.cmsFileManip()
504  out, err, ret = runner.runCommand(command)
505  if err:
506  print >> sys.stderr, out
507  print >> sys.stderr, err
508  return ret
509 
510 def move(src, dest):
511  """Move filename1 to filename2 locally to the same server"""
512 
513  src = eosToLFN(src)
514  dest = eosToLFN(dest)
515 
516  runXRDCommand(src,'mv', lfnToEOS(dest))
517 
518 def matchingFiles( path, regexp):
519  """Return a list of files matching a regexp"""
520 
521  # print path, regexp
522  pattern = re.compile( regexp )
523  #files = ls_EOS(path)
524  files = ls(path)
525  # print files
526  return [f for f in files if pattern.match(os.path.basename(f)) is not None]
527 
528 def datasetNotEmpty( path, regexp ):
529  pattern = re.compile( regexp )
530  files = ls_EOS(path)
531 
532  for f in files:
533  if pattern.match( os.path.basename(f) ) is not None:
534  return 1
535  return 0
536 
537 def cmsStage( absDestDir, files, force):
538  """Runs cmsStage with LFNs if possible"""
539 
540  destIsEOSDir = isEOSDir(absDestDir)
541  if destIsEOSDir:
542  createEOSDir( absDestDir )
543 
544  for fname in files:
545  command = ['cmsStage']
546  if force:
547  command.append('-f')
548  command.append(eosToLFN(fname))
549  command.append(eosToLFN(absDestDir))
550  print ' '.join(command)
551  runner = cmsIO.cmsFileManip()
552  runner.runCommand(command)
def isEOSFile(path, tfcProt='rfio')
Definition: eostools.py:152
def isLFN(path)
Definition: eostools.py:56
def replace(string, replacements)
def isEOS(path)
Definition: eostools.py:61
def eosToLFN(path)
Definition: eostools.py:65
def runEOSCommand(path, cmd, args)
Definition: eostools.py:38
def setCAFPath()
Definition: eostools.py:13
def runXRDCommand(path, cmd, args)
Definition: eostools.py:22
def isEOSDir(path)
Definition: eostools.py:121
def lfnToPFN(path, tfcProt='rfio')
Definition: eostools.py:76
def lfnToEOS(path)
Definition: eostools.py:107
#define str(s)