rucio-clients 32.8.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of rucio-clients might be problematic. Click here for more details.

Files changed (88) hide show
  1. rucio/__init__.py +18 -0
  2. rucio/alembicrevision.py +16 -0
  3. rucio/client/__init__.py +16 -0
  4. rucio/client/accountclient.py +413 -0
  5. rucio/client/accountlimitclient.py +155 -0
  6. rucio/client/baseclient.py +929 -0
  7. rucio/client/client.py +77 -0
  8. rucio/client/configclient.py +113 -0
  9. rucio/client/credentialclient.py +54 -0
  10. rucio/client/didclient.py +691 -0
  11. rucio/client/diracclient.py +48 -0
  12. rucio/client/downloadclient.py +1674 -0
  13. rucio/client/exportclient.py +44 -0
  14. rucio/client/fileclient.py +51 -0
  15. rucio/client/importclient.py +42 -0
  16. rucio/client/lifetimeclient.py +74 -0
  17. rucio/client/lockclient.py +99 -0
  18. rucio/client/metaclient.py +137 -0
  19. rucio/client/pingclient.py +45 -0
  20. rucio/client/replicaclient.py +444 -0
  21. rucio/client/requestclient.py +109 -0
  22. rucio/client/rseclient.py +664 -0
  23. rucio/client/ruleclient.py +287 -0
  24. rucio/client/scopeclient.py +88 -0
  25. rucio/client/subscriptionclient.py +161 -0
  26. rucio/client/touchclient.py +78 -0
  27. rucio/client/uploadclient.py +871 -0
  28. rucio/common/__init__.py +14 -0
  29. rucio/common/cache.py +74 -0
  30. rucio/common/config.py +796 -0
  31. rucio/common/constants.py +92 -0
  32. rucio/common/constraints.py +18 -0
  33. rucio/common/didtype.py +187 -0
  34. rucio/common/exception.py +1092 -0
  35. rucio/common/extra.py +37 -0
  36. rucio/common/logging.py +404 -0
  37. rucio/common/pcache.py +1387 -0
  38. rucio/common/policy.py +84 -0
  39. rucio/common/schema/__init__.py +143 -0
  40. rucio/common/schema/atlas.py +411 -0
  41. rucio/common/schema/belleii.py +406 -0
  42. rucio/common/schema/cms.py +478 -0
  43. rucio/common/schema/domatpc.py +399 -0
  44. rucio/common/schema/escape.py +424 -0
  45. rucio/common/schema/generic.py +431 -0
  46. rucio/common/schema/generic_multi_vo.py +410 -0
  47. rucio/common/schema/icecube.py +404 -0
  48. rucio/common/schema/lsst.py +423 -0
  49. rucio/common/stomp_utils.py +160 -0
  50. rucio/common/stopwatch.py +56 -0
  51. rucio/common/test_rucio_server.py +148 -0
  52. rucio/common/types.py +158 -0
  53. rucio/common/utils.py +1946 -0
  54. rucio/rse/__init__.py +97 -0
  55. rucio/rse/protocols/__init__.py +14 -0
  56. rucio/rse/protocols/cache.py +123 -0
  57. rucio/rse/protocols/dummy.py +112 -0
  58. rucio/rse/protocols/gfal.py +701 -0
  59. rucio/rse/protocols/globus.py +243 -0
  60. rucio/rse/protocols/gsiftp.py +93 -0
  61. rucio/rse/protocols/http_cache.py +83 -0
  62. rucio/rse/protocols/mock.py +124 -0
  63. rucio/rse/protocols/ngarc.py +210 -0
  64. rucio/rse/protocols/posix.py +251 -0
  65. rucio/rse/protocols/protocol.py +530 -0
  66. rucio/rse/protocols/rclone.py +365 -0
  67. rucio/rse/protocols/rfio.py +137 -0
  68. rucio/rse/protocols/srm.py +339 -0
  69. rucio/rse/protocols/ssh.py +414 -0
  70. rucio/rse/protocols/storm.py +207 -0
  71. rucio/rse/protocols/webdav.py +547 -0
  72. rucio/rse/protocols/xrootd.py +295 -0
  73. rucio/rse/rsemanager.py +752 -0
  74. rucio/vcsversion.py +11 -0
  75. rucio/version.py +46 -0
  76. rucio_clients-32.8.6.data/data/etc/rse-accounts.cfg.template +25 -0
  77. rucio_clients-32.8.6.data/data/etc/rucio.cfg.atlas.client.template +42 -0
  78. rucio_clients-32.8.6.data/data/etc/rucio.cfg.template +257 -0
  79. rucio_clients-32.8.6.data/data/requirements.txt +55 -0
  80. rucio_clients-32.8.6.data/data/rucio_client/merge_rucio_configs.py +147 -0
  81. rucio_clients-32.8.6.data/scripts/rucio +2540 -0
  82. rucio_clients-32.8.6.data/scripts/rucio-admin +2434 -0
  83. rucio_clients-32.8.6.dist-info/METADATA +50 -0
  84. rucio_clients-32.8.6.dist-info/RECORD +88 -0
  85. rucio_clients-32.8.6.dist-info/WHEEL +5 -0
  86. rucio_clients-32.8.6.dist-info/licenses/AUTHORS.rst +94 -0
  87. rucio_clients-32.8.6.dist-info/licenses/LICENSE +201 -0
  88. rucio_clients-32.8.6.dist-info/top_level.txt +1 -0
