vortex-nwp 2.0.0b1__py3-none-any.whl → 2.1.0__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.
- vortex/__init__.py +75 -47
- vortex/algo/__init__.py +3 -2
- vortex/algo/components.py +944 -618
- vortex/algo/mpitools.py +802 -497
- vortex/algo/mpitools_templates/__init__.py +1 -0
- vortex/algo/serversynctools.py +34 -33
- vortex/config.py +19 -22
- vortex/data/__init__.py +9 -3
- vortex/data/abstractstores.py +593 -655
- vortex/data/containers.py +217 -162
- vortex/data/contents.py +65 -39
- vortex/data/executables.py +93 -102
- vortex/data/flow.py +40 -34
- vortex/data/geometries.py +228 -132
- vortex/data/handlers.py +436 -227
- vortex/data/outflow.py +15 -15
- vortex/data/providers.py +185 -163
- vortex/data/resources.py +48 -42
- vortex/data/stores.py +540 -417
- vortex/data/sync_templates/__init__.py +0 -0
- vortex/gloves.py +114 -87
- vortex/layout/__init__.py +1 -8
- vortex/layout/contexts.py +150 -84
- vortex/layout/dataflow.py +353 -202
- vortex/layout/monitor.py +264 -128
- vortex/nwp/__init__.py +5 -2
- vortex/nwp/algo/__init__.py +14 -5
- vortex/nwp/algo/assim.py +205 -151
- vortex/nwp/algo/clim.py +683 -517
- vortex/nwp/algo/coupling.py +447 -225
- vortex/nwp/algo/eda.py +437 -229
- vortex/nwp/algo/eps.py +403 -231
- vortex/nwp/algo/forecasts.py +416 -275
- vortex/nwp/algo/fpserver.py +683 -307
- vortex/nwp/algo/ifsnaming.py +205 -145
- vortex/nwp/algo/ifsroot.py +215 -122
- vortex/nwp/algo/monitoring.py +137 -76
- vortex/nwp/algo/mpitools.py +330 -190
- vortex/nwp/algo/odbtools.py +637 -353
- vortex/nwp/algo/oopsroot.py +454 -273
- vortex/nwp/algo/oopstests.py +90 -56
- vortex/nwp/algo/request.py +287 -206
- vortex/nwp/algo/stdpost.py +878 -522
- vortex/nwp/data/__init__.py +22 -4
- vortex/nwp/data/assim.py +125 -137
- vortex/nwp/data/boundaries.py +121 -68
- vortex/nwp/data/climfiles.py +193 -211
- vortex/nwp/data/configfiles.py +73 -69
- vortex/nwp/data/consts.py +426 -401
- vortex/nwp/data/ctpini.py +59 -43
- vortex/nwp/data/diagnostics.py +94 -66
- vortex/nwp/data/eda.py +50 -51
- vortex/nwp/data/eps.py +195 -146
- vortex/nwp/data/executables.py +440 -434
- vortex/nwp/data/fields.py +63 -48
- vortex/nwp/data/gridfiles.py +183 -111
- vortex/nwp/data/logs.py +250 -217
- vortex/nwp/data/modelstates.py +180 -151
- vortex/nwp/data/monitoring.py +72 -99
- vortex/nwp/data/namelists.py +254 -202
- vortex/nwp/data/obs.py +400 -308
- vortex/nwp/data/oopsexec.py +22 -20
- vortex/nwp/data/providers.py +90 -65
- vortex/nwp/data/query.py +71 -82
- vortex/nwp/data/stores.py +49 -36
- vortex/nwp/data/surfex.py +136 -137
- vortex/nwp/syntax/__init__.py +1 -1
- vortex/nwp/syntax/stdattrs.py +173 -111
- vortex/nwp/tools/__init__.py +2 -2
- vortex/nwp/tools/addons.py +22 -17
- vortex/nwp/tools/agt.py +24 -12
- vortex/nwp/tools/bdap.py +16 -5
- vortex/nwp/tools/bdcp.py +4 -1
- vortex/nwp/tools/bdm.py +3 -0
- vortex/nwp/tools/bdmp.py +14 -9
- vortex/nwp/tools/conftools.py +728 -378
- vortex/nwp/tools/drhook.py +12 -8
- vortex/nwp/tools/grib.py +65 -39
- vortex/nwp/tools/gribdiff.py +22 -17
- vortex/nwp/tools/ifstools.py +82 -42
- vortex/nwp/tools/igastuff.py +167 -143
- vortex/nwp/tools/mars.py +14 -2
- vortex/nwp/tools/odb.py +234 -125
- vortex/nwp/tools/partitioning.py +61 -37
- vortex/nwp/tools/satrad.py +27 -12
- vortex/nwp/util/async.py +83 -55
- vortex/nwp/util/beacon.py +10 -10
- vortex/nwp/util/diffpygram.py +174 -86
- vortex/nwp/util/ens.py +144 -63
- vortex/nwp/util/hooks.py +30 -19
- vortex/nwp/util/taskdeco.py +28 -24
- vortex/nwp/util/usepygram.py +278 -172
- vortex/nwp/util/usetnt.py +31 -17
- vortex/sessions.py +72 -39
- vortex/syntax/__init__.py +1 -1
- vortex/syntax/stdattrs.py +410 -171
- vortex/syntax/stddeco.py +31 -22
- vortex/toolbox.py +327 -192
- vortex/tools/__init__.py +11 -2
- vortex/tools/actions.py +110 -121
- vortex/tools/addons.py +111 -92
- vortex/tools/arm.py +42 -22
- vortex/tools/compression.py +72 -69
- vortex/tools/date.py +11 -4
- vortex/tools/delayedactions.py +242 -132
- vortex/tools/env.py +75 -47
- vortex/tools/folder.py +342 -171
- vortex/tools/grib.py +341 -162
- vortex/tools/lfi.py +423 -216
- vortex/tools/listings.py +109 -40
- vortex/tools/names.py +218 -156
- vortex/tools/net.py +655 -299
- vortex/tools/parallelism.py +93 -61
- vortex/tools/prestaging.py +55 -31
- vortex/tools/schedulers.py +172 -105
- vortex/tools/services.py +403 -334
- vortex/tools/storage.py +293 -358
- vortex/tools/surfex.py +24 -24
- vortex/tools/systems.py +1234 -643
- vortex/tools/targets.py +156 -100
- vortex/util/__init__.py +1 -1
- vortex/util/config.py +378 -327
- vortex/util/empty.py +2 -2
- vortex/util/helpers.py +56 -24
- vortex/util/introspection.py +18 -12
- vortex/util/iosponge.py +8 -4
- vortex/util/roles.py +4 -6
- vortex/util/storefunctions.py +39 -13
- vortex/util/structs.py +3 -3
- vortex/util/worker.py +29 -17
- vortex_nwp-2.1.0.dist-info/METADATA +67 -0
- vortex_nwp-2.1.0.dist-info/RECORD +144 -0
- {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.1.0.dist-info}/WHEEL +1 -1
- vortex/layout/appconf.py +0 -109
- vortex/layout/jobs.py +0 -1276
- vortex/layout/nodes.py +0 -1424
- vortex/layout/subjobs.py +0 -464
- vortex_nwp-2.0.0b1.dist-info/METADATA +0 -50
- vortex_nwp-2.0.0b1.dist-info/RECORD +0 -146
- {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.1.0.dist-info/licenses}/LICENSE +0 -0
- {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.1.0.dist-info}/top_level.txt +0 -0
vortex/tools/storage.py
CHANGED
|
@@ -27,13 +27,12 @@ aspects. Using the :mod:`footprints` package, for a given execution target, it
|
|
|
27
27
|
allows to customise the way data are accessed leaving the :class:`Store` objects
|
|
28
28
|
unchanged.
|
|
29
29
|
"""
|
|
30
|
+
|
|
30
31
|
import contextlib
|
|
31
32
|
import ftplib
|
|
32
33
|
import re
|
|
33
34
|
import time
|
|
34
|
-
from collections import defaultdict
|
|
35
35
|
from datetime import datetime
|
|
36
|
-
import os
|
|
37
36
|
|
|
38
37
|
import footprints
|
|
39
38
|
from bronx.fancies import loggers
|
|
@@ -42,9 +41,7 @@ from bronx.syntax.decorators import nicedeco
|
|
|
42
41
|
from vortex import sessions
|
|
43
42
|
from vortex.tools.actions import actiond as ad
|
|
44
43
|
from vortex.tools.delayedactions import d_action_status
|
|
45
|
-
|
|
46
|
-
# TODO clean instances of GenericConfigParser
|
|
47
|
-
from vortex.util.config import GenericConfigParser
|
|
44
|
+
|
|
48
45
|
from vortex import config
|
|
49
46
|
|
|
50
47
|
#: No automatic export
|
|
@@ -60,6 +57,7 @@ HARDLINK_THRESHOLD = 1048576
|
|
|
60
57
|
# Decorators: for internal use in the Storage class
|
|
61
58
|
# -------------------------------------------------
|
|
62
59
|
|
|
60
|
+
|
|
63
61
|
def do_recording(flag):
|
|
64
62
|
"""Add a record line in the History object (if sensible)."""
|
|
65
63
|
|
|
@@ -92,6 +90,7 @@ def enforce_readonly(f):
|
|
|
92
90
|
# Main Storage abstract class
|
|
93
91
|
# ---------------------------
|
|
94
92
|
|
|
93
|
+
|
|
95
94
|
class Storage(footprints.FootprintBase):
|
|
96
95
|
"""Root class for any Storage class, ex: Cache, Archive, ...
|
|
97
96
|
|
|
@@ -111,14 +110,10 @@ class Storage(footprints.FootprintBase):
|
|
|
111
110
|
dictionary whose items will be written in the object's record.
|
|
112
111
|
"""
|
|
113
112
|
|
|
114
|
-
_abstract = True,
|
|
113
|
+
_abstract = (True,)
|
|
115
114
|
_footprint = dict(
|
|
116
|
-
info
|
|
117
|
-
attr
|
|
118
|
-
kind=dict(
|
|
119
|
-
info="The storage place's kind.",
|
|
120
|
-
values=['std'],
|
|
121
|
-
),
|
|
115
|
+
info="Default/Abstract storage place description.",
|
|
116
|
+
attr=dict(
|
|
122
117
|
storage=dict(
|
|
123
118
|
info="The storage target.",
|
|
124
119
|
),
|
|
@@ -127,7 +122,7 @@ class Storage(footprints.FootprintBase):
|
|
|
127
122
|
type=bool,
|
|
128
123
|
optional=True,
|
|
129
124
|
default=False,
|
|
130
|
-
access=
|
|
125
|
+
access="rwx",
|
|
131
126
|
),
|
|
132
127
|
readonly=dict(
|
|
133
128
|
info="Disallow insert and delete action for this storage place.",
|
|
@@ -135,11 +130,11 @@ class Storage(footprints.FootprintBase):
|
|
|
135
130
|
optional=True,
|
|
136
131
|
default=False,
|
|
137
132
|
),
|
|
138
|
-
)
|
|
133
|
+
),
|
|
139
134
|
)
|
|
140
135
|
|
|
141
136
|
def __init__(self, *args, **kw):
|
|
142
|
-
logger.debug(
|
|
137
|
+
logger.debug("Abstract storage init %s", self.__class__)
|
|
143
138
|
super().__init__(*args, **kw)
|
|
144
139
|
self._history = History(tag=self.tag)
|
|
145
140
|
|
|
@@ -150,10 +145,10 @@ class Storage(footprints.FootprintBase):
|
|
|
150
145
|
|
|
151
146
|
@property
|
|
152
147
|
def realkind(self):
|
|
153
|
-
return
|
|
148
|
+
return "storage"
|
|
154
149
|
|
|
155
150
|
def _str_more(self):
|
|
156
|
-
return
|
|
151
|
+
return "tag={:s}".format(self.tag)
|
|
157
152
|
|
|
158
153
|
@property
|
|
159
154
|
def context(self):
|
|
@@ -239,7 +234,7 @@ class Storage(footprints.FootprintBase):
|
|
|
239
234
|
return rc
|
|
240
235
|
|
|
241
236
|
@enforce_readonly
|
|
242
|
-
@do_recording(
|
|
237
|
+
@do_recording("INSERT")
|
|
243
238
|
def insert(self, item, local, **kwargs):
|
|
244
239
|
"""Insert an **item** in the current storage place.
|
|
245
240
|
|
|
@@ -247,7 +242,7 @@ class Storage(footprints.FootprintBase):
|
|
|
247
242
|
"""
|
|
248
243
|
return self._actual_insert(item, local, **kwargs)
|
|
249
244
|
|
|
250
|
-
@do_recording(
|
|
245
|
+
@do_recording("RETRIEVE")
|
|
251
246
|
def retrieve(self, item, local, **kwargs):
|
|
252
247
|
"""Retrieve an **item** from the current storage place.
|
|
253
248
|
|
|
@@ -271,19 +266,23 @@ class Storage(footprints.FootprintBase):
|
|
|
271
266
|
|
|
272
267
|
:note: **local** may be a path to a file or any kind of file like objects.
|
|
273
268
|
"""
|
|
274
|
-
rc, idict = self._actual_finaliseretrieve(
|
|
269
|
+
rc, idict = self._actual_finaliseretrieve(
|
|
270
|
+
retrieve_id, item, local, **kwargs
|
|
271
|
+
)
|
|
275
272
|
if rc is not None:
|
|
276
273
|
infos = self._findout_record_infos(kwargs)
|
|
277
274
|
infos.update(idict)
|
|
278
|
-
self.addrecord(
|
|
275
|
+
self.addrecord("RETRIEVE", item, status=rc, **infos)
|
|
279
276
|
return rc
|
|
280
277
|
|
|
281
|
-
def _actual_finaliseretrieve(
|
|
278
|
+
def _actual_finaliseretrieve(
|
|
279
|
+
self, retrieve_id, item, local, **kwargs
|
|
280
|
+
): # @UnusedVariable
|
|
282
281
|
"""No delayedretrieve implemented by default."""
|
|
283
282
|
return None, dict()
|
|
284
283
|
|
|
285
284
|
@enforce_readonly
|
|
286
|
-
@do_recording(
|
|
285
|
+
@do_recording("DELETE")
|
|
287
286
|
def delete(self, item, **kwargs):
|
|
288
287
|
"""Delete an **item** from the current storage place."""
|
|
289
288
|
return self._actual_delete(item, **kwargs)
|
|
@@ -292,44 +291,46 @@ class Storage(footprints.FootprintBase):
|
|
|
292
291
|
# Defining the two main flavours of storage places
|
|
293
292
|
# -----------------------------------------------
|
|
294
293
|
|
|
294
|
+
|
|
295
295
|
class Cache(Storage):
|
|
296
296
|
"""Root class for any :class:Cache subclasses."""
|
|
297
297
|
|
|
298
|
-
|
|
299
|
-
_collector = ('cache',)
|
|
298
|
+
_collector = ("cache",)
|
|
300
299
|
_footprint = dict(
|
|
301
|
-
info
|
|
302
|
-
attr
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
300
|
+
info="Default cache description",
|
|
301
|
+
attr=dict(
|
|
302
|
+
entry=dict(
|
|
303
|
+
optional=False,
|
|
304
|
+
type=str,
|
|
305
|
+
info="The absolute path to the cache space",
|
|
307
306
|
),
|
|
308
307
|
# TODO is 'storage' used in any way?
|
|
309
|
-
storage
|
|
310
|
-
optional
|
|
311
|
-
default
|
|
308
|
+
storage=dict(
|
|
309
|
+
optional=True,
|
|
310
|
+
default="localhost",
|
|
312
311
|
),
|
|
313
|
-
rtouch
|
|
314
|
-
info
|
|
315
|
-
type
|
|
316
|
-
optional
|
|
317
|
-
default
|
|
312
|
+
rtouch=dict(
|
|
313
|
+
info="Perform the recursive touch command on the directory structure.",
|
|
314
|
+
type=bool,
|
|
315
|
+
optional=True,
|
|
316
|
+
default=False,
|
|
318
317
|
),
|
|
319
|
-
rtouchskip
|
|
320
|
-
info
|
|
321
|
-
type
|
|
322
|
-
optional
|
|
323
|
-
default
|
|
318
|
+
rtouchskip=dict(
|
|
319
|
+
info="Do not 'touch' the first **rtouchskip** directories.",
|
|
320
|
+
type=int,
|
|
321
|
+
optional=True,
|
|
322
|
+
default=0,
|
|
324
323
|
),
|
|
325
|
-
rtouchdelay
|
|
326
|
-
info
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
324
|
+
rtouchdelay=dict(
|
|
325
|
+
info=(
|
|
326
|
+
"Do not perfom a touch if it has already been done in "
|
|
327
|
+
+ "the last X seconds."
|
|
328
|
+
),
|
|
329
|
+
type=float,
|
|
330
|
+
optional=True,
|
|
331
|
+
default=600.0, # 10 minutes
|
|
331
332
|
),
|
|
332
|
-
)
|
|
333
|
+
),
|
|
333
334
|
)
|
|
334
335
|
|
|
335
336
|
def __init__(self, *kargs, **kwargs):
|
|
@@ -338,22 +339,24 @@ class Cache(Storage):
|
|
|
338
339
|
|
|
339
340
|
@property
|
|
340
341
|
def realkind(self):
|
|
341
|
-
return
|
|
342
|
+
return "cache"
|
|
342
343
|
|
|
343
344
|
@property
|
|
344
345
|
def tag(self):
|
|
345
346
|
"""The identifier of this cache place."""
|
|
346
|
-
return
|
|
347
|
+
return "{:s}_{:s}".format(self.realkind, self.entry)
|
|
347
348
|
|
|
348
349
|
def _formatted_path(self, subpath, **kwargs): # @UnusedVariable
|
|
349
|
-
|
|
350
|
+
return self.sh.path.join(self.entry, subpath.lstrip("/"))
|
|
350
351
|
|
|
351
352
|
def catalog(self):
|
|
352
353
|
"""List all files present in this cache.
|
|
353
354
|
|
|
354
355
|
:note: It might be quite slow...
|
|
355
356
|
"""
|
|
356
|
-
|
|
357
|
+
entry = self.sh.path.expanduser(self.entry)
|
|
358
|
+
files = self.sh.ffind(entry)
|
|
359
|
+
return [f[len(entry) :] for f in files]
|
|
357
360
|
|
|
358
361
|
def _xtouch(self, path):
|
|
359
362
|
"""
|
|
@@ -363,11 +366,11 @@ class Cache(Storage):
|
|
|
363
366
|
ts = time.time()
|
|
364
367
|
ts_delay = ts - self._touch_tracker.get(path, 0)
|
|
365
368
|
if ts_delay > self.rtouchdelay:
|
|
366
|
-
logger.debug(
|
|
369
|
+
logger.debug("Touching: %s (delay was %.2f)", path, ts_delay)
|
|
367
370
|
self.sh.touch(path)
|
|
368
371
|
self._touch_tracker[path] = ts
|
|
369
372
|
else:
|
|
370
|
-
logger.debug(
|
|
373
|
+
logger.debug("Skipping touch: %s (delay was %.2f)", path, ts_delay)
|
|
371
374
|
|
|
372
375
|
def _recursive_touch(self, rc, item, writing=False):
|
|
373
376
|
"""Make recursive touches on parent directories.
|
|
@@ -375,13 +378,15 @@ class Cache(Storage):
|
|
|
375
378
|
It might be useful for cleaning scripts.
|
|
376
379
|
"""
|
|
377
380
|
if self.rtouch and (not self.readonly) and rc:
|
|
378
|
-
items = item.lstrip(
|
|
381
|
+
items = item.lstrip("/").split("/")
|
|
379
382
|
items = items[:-1]
|
|
380
383
|
if writing:
|
|
381
384
|
# It's useless to touch the rightmost directory
|
|
382
385
|
items = items[:-1] if len(items) > 1 else []
|
|
383
386
|
for index in range(len(items), self.rtouchskip, -1):
|
|
384
|
-
self._xtouch(
|
|
387
|
+
self._xtouch(
|
|
388
|
+
self._formatted_path(self.sh.path.join(*items[:index]))
|
|
389
|
+
)
|
|
385
390
|
|
|
386
391
|
def _actual_fullpath(self, item, **kwargs):
|
|
387
392
|
"""Return the path/URI to the **item**'s storage location."""
|
|
@@ -389,8 +394,9 @@ class Cache(Storage):
|
|
|
389
394
|
|
|
390
395
|
def _actual_prestageinfo(self, item, **kwargs):
|
|
391
396
|
"""Returns pre-staging informations."""
|
|
392
|
-
return dict(
|
|
393
|
-
|
|
397
|
+
return dict(
|
|
398
|
+
strategy="std", location=self.fullpath(item, **kwargs)
|
|
399
|
+
), dict()
|
|
394
400
|
|
|
395
401
|
def _actual_check(self, item, **kwargs):
|
|
396
402
|
"""Check/Stat an **item** from the current storage place."""
|
|
@@ -421,11 +427,18 @@ class Cache(Storage):
|
|
|
421
427
|
fmt = kwargs.get("fmt", "foo")
|
|
422
428
|
# Insert the element
|
|
423
429
|
tpath = self._formatted_path(item)
|
|
430
|
+
if not self.sh.path.exists(self.entry):
|
|
431
|
+
self.sh.mkdir(self.entry)
|
|
424
432
|
if tpath is not None:
|
|
425
|
-
rc = self.sh.cp(
|
|
426
|
-
|
|
433
|
+
rc = self.sh.cp(
|
|
434
|
+
local,
|
|
435
|
+
tpath,
|
|
436
|
+
intent=intent,
|
|
437
|
+
fmt=fmt,
|
|
438
|
+
smartcp_threshold=HARDLINK_THRESHOLD,
|
|
439
|
+
)
|
|
427
440
|
else:
|
|
428
|
-
logger.warning(
|
|
441
|
+
logger.warning("No target location for < %s >", item)
|
|
429
442
|
rc = False
|
|
430
443
|
self._recursive_touch(rc, item, writing=True)
|
|
431
444
|
return rc, dict(intent=intent, fmt=fmt)
|
|
@@ -442,30 +455,55 @@ class Cache(Storage):
|
|
|
442
455
|
source = self._formatted_path(item)
|
|
443
456
|
if source is not None:
|
|
444
457
|
# If auto_dirextract, copy recursively each file contained in source
|
|
445
|
-
if
|
|
458
|
+
if (
|
|
459
|
+
dirextract
|
|
460
|
+
and self.sh.path.isdir(source)
|
|
461
|
+
and self.sh.is_tarname(local)
|
|
462
|
+
):
|
|
446
463
|
rc = True
|
|
447
464
|
destdir = self.sh.path.dirname(self.sh.path.realpath(local))
|
|
448
|
-
logger.info(
|
|
449
|
-
for subpath in self.sh.glob(source +
|
|
450
|
-
rc = rc and self.sh.cp(
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
465
|
+
logger.info("Automatic directory extract to: %s", destdir)
|
|
466
|
+
for subpath in self.sh.glob(source + "/*"):
|
|
467
|
+
rc = rc and self.sh.cp(
|
|
468
|
+
subpath,
|
|
469
|
+
self.sh.path.join(
|
|
470
|
+
destdir, self.sh.path.basename(subpath)
|
|
471
|
+
),
|
|
472
|
+
intent=intent,
|
|
473
|
+
fmt=fmt,
|
|
474
|
+
smartcp_threshold=HARDLINK_THRESHOLD,
|
|
475
|
+
)
|
|
454
476
|
# For the insitu feature to work...
|
|
455
477
|
rc = rc and self.sh.touch(local)
|
|
456
478
|
# The usual case: just copy source
|
|
457
479
|
else:
|
|
458
|
-
rc = self.sh.cp(
|
|
459
|
-
|
|
480
|
+
rc = self.sh.cp(
|
|
481
|
+
source,
|
|
482
|
+
local,
|
|
483
|
+
intent=intent,
|
|
484
|
+
fmt=fmt,
|
|
485
|
+
silent=silent,
|
|
486
|
+
smartcp_threshold=HARDLINK_THRESHOLD,
|
|
487
|
+
)
|
|
460
488
|
# If auto_tarextract, a potential tar file is extracted
|
|
461
|
-
if (
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
489
|
+
if (
|
|
490
|
+
rc
|
|
491
|
+
and tarextract
|
|
492
|
+
and not self.sh.path.isdir(local)
|
|
493
|
+
and self.sh.is_tarname(local)
|
|
494
|
+
and self.sh.is_tarfile(local)
|
|
495
|
+
):
|
|
496
|
+
destdir = self.sh.path.dirname(
|
|
497
|
+
self.sh.path.realpath(local)
|
|
498
|
+
)
|
|
499
|
+
logger.info("Automatic Tar extract to: %s", destdir)
|
|
500
|
+
rc = rc and self.sh.smartuntar(
|
|
501
|
+
local, destdir, uniquelevel_ignore=uniquelevel_ignore
|
|
502
|
+
)
|
|
467
503
|
else:
|
|
468
|
-
getattr(logger,
|
|
504
|
+
getattr(logger, "info" if silent else "warning")(
|
|
505
|
+
"No readable source for < %s >", item
|
|
506
|
+
)
|
|
469
507
|
rc = False
|
|
470
508
|
self._recursive_touch(rc, item)
|
|
471
509
|
return rc, dict(intent=intent, fmt=fmt)
|
|
@@ -479,43 +517,60 @@ class Cache(Storage):
|
|
|
479
517
|
if tpath is not None:
|
|
480
518
|
rc = self.sh.remove(tpath, fmt=fmt)
|
|
481
519
|
else:
|
|
482
|
-
logger.warning(
|
|
520
|
+
logger.warning("No target location for < %s >", item)
|
|
483
521
|
rc = False
|
|
484
522
|
return rc, dict(fmt=fmt)
|
|
485
523
|
|
|
524
|
+
def flush(self, dumpfile=None):
|
|
525
|
+
"""Flush actual history to the specified ``dumpfile`` if record is on."""
|
|
526
|
+
if dumpfile is None:
|
|
527
|
+
logfile = ".".join(
|
|
528
|
+
(
|
|
529
|
+
"HISTORY",
|
|
530
|
+
datetime.now().strftime("%Y%m%d%H%M%S.%f"),
|
|
531
|
+
"P{:06d}".format(self.sh.getpid()),
|
|
532
|
+
self.sh.getlogname(),
|
|
533
|
+
)
|
|
534
|
+
)
|
|
535
|
+
dumpfile = self.sh.path.join(self.entry, ".history", logfile)
|
|
536
|
+
if self.record:
|
|
537
|
+
self.sh.pickle_dump(self.history, dumpfile)
|
|
538
|
+
|
|
486
539
|
|
|
487
540
|
class AbstractArchive(Storage):
|
|
488
541
|
"""The default class to handle storage to some kind if Archive."""
|
|
489
542
|
|
|
490
543
|
_abstract = True
|
|
491
|
-
_collector = (
|
|
544
|
+
_collector = ("archive",)
|
|
492
545
|
_footprint = dict(
|
|
493
|
-
info
|
|
494
|
-
attr
|
|
495
|
-
tube
|
|
496
|
-
info
|
|
546
|
+
info="Default archive description",
|
|
547
|
+
attr=dict(
|
|
548
|
+
tube=dict(
|
|
549
|
+
info="How to communicate with the archive ?",
|
|
497
550
|
),
|
|
498
|
-
)
|
|
551
|
+
),
|
|
499
552
|
)
|
|
500
553
|
|
|
501
554
|
@property
|
|
502
555
|
def tag(self):
|
|
503
556
|
"""The identifier of this cache place."""
|
|
504
|
-
return
|
|
557
|
+
return "{:s}_{:s}".format(self.realkind, self.storage)
|
|
505
558
|
|
|
506
559
|
@property
|
|
507
560
|
def realkind(self):
|
|
508
|
-
return
|
|
561
|
+
return "archive"
|
|
509
562
|
|
|
510
563
|
def _formatted_path(self, rawpath, **kwargs):
|
|
511
|
-
root = kwargs.get(
|
|
564
|
+
root = kwargs.get("root", None)
|
|
512
565
|
if root is not None:
|
|
513
|
-
rawpath = self.sh.path.join(root, rawpath.lstrip(
|
|
566
|
+
rawpath = self.sh.path.join(root, rawpath.lstrip("/"))
|
|
514
567
|
# Deal with compression
|
|
515
|
-
compressionpipeline = kwargs.get(
|
|
568
|
+
compressionpipeline = kwargs.get("compressionpipeline", None)
|
|
516
569
|
if compressionpipeline is not None:
|
|
517
570
|
rawpath += compressionpipeline.suffix
|
|
518
|
-
return self.sh.anyft_remote_rewrite(
|
|
571
|
+
return self.sh.anyft_remote_rewrite(
|
|
572
|
+
rawpath, fmt=kwargs.get("fmt", "foo")
|
|
573
|
+
)
|
|
519
574
|
|
|
520
575
|
def _actual_proxy_method(self, pmethod):
|
|
521
576
|
"""Create a proxy method based on the **pmethod** actual method."""
|
|
@@ -532,18 +587,23 @@ class AbstractArchive(Storage):
|
|
|
532
587
|
|
|
533
588
|
def __getattr__(self, attr):
|
|
534
589
|
"""Provides proxy methods for _actual_* methods."""
|
|
535
|
-
methods = r
|
|
536
|
-
mattr = re.match(r
|
|
590
|
+
methods = r"fullpath|prestageinfo|check|list|insert|retrieve|delete"
|
|
591
|
+
mattr = re.match(r"_actual_(?P<action>" + methods + r")", attr)
|
|
537
592
|
if mattr:
|
|
538
|
-
pmethod = getattr(
|
|
593
|
+
pmethod = getattr(
|
|
594
|
+
self, "_{:s}{:s}".format(self.tube, mattr.group("action"))
|
|
595
|
+
)
|
|
539
596
|
return self._actual_proxy_method(pmethod)
|
|
540
597
|
else:
|
|
541
|
-
raise AttributeError(
|
|
542
|
-
|
|
598
|
+
raise AttributeError(
|
|
599
|
+
"The {:s} attribute was not found in this object".format(attr)
|
|
600
|
+
)
|
|
543
601
|
|
|
544
602
|
def _actual_earlyretrieve(self, item, local, **kwargs):
|
|
545
603
|
"""Proxy to the appropriate tube dependent earlyretrieve method (if available)."""
|
|
546
|
-
pmethod = getattr(
|
|
604
|
+
pmethod = getattr(
|
|
605
|
+
self, "_{:s}{:s}".format(self.tube, "earlyretrieve"), None
|
|
606
|
+
)
|
|
547
607
|
if pmethod:
|
|
548
608
|
return self._actual_proxy_method(pmethod)(item, local, **kwargs)
|
|
549
609
|
else:
|
|
@@ -551,9 +611,13 @@ class AbstractArchive(Storage):
|
|
|
551
611
|
|
|
552
612
|
def _actual_finaliseretrieve(self, retrieve_id, item, local, **kwargs):
|
|
553
613
|
"""Proxy to the appropriate tube dependent finaliseretrieve method (if available)."""
|
|
554
|
-
pmethod = getattr(
|
|
614
|
+
pmethod = getattr(
|
|
615
|
+
self, "_{:s}{:s}".format(self.tube, "finaliseretrieve"), None
|
|
616
|
+
)
|
|
555
617
|
if pmethod:
|
|
556
|
-
return self._actual_proxy_method(pmethod)(
|
|
618
|
+
return self._actual_proxy_method(pmethod)(
|
|
619
|
+
item, local, retrieve_id, **kwargs
|
|
620
|
+
)
|
|
557
621
|
else:
|
|
558
622
|
return None, dict()
|
|
559
623
|
|
|
@@ -562,41 +626,46 @@ class Archive(AbstractArchive):
|
|
|
562
626
|
"""The default class to handle storage to a remote location."""
|
|
563
627
|
|
|
564
628
|
_footprint = dict(
|
|
565
|
-
info
|
|
566
|
-
attr
|
|
567
|
-
tube
|
|
568
|
-
values
|
|
629
|
+
info="Default archive description",
|
|
630
|
+
attr=dict(
|
|
631
|
+
tube=dict(
|
|
632
|
+
values=["ftp"],
|
|
569
633
|
),
|
|
570
|
-
)
|
|
634
|
+
),
|
|
571
635
|
)
|
|
572
636
|
|
|
573
637
|
def __init__(self, *kargs, **kwargs):
|
|
574
638
|
super().__init__(*kargs, **kwargs)
|
|
575
639
|
self.default_usejeeves = config.from_config(
|
|
576
|
-
section="storage",
|
|
640
|
+
section="storage",
|
|
641
|
+
key="usejeeves",
|
|
577
642
|
)
|
|
578
643
|
|
|
579
644
|
@property
|
|
580
645
|
def _ftp_hostinfos(self):
|
|
581
646
|
"""Return the FTP hostname end port number."""
|
|
582
|
-
s_storage = self.storage.split(
|
|
647
|
+
s_storage = self.storage.split(":", 1)
|
|
583
648
|
hostname = s_storage[0]
|
|
584
649
|
port = None
|
|
585
650
|
if len(s_storage) > 1:
|
|
586
651
|
try:
|
|
587
652
|
port = int(s_storage[1])
|
|
588
653
|
except ValueError:
|
|
589
|
-
logger.error(
|
|
654
|
+
logger.error(
|
|
655
|
+
"Invalid port number < %s >. Ignoring it", s_storage[1]
|
|
656
|
+
)
|
|
590
657
|
return hostname, port
|
|
591
658
|
|
|
592
659
|
def _ftp_client(self, logname=None, delayed=False):
|
|
593
660
|
"""Return a FTP client object."""
|
|
594
661
|
hostname, port = self._ftp_hostinfos
|
|
595
|
-
return self.sh.ftp(
|
|
662
|
+
return self.sh.ftp(
|
|
663
|
+
hostname, logname=logname, delayed=delayed, port=port
|
|
664
|
+
)
|
|
596
665
|
|
|
597
666
|
def _ftpfullpath(self, item, **kwargs):
|
|
598
667
|
"""Actual _fullpath using ftp."""
|
|
599
|
-
username = kwargs.get(
|
|
668
|
+
username = kwargs.get("username", None)
|
|
600
669
|
rc = None
|
|
601
670
|
ftp = self._ftp_client(logname=username, delayed=True)
|
|
602
671
|
if ftp:
|
|
@@ -608,7 +677,7 @@ class Archive(AbstractArchive):
|
|
|
608
677
|
|
|
609
678
|
def _ftpprestageinfo(self, item, **kwargs):
|
|
610
679
|
"""Actual _prestageinfo using ftp."""
|
|
611
|
-
username = kwargs.get(
|
|
680
|
+
username = kwargs.get("username", None)
|
|
612
681
|
if username is None:
|
|
613
682
|
ftp = self._ftp_client(logname=username, delayed=True)
|
|
614
683
|
if ftp:
|
|
@@ -616,15 +685,17 @@ class Archive(AbstractArchive):
|
|
|
616
685
|
username = ftp.logname
|
|
617
686
|
finally:
|
|
618
687
|
ftp.close()
|
|
619
|
-
baseinfo = dict(
|
|
620
|
-
|
|
621
|
-
|
|
688
|
+
baseinfo = dict(
|
|
689
|
+
storage=self.storage,
|
|
690
|
+
logname=username,
|
|
691
|
+
location=item,
|
|
692
|
+
)
|
|
622
693
|
return baseinfo, dict()
|
|
623
694
|
|
|
624
695
|
def _ftpcheck(self, item, **kwargs):
|
|
625
696
|
"""Actual _check using ftp."""
|
|
626
697
|
rc = None
|
|
627
|
-
ftp = self._ftp_client(logname=kwargs.get(
|
|
698
|
+
ftp = self._ftp_client(logname=kwargs.get("username", None))
|
|
628
699
|
if ftp:
|
|
629
700
|
try:
|
|
630
701
|
rc = ftp.size(item)
|
|
@@ -638,7 +709,7 @@ class Archive(AbstractArchive):
|
|
|
638
709
|
|
|
639
710
|
def _ftplist(self, item, **kwargs):
|
|
640
711
|
"""Actual _list using ftp."""
|
|
641
|
-
ftp = self._ftp_client(logname=kwargs.get(
|
|
712
|
+
ftp = self._ftp_client(logname=kwargs.get("username", None))
|
|
642
713
|
rc = None
|
|
643
714
|
if ftp:
|
|
644
715
|
try:
|
|
@@ -657,26 +728,30 @@ class Archive(AbstractArchive):
|
|
|
657
728
|
else:
|
|
658
729
|
# Content of the directory...
|
|
659
730
|
if rc:
|
|
660
|
-
rc = ftp.nlst(
|
|
731
|
+
rc = ftp.nlst(".")
|
|
661
732
|
finally:
|
|
662
733
|
ftp.close()
|
|
663
734
|
return rc, dict()
|
|
664
735
|
|
|
665
736
|
def _ftpretrieve(self, item, local, **kwargs):
|
|
666
737
|
"""Actual _retrieve using ftp."""
|
|
667
|
-
logger.info(
|
|
668
|
-
|
|
669
|
-
|
|
738
|
+
logger.info(
|
|
739
|
+
"ftpget on ftp://%s/%s (to: %s)", self.storage, item, local
|
|
740
|
+
)
|
|
741
|
+
extras = dict(
|
|
742
|
+
fmt=kwargs.get("fmt", "foo"),
|
|
743
|
+
cpipeline=kwargs.get("compressionpipeline", None),
|
|
744
|
+
)
|
|
670
745
|
hostname, port = self._ftp_hostinfos
|
|
671
746
|
if port is not None:
|
|
672
|
-
extras[
|
|
747
|
+
extras["port"] = port
|
|
673
748
|
rc = self.sh.smartftget(
|
|
674
749
|
item,
|
|
675
750
|
local,
|
|
676
751
|
# Ftp control
|
|
677
752
|
hostname=hostname,
|
|
678
|
-
logname=kwargs.get(
|
|
679
|
-
**extras
|
|
753
|
+
logname=kwargs.get("username", None),
|
|
754
|
+
**extras,
|
|
680
755
|
)
|
|
681
756
|
return rc, extras
|
|
682
757
|
|
|
@@ -685,32 +760,42 @@ class Archive(AbstractArchive):
|
|
|
685
760
|
If FtServ/ftraw is used, trigger a delayed action in order to fetch
|
|
686
761
|
several files at once.
|
|
687
762
|
"""
|
|
688
|
-
cpipeline = kwargs.get(
|
|
763
|
+
cpipeline = kwargs.get("compressionpipeline", None)
|
|
689
764
|
if self.sh.rawftget_worthy(item, local, cpipeline):
|
|
690
|
-
return self.context.delayedactions_hub.register(
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
765
|
+
return self.context.delayedactions_hub.register(
|
|
766
|
+
(item, kwargs.get("fmt", "foo")),
|
|
767
|
+
kind="archive",
|
|
768
|
+
storage=self.storage,
|
|
769
|
+
goal="get",
|
|
770
|
+
tube="ftp",
|
|
771
|
+
raw=True,
|
|
772
|
+
logname=kwargs.get("username", None),
|
|
773
|
+
)
|
|
697
774
|
else:
|
|
698
775
|
return None
|
|
699
776
|
|
|
700
|
-
def _ftpfinaliseretrieve(
|
|
777
|
+
def _ftpfinaliseretrieve(
|
|
778
|
+
self, item, local, retrieve_id, **kwargs
|
|
779
|
+
): # @UnusedVariable
|
|
701
780
|
"""
|
|
702
781
|
Get the resource given the **retrieve_id** identifier returned by the
|
|
703
782
|
:meth:`_ftpearlyretrieve` method.
|
|
704
783
|
"""
|
|
705
|
-
extras = dict(
|
|
706
|
-
|
|
784
|
+
extras = dict(
|
|
785
|
+
fmt=kwargs.get("fmt", "foo"),
|
|
786
|
+
)
|
|
787
|
+
d_action = self.context.delayedactions_hub.retrieve(
|
|
788
|
+
retrieve_id, bareobject=True
|
|
789
|
+
)
|
|
707
790
|
if d_action.status == d_action_status.done:
|
|
708
791
|
if self.sh.filecocoon(local):
|
|
709
792
|
rc = self.sh.mv(d_action.result, local, **extras)
|
|
710
793
|
else:
|
|
711
|
-
raise OSError(
|
|
794
|
+
raise OSError("Could not cocoon: {!s}".format(local))
|
|
712
795
|
elif d_action.status == d_action_status.failed:
|
|
713
|
-
logger.info(
|
|
796
|
+
logger.info(
|
|
797
|
+
"The earlyretrieve failed (retrieve_id=%s)", retrieve_id
|
|
798
|
+
)
|
|
714
799
|
rc = False
|
|
715
800
|
else:
|
|
716
801
|
rc = None
|
|
@@ -718,64 +803,78 @@ class Archive(AbstractArchive):
|
|
|
718
803
|
|
|
719
804
|
def _ftpinsert(self, item, local, **kwargs):
|
|
720
805
|
"""Actual _insert using ftp."""
|
|
721
|
-
usejeeves = kwargs.get(
|
|
806
|
+
usejeeves = kwargs.get("usejeeves", None)
|
|
722
807
|
if usejeeves is None:
|
|
723
808
|
usejeeves = self.default_usejeeves
|
|
724
809
|
hostname, port = self._ftp_hostinfos
|
|
725
810
|
if not usejeeves:
|
|
726
|
-
logger.info(
|
|
727
|
-
|
|
728
|
-
|
|
811
|
+
logger.info(
|
|
812
|
+
"ftpput to ftp://%s/%s (from: %s)", self.storage, item, local
|
|
813
|
+
)
|
|
814
|
+
extras = dict(
|
|
815
|
+
fmt=kwargs.get("fmt", "foo"),
|
|
816
|
+
cpipeline=kwargs.get("compressionpipeline", None),
|
|
817
|
+
)
|
|
729
818
|
if port is not None:
|
|
730
|
-
extras[
|
|
819
|
+
extras["port"] = port
|
|
731
820
|
rc = self.sh.smartftput(
|
|
732
821
|
local,
|
|
733
822
|
item,
|
|
734
823
|
# Ftp control
|
|
735
824
|
hostname=hostname,
|
|
736
|
-
logname=kwargs.get(
|
|
737
|
-
sync=kwargs.get(
|
|
738
|
-
**extras
|
|
825
|
+
logname=kwargs.get("username", None),
|
|
826
|
+
sync=kwargs.get("enforcesync", False),
|
|
827
|
+
**extras,
|
|
739
828
|
)
|
|
740
829
|
else:
|
|
741
|
-
logger.info(
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
830
|
+
logger.info(
|
|
831
|
+
"delayed ftpput to ftp://%s/%s (from: %s)",
|
|
832
|
+
self.storage,
|
|
833
|
+
item,
|
|
834
|
+
local,
|
|
835
|
+
)
|
|
836
|
+
tempo = footprints.proxy.service(
|
|
837
|
+
kind="hiddencache", asfmt=kwargs.get("fmt")
|
|
838
|
+
)
|
|
839
|
+
compressionpipeline = kwargs.get("compressionpipeline", "")
|
|
745
840
|
if compressionpipeline:
|
|
746
841
|
compressionpipeline = compressionpipeline.description_string
|
|
747
|
-
extras = dict(
|
|
748
|
-
|
|
842
|
+
extras = dict(
|
|
843
|
+
fmt=kwargs.get("fmt", "foo"), cpipeline=compressionpipeline
|
|
844
|
+
)
|
|
749
845
|
if port is not None:
|
|
750
|
-
extras[
|
|
846
|
+
extras["port"] = port
|
|
751
847
|
|
|
752
848
|
rc = ad.jeeves(
|
|
753
849
|
hostname=hostname,
|
|
754
850
|
# Explicitly resolve the logname (because jeeves FTP client is not
|
|
755
851
|
# running with the same glove (i.e. Jeeves ftuser configuration may
|
|
756
852
|
# be different).
|
|
757
|
-
logname=self.sh.fix_ftuser(
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
853
|
+
logname=self.sh.fix_ftuser(
|
|
854
|
+
hostname, kwargs.get("username", None)
|
|
855
|
+
),
|
|
856
|
+
todo="ftput",
|
|
857
|
+
rhandler=kwargs.get("info", None),
|
|
761
858
|
source=tempo(local),
|
|
762
859
|
destination=item,
|
|
763
860
|
original=self.sh.path.abspath(local),
|
|
764
|
-
**extras
|
|
861
|
+
**extras,
|
|
765
862
|
)
|
|
766
863
|
return rc, extras
|
|
767
864
|
|
|
768
865
|
def _ftpdelete(self, item, **kwargs):
|
|
769
866
|
"""Actual _delete using ftp."""
|
|
770
867
|
rc = None
|
|
771
|
-
ftp = self._ftp_client(logname=kwargs.get(
|
|
868
|
+
ftp = self._ftp_client(logname=kwargs.get("username", None))
|
|
772
869
|
if ftp:
|
|
773
870
|
if self._ftpcheck(item, **kwargs)[0]:
|
|
774
|
-
logger.info(
|
|
871
|
+
logger.info("ftpdelete on ftp://%s/%s", self.storage, item)
|
|
775
872
|
rc = ftp.delete(item)
|
|
776
873
|
ftp.close()
|
|
777
874
|
else:
|
|
778
|
-
logger.error(
|
|
875
|
+
logger.error(
|
|
876
|
+
"Try to remove a non-existing resource <%s>", item
|
|
877
|
+
)
|
|
779
878
|
return rc, dict()
|
|
780
879
|
|
|
781
880
|
|
|
@@ -784,12 +883,14 @@ class AbstractLocalArchive(AbstractArchive):
|
|
|
784
883
|
|
|
785
884
|
_abstract = True
|
|
786
885
|
_footprint = dict(
|
|
787
|
-
info
|
|
788
|
-
attr
|
|
789
|
-
tube
|
|
790
|
-
values
|
|
886
|
+
info="Generic local archive description",
|
|
887
|
+
attr=dict(
|
|
888
|
+
tube=dict(
|
|
889
|
+
values=[
|
|
890
|
+
"inplace",
|
|
891
|
+
],
|
|
791
892
|
),
|
|
792
|
-
)
|
|
893
|
+
),
|
|
793
894
|
)
|
|
794
895
|
|
|
795
896
|
def _inplacefullpath(self, item, **kwargs):
|
|
@@ -818,14 +919,14 @@ class AbstractLocalArchive(AbstractArchive):
|
|
|
818
919
|
|
|
819
920
|
def _inplaceretrieve(self, item, local, **kwargs):
|
|
820
921
|
"""Actual _retrieve using ftp."""
|
|
821
|
-
logger.info(
|
|
822
|
-
fmt = kwargs.get(
|
|
823
|
-
cpipeline = kwargs.get(
|
|
922
|
+
logger.info("inplaceget on file:///%s (to: %s)", item, local)
|
|
923
|
+
fmt = kwargs.get("fmt", "foo")
|
|
924
|
+
cpipeline = kwargs.get("compressionpipeline", None)
|
|
824
925
|
if cpipeline:
|
|
825
926
|
rc = cpipeline.file2uncompress(item, local)
|
|
826
927
|
else:
|
|
827
928
|
# Do not use fmt=... on purpose (otherwise "forceunpack" may be called twice)
|
|
828
|
-
rc = self.sh.cp(item, local, intent=
|
|
929
|
+
rc = self.sh.cp(item, local, intent="in")
|
|
829
930
|
rc = rc and self.sh.forceunpack(local, fmt=fmt)
|
|
830
931
|
return rc, dict(fmt=fmt, cpipeline=cpipeline)
|
|
831
932
|
|
|
@@ -842,20 +943,20 @@ class AbstractLocalArchive(AbstractArchive):
|
|
|
842
943
|
|
|
843
944
|
def _inplaceinsert(self, item, local, **kwargs):
|
|
844
945
|
"""Actual _insert using ftp."""
|
|
845
|
-
logger.info(
|
|
846
|
-
cpipeline = kwargs.get(
|
|
847
|
-
fmt = kwargs.get(
|
|
946
|
+
logger.info("inplaceput to file:///%s (from: %s)", item, local)
|
|
947
|
+
cpipeline = kwargs.get("compressionpipeline", None)
|
|
948
|
+
fmt = kwargs.get("fmt", "foo")
|
|
848
949
|
with self._inplaceinsert_pack(local, fmt) as local_packed:
|
|
849
950
|
if cpipeline:
|
|
850
951
|
rc = cpipeline.compress2file(local_packed, item)
|
|
851
952
|
else:
|
|
852
953
|
# Do not use fmt=... on purpose (otherwise "forcepack" may be called twice)
|
|
853
|
-
rc = self.sh.cp(local_packed, item, intent=
|
|
954
|
+
rc = self.sh.cp(local_packed, item, intent="in")
|
|
854
955
|
return rc, dict(fmt=fmt, cpipeline=cpipeline)
|
|
855
956
|
|
|
856
957
|
def _inplacedelete(self, item, **kwargs):
|
|
857
958
|
"""Actual _delete using ftp."""
|
|
858
|
-
fmt = kwargs.get(
|
|
959
|
+
fmt = kwargs.get("fmt", "foo")
|
|
859
960
|
rc = None
|
|
860
961
|
if self._inplacecheck(item, **kwargs)[0]:
|
|
861
962
|
rc = self.sh.rm(item, fmt=fmt)
|
|
@@ -866,196 +967,30 @@ class LocalArchive(AbstractLocalArchive):
|
|
|
866
967
|
"""The default class to handle storage to the same host."""
|
|
867
968
|
|
|
868
969
|
_footprint = dict(
|
|
869
|
-
info
|
|
870
|
-
attr
|
|
871
|
-
storage
|
|
872
|
-
values
|
|
970
|
+
info="Default local archive description",
|
|
971
|
+
attr=dict(
|
|
972
|
+
storage=dict(
|
|
973
|
+
values=[
|
|
974
|
+
"localhost",
|
|
975
|
+
],
|
|
873
976
|
),
|
|
874
|
-
auto_self_expand
|
|
875
|
-
info
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
977
|
+
auto_self_expand=dict(
|
|
978
|
+
info=(
|
|
979
|
+
"Automatically expand the current user home if "
|
|
980
|
+
+ "a relative path is given (should always be True "
|
|
981
|
+
+ "except during unit-testing)"
|
|
982
|
+
),
|
|
983
|
+
type=bool,
|
|
984
|
+
default=True,
|
|
985
|
+
optional=True,
|
|
881
986
|
),
|
|
882
|
-
)
|
|
987
|
+
),
|
|
883
988
|
)
|
|
884
989
|
|
|
885
990
|
def _formatted_path(self, rawpath, **kwargs):
|
|
886
991
|
rawpath = self.sh.path.expanduser(rawpath)
|
|
887
|
-
if
|
|
992
|
+
if "~" in rawpath:
|
|
888
993
|
raise OSError('User expansion failed for "{:s}"'.format(rawpath))
|
|
889
994
|
if self.auto_self_expand and not self.sh.path.isabs(rawpath):
|
|
890
|
-
rawpath = self.sh.path.expanduser(self.sh.path.join(
|
|
995
|
+
rawpath = self.sh.path.expanduser(self.sh.path.join("~", rawpath))
|
|
891
996
|
return super()._formatted_path(rawpath, **kwargs)
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
# Concrete cache implementations
|
|
895
|
-
# ------------------------------
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
class FixedEntryCache(Cache):
|
|
899
|
-
_abstract = True
|
|
900
|
-
_footprint = dict(
|
|
901
|
-
info = 'Default cache description (with a fixed entry point)',
|
|
902
|
-
attr = dict(
|
|
903
|
-
rootdir = dict(
|
|
904
|
-
info = "The cache's location (usually on a filesystem).",
|
|
905
|
-
optional = True,
|
|
906
|
-
default = None,
|
|
907
|
-
),
|
|
908
|
-
)
|
|
909
|
-
)
|
|
910
|
-
|
|
911
|
-
@property
|
|
912
|
-
def entry(self):
|
|
913
|
-
"""Tries to figure out what could be the actual entry point for storage space."""
|
|
914
|
-
if not self.rootdir:
|
|
915
|
-
self.rootdir = "/tmp"
|
|
916
|
-
return self.sh.path.join(self.rootdir, self.kind, self.headdir)
|
|
917
|
-
|
|
918
|
-
@property
|
|
919
|
-
def tag(self):
|
|
920
|
-
"""The identifier of this cache place."""
|
|
921
|
-
return '{:s}_{:s}'.format(self.realkind, self.entry)
|
|
922
|
-
|
|
923
|
-
def _formatted_path(self, subpath, **kwargs): # @UnusedVariable
|
|
924
|
-
return self.sh.path.join(self.entry, subpath.lstrip('/'))
|
|
925
|
-
|
|
926
|
-
def catalog(self):
|
|
927
|
-
"""List all files present in this cache.
|
|
928
|
-
|
|
929
|
-
:note: It might be quite slow...
|
|
930
|
-
"""
|
|
931
|
-
entry = self.sh.path.expanduser(self.entry)
|
|
932
|
-
files = self.sh.ffind(entry)
|
|
933
|
-
return [f[len(entry):] for f in files]
|
|
934
|
-
|
|
935
|
-
def flush(self, dumpfile=None):
|
|
936
|
-
"""Flush actual history to the specified ``dumpfile`` if record is on."""
|
|
937
|
-
if dumpfile is None:
|
|
938
|
-
logfile = '.'.join((
|
|
939
|
-
'HISTORY',
|
|
940
|
-
datetime.now().strftime('%Y%m%d%H%M%S.%f'),
|
|
941
|
-
'P{:06d}'.format(self.sh.getpid()),
|
|
942
|
-
self.sh.getlogname()
|
|
943
|
-
))
|
|
944
|
-
dumpfile = self.sh.path.join(self.entry, '.history', logfile)
|
|
945
|
-
if self.record:
|
|
946
|
-
self.sh.pickle_dump(self.history, dumpfile)
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
class MtoolCache(FixedEntryCache):
|
|
950
|
-
"""Cache items for the MTOOL jobs (or any job that acts like it)."""
|
|
951
|
-
|
|
952
|
-
_footprint = dict(
|
|
953
|
-
info = 'MTOOL like Cache',
|
|
954
|
-
attr = dict(
|
|
955
|
-
kind = dict(
|
|
956
|
-
values = ['mtool', 'swapp'],
|
|
957
|
-
remap = dict(swapp = 'mtool'),
|
|
958
|
-
),
|
|
959
|
-
headdir = dict(
|
|
960
|
-
optional = True,
|
|
961
|
-
default = "",
|
|
962
|
-
),
|
|
963
|
-
)
|
|
964
|
-
)
|
|
965
|
-
|
|
966
|
-
@property
|
|
967
|
-
def entry(self):
|
|
968
|
-
"""Tries to figure out what could be the actual entry point
|
|
969
|
-
for cache space.
|
|
970
|
-
|
|
971
|
-
"""
|
|
972
|
-
if self.rootdir:
|
|
973
|
-
return os.path.join(self.rootdir, self.headdir)
|
|
974
|
-
|
|
975
|
-
if config.is_defined(section="data-tree", key="rootdir"):
|
|
976
|
-
rootdir = config.from_config(
|
|
977
|
-
section="data-tree", key="rootdir",
|
|
978
|
-
)
|
|
979
|
-
else:
|
|
980
|
-
rootdir = self.sh.path.join(os.environ["HOME"], ".vortex.d")
|
|
981
|
-
|
|
982
|
-
return self.sh.path.join(rootdir, self.headdir)
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
class FtStashCache(MtoolCache):
|
|
986
|
-
"""A place to store file to be sent with ftserv."""
|
|
987
|
-
|
|
988
|
-
_footprint = dict(
|
|
989
|
-
info = 'A place to store file to be sent with ftserv',
|
|
990
|
-
attr = dict(
|
|
991
|
-
kind = dict(
|
|
992
|
-
values = ['ftstash', ],
|
|
993
|
-
),
|
|
994
|
-
headdir = dict(
|
|
995
|
-
optional = True,
|
|
996
|
-
default = 'ftspool',
|
|
997
|
-
),
|
|
998
|
-
)
|
|
999
|
-
)
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
class Op2ResearchCache(FixedEntryCache):
|
|
1003
|
-
"""Cache of the operational suite (read-only)."""
|
|
1004
|
-
|
|
1005
|
-
_footprint = dict(
|
|
1006
|
-
info = 'MTOOL like Operations Cache (read-only)',
|
|
1007
|
-
attr = dict(
|
|
1008
|
-
kind = dict(
|
|
1009
|
-
values = ['op2r'],
|
|
1010
|
-
),
|
|
1011
|
-
headdir = dict(
|
|
1012
|
-
optional = True,
|
|
1013
|
-
default = 'vortex',
|
|
1014
|
-
),
|
|
1015
|
-
readonly = dict(
|
|
1016
|
-
values = [True, ],
|
|
1017
|
-
default = True,
|
|
1018
|
-
)
|
|
1019
|
-
)
|
|
1020
|
-
)
|
|
1021
|
-
|
|
1022
|
-
@property
|
|
1023
|
-
def entry(self):
|
|
1024
|
-
cache = (
|
|
1025
|
-
self.rootdir or
|
|
1026
|
-
config.from_config(section="data-tree", key="op_rootdir")
|
|
1027
|
-
)
|
|
1028
|
-
return self.sh.path.join(cache, self.headdir)
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
class HackerCache(FixedEntryCache):
|
|
1032
|
-
"""A dirty cache where users can hack things."""
|
|
1033
|
-
|
|
1034
|
-
_footprint = dict(
|
|
1035
|
-
info = 'A place to hack things...',
|
|
1036
|
-
attr = dict(
|
|
1037
|
-
kind = dict(
|
|
1038
|
-
values = ['hack'],
|
|
1039
|
-
),
|
|
1040
|
-
rootdir = dict(
|
|
1041
|
-
optional = True,
|
|
1042
|
-
default = 'auto'
|
|
1043
|
-
),
|
|
1044
|
-
readonly = dict(
|
|
1045
|
-
default = True,
|
|
1046
|
-
),
|
|
1047
|
-
)
|
|
1048
|
-
)
|
|
1049
|
-
|
|
1050
|
-
@property
|
|
1051
|
-
def entry(self):
|
|
1052
|
-
"""Tries to figure out what could be the actual entry point for cache space."""
|
|
1053
|
-
sh = self.sh
|
|
1054
|
-
if self.rootdir == 'auto':
|
|
1055
|
-
gl = sessions.current().glove
|
|
1056
|
-
sweethome = sh.path.join(gl.configrc, 'hack')
|
|
1057
|
-
sh.mkdir(sweethome)
|
|
1058
|
-
logger.debug('Using %s hack cache: %s', self.__class__, sweethome)
|
|
1059
|
-
else:
|
|
1060
|
-
sweethome = self.rootdir
|
|
1061
|
-
return sh.path.join(sweethome, self.headdir)
|