vortex-nwp 2.0.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.
Files changed (144) hide show
  1. vortex/__init__.py +159 -0
  2. vortex/algo/__init__.py +13 -0
  3. vortex/algo/components.py +2462 -0
  4. vortex/algo/mpitools.py +1953 -0
  5. vortex/algo/mpitools_templates/__init__.py +1 -0
  6. vortex/algo/mpitools_templates/envelope_wrapper_default.tpl +27 -0
  7. vortex/algo/mpitools_templates/envelope_wrapper_mpiauto.tpl +29 -0
  8. vortex/algo/mpitools_templates/wrapstd_wrapper_default.tpl +18 -0
  9. vortex/algo/serversynctools.py +171 -0
  10. vortex/config.py +112 -0
  11. vortex/data/__init__.py +19 -0
  12. vortex/data/abstractstores.py +1510 -0
  13. vortex/data/containers.py +835 -0
  14. vortex/data/contents.py +622 -0
  15. vortex/data/executables.py +275 -0
  16. vortex/data/flow.py +119 -0
  17. vortex/data/geometries.ini +2689 -0
  18. vortex/data/geometries.py +799 -0
  19. vortex/data/handlers.py +1230 -0
  20. vortex/data/outflow.py +67 -0
  21. vortex/data/providers.py +487 -0
  22. vortex/data/resources.py +207 -0
  23. vortex/data/stores.py +1390 -0
  24. vortex/data/sync_templates/__init__.py +0 -0
  25. vortex/gloves.py +309 -0
  26. vortex/layout/__init__.py +20 -0
  27. vortex/layout/contexts.py +577 -0
  28. vortex/layout/dataflow.py +1220 -0
  29. vortex/layout/monitor.py +969 -0
  30. vortex/nwp/__init__.py +14 -0
  31. vortex/nwp/algo/__init__.py +21 -0
  32. vortex/nwp/algo/assim.py +537 -0
  33. vortex/nwp/algo/clim.py +1086 -0
  34. vortex/nwp/algo/coupling.py +831 -0
  35. vortex/nwp/algo/eda.py +840 -0
  36. vortex/nwp/algo/eps.py +785 -0
  37. vortex/nwp/algo/forecasts.py +886 -0
  38. vortex/nwp/algo/fpserver.py +1303 -0
  39. vortex/nwp/algo/ifsnaming.py +463 -0
  40. vortex/nwp/algo/ifsroot.py +404 -0
  41. vortex/nwp/algo/monitoring.py +263 -0
  42. vortex/nwp/algo/mpitools.py +694 -0
  43. vortex/nwp/algo/odbtools.py +1258 -0
  44. vortex/nwp/algo/oopsroot.py +916 -0
  45. vortex/nwp/algo/oopstests.py +220 -0
  46. vortex/nwp/algo/request.py +660 -0
  47. vortex/nwp/algo/stdpost.py +1641 -0
  48. vortex/nwp/data/__init__.py +30 -0
  49. vortex/nwp/data/assim.py +380 -0
  50. vortex/nwp/data/boundaries.py +314 -0
  51. vortex/nwp/data/climfiles.py +521 -0
  52. vortex/nwp/data/configfiles.py +153 -0
  53. vortex/nwp/data/consts.py +954 -0
  54. vortex/nwp/data/ctpini.py +149 -0
  55. vortex/nwp/data/diagnostics.py +209 -0
  56. vortex/nwp/data/eda.py +147 -0
  57. vortex/nwp/data/eps.py +432 -0
  58. vortex/nwp/data/executables.py +1045 -0
  59. vortex/nwp/data/fields.py +111 -0
  60. vortex/nwp/data/gridfiles.py +380 -0
  61. vortex/nwp/data/logs.py +584 -0
  62. vortex/nwp/data/modelstates.py +363 -0
  63. vortex/nwp/data/monitoring.py +193 -0
  64. vortex/nwp/data/namelists.py +696 -0
  65. vortex/nwp/data/obs.py +840 -0
  66. vortex/nwp/data/oopsexec.py +74 -0
  67. vortex/nwp/data/providers.py +207 -0
  68. vortex/nwp/data/query.py +206 -0
  69. vortex/nwp/data/stores.py +160 -0
  70. vortex/nwp/data/surfex.py +337 -0
  71. vortex/nwp/syntax/__init__.py +9 -0
  72. vortex/nwp/syntax/stdattrs.py +437 -0
  73. vortex/nwp/tools/__init__.py +10 -0
  74. vortex/nwp/tools/addons.py +40 -0
  75. vortex/nwp/tools/agt.py +67 -0
  76. vortex/nwp/tools/bdap.py +59 -0
  77. vortex/nwp/tools/bdcp.py +41 -0
  78. vortex/nwp/tools/bdm.py +24 -0
  79. vortex/nwp/tools/bdmp.py +54 -0
  80. vortex/nwp/tools/conftools.py +1661 -0
  81. vortex/nwp/tools/drhook.py +66 -0
  82. vortex/nwp/tools/grib.py +294 -0
  83. vortex/nwp/tools/gribdiff.py +104 -0
  84. vortex/nwp/tools/ifstools.py +203 -0
  85. vortex/nwp/tools/igastuff.py +273 -0
  86. vortex/nwp/tools/mars.py +68 -0
  87. vortex/nwp/tools/odb.py +657 -0
  88. vortex/nwp/tools/partitioning.py +258 -0
  89. vortex/nwp/tools/satrad.py +71 -0
  90. vortex/nwp/util/__init__.py +6 -0
  91. vortex/nwp/util/async.py +212 -0
  92. vortex/nwp/util/beacon.py +40 -0
  93. vortex/nwp/util/diffpygram.py +447 -0
  94. vortex/nwp/util/ens.py +279 -0
  95. vortex/nwp/util/hooks.py +139 -0
  96. vortex/nwp/util/taskdeco.py +85 -0
  97. vortex/nwp/util/usepygram.py +697 -0
  98. vortex/nwp/util/usetnt.py +101 -0
  99. vortex/proxy.py +6 -0
  100. vortex/sessions.py +374 -0
  101. vortex/syntax/__init__.py +9 -0
  102. vortex/syntax/stdattrs.py +867 -0
  103. vortex/syntax/stddeco.py +185 -0
  104. vortex/toolbox.py +1117 -0
  105. vortex/tools/__init__.py +20 -0
  106. vortex/tools/actions.py +523 -0
  107. vortex/tools/addons.py +316 -0
  108. vortex/tools/arm.py +96 -0
  109. vortex/tools/compression.py +325 -0
  110. vortex/tools/date.py +27 -0
  111. vortex/tools/ddhpack.py +10 -0
  112. vortex/tools/delayedactions.py +782 -0
  113. vortex/tools/env.py +541 -0
  114. vortex/tools/folder.py +834 -0
  115. vortex/tools/grib.py +738 -0
  116. vortex/tools/lfi.py +953 -0
  117. vortex/tools/listings.py +423 -0
  118. vortex/tools/names.py +637 -0
  119. vortex/tools/net.py +2124 -0
  120. vortex/tools/odb.py +10 -0
  121. vortex/tools/parallelism.py +368 -0
  122. vortex/tools/prestaging.py +210 -0
  123. vortex/tools/rawfiles.py +10 -0
  124. vortex/tools/schedulers.py +480 -0
  125. vortex/tools/services.py +940 -0
  126. vortex/tools/storage.py +996 -0
  127. vortex/tools/surfex.py +61 -0
  128. vortex/tools/systems.py +3976 -0
  129. vortex/tools/targets.py +440 -0
  130. vortex/util/__init__.py +9 -0
  131. vortex/util/config.py +1122 -0
  132. vortex/util/empty.py +24 -0
  133. vortex/util/helpers.py +216 -0
  134. vortex/util/introspection.py +69 -0
  135. vortex/util/iosponge.py +80 -0
  136. vortex/util/roles.py +49 -0
  137. vortex/util/storefunctions.py +129 -0
  138. vortex/util/structs.py +26 -0
  139. vortex/util/worker.py +162 -0
  140. vortex_nwp-2.0.0.dist-info/METADATA +67 -0
  141. vortex_nwp-2.0.0.dist-info/RECORD +144 -0
  142. vortex_nwp-2.0.0.dist-info/WHEEL +5 -0
  143. vortex_nwp-2.0.0.dist-info/licenses/LICENSE +517 -0
  144. vortex_nwp-2.0.0.dist-info/top_level.txt +1 -0
