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