CMS 3D CMS Logo

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