vortex/tools/lfi.py ADDED
@@ -0,0 +1,953 @@
1
+ """
2
+ Module needed to interact with FA and LFI files.
3
+
4
+
5
+ It provides shell addons to deal with:
6
+
7
+ * Splitted FA files (as produced by the Arpege/IFS IO server)
8
+ * The ability to compare Fa or LFI files
9
+
10
+ """
11
+
12
+ import re
13
+
14
+ import footprints
15
+
16
+ from bronx.fancies import loggers
17
+ from bronx.stdtypes.tracking import Tracker
18
+
19
+ from . import addons, systems
20
+
21
+ from vortex.layout import contexts
22
+ from vortex.tools.net import DEFAULT_FTP_PORT
23
+
24
+ #: Export nothing
25
+ __all__ = []
26
+
27
+ logger = loggers.getLogger(__name__)
28
+
29
+
30
+ def use_in_shell(sh, **kw):
31
+ """Extend current shell with the LFI interface defined by optional arguments."""
32
+ kw["shell"] = sh
33
+ return footprints.proxy.addon(**kw)
34
+
35
+
36
+ class LFI_Status:
37
+ """
38
+ Store lfi commands status as a set of attributes:
39
+ * rc = return code
40
+ * stdout = raw standard output
41
+ * result = an optional processing specific to each command
42
+ """
43
+
44
+ def __init__(self, rc=0, ok=None, stdout=None, stderr=None, result=None):
45
+ self._rc = rc
46
+ self._ok = ok or [0]
47
+ self._stdout = stdout
48
+ self._stderr = stderr
49
+ self._result = result or list()
50
+
51
+ def __str__(self):
52
+ return "{:s} | rc={:d} result={:d}>".format(
53
+ repr(self).rstrip(">"), self.rc, len(self.result)
54
+ )
55
+
56
+ @property
57
+ def ok(self):
58
+ return self._ok
59
+
60
+ def _get_rc(self):
61
+ return self._rc
62
+
63
+ def _set_rc(self, value):
64
+ if value is not None:
65
+ if type(value) is bool:
66
+ value = 1 - int(value)
67
+ self._rc = self._rc + value
68
+
69
+ rc = property(_get_rc, _set_rc, None, None)
70
+
71
+ def _get_stdout(self):
72
+ return self._stdout
73
+
74
+ def _set_stdout(self, value):
75
+ self._stdout = list(value)
76
+
77
+ stdout = property(_get_stdout, _set_stdout, None, None)
78
+
79
+ def _get_stderr(self):
80
+ return self._stderr
81
+
82
+ def _set_stderr(self, value):
83
+ self._stderr = list(value)
84
+
85
+ stderr = property(_get_stderr, _set_stderr, None, None)
86
+
87
+ def _get_result(self):
88
+ return self._result
89
+
90
+ def _set_result(self, value):
91
+ self._result = list(value)
92
+
93
+ result = property(_get_result, _set_result, None, None)
94
+
95
+ def cat(self, maxlines=None):
96
+ """Cat the last stdout command up to ``maxlines`` lines. If maxlines is None, print all."""
97
+ if self.stdout is not None:
98
+ if maxlines is None:
99
+ maxlines = len(self.stdout) + 1
100
+ for l in self.stdout[:maxlines]:
101
+ print(l)
102
+
103
+ def __bool__(self):
104
+ return bool(self.rc in self.ok)
105
+
106
+
107
+ class LFI_Tool_Raw(addons.FtrawEnableAddon):
108
+ """
109
+ Interface to LFI commands through Perl wrappers.
110
+ """
111
+
112
+ LFI_HNDL_SPEC = ":1"
113
+ DR_HOOK_SILENT = 1
114
+ DR_HOOK_NOT_MPI = 1
115
+ DR_HOOK_ASSERT_MPI_INITIALIZED = 0
116
+ OMP_STACKSIZE = "32M"
117
+ KMP_STACKSIZE = "32M"
118
+ KMP_MONITOR_STACKSIZE = "32M"
119
+
120
+ _footprint = dict(
121
+ info="Default LFI system interface (Perl)",
122
+ attr=dict(
123
+ kind=dict(
124
+ values=["lfi"],
125
+ ),
126
+ cmd=dict(
127
+ alias=("lficmd",),
128
+ default="lfitools",
129
+ ),
130
+ path=dict(
131
+ alias=("lfipath",),
132
+ ),
133
+ warnpack=dict(
134
+ type=bool,
135
+ optional=True,
136
+ default=False,
137
+ ),
138
+ wraplanguage=dict(
139
+ values=[
140
+ "perl",
141
+ ],
142
+ default="perl",
143
+ optional=True,
144
+ doc_visibility=footprints.doc.visibility.ADVANCED,
145
+ ),
146
+ toolkind=dict(
147
+ default="lfitools",
148
+ ),
149
+ ),
150
+ )
151
+
152
+ def __init__(self, *args, **kw):
153
+ """LFI tools initialisation."""
154
+ super().__init__(*args, **kw)
155
+ self._lfitools_path = dict()
156
+
157
+ @property
158
+ def lfitools_path(self):
159
+ ctxtag = contexts.Context.tag_focus()
160
+ if ctxtag not in self._lfitools_path:
161
+ self._lfitools_path[ctxtag] = self.sh.path.join(
162
+ self.actual_path, self.actual_cmd
163
+ )
164
+ self.sh.xperm(self._lfitools_path[ctxtag], force=True)
165
+ return self._lfitools_path[ctxtag]
166
+
167
+ def _spawn(self, cmd, **kw):
168
+ """Tube to set LFITOOLS env variable."""
169
+ self.env.LFITOOLS = self.lfitools_path
170
+ return super()._spawn(cmd, **kw)
171
+
172
+ def _spawn_wrap(self, func, cmd, **kw):
173
+ """Tube to set LFITOOLS env variable."""
174
+ self.env.LFITOOLS = self.lfitools_path
175
+ return super()._spawn_wrap(
176
+ [
177
+ "lfi_" + func,
178
+ ]
179
+ + cmd,
180
+ **kw,
181
+ )
182
+
183
+ def is_xlfi(self, source):
184
+ """Check if the given ``source`` is a multipart-lfi file."""
185
+ rc = False
186
+ if source and isinstance(source, str) and self.sh.path.exists(source):
187
+ with open(source, "rb") as fd:
188
+ rc = fd.read(8) == b"LFI_ALTM"
189
+ return rc
190
+
191
+ def _std_table(self, lfifile, **kw):
192
+ """
193
+ List of contents of a lfi-file.
194
+
195
+ Mandatory args are:
196
+ * lfifile : lfi file name
197
+
198
+ """
199
+ cmd = ["lfilist", lfifile]
200
+ kw["output"] = True
201
+ rawout = self._spawn(cmd, **kw)
202
+ return LFI_Status(
203
+ rc=0,
204
+ stdout=rawout,
205
+ result=[tuple(eval(x)[0]) for x in rawout if x.startswith("[")],
206
+ )
207
+
208
+ fa_table = lfi_table = _std_table
209
+
210
+ def _std_diff(self, lfi1, lfi2, **kw):
211
+ """
212
+ Difference between two lfi-files.
213
+
214
+ Mandatory args are:
215
+ * lfi1 : first file to compare
216
+ * lfi2 : second file to compare
217
+
218
+ Options are:
219
+ * maxprint : Maximum number of values to print
220
+ * skipfields : LFI fields not to be compared
221
+ * skiplength : Offset at which the comparison starts for each LFI fields
222
+ """
223
+ cmd = ["lfidiff", "--lfi-file-1", lfi1, "--lfi-file-2", lfi2]
224
+
225
+ maxprint = kw.pop("maxprint", 2)
226
+ if maxprint:
227
+ cmd.extend(["--max-print-diff", str(maxprint)])
228
+
229
+ skipfields = kw.pop("skipfields", 0)
230
+ if skipfields:
231
+ cmd.extend(["--lfi-skip-fields", str(skipfields)])
232
+
233
+ skiplength = kw.pop("skiplength", 0)
234
+ if skiplength:
235
+ cmd.extend(["--lfi-skip-length", str(skiplength)])
236
+
237
+ kw["output"] = True
238
+
239
+ rawout = self._spawn(cmd, **kw)
240
+ fields = [
241
+ tuple(x.split(" ", 2)[-2:])
242
+ for x in rawout
243
+ if re.match(r" (?:\!=|\+\+|\-\-)", x)
244
+ ]
245
+
246
+ trfields = Tracker(
247
+ deleted=[x[1] for x in fields if x[0] == "--"],
248
+ created=[x[1] for x in fields if x[0] == "++"],
249
+ updated=[x[1] for x in fields if x[0] == "!="],
250
+ )
251
+
252
+ stlist = self.lfi_table(lfi1, output=True)
253
+ trfields.unchanged = {x[0] for x in stlist.result} - set(trfields)
254
+
255
+ return LFI_Status(rc=int(bool(fields)), stdout=rawout, result=trfields)
256
+
257
+ fa_diff = lfi_diff = _std_diff
258
+
259
+ def fa_empty(self, fa1, fa2, **kw):
260
+ """
261
+ Create an empty FA file
262
+
263
+ Mandatory args are:
264
+ * fa1 : The reference file
265
+ * fa2 : The new empty file
266
+
267
+ """
268
+ cmd = ["faempty", fa1, fa2]
269
+ self._spawn(cmd, **kw)
270
+
271
+ def _pack_stream(self, source):
272
+ return self._spawn_wrap(
273
+ "pack",
274
+ [
275
+ source,
276
+ ],
277
+ output=False,
278
+ inpipe=True,
279
+ bufsize=8192,
280
+ )
281
+
282
+ def _packed_size(self, source):
283
+ out = self._spawn_wrap(
284
+ "size",
285
+ [
286
+ source,
287
+ ],
288
+ output=True,
289
+ inpipe=False,
290
+ )
291
+ try:
292
+ return int(out[0])
293
+ except ValueError:
294
+ pass
295
+ return None
296
+
297
+ def _std_forcepack(self, source, destination=None):
298
+ """Returned a path to a packed data."""
299
+ if self.is_xlfi(source):
300
+ destination = (
301
+ destination
302
+ if destination
303
+ else self.sh.safe_fileaddsuffix(source)
304
+ )
305
+ if not self.sh.path.exists(destination):
306
+ st = self._std_copy(
307
+ source=source, destination=destination, pack=True
308
+ )
309
+ if st:
310
+ return destination
311
+ else:
312
+ raise OSError("XLFI packing failed")
313
+ else:
314
+ return destination
315
+ else:
316
+ return source
317
+
318
+ fa_forcepack = lfi_forcepack = _std_forcepack
319
+
320
+ def _std_ftput(
321
+ self,
322
+ source,
323
+ destination,
324
+ hostname=None,
325
+ logname=None,
326
+ port=DEFAULT_FTP_PORT,
327
+ cpipeline=None,
328
+ sync=False,
329
+ ):
330
+ """On the fly packing and ftp."""
331
+ if self.is_xlfi(source):
332
+ if cpipeline is not None:
333
+ raise OSError("It's not allowed to compress xlfi files.")
334
+ hostname = self.sh.fix_fthostname(hostname)
335
+
336
+ st = LFI_Status()
337
+ ftp = self.sh.ftp(hostname, logname, port=port)
338
+ if ftp:
339
+ packed_size = self._packed_size(source)
340
+ p = self._pack_stream(source)
341
+ st.rc = ftp.put(
342
+ p.stdout, destination, size=packed_size, exact=True
343
+ )
344
+ self.sh.pclose(p)
345
+ st.result = [destination]
346
+ st.stdout = [
347
+ "Connection time : {:f}".format(ftp.length),
348
+ "Packed source size: {:d}".format(packed_size),
349
+ "Actual target size: {:d}".format(ftp.size(destination)),
350
+ ]
351
+ ftp.close()
352
+ else:
353
+ st.rc = 1
354
+ st.result = [
355
+ "Could not connect to " + hostname + " as user " + logname
356
+ ]
357
+ return st
358
+ else:
359
+ return self.sh.ftput(
360
+ source,
361
+ destination,
362
+ hostname=hostname,
363
+ logname=logname,
364
+ cpipeline=cpipeline,
365
+ port=port,
366
+ sync=sync,
367
+ )
368
+
369
+ def _std_rawftput(
370
+ self,
371
+ source,
372
+ destination,
373
+ hostname=None,
374
+ logname=None,
375
+ port=None,
376
+ cpipeline=None,
377
+ sync=False,
378
+ ):
379
+ """Use ftserv as much as possible."""
380
+ if self.is_xlfi(source):
381
+ if cpipeline is not None:
382
+ raise OSError("It's not allowed to compress xlfi files.")
383
+ if self.sh.ftraw and self.rawftshell is not None:
384
+ newsource = self.sh.copy2ftspool(source, fmt="lfi")
385
+ rc = self.sh.ftserv_put(
386
+ newsource,
387
+ destination,
388
+ hostname=hostname,
389
+ logname=logname,
390
+ port=port,
391
+ specialshell=self.rawftshell,
392
+ sync=sync,
393
+ )
394
+ self.sh.rm(newsource) # Delete the request file
395
+ return rc
396
+ else:
397
+ if port is None:
398
+ port = DEFAULT_FTP_PORT
399
+ return self._std_ftput(
400
+ source,
401
+ destination,
402
+ hostname,
403
+ logname,
404
+ port=port,
405
+ sync=sync,
406
+ )
407
+ else:
408
+ return self.sh.rawftput(
409
+ source,
410
+ destination,
411
+ hostname=hostname,
412
+ logname=logname,
413
+ port=port,
414
+ cpipeline=cpipeline,
415
+ sync=sync,
416
+ )
417
+
418
+ fa_ftput = lfi_ftput = _std_ftput
419
+ fa_rawftput = lfi_rawftput = _std_rawftput
420
+
421
+ def _std_prepare(self, source, destination, intent="in"):
422
+ """Check for the source and prepare the destination."""
423
+ if intent not in ("in", "inout"):
424
+ raise ValueError("Incorrect value for intent ({})".format(intent))
425
+ st = LFI_Status()
426
+ if not self.sh.path.exists(source):
427
+ logger.error("Missing source %s", source)
428
+ st.rc = 2
429
+ st.stderr = "No such source file or directory : [" + source + "]"
430
+ return st
431
+ if not self.sh.filecocoon(destination):
432
+ raise OSError("Could not cocoon [" + destination + "]")
433
+ if not self.lfi_rm(destination):
434
+ raise OSError("Could not clean destination [" + destination + "]")
435
+ return st
436
+
437
+ def _std_remove(self, *args):
438
+ """Remove (possibly) multi lfi files."""
439
+ st = LFI_Status(result=list())
440
+ for pname in args:
441
+ for objpath in self.sh.glob(pname):
442
+ rc = self._spawn_wrap(
443
+ "remove",
444
+ [
445
+ objpath,
446
+ ],
447
+ output=False,
448
+ )
449
+ st.result.append(
450
+ dict(path=objpath, multi=self.is_xlfi(objpath), rc=rc)
451
+ )
452
+ st.rc = rc
453
+ return st
454
+
455
+ lfi_rm = lfi_remove = fa_rm = fa_remove = _std_remove
456
+
457
+ def _std_copy(
458
+ self,
459
+ source,
460
+ destination,
461
+ smartcp_threshold=0,
462
+ intent="in",
463
+ pack=False,
464
+ silent=False,
465
+ ):
466
+ """Extended copy for (possibly) multi lfi file."""
467
+ st = self._std_prepare(source, destination, intent)
468
+ if st.rc == 0:
469
+ ln_same_fs = self.sh.is_samefs(source, destination)
470
+ ln_cross_users = ln_same_fs and not self.sh.usr_file(source)
471
+ actual_pack = pack
472
+ actual_intent = intent
473
+ if ln_cross_users and not self.sh.allow_cross_users_links:
474
+ actual_pack = True
475
+ actual_intent = "inout"
476
+ try:
477
+ st.rc = self._spawn_wrap(
478
+ "copy",
479
+ (
480
+ [
481
+ "-pack",
482
+ ]
483
+ if actual_pack
484
+ else []
485
+ )
486
+ + [
487
+ "-intent={}".format(actual_intent),
488
+ source,
489
+ destination,
490
+ ],
491
+ output=False,
492
+ )
493
+ except systems.ExecutionError:
494
+ if self.sh.allow_cross_users_links and ln_cross_users:
495
+ # This is expected to fail if the fs.protected_hardlinks
496
+ # Linux kernel setting is 1.
497
+ logger.info(
498
+ "Force System's allow_cross_users_links to False"
499
+ )
500
+ self.sh.allow_cross_users_links = False
501
+ logger.info("Re-running the cp command on this LFI file")
502
+ st = self._std_copy(
503
+ source,
504
+ destination,
505
+ intent=intent,
506
+ pack=pack,
507
+ silent=silent,
508
+ )
509
+ else:
510
+ raise
511
+ else:
512
+ if (
513
+ ln_cross_users
514
+ and not self.sh.allow_cross_users_links
515
+ and intent == "in"
516
+ ):
517
+ self.sh.readonly(destination)
518
+ return st
519
+
520
+ lfi_cp = lfi_copy = fa_cp = fa_copy = _std_copy
521
+
522
+ def _std_move(self, source, destination, intent=None, pack=False):
523
+ """Extended mv for (possibly) multi lfi file."""
524
+ if self.is_xlfi(source):
525
+ if intent is None:
526
+ intent = (
527
+ "inout" if self.sh.access(source, self.sh.W_OK) else "in"
528
+ )
529
+ st = self._std_prepare(source, destination, intent)
530
+ if st.rc == 0:
531
+ st.rc = self._spawn_wrap(
532
+ "move",
533
+ (
534
+ [
535
+ "-pack",
536
+ ]
537
+ if pack
538
+ else []
539
+ )
540
+ + ["-intent={}".format(intent), source, destination],
541
+ output=False,
542
+ )
543
+ else:
544
+ st = LFI_Status()
545
+ st.rc = self.sh.mv(source, destination)
546
+ return st
547
+
548
+ lfi_mv = lfi_move = fa_mv = fa_move = _std_move
549
+
550
+ def _std_scpput(
551
+ self, source, destination, hostname, logname=None, cpipeline=None
552
+ ):
553
+ """On the fly packing and scp."""
554
+ if not self.is_xlfi(source):
555
+ rc = self.sh.scpput(
556
+ source, destination, hostname, logname, cpipeline
557
+ )
558
+ else:
559
+ if cpipeline is not None:
560
+ raise OSError("It's not allowed to compress xlfi files.")
561
+ logname = self.sh.fix_ftuser(
562
+ hostname, logname, fatal=False, defaults_to_user=False
563
+ )
564
+ ssh = self.sh.ssh(hostname, logname)
565
+ permissions = ssh.get_permissions(source)
566
+ # remove the .d companion directory (scp_stream removes the destination)
567
+ # go on on failure : the .d lingers on, but the lfi will be self-contained
568
+ ssh.remove(destination + ".d")
569
+ p = self._pack_stream(source)
570
+ rc = ssh.scpput_stream(
571
+ p.stdout, destination, permissions=permissions
572
+ )
573
+ self.sh.pclose(p)
574
+ return rc
575
+
576
+ fa_scpput = lfi_scpput = _std_scpput
577
+
578
+ @addons.require_external_addon("ecfs")
579
+ def _std_ecfsput(self, source, target, cpipeline=None, options=None):
580
+ """
581
+ :param source: source file
582
+ :param target: target file
583
+ :param cpipeline: compression pipeline to be used, if provided
584
+ :param options: list of options to be used
585
+ :return: return code and additional attributes used
586
+ """
587
+ if self.is_xlfi(source):
588
+ if cpipeline is not None:
589
+ raise OSError("It's not allowed to compress xlfi files.")
590
+ psource = self.sh.safe_fileaddsuffix(source)
591
+ rc = LFI_Status()
592
+ try:
593
+ st = self._std_copy(
594
+ source=source, destination=psource, pack=True
595
+ )
596
+ rc = rc and st.rc
597
+ dict_args = dict()
598
+ if rc:
599
+ rc, dict_args = self.sh.ecfsput(
600
+ source=psource, target=target, options=options
601
+ )
602
+ finally:
603
+ self.sh.rm(psource)
604
+ return rc, dict_args
605
+ else:
606
+ return self.sh.ecfsput(
607
+ source=source,
608
+ target=target,
609
+ options=options,
610
+ cpipeline=cpipeline,
611
+ )
612
+
613
+ fa_ecfsput = lfi_ecfsput = _std_ecfsput
614
+
615
+ @addons.require_external_addon("ectrans")
616
+ def _std_ectransput(
617
+ self,
618
+ source,
619
+ target,
620
+ gateway=None,
621
+ remote=None,
622
+ cpipeline=None,
623
+ sync=False,
624
+ ):
625
+ """
626
+ :param source: source file
627
+ :param target: target file
628
+ :param gateway: gateway used by ECtrans
629
+ :param remote: remote used by ECtrans
630
+ :param cpipeline: compression pipeline to be used, if provided
631
+ :param bool sync: If False, allow asynchronous transfers
632
+ :return: return code and additional attributes used
633
+ """
634
+ if self.is_xlfi(source):
635
+ if cpipeline is not None:
636
+ raise OSError("It's not allowed to compress xlfi files.")
637
+ psource = self.sh.safe_fileaddsuffix(source)
638
+ rc = LFI_Status()
639
+ try:
640
+ st = self._std_copy(
641
+ source=source, destination=psource, pack=True
642
+ )
643
+ rc = rc and st.rc
644
+ dict_args = dict()
645
+ if rc:
646
+ rc, dict_args = self.sh.raw_ectransput(
647
+ source=psource,
648
+ target=target,
649
+ gateway=gateway,
650
+ remote=remote,
651
+ sync=sync,
652
+ )
653
+ finally:
654
+ self.sh.rm(psource)
655
+ return rc, dict_args
656
+ else:
657
+ return self.sh.ectransput(
658
+ source=source,
659
+ target=target,
660
+ gateway=gateway,
661
+ remote=remote,
662
+ cpipeline=cpipeline,
663
+ sync=sync,
664
+ )
665
+
666
+ fa_ectransput = lfi_ectransput = _std_ectransput
667
+
668
+
669
+ class LFI_Tool_Py(LFI_Tool_Raw):
670
+ """
671
+ Rewritten Python interface to LFITOOLS command.
672
+ These commands are the one defined by the ``lfitools`` binary found in the IFS-ARPEGE framework.
673
+ WARNING: This interface is broken from cy41 onward.
674
+ """
675
+
676
+ _footprint = dict(
677
+ info="Default LFI system interface (Python)",
678
+ attr=dict(
679
+ wraplanguage=dict(
680
+ values=[
681
+ "python",
682
+ ],
683
+ ),
684
+ ),
685
+ )
686
+
687
+ def _pack_stream(self, source):
688
+ return self._spawn(
689
+ ["lfi_alt_pack", "--lfi-file-in", source, "--lfi-file-out", "-"],
690
+ output=False,
691
+ inpipe=True,
692
+ bufsize=8192,
693
+ )
694
+
695
+ def _std_remove(self, *args):
696
+ """Remove (possibly) multi lfi files."""
697
+ st = LFI_Status(result=list())
698
+ for pname in args:
699
+ for objpath in self.sh.glob(pname):
700
+ xlfi = self.is_xlfi(objpath)
701
+ if xlfi:
702
+ rc = self._spawn(
703
+ ["lfi_alt_remv", "--lfi-file", objpath], output=False
704
+ )
705
+ else:
706
+ rc = self.sh.remove(objpath)
707
+ st.result.append(dict(path=objpath, multi=xlfi, rc=rc))
708
+ st.rc = rc
709
+ for dirpath in self.sh.glob(pname + ".d"):
710
+ if self.sh.path.exists(dirpath):
711
+ rc = self.sh.remove(dirpath)
712
+ st.result.append(dict(path=dirpath, multi=True, rc=rc))
713
+ st.rc = rc
714
+ return st
715
+
716
+ lfi_rm = lfi_remove = fa_rm = fa_remove = _std_remove
717
+
718
+ def _cp_pack_read(self, source, destination):
719
+ if self.warnpack:
720
+ logger.warning("Suspicious packing <%s>", source)
721
+ rc = self._spawn(
722
+ [
723
+ "lfi_alt_pack",
724
+ "--lfi-file-in",
725
+ source,
726
+ "--lfi-file-out",
727
+ destination,
728
+ ],
729
+ output=False,
730
+ )
731
+ self.sh.chmod(destination, 0o444)
732
+ return rc
733
+
734
+ def _cp_pack_write(self, source, destination):
735
+ if self.warnpack:
736
+ logger.warning("Suspicious packing <%s>", source)
737
+ rc = self._spawn(
738
+ [
739
+ "lfi_alt_pack",
740
+ "--lfi-file-in",
741
+ source,
742
+ "--lfi-file-out",
743
+ destination,
744
+ ],
745
+ output=False,
746
+ )
747
+ self.sh.chmod(destination, 0o644)
748
+ return rc
749
+
750
+ def _cp_copy_read(self, source, destination):
751
+ rc = self._spawn(
752
+ [
753
+ "lfi_alt_copy",
754
+ "--lfi-file-in",
755
+ source,
756
+ "--lfi-file-out",
757
+ destination,
758
+ ],
759
+ output=False,
760
+ )
761
+ self.sh.chmod(destination, 0o444)
762
+ return rc
763
+
764
+ def _cp_copy_write(self, source, destination):
765
+ rc = self._spawn(
766
+ [
767
+ "lfi_alt_copy",
768
+ "--lfi-file-in",
769
+ source,
770
+ "--lfi-file-out",
771
+ destination,
772
+ ],
773
+ output=False,
774
+ )
775
+ self.sh.chmod(destination, 0o644)
776
+ return rc
777
+
778
+ _cp_aspack_fsok_read = _cp_pack_read
779
+ _cp_aspack_fsok_write = _cp_pack_write
780
+ _cp_aspack_fsko_read = _cp_pack_read
781
+ _cp_aspack_fsko_write = _cp_pack_write
782
+
783
+ _cp_nopack_fsok_read = _cp_copy_read
784
+ _cp_nopack_fsok_write = _cp_copy_write
785
+ _cp_nopack_fsko_read = _cp_pack_read
786
+ _cp_nopack_fsko_write = _cp_pack_write
787
+
788
+ def _multicpmethod(self, pack=False, intent="in", samefs=False):
789
+ return "_cp_{:s}_{:s}_{:s}".format(
790
+ "aspack" if pack else "nopack",
791
+ "fsok" if samefs else "fsko",
792
+ "read" if intent == "in" else "write",
793
+ )
794
+
795
+ def _std_copy(
796
+ self,
797
+ source,
798
+ destination,
799
+ smartcp_threshold=0,
800
+ intent="in",
801
+ pack=False,
802
+ silent=False,
803
+ ):
804
+ """Extended copy for (possibly) multi lfi file."""
805
+ st = LFI_Status()
806
+ if not self.sh.path.exists(source):
807
+ logger.error("Missing source %s", source)
808
+ st.rc = 2
809
+ st.stderr = "No such source file or directory : [" + source + "]"
810
+ return st
811
+ if self.is_xlfi(source):
812
+ if not self.sh.filecocoon(destination):
813
+ raise OSError("Could not cocoon [" + destination + "]")
814
+ if not self.lfi_rm(destination):
815
+ raise OSError(
816
+ "Could not clean destination [" + destination + "]"
817
+ )
818
+ xcp = self._multicpmethod(
819
+ pack=pack,
820
+ intent=intent,
821
+ samefs=self.sh.is_samefs(source, destination),
822
+ )
823
+ actualcp = getattr(self, xcp, None)
824
+ if actualcp is None:
825
+ raise AttributeError("No actual LFI cp command " + xcp)
826
+ else:
827
+ st.rc = actualcp(source, self.sh.path.realpath(destination))
828
+ else:
829
+ if intent == "in":
830
+ st.rc = self.sh.smartcp(
831
+ source, destination, smartcp_threshold=smartcp_threshold
832
+ )
833
+ else:
834
+ st.rc = self.sh.cp(
835
+ source, destination, smartcp_threshold=smartcp_threshold
836
+ )
837
+ return st
838
+
839
+ lfi_cp = lfi_copy = fa_cp = fa_copy = _std_copy
840
+
841
+ def _std_move(self, source, destination, intent=None, pack=False):
842
+ """Extended mv for (possibly) multi lfi file."""
843
+ if self.is_xlfi(source):
844
+ if intent is None:
845
+ intent = (
846
+ "inout" if self.sh.access(source, self.sh.W_OK) else "in"
847
+ )
848
+ st = self.lfi_cp(source, destination, intent=intent, pack=pack)
849
+ if st:
850
+ st = self.lfi_rm(source)
851
+ else:
852
+ st = LFI_Status()
853
+ st.rc = self.sh.mv(source, destination)
854
+ return st
855
+
856
+ lfi_mv = lfi_move = fa_mv = fa_move = _std_move
857
+
858
+
859
+ class IO_Poll(addons.Addon):
860
+ """
861
+ Default interface to ``io_poll`` utility.
862
+ This addon is in charge of multi-file reshaping after IFS-ARPEGE execution.
863
+ """
864
+
865
+ LFI_HNDL_SPEC = ":1"
866
+ DR_HOOK_SILENT = 1
867
+ DR_HOOK_NOT_MPI = 1
868
+ DR_HOOK_ASSERT_MPI_INITIALIZED = 0
869
+ OMP_STACKSIZE = "32M"
870
+ KMP_STACKSIZE = "32M"
871
+ KMP_MONITOR_STACKSIZE = "32M"
872
+
873
+ _footprint = dict(
874
+ info="Default io_poll system interface",
875
+ attr=dict(
876
+ kind=dict(
877
+ values=["iopoll", "io_poll"],
878
+ remap=dict(
879
+ io_poll="iopoll",
880
+ ),
881
+ ),
882
+ cfginfo=dict(
883
+ default="lfi",
884
+ ),
885
+ cmd=dict(
886
+ alias=("iopollcmd", "io_pollcmd", "io_poll_cmd"),
887
+ default="io_poll",
888
+ ),
889
+ path=dict(
890
+ alias=("iopollpath", "io_pollpath", "io_poll_path", "iopath"),
891
+ ),
892
+ interpreter=dict(
893
+ values=["perl", "none", "None"],
894
+ remap=dict(
895
+ {
896
+ "None": "none",
897
+ }
898
+ ),
899
+ default="perl",
900
+ optional=True,
901
+ ),
902
+ toolkind=dict(default="iopoll"),
903
+ ),
904
+ )
905
+
906
+ def __init__(self, *args, **kw):
907
+ """Abstract Addon initialisation."""
908
+ logger.debug("IO_Poll init %s", self.__class__)
909
+ super().__init__(*args, **kw)
910
+ self._polled = set()
911
+
912
+ def _spawn(self, cmd, **kw):
913
+ """Tube to set LFITOOLS env variable."""
914
+ if "LFITOOLS" not in self.env:
915
+ active_lfi = LFI_Tool_Raw.in_shell(
916
+ self.sh
917
+ ) or LFI_Tool_Py.in_shell(self.sh)
918
+ if active_lfi is None:
919
+ raise RuntimeError("Could not find any active LFI Tool")
920
+ self.env.LFITOOLS = (
921
+ active_lfi.actual_path + "/" + active_lfi.actual_cmd
922
+ )
923
+ # Is there a need for an interpreter ?
924
+ if self.interpreter != "none":
925
+ kw["interpreter"] = self.interpreter
926
+ return super()._spawn(cmd, **kw)
927
+
928
+ def io_poll(self, prefix, nproc_io=None):
929
+ """Do the actual job of polling files prefixed by ``prefix``."""
930
+ cmd = ["--prefix", prefix]
931
+ if nproc_io is None:
932
+ if not self.sh.path.exists("fort.4"):
933
+ raise OSError(
934
+ "The proc_io option or a fort.4 file should be provided."
935
+ )
936
+ else:
937
+ cmd.extend(["--nproc_io", str(nproc_io)])
938
+
939
+ # Catch the file processed
940
+ rawout = self._spawn(cmd)
941
+
942
+ # Cumulative results
943
+ st = LFI_Status()
944
+ st.result = rawout
945
+ for polledfile in st.result:
946
+ self._polled.add(polledfile)
947
+ st.rc &= self.sh.rclast
948
+ return st
949
+
950
+ @property
951
+ def polled(self):
952
+ """List of files already polled."""
953
+ return sorted(self._polled)