rucio/common/pcache.py ADDED
@@ -0,0 +1,1387 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ # Copyright European Organization for Nuclear Research (CERN) since 2012
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ import errno
18
+ import getopt
19
+ import os
20
+ import re
21
+ import signal
22
+ import subprocess
23
+ import sys
24
+ import time
25
+ from socket import gethostname
26
+ from urllib.parse import urlencode
27
+ from urllib.request import urlopen
28
+
29
+ import fcntl
30
+
31
+ # The pCache Version
32
+ pcacheversion = "4.2.3"
33
+
34
+ # Log message levels
35
+ DEBUG, INFO, WARN, ERROR = "DEBUG", "INFO ", "WARN ", "ERROR"
36
+
37
+ # filename for locking
38
+ LOCK_NAME = ".LOCK"
39
+
40
+ # Session ID
41
+ sessid = "%s.%s" % (int(time.time()), os.getpid())
42
+
43
+
44
+ # Run a command with a timeout
45
+ def run_cmd(args, timeout=0):
46
+
47
+ class Alarm(Exception):
48
+ pass
49
+
50
+ def alarm_handler(signum, frame):
51
+ raise Alarm
52
+
53
+ # Execute the command as a subprocess
54
+ try:
55
+ p = subprocess.Popen(args=args,
56
+ shell=False,
57
+ stdout=subprocess.PIPE,
58
+ stderr=subprocess.STDOUT)
59
+
60
+ except:
61
+ return (-2, None)
62
+
63
+ # Set the timer if a timeout value was given
64
+ if (timeout > 0):
65
+ signal.signal(signal.SIGALRM, alarm_handler)
66
+ signal.alarm(timeout)
67
+
68
+ # Wait for the command to complete
69
+ try:
70
+
71
+ # Collect the output when the command completes
72
+ stdout = p.communicate()[0][:-1]
73
+
74
+ # Commmand completed in time, cancel the alarm
75
+ if (timeout > 0):
76
+ signal.alarm(0)
77
+
78
+ # Command timed out
79
+ except Alarm:
80
+
81
+ # The pid of our spawn
82
+ pids = [p.pid]
83
+
84
+ # The pids of the spawn of our spawn
85
+ pids.extend(get_process_children(p.pid))
86
+
87
+ # Terminate all of the evil spawn
88
+ for pid in pids:
89
+ try:
90
+ os.kill(pid, signal.SIGKILL)
91
+ except OSError:
92
+ pass
93
+
94
+ # Return a timeout error
95
+ return (-1, None)
96
+
97
+ return (p.returncode, stdout)
98
+
99
+
100
+ def get_process_children(pid):
101
+
102
+ # Get a list of all pids assocaited with a given pid
103
+ p = subprocess.Popen(args='ps --no-headers -o pid --ppid %d' % pid,
104
+ shell=True,
105
+ stdout=subprocess.PIPE,
106
+ stderr=subprocess.PIPE)
107
+
108
+ # Wait and fetch the stdout
109
+ stdout, stderr = p.communicate()
110
+
111
+ # Return a list of pids as tuples
112
+ return [int(pr) for pr in stdout.split()]
113
+
114
+
115
+ def unitize(x):
116
+
117
+ suff = 'BKMGTPEZY'
118
+
119
+ while ((x >= 1024) and suff):
120
+ x /= 1024.0
121
+ suff = suff[1:]
122
+ return "%.4g%s" % (x, suff[0])
123
+
124
+
125
+ class Pcache:
126
+
127
+ def Usage(self):
128
+ msg = """Usage: %s [flags] copy_prog [copy_flags] input output""" % self.progname
129
+ sys.stderr.write("%s\n" % msg) # py3, py2
130
+ # print>>sys.stderr, " flags are: "
131
+ # "s:r:m:Cy:A:R:t:r:g:fFl:VvqpH:S:",
132
+ # "scratch-dir=",
133
+ # "storage-root=",
134
+ # "max-space=",
135
+ # "clean",
136
+ # "hysterisis=",
137
+ # "accept=",
138
+ # "reject=",
139
+ # "timeout=",
140
+ # "retry=",
141
+ # "force",
142
+ # "flush-cache",
143
+ # "guid=",
144
+ # "log=",
145
+ # "version",
146
+ # "verbose",
147
+ # "debug",
148
+ # "quiet",
149
+ # "panda",
150
+ # "hostname",
151
+ # "sitename"
152
+
153
+ def __init__(self):
154
+ os.umask(0)
155
+ self.storage_root = "/pnfs"
156
+ self.scratch_dir = "/scratch/"
157
+ self.pcache_dir = self.scratch_dir + "pcache/"
158
+ self.log_file = self.pcache_dir + "pcache.log"
159
+ self.max_space = "80%"
160
+ self.percent_max = None
161
+ self.bytes_max = None
162
+ # self.max_space = "10T"
163
+ self.hysterisis = 0.75
164
+ # self.hysterisis = 0.9
165
+ self.clean = False
166
+ self.transfer_timeout = "600"
167
+ self.max_retries = 3
168
+ self.guid = None
169
+ self.accept_patterns = []
170
+ self.reject_patterns = []
171
+ self.force = False
172
+ self.flush = False
173
+ self.verbose = False
174
+ self.quiet = False
175
+ self.debug = False
176
+ self.hostname = None
177
+ self.sitename = None # XXX can we get this from somewhere?
178
+ self.update_panda = False
179
+ self.panda_url = "https://pandaserver.cern.ch:25443/server/panda/"
180
+ self.local_src = None
181
+ self.skip_download = False
182
+
183
+ # internal variables
184
+ self.sleep_interval = 15
185
+ self.force = False
186
+ self.locks = {}
187
+ self.deleted_guids = []
188
+ self.version = pcacheversion
189
+
190
+ def parse_args(self, args):
191
+ # handle pcache flags and leave the rest in self.args
192
+
193
+ try:
194
+ opts, args = getopt.getopt(args,
195
+ "s:x:m:Cy:A:R:t:r:g:fFl:VvdqpPH:S:L:X:",
196
+ ["scratch-dir=",
197
+ "storage-root=",
198
+ "max-space=",
199
+ "clean",
200
+ "hysterisis=",
201
+ "accept=",
202
+ "reject=",
203
+ "timeout=",
204
+ "retry=",
205
+ "force",
206
+ "flush-cache",
207
+ "guid=",
208
+ "log=",
209
+ "version",
210
+ "verbose",
211
+ "debug",
212
+ "quiet",
213
+ "print-stats",
214
+ "panda",
215
+ "hostname",
216
+ "sitename",
217
+ "local-src"])
218
+
219
+ # XXXX cache, stats, reset, clean, delete, inventory
220
+ # TODO: move checksum/size validation from lsm to pcache
221
+ except getopt.GetoptError as err:
222
+ sys.stderr.write("%s\n" % str(err))
223
+ self.Usage()
224
+ self.fail(100)
225
+
226
+ for opt, arg in opts:
227
+ if opt in ("-s", "--scratch-dir"):
228
+ self.scratch_dir = arg
229
+ # Make sure scratch_dir endswith /
230
+ if not self.scratch_dir.endswith("/"):
231
+ self.scratch_dir += "/"
232
+ self.pcache_dir = self.scratch_dir + "pcache/"
233
+ self.log_file = self.pcache_dir + "pcache.log"
234
+ elif opt in ("-x", "--storage-root"):
235
+ self.storage_root = arg
236
+ elif opt in ("-m", "--max-space"):
237
+ self.max_space = arg
238
+ elif opt in ("-y", "--hysterisis"):
239
+ if arg.endswith('%'):
240
+ self.hysterisis = float(arg[:-1]) / 100
241
+ else:
242
+ self.hysterisis = float(arg)
243
+ elif opt in ("-A", "--accept"):
244
+ self.accept_patterns.append(arg)
245
+ elif opt in ("-R", "--reject"):
246
+ self.reject_patterns.append(arg)
247
+ elif opt in ("-t", "--timeout"):
248
+ self.transfer_timeout = arg
249
+ elif opt in ("-f", "--force"):
250
+ self.force = True
251
+ elif opt in ("-F", "--flush-cache"):
252
+ self.flush = True
253
+ elif opt in ("-C", "--clean"):
254
+ self.clean = True
255
+ elif opt in ("-g", "--guid"):
256
+ if arg == 'None':
257
+ self.guid = None
258
+ else:
259
+ self.guid = arg
260
+ elif opt in ("-r", "--retry"):
261
+ self.max_retries = int(arg)
262
+ elif opt in ("-V", "--version"):
263
+ print((str(self.version)))
264
+ sys.exit(0)
265
+ elif opt in ("-l", "--log"):
266
+ self.log_file = arg
267
+ elif opt in ("-v", "--verbose"):
268
+ self.verbose = True
269
+ elif opt in ("-d", "--debug"):
270
+ self.debug = True
271
+ elif opt in ("-q", "--quiet"):
272
+ self.quiet = True
273
+ elif opt in ("-p", "--print-stats"):
274
+ self.print_stats()
275
+ sys.exit(0)
276
+ elif opt in ("-P", "--panda"):
277
+ self.update_panda = True
278
+ elif opt in ("-H", "--hostname"):
279
+ self.hostname = arg
280
+ elif opt in ("-S", "--sitename"):
281
+ self.sitename = arg
282
+ elif opt in ("-L", "--local-src"):
283
+ self.local_src = str(arg)
284
+ elif opt in ("-X", "--skip-download"):
285
+ if str(arg) in ('True', 'true') or arg:
286
+ self.skip_download = True
287
+
288
+ # Treatment of limits on pcache size
289
+ self._convert_max_space()
290
+
291
+ # Convert timeout to seconds
292
+ t = self.transfer_timeout
293
+ mult = 1
294
+ suff = t[-1]
295
+ if suff in ('H', 'h'):
296
+ mult = 3600
297
+ t = t[:-1]
298
+ elif suff in ('M', 'm'):
299
+ mult = 60
300
+ t = t[:1]
301
+ elif suff in ('S', 's'):
302
+ mult = 1
303
+ t = t[:-1]
304
+ self.transfer_timeout = mult * int(t)
305
+
306
+ # Pre-compile regexes
307
+ self.accept_patterns = list(map(re.compile, self.accept_patterns))
308
+ self.reject_patterns = list(map(re.compile, self.reject_patterns))
309
+
310
+ # Set host and name
311
+ if self.hostname is None:
312
+ self.hostname = gethostname()
313
+ if self.sitename is None:
314
+ self.sitename = os.environ.get("SITE", "") # XXXX
315
+
316
+ # All done
317
+ self.args = args
318
+
319
+ def _convert_max_space(self):
320
+ '''
321
+ Added by Rucio team. Converts max allowed space usage of pcache into units used by this tool.
322
+ :input self.max_space: limit set by user
323
+ :output self.percent_max: max percentage of pcache space used
324
+ :output self.bytes_max: max size in bytes of pcache space used
325
+ '''
326
+
327
+ # Convert max_space arg to percent_max or bytes_max
328
+ if self.max_space.endswith('%'):
329
+ self.percent_max = float(self.max_space[:-1])
330
+ self.bytes_max = None
331
+ else: # handle suffix
332
+ self.percent_max = None
333
+ m = self.max_space.upper()
334
+ index = "BKMGTPEZY".find(m[-1])
335
+ if index >= 0:
336
+ self.bytes_max = float(m[:-1]) * (1024**index)
337
+ else: # Numeric value w/o units (exception if invalid)
338
+ self.bytes_max = float(self.max_space)
339
+
340
+ def clean_pcache(self, max_space=None):
341
+ '''
342
+ Added by Rucio team. Cleans pcache in case it is over limit.
343
+ Used for tests of the pcache functionality. Can be called without other init.
344
+ '''
345
+
346
+ self.t0 = time.time()
347
+ self.progname = "pcache"
348
+
349
+ # set max. occupancy of pcache:
350
+ if max_space:
351
+ self.max_space = max_space
352
+ self._convert_max_space()
353
+
354
+ # Fail on extra args
355
+ if not self.scratch_dir:
356
+ self.Usage()
357
+ self.fail(100)
358
+
359
+ # hardcoded pcache dir
360
+ self.pcache_dir = self.scratch_dir + '/pcache/'
361
+
362
+ # clean pcache
363
+ self.maybe_start_cleaner_thread()
364
+
365
+ def check_and_link(self, src='', dst='', dst_prefix='', scratch_dir='/scratch/', storage_root=None, force=False,
366
+ guid=None, log_file=None, version='', hostname=None, sitename=None, local_src=None):
367
+ '''
368
+ Added by Rucio team. Replacement for the main method.
369
+ Checks whether a file is in pcache:
370
+ - if yes: creates a hardlink to the file in pcahce
371
+ - if not:
372
+ - returns 500 and leaves it to Rucio
373
+ - Rucio downloads a file
374
+ Makes hardlink in pcache to downloaded file:
375
+ - needs :param local_src: path to downloaded file
376
+ '''
377
+ self.t0 = time.time()
378
+ self.progname = "pcache"
379
+ self.pcache_dir = scratch_dir + '/pcache/'
380
+ self.src = src
381
+ self.dst = dst
382
+ self.dst_prefix = dst_prefix
383
+ self.sitename = sitename
384
+ self.hostname = hostname
385
+ self.guid = guid
386
+ if log_file:
387
+ self.log_file = log_file
388
+ self.local_src = local_src
389
+ self.version = version
390
+ self.storage_root = storage_root
391
+
392
+ # Cache dir may have been wiped
393
+ if ((not os.path.exists(self.pcache_dir)) and self.update_panda):
394
+ self.panda_flush_cache()
395
+
396
+ # Create the pCache directory
397
+ if (self.mkdir_p(self.pcache_dir)):
398
+ self.fail(101)
399
+
400
+ self.log(INFO, "%s %s invoked as: API", self.progname, self.version)
401
+
402
+ # Fail on extra args
403
+ if not scratch_dir:
404
+ self.Usage()
405
+ self.fail(100)
406
+
407
+ # If the source is lfn:, execute original command, no further action
408
+ if (self.src.startswith('lfn:')):
409
+ # status = os.execvp(self.copy_util, self.args)
410
+ os._exit(1)
411
+
412
+ # If the destination is a local file, do some rewrites
413
+ if (self.dst.startswith('file:')):
414
+ self.dst_prefix = 'file:'
415
+ self.dst = self.dst[5:]
416
+ # Leave one '/' on dst
417
+ while ((len(self.dst) > 1) and (self.dst[1] == '/')):
418
+ self.dst_prefix += '/'
419
+ self.dst = self.dst[1:]
420
+
421
+ # load file into pcache
422
+ self.create_pcache_dst_dir()
423
+ # XXXX TODO _ dst_dir can get deleted before lock!
424
+ waited = False
425
+
426
+ # If another transfer is active, lock_dir will block
427
+ if (self.lock_dir(self.pcache_dst_dir, blocking=False)):
428
+ waited = True
429
+ self.log(INFO, "%s locked, waiting", self.pcache_dst_dir)
430
+ self.lock_dir(self.pcache_dst_dir, blocking=True)
431
+
432
+ if (waited):
433
+ self.log(INFO, "waited %.2f secs", time.time() - self.t0)
434
+
435
+ if force:
436
+ self.empty_dir(self.pcache_dst_dir)
437
+
438
+ # The name of the cached version of this file
439
+ cache_file = self.pcache_dst_dir + "data"
440
+
441
+ # Check if the file is in cache or we need to transfer it down
442
+ if (os.path.exists(cache_file)):
443
+ exit_status = 0
444
+ copy_status = None
445
+ self.log(INFO, "check_and_link: file found in cache")
446
+ self.log(INFO, "cache hit %s", self.src)
447
+ self.update_stats("cache_hits")
448
+ self.finish()
449
+ if (os.path.exists(self.dst)):
450
+ copy_status = 1
451
+ elif self.local_src:
452
+ exit_status = 1
453
+ copy_status = None
454
+ self.log(INFO, "check_and_link: local replica found, linking to pcache")
455
+ self.finish()
456
+ else:
457
+ self.log(INFO, "check_and_link: %s file not found in pcache and was not downloaded yet", self.src)
458
+ return (500, None)
459
+
460
+ self.unlock_dir(self.pcache_dst_dir)
461
+ self.log(INFO, "total time %.2f secs", time.time() - self.t0)
462
+
463
+ # in case that the pcache is over limit
464
+ self.maybe_start_cleaner_thread()
465
+
466
+ # Return if the file was cached, copied or an error (and its code)
467
+ return (exit_status, copy_status)
468
+
469
+ def main(self, args):
470
+
471
+ # args
472
+ self.cmdline = ' '.join(args)
473
+ self.t0 = time.time()
474
+ self.progname = args[0] or "pcache"
475
+
476
+ # Must have a list of arguments
477
+ if (self.parse_args(args[1:])):
478
+ self.Usage()
479
+ self.fail(100)
480
+
481
+ # Cache dir may have been wiped
482
+ if ((not os.path.exists(self.pcache_dir)) and self.update_panda):
483
+ self.panda_flush_cache()
484
+
485
+ # Create the pCache directory
486
+ if (self.mkdir_p(self.pcache_dir)):
487
+ self.fail(101)
488
+
489
+ self.log(INFO, "%s %s invoked as: %s", self.progname, self.version, self.cmdline)
490
+
491
+ # Are we flushing the cache
492
+ if (self.flush):
493
+ if (self.args):
494
+ sys.stderr.write("--flush not compatible with other options")
495
+ self.fail(100)
496
+ else:
497
+ self.flush_cache()
498
+ sys.exit(0)
499
+
500
+ # Are we cleaning the cache
501
+ if (self.clean):
502
+ # size = self.do_cache_inventory()
503
+ self.maybe_start_cleaner_thread()
504
+ if (len(self.args) < 1):
505
+ sys.exit(0)
506
+
507
+ # Fail on extra args
508
+ if (len(self.args) < 3):
509
+ self.Usage()
510
+ self.fail(100)
511
+
512
+ self.copy_util = self.args[0]
513
+ self.copy_args = self.args[1:-2]
514
+ self.src = self.args[-2]
515
+ self.dst = self.args[-1]
516
+ self.dst_prefix = ''
517
+
518
+ # If the source is lfn:, execute original command, no further action
519
+ if (self.src.startswith('lfn:')):
520
+ # status = os.execvp(self.copy_util, self.args)
521
+ os._exit(1)
522
+
523
+ # If the destination is a local file, do some rewrites
524
+ if (self.dst.startswith('file:')):
525
+ self.dst_prefix = 'file:'
526
+ self.dst = self.dst[5:]
527
+ # Leave one '/' on dst
528
+ while ((len(self.dst) > 1) and (self.dst[1] == '/')):
529
+ self.dst_prefix += '/'
530
+ self.dst = self.dst[1:]
531
+
532
+ # Execute original command, no further action
533
+ if (not (self.dst.startswith(self.scratch_dir) and self.accept(self.src) and (not self.reject(self.src)))):
534
+ os.execvp(self.copy_util, self.args)
535
+ os._exit(1)
536
+
537
+ # XXXX todo: fast-path - try to acquire lock
538
+ # first, if that succeeds, don't call
539
+ # create_pcache_dst_dir
540
+
541
+ # load file into pcache
542
+ self.create_pcache_dst_dir()
543
+ # XXXX TODO _ dst_dir can get deleted before lock!
544
+ waited = False
545
+
546
+ # If another transfer is active, lock_dir will block
547
+ if (self.lock_dir(self.pcache_dst_dir, blocking=False)):
548
+ waited = True
549
+ self.log(INFO, "%s locked, waiting", self.pcache_dst_dir)
550
+ self.lock_dir(self.pcache_dst_dir, blocking=True)
551
+
552
+ if (waited):
553
+ self.log(INFO, "waited %.2f secs", time.time() - self.t0)
554
+
555
+ if (self.force):
556
+ self.empty_dir(self.pcache_dst_dir)
557
+
558
+ # The name of the cached version of this file
559
+ cache_file = self.pcache_dst_dir + "data"
560
+
561
+ # Check if the file is in cache or we need to transfer it down
562
+ if (os.path.exists(cache_file)):
563
+ exit_status = 1
564
+ copy_status = None
565
+ self.log(INFO, "cache hit %s", self.src)
566
+ self.update_stats("cache_hits")
567
+ self.finish()
568
+ else:
569
+ if self.skip_download:
570
+ return (500, None)
571
+ self.update_stats("cache_misses")
572
+ exit_status, copy_status = self.pcache_copy_in()
573
+ if ((exit_status == 0) or (exit_status == 2)):
574
+ self.finish()
575
+
576
+ self.unlock_dir(self.pcache_dst_dir)
577
+ self.log(INFO, "total time %.2f secs", time.time() - self.t0)
578
+
579
+ self.maybe_start_cleaner_thread()
580
+
581
+ # Return if the file was cached, copied or an error (and its code)
582
+ return (exit_status, copy_status)
583
+
584
+ def finish(self, local_src=None):
585
+ cache_file = self.pcache_dst_dir + "data"
586
+ self.update_mru()
587
+ if self.local_src:
588
+ if (self.make_hard_link(self.local_src, cache_file)):
589
+ self.fail(102)
590
+ else:
591
+ if (self.make_hard_link(cache_file, self.dst)):
592
+ self.fail(102)
593
+
594
+ def pcache_copy_in(self):
595
+
596
+ cache_file = self.pcache_dst_dir + "data"
597
+
598
+ # Record source URL
599
+ try:
600
+ fname = self.pcache_dst_dir + "src"
601
+ f = open(fname, 'w')
602
+ f.write(self.src + '\n')
603
+ f.close()
604
+ self.chmod(fname, 0o666)
605
+ except:
606
+ pass
607
+
608
+ # Record GUID if given
609
+ if (self.guid):
610
+ try:
611
+ fname = self.pcache_dst_dir + "guid"
612
+ f = open(fname, 'w')
613
+ f.write(self.guid + '\n')
614
+ f.close()
615
+ self.chmod(fname, 0o666)
616
+ except:
617
+ pass
618
+
619
+ # Try to transfer the file up the the number of retries allowed
620
+ retry = 0
621
+ while (retry <= self.max_retries):
622
+
623
+ # Is this is a retry attempt, log it as such
624
+ if (retry > 0):
625
+ self.log(INFO, "do_transfer: retry %s", retry)
626
+
627
+ # Do the transfer. exit_status will be either
628
+ # 0 - success
629
+ # 3 - Transfer command failed. copy_status has the return code
630
+ # 4 - Transfer command timed out
631
+ # 5 - Transfer command was not found
632
+ exit_status, copy_status = self.do_transfer()
633
+
634
+ # If success, stop trying, otherwise increment the retry count
635
+ if (exit_status == 0):
636
+ break
637
+ retry += 1
638
+
639
+ # Did the transfer succeed
640
+ if (exit_status == 0):
641
+
642
+ # If we succeeded on a retry, return status 2 and the retries
643
+ if (retry == 0):
644
+ copy_status = None
645
+ else:
646
+ exit_status = 2
647
+ copy_status = retry
648
+
649
+ # Update the cache information
650
+ if self.local_src:
651
+ self.update_cache_size(os.stat(self.local_src).st_size)
652
+ else:
653
+ self.update_cache_size(os.stat(cache_file).st_size)
654
+
655
+ # Update the panda cache
656
+ if (self.guid and self.update_panda):
657
+ self.panda_add_cache_files((self.guid,))
658
+
659
+ return (exit_status, copy_status)
660
+
661
+ def create_pcache_dst_dir(self):
662
+
663
+ d = self.src
664
+ index = d.find(self.storage_root)
665
+
666
+ if (index >= 0):
667
+ d = d[index:]
668
+ else:
669
+ index = d.find("SFN=")
670
+ if (index >= 0):
671
+ d = d[index + 4:]
672
+
673
+ # self.log(INFO, '%s', self.storage_root)
674
+ # self.log(INFO, '%s', d)
675
+ # XXXX any more patterns to look for?
676
+ d = os.path.normpath(self.pcache_dir + "CACHE/" + d)
677
+ if (not d.endswith('/')):
678
+ d += '/'
679
+
680
+ self.pcache_dst_dir = d
681
+ status = self.mkdir_p(d)
682
+ if (status):
683
+ self.log(ERROR, "mkdir %s %s", d, status)
684
+ self.fail(103)
685
+
686
+ def get_disk_usage(self):
687
+ p = os.popen("df -P %s | tail -1" % self.pcache_dir, 'r')
688
+ data = p.read()
689
+ status = p.close()
690
+ if status:
691
+ self.log(ERROR, "get_disk_usage: df command failed, status=%s", status)
692
+ sys.exit(1)
693
+ tok = data.split()
694
+ percent = tok[-2]
695
+ if not percent.endswith('%'):
696
+ self.log(ERROR, "get_disk_usage: cannot parse df output: %s", data)
697
+ sys.exit(1)
698
+ percent = int(percent[:-1])
699
+ return percent
700
+
701
+ def over_limit(self, factor=1.0):
702
+ if self.percent_max:
703
+ return self.get_disk_usage() > factor * self.percent_max
704
+ if self.bytes_max:
705
+ return self.get_cache_size() > factor * self.bytes_max
706
+ return False
707
+
708
+ def clean_cache(self):
709
+ t0 = time.time()
710
+
711
+ self.log(INFO, "starting cleanup, cache size=%s, usage=%s%%",
712
+ unitize(self.get_cache_size()),
713
+ self.get_disk_usage())
714
+
715
+ for link in self.list_by_mru():
716
+ try:
717
+ d = os.readlink(link)
718
+
719
+ except OSError as e:
720
+ self.log(ERROR, "readlink: %s", e)
721
+ continue
722
+
723
+ self.log(DEBUG, "deleting %s", d)
724
+
725
+ if os.path.exists(d):
726
+ self.empty_dir(d)
727
+ else:
728
+ self.log(WARN, "Attempt to delete missing file %s", d)
729
+ self.flush_cache()
730
+ break
731
+
732
+ # empty_dir should also delete MRU symlink, but
733
+ # mop up here in there is some problem with the
734
+ # backlink
735
+
736
+ try:
737
+ os.unlink(link)
738
+
739
+ except OSError as e:
740
+ if e.errno != errno.ENOENT:
741
+ self.log(ERROR, "unlink: %s", e)
742
+
743
+ if not self.over_limit(self.hysterisis):
744
+ break
745
+
746
+ self.log(INFO, "cleanup complete, cache size=%s, usage=%s%%, time=%.2f secs",
747
+ self.get_cache_size(),
748
+ self.get_disk_usage(),
749
+ time.time() - t0)
750
+
751
+ def list_by_mru(self):
752
+ mru_dir = self.pcache_dir + "MRU/"
753
+ for root, dirs, files in os.walk(mru_dir):
754
+ dirs.sort()
755
+ for d in dirs:
756
+ path = os.path.join(root, d)
757
+ if os.path.islink(path):
758
+ dirs.remove(d)
759
+ yield path
760
+ if files:
761
+ files.sort()
762
+ for file in files:
763
+ path = os.path.join(root, file)
764
+ yield path
765
+
766
+ def flush_cache(self):
767
+ # Delete everything in CACHE, MRU, and reset stats
768
+ self.log(INFO, "flushing cache")
769
+ if self.update_panda:
770
+ self.panda_flush_cache()
771
+ self.reset_stats()
772
+ ts = '.' + str(time.time())
773
+ for d in "CACHE", "MRU":
774
+ d = self.pcache_dir + d
775
+ try:
776
+ os.rename(d, d + ts)
777
+ os.system("rm -rf %s &" % (d + ts))
778
+ except OSError as e:
779
+ if e.errno != errno.ENOENT:
780
+ self.log(ERROR, "%s: %s", d, e)
781
+
782
+ def do_transfer(self):
783
+
784
+ # Cache file and transfer file locations
785
+ cache_file = self.pcache_dst_dir + "data"
786
+ xfer_file = self.pcache_dst_dir + "xfer"
787
+
788
+ # Remove any transfer file with the same name
789
+ try:
790
+ os.unlink(xfer_file)
791
+ except OSError as e:
792
+ if e.errno != errno.ENOENT:
793
+ self.log(ERROR, "unlink: %s", e)
794
+
795
+ # Build the copy command with the destination into the xfer location
796
+ args = self.args[:]
797
+ args[-1] = self.dst_prefix + xfer_file
798
+
799
+ # Save the current time for timing output
800
+ t0 = time.time()
801
+
802
+ # Do the copy with a timeout
803
+ if self.local_src:
804
+ return (0, None)
805
+ else:
806
+ copy_status, copy_output = run_cmd(args, self.transfer_timeout)
807
+
808
+ # Did the command timeout
809
+ if (copy_status == -1):
810
+ self.log(ERROR, "copy command timed out, elapsed time = %.2f sec", time.time() - t0)
811
+ self.cleanup_failed_transfer()
812
+ return (4, None)
813
+ elif (copy_status == -2):
814
+ self.log(ERROR, "copy command was not found")
815
+ self.cleanup_failed_transfer()
816
+ return (5, None)
817
+
818
+ # Display any output from the copy
819
+ if (copy_output):
820
+ print('%s' % copy_output)
821
+
822
+ # Did the copy succeed (good status and an existing file)
823
+ if ((copy_status > 0) or (not os.path.exists(xfer_file))):
824
+ self.log(ERROR, "copy command failed, elapsed time = %.2f sec", time.time() - t0)
825
+ self.cleanup_failed_transfer()
826
+ return (3, copy_status)
827
+
828
+ self.log(INFO, "copy command succeeded, elapsed time = %.2f sec", time.time() - t0)
829
+
830
+ try:
831
+ os.rename(xfer_file, cache_file)
832
+ # self.log(INFO, "rename %s %s", xfer_file, cache_file)
833
+ except OSError: # Fatal error if we can't do this
834
+ self.log(ERROR, "rename %s %s", xfer_file, cache_file)
835
+ try:
836
+ os.unlink(xfer_file)
837
+ except:
838
+ pass
839
+ self.fail(104)
840
+
841
+ # Make the file readable to all
842
+ self.chmod(cache_file, 0o666)
843
+
844
+ # Transfer completed, return the transfer command status
845
+ return (0, None)
846
+
847
+ def maybe_start_cleaner_thread(self):
848
+ if not self.over_limit():
849
+ return
850
+ # exit immediately if another cleaner is active
851
+ cleaner_lock = os.path.join(self.pcache_dir, ".clean")
852
+ if self.lock_file(cleaner_lock, blocking=False):
853
+ self.log(INFO, "cleanup not starting: %s locked", cleaner_lock)
854
+ return
855
+ # see http://www.faqs.org/faqs/unix-faq/faq/part3/section-13.html
856
+ # for explanation of double-fork
857
+ pid = os.fork()
858
+ if pid: # parent
859
+ os.waitpid(pid, 0)
860
+ return
861
+ else: # child
862
+ self.daemonize()
863
+ pid = os.fork()
864
+ if pid:
865
+ os._exit(0)
866
+ # grandchild
867
+ self.clean_cache()
868
+ self.unlock_file(cleaner_lock)
869
+ os._exit(0)
870
+
871
+ def make_hard_link(self, src, dst):
872
+ self.log(INFO, "linking %s to %s", src, dst)
873
+ try:
874
+ if os.path.exists(dst):
875
+ os.unlink(dst)
876
+ os.link(src, dst)
877
+ except OSError as e:
878
+ self.log(ERROR, "make_hard_link: %s", e)
879
+ ret = e.errno
880
+ if ret == errno.ENOENT:
881
+ try:
882
+ stat_info = os.stat(src)
883
+ self.log(INFO, "stat(%s) = %s", src, stat_info)
884
+ except:
885
+ self.log(INFO, "cannot stat %s", src)
886
+ try:
887
+ stat_info = os.stat(dst)
888
+ self.log(INFO, "stat(%s) = %s", dst, stat_info)
889
+ except:
890
+ self.log(INFO, "cannot stat %s", dst)
891
+ return ret
892
+
893
+ def reject(self, name):
894
+ for pat in self.reject_patterns:
895
+ if pat.search(name):
896
+ return True
897
+ return False
898
+
899
+ def accept(self, name):
900
+ if not self.accept_patterns:
901
+ return True
902
+ for pat in self.accept_patterns:
903
+ if pat.search(name):
904
+ return True
905
+ return False
906
+
907
+ def get_stat(self, stats_dir, stat_name):
908
+ filename = os.path.join(self.pcache_dir, stats_dir, stat_name)
909
+ try:
910
+ f = open(filename, 'r')
911
+ data = int(f.read().strip())
912
+ f.close()
913
+ except:
914
+ data = 0
915
+ return data
916
+
917
+ def print_stats(self):
918
+ print(("Cache size: %s", unitize(self.get_stat("CACHE", "size"))))
919
+ print(("Cache hits: %s", self.get_stat("stats", "cache_hits")))
920
+ print(("Cache misses: %s", self.get_stat("stats", "cache_misses")))
921
+
922
+ def reset_stats(self):
923
+ stats_dir = os.path.join(self.pcache_dir, "stats")
924
+ try:
925
+ for f in os.listdir(stats_dir):
926
+ try:
927
+ os.unlink(os.path.join(stats_dir, f))
928
+ except:
929
+ pass
930
+ except:
931
+ pass
932
+ # XXXX error handling
933
+ pass
934
+
935
+ def update_stat_file(self, stats_dir, name, delta): # internal
936
+ stats_dir = os.path.join(self.pcache_dir, stats_dir)
937
+ self.mkdir_p(stats_dir)
938
+ self.lock_dir(stats_dir)
939
+ stats_file = os.path.join(stats_dir, name)
940
+ try:
941
+ f = open(stats_file, 'r')
942
+ data = f.read()
943
+ f.close()
944
+ value = int(data)
945
+ except:
946
+ # XXXX
947
+ value = 0
948
+ value += delta
949
+ try:
950
+ f = open(stats_file, 'w')
951
+ f.write("%s\n" % value)
952
+ f.close()
953
+ self.chmod(stats_file, 0o666)
954
+ except:
955
+ pass
956
+ # XXX
957
+ self.unlock_dir(stats_dir)
958
+
959
+ def update_stats(self, name, delta=1):
960
+ return self.update_stat_file("stats", name, delta)
961
+
962
+ def update_cache_size(self, bytes_):
963
+ return self.update_stat_file("CACHE", "size", bytes_)
964
+
965
+ def get_cache_size(self):
966
+ filename = os.path.join(self.pcache_dir, "CACHE", "size")
967
+ size = 0
968
+
969
+ try:
970
+ f = open(filename)
971
+ data = f.read()
972
+ size = int(data)
973
+ except:
974
+ pass
975
+
976
+ # If we could not fetch the size, do a reinventory
977
+ if size == 0:
978
+ size = self.do_cache_inventory()
979
+
980
+ # The size should never be negative, so lets cleanup and start over
981
+ if size < 0:
982
+ self.log(WARN, "CACHE corruption found. Negative CACHE size: %d", size)
983
+ self.flush_cache()
984
+ size = 0
985
+
986
+ return size
987
+
988
+ def do_cache_inventory(self):
989
+
990
+ inventory_lock = os.path.join(self.pcache_dir, ".inventory")
991
+ if self.lock_file(inventory_lock, blocking=False):
992
+ return
993
+
994
+ size = 0
995
+
996
+ self.log(INFO, "starting inventory")
997
+
998
+ for root, dirs, files in os.walk(self.pcache_dir):
999
+ for f in files:
1000
+ if f == "data":
1001
+ fullname = os.path.join(root, f)
1002
+ try:
1003
+ size += os.stat(fullname).st_size
1004
+ except OSError as e:
1005
+ self.log(ERROR, "stat(%s): %s", fullname, e)
1006
+
1007
+ filename = os.path.join(self.pcache_dir, "CACHE", "size")
1008
+
1009
+ try:
1010
+ f = open(filename, 'w')
1011
+ f.write("%s\n" % size)
1012
+ f.close()
1013
+ self.chmod(filename, 0o666)
1014
+ except:
1015
+ pass # XXXX
1016
+
1017
+ self.unlock_file(inventory_lock)
1018
+ self.log(INFO, "inventory complete, cache size %s", size)
1019
+ return size
1020
+
1021
+ def daemonize(self):
1022
+ if self.debug:
1023
+ return
1024
+ try:
1025
+ os.setsid()
1026
+ except OSError:
1027
+ pass
1028
+ try:
1029
+ os.chdir("/")
1030
+ except OSError:
1031
+ pass
1032
+ try:
1033
+ os.umask(0)
1034
+ except OSError:
1035
+ pass
1036
+ n = os.open("/dev/null", os.O_RDWR)
1037
+ i, o, e = sys.stdin.fileno(), sys.stdout.fileno(), sys.stderr.fileno()
1038
+ os.dup2(n, i)
1039
+ os.dup2(n, o)
1040
+ os.dup2(n, e)
1041
+ MAXFD = 1024
1042
+ try:
1043
+ import resource # Resource usage information.
1044
+ maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1045
+ if (maxfd == resource.RLIM_INFINITY):
1046
+ maxfd = MAXFD
1047
+ except:
1048
+ try:
1049
+ maxfd = os.sysconf("SC_OPEN_MAX")
1050
+ except:
1051
+ maxfd = MAXFD # use default value
1052
+
1053
+ for fd in range(0, maxfd + 1):
1054
+ try:
1055
+ os.close(fd)
1056
+ except:
1057
+ pass
1058
+
1059
+ # Panda server callback functions
1060
+ def do_http_post(self, url, data):
1061
+ # see http://www.faqs.org/faqs/unix-faq/faq/part3/section-13.html
1062
+ # for explanation of double-fork (is it overkill here?)
1063
+ pid = os.fork()
1064
+ if pid: # parent
1065
+ os.waitpid(pid, 0)
1066
+ return
1067
+ else: # child
1068
+ self.daemonize()
1069
+ pid = os.fork()
1070
+ if pid:
1071
+ os._exit(0)
1072
+ # grandchild
1073
+ retry = 0
1074
+ # This will retry for up to 1 hour, at 2 minute intervals
1075
+ while retry < 30:
1076
+ try:
1077
+ u = urlopen(url, data=urlencode(data))
1078
+ ret = u.read()
1079
+ u.close()
1080
+ self.log(INFO, "http post to %s, retry %s, data='%s', return='%s'",
1081
+ url, retry, data, ret)
1082
+ if ret == "True":
1083
+ break
1084
+ except:
1085
+ exc, msg, tb = sys.exc_info()
1086
+ self.log(ERROR, "post to %s, data=%s, error=%s", url, data, msg)
1087
+ retry += 1
1088
+ time.sleep(120)
1089
+ # finished, don't keep the child thread around!
1090
+ os._exit(0)
1091
+
1092
+ def panda_flush_cache(self):
1093
+ self.do_http_post(self.panda_url + "flushCacheDB",
1094
+ data={"site": self.sitename,
1095
+ "node": self.hostname})
1096
+
1097
+ def panda_add_cache_files(self, guids):
1098
+ self.do_http_post(self.panda_url + "addFilesToCacheDB",
1099
+ data={"site": self.sitename,
1100
+ "node": self.hostname,
1101
+ "guids": ','.join(guids)})
1102
+
1103
+ def panda_del_cache_files(self, guids):
1104
+ self.do_http_post(self.panda_url + "deleteFilesFromCacheDB",
1105
+ data={"site": self.sitename,
1106
+ "node": self.hostname,
1107
+ "guids": ','.join(guids)})
1108
+
1109
+ # Locking functions
1110
+ def lock_dir(self, d, create=True, blocking=True):
1111
+ lock_name = os.path.join(d, LOCK_NAME)
1112
+ lock_status = self.lock_file(lock_name, blocking)
1113
+ if (not lock_status): # succeeded
1114
+ return
1115
+ if ((lock_status == errno.ENOENT) and create):
1116
+ mkdir_status = self.mkdir_p(d)
1117
+ if (mkdir_status):
1118
+ self.log(ERROR, "mkdir %s %s", d, mkdir_status)
1119
+ self.fail(105)
1120
+ lock_status = self.lock_file(lock_name, blocking)
1121
+ return lock_status
1122
+
1123
+ def unlock_dir(self, d):
1124
+ return self.unlock_file(os.path.join(d, LOCK_NAME))
1125
+
1126
+ def lock_file(self, name, blocking=True):
1127
+ if name in self.locks:
1128
+ self.log(DEBUG, "lock_file: %s already locked", name)
1129
+ return
1130
+ try:
1131
+ f = open(name, 'w')
1132
+ except IOError as e:
1133
+ self.log(ERROR, "open: %s", e)
1134
+ return e.errno
1135
+
1136
+ self.locks[name] = f
1137
+ flag = fcntl.LOCK_EX
1138
+ if not blocking:
1139
+ flag |= fcntl.LOCK_NB
1140
+ while True:
1141
+ try:
1142
+ status = fcntl.lockf(f, flag)
1143
+ break
1144
+ except IOError as e:
1145
+ if e.errno in (errno.EAGAIN, errno.EACCES) and not blocking:
1146
+ f.close()
1147
+ del self.locks[name]
1148
+ return e.errno
1149
+ if e.errno != errno.EINTR:
1150
+ status = e.errno
1151
+ self.log(ERROR, "lockf: %s", e)
1152
+ self.fail(106)
1153
+ return status
1154
+
1155
+ def unlock_file(self, name):
1156
+ f = self.locks.get(name)
1157
+ if not f:
1158
+ self.log(DEBUG, "unlock_file: %s not locked", name)
1159
+ return
1160
+
1161
+ # XXXX does this create a possible race condition?
1162
+ if 0:
1163
+ try:
1164
+ os.unlink(name)
1165
+ except:
1166
+ pass
1167
+ status = fcntl.lockf(f, fcntl.LOCK_UN)
1168
+ f.close()
1169
+ del self.locks[name]
1170
+ return status
1171
+
1172
+ def unlock_all(self):
1173
+ for filename, f in list(self.locks.items()):
1174
+ try:
1175
+ f.close()
1176
+ os.unlink(filename)
1177
+ except:
1178
+ pass
1179
+
1180
+ # Cleanup functions
1181
+ def delete_file_and_parents(self, name):
1182
+ try:
1183
+ os.unlink(name)
1184
+ except OSError as e:
1185
+ if e.errno != errno.ENOENT:
1186
+ self.log(ERROR, "unlink: %s", e)
1187
+ self.fail(107)
1188
+ self.delete_parents_recursive(name)
1189
+
1190
+ def delete_parents_recursive(self, name): # internal
1191
+ try:
1192
+ dirname = os.path.dirname(name)
1193
+ if not os.listdir(dirname):
1194
+ os.rmdir(dirname)
1195
+ self.delete_parents_recursive(dirname)
1196
+ except OSError as e:
1197
+ self.log(DEBUG, "delete_parents_recursive: %s", e)
1198
+
1199
+ def update_mru(self):
1200
+ now = time.time()
1201
+ link_to_mru = self.pcache_dst_dir + "mru"
1202
+ if os.path.exists(link_to_mru):
1203
+ link = os.readlink(link_to_mru)
1204
+ self.delete_file_and_parents(link)
1205
+
1206
+ try:
1207
+ os.unlink(link_to_mru)
1208
+ except OSError as e:
1209
+ if e.errno != errno.ENOENT:
1210
+ self.log(ERROR, "unlink: %s", e)
1211
+ self.fail(108)
1212
+
1213
+ mru_dir = self.pcache_dir + "MRU/" + time.strftime("%Y/%m/%d/%H/%M/",
1214
+ time.localtime(now))
1215
+
1216
+ self.mkdir_p(mru_dir)
1217
+
1218
+ # getting symlink
1219
+ name = "%.3f" % (now % 60)
1220
+ ext = 0
1221
+ while True:
1222
+ if ext:
1223
+ link_from_mru = "%s%s-%s" % (mru_dir, name, ext)
1224
+ else:
1225
+ link_from_mru = "%s%s" % (mru_dir, name)
1226
+
1227
+ try:
1228
+ os.symlink(self.pcache_dst_dir, link_from_mru)
1229
+ break
1230
+ except OSError as e:
1231
+ if e.errno == errno.EEXIST:
1232
+ ext += 1 # add an extension & retry if file exists
1233
+ continue
1234
+ else:
1235
+ self.log(ERROR, "symlink: %s %s", e, link_from_mru)
1236
+ self.fail(109)
1237
+
1238
+ while True:
1239
+ try:
1240
+ os.symlink(link_from_mru, link_to_mru)
1241
+ break
1242
+ except OSError as e:
1243
+ if e.errno == errno.EEXIST:
1244
+ try:
1245
+ os.unlink(link_to_mru)
1246
+ except OSError as e:
1247
+ if e.errno != errno.ENOENT:
1248
+ self.log(ERROR, "unlink: %s %s", e, link_to_mru)
1249
+ self.fail(109)
1250
+ else:
1251
+ self.log(ERROR, "symlink: %s %s", e, link_from_mru)
1252
+ self.fail(109)
1253
+
1254
+ def cleanup_failed_transfer(self):
1255
+ try:
1256
+ os.unlink(self.pcache_dir + 'xfer')
1257
+ except:
1258
+ pass
1259
+
1260
+ def empty_dir(self, d):
1261
+ status = None
1262
+ bytes_deleted = 0
1263
+ for name in os.listdir(d):
1264
+ size = 0
1265
+ fullname = os.path.join(d, name)
1266
+ if name == "data":
1267
+ try:
1268
+ size = os.stat(fullname).st_size
1269
+ except OSError as e:
1270
+ if e.errno != errno.ENOENT:
1271
+ self.log(WARN, "stat: %s", e)
1272
+ elif name == "guid":
1273
+ try:
1274
+ guid = open(fullname).read().strip()
1275
+ self.deleted_guids.append(guid)
1276
+ except:
1277
+ pass # XXXX
1278
+ elif name == "mru" and os.path.islink(fullname):
1279
+ try:
1280
+ mru_file = os.readlink(fullname)
1281
+ os.unlink(fullname)
1282
+ self.delete_file_and_parents(mru_file)
1283
+ except OSError as e:
1284
+ if e.errno != errno.ENOENT:
1285
+ self.log(WARN, "empty_dir: %s", e)
1286
+ try:
1287
+ if self.debug:
1288
+ print(("UNLINK %s", fullname))
1289
+ os.unlink(fullname)
1290
+ bytes_deleted += size
1291
+ except OSError as e:
1292
+ if e.errno != errno.ENOENT:
1293
+ self.log(WARN, "empty_dir2: %s", e)
1294
+ # self.fail()
1295
+ self.update_cache_size(-bytes_deleted)
1296
+ self.delete_parents_recursive(d)
1297
+ return status
1298
+
1299
+ def chmod(self, path, mode):
1300
+ try:
1301
+ os.chmod(path, mode)
1302
+ except OSError as e:
1303
+ if e.errno != errno.EPERM: # Cannot chmod files we don't own!
1304
+ self.log(ERROR, "chmod %s %s", path, e)
1305
+
1306
+ def mkdir_p(self, d, mode=0o777):
1307
+ # Thread-safe
1308
+ try:
1309
+ os.makedirs(d, mode)
1310
+ return 0
1311
+ except OSError as e:
1312
+ if e.errno == errno.EEXIST:
1313
+ pass
1314
+ else:
1315
+ # Don't use log here, log dir may not exist
1316
+ sys.stderr.write("%s\n" % str(e))
1317
+ return e.errno
1318
+
1319
+ def log(self, level, msg, *args):
1320
+
1321
+ # Disable all logging
1322
+ if (self.quiet):
1323
+ return
1324
+
1325
+ if ((level == DEBUG) and (not self.debug)):
1326
+ return
1327
+
1328
+ msg = "%s %s %s %s %s\n" % (time.strftime("%F %H:%M:%S"),
1329
+ sessid,
1330
+ self.hostname,
1331
+ level,
1332
+ str(msg) % args)
1333
+
1334
+ try:
1335
+ f = open(self.log_file, "a+", 0o666)
1336
+ f.write(msg)
1337
+ f.close()
1338
+
1339
+ except Exception as e:
1340
+ sys.stderr.write("%s\n" % str(e))
1341
+ sys.stderr.write(msg)
1342
+ sys.stderr.flush()
1343
+
1344
+ if (self.debug or self.verbose or (level == ERROR)):
1345
+ sys.stderr.write(msg)
1346
+ sys.stderr.flush()
1347
+
1348
+ def fail(self, errcode=1):
1349
+ self.unlock_all()
1350
+ sys.exit(errcode)
1351
+
1352
+ ##################################################################################
1353
+
1354
+ # pCache exit_status will be
1355
+ #
1356
+ # 0 - File was transferred into cache and is ready
1357
+ # 1 - File is cached and ready to use
1358
+ # 2 - File was transferred but with a retry (copy_status has the retry count)
1359
+ # 3 - Transfer command failed (copy_status has the transfer return code)
1360
+ # 4 - Transfer command timed out
1361
+ #
1362
+ # 100 - Usage error
1363
+ # 101 - Cache directory does not exist
1364
+ # 102 - Cache hard link error
1365
+ # 103 - Cache destination mkdir error
1366
+ # 104 - Cache rename error
1367
+ # 105 - Cache locking error
1368
+ # 106 - Cache file locking error
1369
+ # 107 - Cache cleanup error
1370
+ # 108 - Cache MRU update error
1371
+ # 109 - Cache MRU link error
1372
+ # 500 - Is file in pcache? No other action
1373
+
1374
+
1375
+ if (__name__ == "__main__"):
1376
+
1377
+ # Load pCache
1378
+ p = Pcache()
1379
+
1380
+ # Save the passed arguments
1381
+ args = sys.argv
1382
+
1383
+ # Find the file
1384
+ exit_status, copy_status = p.main(args)
1385
+
1386
+ # Take us home percy...
1387
+ sys.exit(exit_status)