CMS 3D CMS Logo

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