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
@@ -0,0 +1,413 @@
1
+ """
2
+ Interface to SMS commands.
3
+ """
4
+
5
+ import contextlib
6
+ import functools
7
+
8
+ from bronx.fancies import loggers
9
+ import footprints
10
+
11
+ from .services import Service
12
+
13
+ __all__ = []
14
+
15
+ logger = loggers.getLogger(__name__)
16
+
17
+
18
+ class Scheduler(Service):
19
+ """Abstract class for scheduling systems."""
20
+
21
+ _abstract = True
22
+ _footprint = dict(
23
+ info = 'Scheduling service class',
24
+ attr = dict(
25
+ muteset = dict(
26
+ optional = True,
27
+ default = footprints.FPSet(),
28
+ type = footprints.FPSet,
29
+ )
30
+ )
31
+ )
32
+
33
+ def __init__(self, *args, **kw):
34
+ logger.debug('Scheduler init %s', self.__class__)
35
+ super().__init__(*args, **kw)
36
+
37
+ @property
38
+ def env(self):
39
+ """Return the current active environment."""
40
+ return self.sh.env
41
+
42
+ def cmd_rename(self, cmd):
43
+ """Remap command name. Default is lowercase command name."""
44
+ return cmd.lower()
45
+
46
+ def mute(self, cmd):
47
+ """Switch off the given command."""
48
+ self.muteset.add(self.cmd_rename(cmd))
49
+
50
+ def play(self, cmd):
51
+ """Switch on the given command."""
52
+ self.muteset.discard(self.cmd_rename(cmd))
53
+
54
+ def clear(self):
55
+ """Clear set of mute commands."""
56
+ self.muteset.clear()
57
+
58
+
59
+ class EcmwfLikeScheduler(Scheduler):
60
+ """Abstract class for any ECMWF scheduling systems (SMS, Ecflow)."""
61
+
62
+ _abstract = True
63
+ _footprint = dict(
64
+ attr = dict(
65
+ env_pattern = dict(
66
+ info = 'Scheduler configuration variables start with...',
67
+ ),
68
+ non_critical_timeout = dict(
69
+ info = 'Timeout in seconds for non-critical commands',
70
+ type = int,
71
+ default = 5,
72
+ optional = True,
73
+ ),
74
+ )
75
+ )
76
+
77
+ _KNOWN_CMD = ()
78
+
79
+ def __init__(self, *args, **kw):
80
+ logger.debug('Scheduler init %s', self.__class__)
81
+ super(Scheduler, self).__init__(*args, **kw)
82
+ self._inside_child_session = False
83
+
84
+ def conf(self, kwenv):
85
+ """Possibly export the provided variables and return a dictionary of positioned variables."""
86
+ if kwenv:
87
+ for schedvar in [x.upper() for x in kwenv.keys() if x.upper().startswith(self.env_pattern)]:
88
+ self.env[schedvar] = str(kwenv[schedvar])
89
+ subenv = dict()
90
+ for schedvar in [x for x in self.env.keys() if x.startswith(self.env_pattern)]:
91
+ subenv[schedvar] = self.env.get(schedvar)
92
+ return subenv
93
+
94
+ def info(self):
95
+ """Dump current defined variables."""
96
+ for schedvar, schedvalue in self.conf(dict()).items():
97
+ print('{:s}="{!s}"'.format(schedvar, schedvalue))
98
+
99
+ def __call__(self, *args):
100
+ """By default call the :meth:`info` method."""
101
+ return self.info()
102
+
103
+ @contextlib.contextmanager
104
+ def child_session_setup(self):
105
+ """This may be customised in order to setup session related stuff."""
106
+ yield True
107
+
108
+ @contextlib.contextmanager
109
+ def child_session(self):
110
+ """Prepare the environment and possibly setup the session.
111
+
112
+ It will only clone the environment and call child_session_setup
113
+ once (even if child is called again from within the first child call).
114
+ """
115
+ if not self._inside_child_session:
116
+ self._inside_child_session = True
117
+ try:
118
+ with self.env.clone():
119
+ with self.child_session_setup() as setup_rc:
120
+ yield setup_rc
121
+ finally:
122
+ self._inside_child_session = False
123
+ else:
124
+ yield True
125
+
126
+ def setup_default(self, *args):
127
+ """Fake method for any missing callback, ie: setup_init, setup_abort, etc."""
128
+ return True
129
+
130
+ def close_default(self, *args):
131
+ """Fake method for any missing callback, ie: close_init, close_abort, etc."""
132
+ return True
133
+
134
+ @contextlib.contextmanager
135
+ def wrap_actual_child_command(self, kwoptions):
136
+ """Last minute wrap before binary child command."""
137
+ yield True
138
+
139
+ def child(self, cmd, *options, **kwoptions):
140
+ """Miscellaneous sms/ecflow child sub-command."""
141
+ rc = None
142
+ cmd = self.cmd_rename(cmd)
143
+ if cmd in self.muteset:
144
+ logger.warning('%s mute command [%s]', self.kind, cmd)
145
+ else:
146
+ with self.child_session() as session_rc:
147
+ if session_rc:
148
+ if getattr(self, 'setup_' + cmd, self.setup_default)(*options):
149
+ wrapp_rc = False
150
+ try:
151
+ with self.wrap_actual_child_command(kwoptions) as wrapp_rc:
152
+ if wrapp_rc:
153
+ rc = self._actual_child(cmd, options, **kwoptions)
154
+ else:
155
+ logger.warning('Actual [%s %s] command wrap failed', self.kind, cmd)
156
+ finally:
157
+ if wrapp_rc:
158
+ getattr(self, 'close_' + cmd, self.close_default)(*options)
159
+ else:
160
+ logger.warning('Actual [%s %s] command skipped due to setup action',
161
+ self.kind, cmd)
162
+ else:
163
+ logger.warning('Actual [%s %s] command skipped session setup failure',
164
+ self.kind, cmd)
165
+ return rc
166
+
167
+ def _actual_child(self, cmd, options, critical=True):
168
+ """The actual child command implementation."""
169
+ raise NotImplementedError("This an abstract method.")
170
+
171
+ def __getattr__(self, name):
172
+ """Deal with any known commands."""
173
+ if name in self._KNOWN_CMD:
174
+ return functools.partial(self.child, name)
175
+ else:
176
+ raise AttributeError(name)
177
+
178
+
179
+ class SMS(EcmwfLikeScheduler):
180
+ """
181
+ Client interface to SMS scheduling and monitoring system.
182
+ """
183
+
184
+ _footprint = dict(
185
+ info = 'SMS client service',
186
+ attr = dict(
187
+ kind = dict(
188
+ values = ['sms'],
189
+ ),
190
+ rootdir = dict(
191
+ optional = True,
192
+ default = None,
193
+ alias = ('install',),
194
+ ),
195
+ env_pattern = dict(
196
+ default = 'SMS',
197
+ optional = True,
198
+ )
199
+ )
200
+ )
201
+
202
+ _KNOWN_CMD = ('abort', 'complete', 'event', 'init', 'label', 'meter',
203
+ 'msg', 'variable', 'fix')
204
+
205
+ def __init__(self, *args, **kw):
206
+ logger.debug('SMS scheduler client init %s', self)
207
+ super().__init__(*args, **kw)
208
+ self._actual_rootdir = self.rootdir
209
+ if self._actual_rootdir is None:
210
+ self._actual_rootdir = (
211
+ self.env.SMS_INSTALL_ROOT or
212
+ from_config(section="sms", key="rootdir")
213
+ )
214
+ if self._actual_rootdir is None:
215
+ logger.warning(
216
+ 'SMS service could not guess install location [%s]',
217
+ str(guesspath)
218
+ )
219
+ if self.sh.path.exists(self.cmdpath('init')):
220
+ self.env.setbinpath(self._actual_rootdir)
221
+ else:
222
+ logger.warning(
223
+ 'No SMS client found at init time [rootdir:%s]>',
224
+ self._actual_rootdir
225
+ )
226
+
227
+ def cmd_rename(self, cmd):
228
+ """Remap command name. Strip any sms prefix."""
229
+ cmd = super().cmd_rename(cmd)
230
+ while cmd.startswith('sms'):
231
+ cmd = cmd[3:]
232
+ return cmd
233
+
234
+ def cmdpath(self, cmd):
235
+ """Return a complete binary path to cmd."""
236
+ cmd = 'sms' + self.cmd_rename(cmd)
237
+ if self._actual_rootdir:
238
+ return self.sh.path.join(self._actual_rootdir, cmd)
239
+ else:
240
+ return cmd
241
+
242
+ def path(self):
243
+ """Return actual binary path to SMS commands."""
244
+ return self._actual_rootdir
245
+
246
+ @contextlib.contextmanager
247
+ def child_session_setup(self):
248
+ """Setup the path to the SMS client."""
249
+ with super().child_session_setup() as setup_rc:
250
+ self.env.SMSACTUALPATH = self._actual_rootdir
251
+ yield setup_rc
252
+
253
+ @contextlib.contextmanager
254
+ def wrap_actual_child_command(self, kwoptions):
255
+ """Last minute wrap before binary child command."""
256
+ with super().wrap_actual_child_command(kwoptions) as wrapp_rc:
257
+ upd_env = dict()
258
+ if not kwoptions.get('critical', True):
259
+ upd_env['SMSDENIED'] = 1
260
+ if self.non_critical_timeout:
261
+ upd_env['SMSTIMEOUT'] = self.non_critical_timeout
262
+ if upd_env:
263
+ with self.env.delta_context(** upd_env):
264
+ yield wrapp_rc
265
+ else:
266
+ yield wrapp_rc
267
+
268
+ def _actual_child(self, cmd, options, critical=True):
269
+ """Miscellaneous smschild subcommand."""
270
+ args = [self.cmdpath(cmd)]
271
+ args.extend(options)
272
+ return self.sh.spawn(args, output=False, fatal=critical)
273
+
274
+
275
+ class SMSColor(SMS):
276
+ """
277
+ Default SMS service with some extra colorful features.
278
+ """
279
+
280
+ _footprint = dict(
281
+ info = 'SMS color client service',
282
+ attr = dict(
283
+ kind = dict(
284
+ values = ['smscolor'],
285
+ ),
286
+ )
287
+ )
288
+
289
+ @contextlib.contextmanager
290
+ def wrap_actual_child_command(self, kwoptions):
291
+ """Last minute wrap before binary child command."""
292
+ with super().wrap_actual_child_command(kwoptions) as wrapp_rc:
293
+ print("SMS COLOR")
294
+ yield wrapp_rc
295
+
296
+
297
+ class EcFlow(EcmwfLikeScheduler):
298
+ """
299
+ Client interface to the ecFlow scheduling and monitoring system.
300
+ """
301
+
302
+ _footprint = dict(
303
+ info = 'SMS client service',
304
+ attr = dict(
305
+ kind = dict(
306
+ values = ['ecflow'],
307
+ ),
308
+ clientpath = dict(
309
+ info = ("Path to the ecFlow client binary (if omitted, " +
310
+ "it's read in the configuration file)"),
311
+ optional = True,
312
+ default = None,
313
+ ),
314
+ env_pattern = dict(
315
+ default = 'ECF_',
316
+ optional = True,
317
+ )
318
+ )
319
+ )
320
+
321
+ _KNOWN_CMD = ('abort', 'complete', 'event', 'init', 'label', 'meter', 'msg', 'alter')
322
+
323
+ def __init__(self, *args, **kw):
324
+ logger.debug('EcFlow scheduler client init %s', self)
325
+ super().__init__(*args, **kw)
326
+ self._actual_clientpath = self.clientpath
327
+
328
+ def path(self):
329
+ """Return the actual binary path to the EcFlow client."""
330
+ if self._actual_clientpath is None:
331
+ thistarget = self.sh.default_target
332
+ guesspath = self.env.ECF_CLIENT_PATH or thistarget.get('ecflow:clientpath')
333
+ ecfversion = self.env.get('ECF_VERSION', 'default')
334
+ guesspath = guesspath.format(version=ecfversion)
335
+ if guesspath is None:
336
+ logger.warning('ecFlow service could not guess the install location [%s]', str(guesspath))
337
+ else:
338
+ self._actual_clientpath = guesspath
339
+ if not self.sh.path.exists(self._actual_clientpath):
340
+ logger.warning('No ecFlow client found at init time [path:%s]>', self._actual_clientpath)
341
+ return self._actual_clientpath
342
+
343
+ @contextlib.contextmanager
344
+ def child_session_setup(self):
345
+ """Setup a SSH tunnel if necessary."""
346
+ with super().child_session_setup() as setup_rc:
347
+ if setup_rc and not self.sh.default_target.isnetworknode:
348
+ tunnel = None
349
+ # wait and retries from config
350
+ thistarget = self.sh.default_target
351
+ sshwait = float(thistarget.get('ecflow:sshproxy_wait', 6))
352
+ sshretries = float(thistarget.get('ecflow:sshproxy_retries', 2))
353
+ sshretrydelay = float(thistarget.get('ecflow:sshproxy_retrydelay', 1))
354
+ # Build up an SSH tunnel to convey the EcFlow command
355
+ ecconf = self.conf(dict())
356
+ echost = ecconf.get('{:s}HOST'.format(self.env_pattern), None)
357
+ ecport = ecconf.get('{:s}PORT'.format(self.env_pattern), None)
358
+ if not (echost and ecport):
359
+ setup_rc = False
360
+ else:
361
+ sshobj = self.sh.ssh('network', virtualnode=True, mandatory_hostcheck=False,
362
+ maxtries=sshretries, triesdelay=sshretrydelay)
363
+ tunnel = sshobj.tunnel(echost, int(ecport), maxwait=sshwait)
364
+ if not tunnel:
365
+ setup_rc = False
366
+ else:
367
+ newvars = {'{:s}HOST'.format(self.env_pattern): 'localhost',
368
+ '{:s}PORT'.format(self.env_pattern): tunnel.entranceport}
369
+ self.env.update(**newvars)
370
+ try:
371
+ yield setup_rc
372
+ finally:
373
+ # Close the SSH tunnel regardless of the exit status
374
+ if tunnel:
375
+ tunnel.close()
376
+ else:
377
+ yield setup_rc
378
+
379
+ @contextlib.contextmanager
380
+ def wrap_actual_child_command(self, kwoptions):
381
+ """Last minute wrap before binary child command."""
382
+ with super().wrap_actual_child_command(kwoptions) as wrapp_rc:
383
+ upd_env = dict()
384
+ if not kwoptions.get('critical', True):
385
+ upd_env['{:s}DENIED'.format(self.env_pattern)] = 1
386
+ if self.non_critical_timeout:
387
+ upd_env['{:s}TIMEOUT'.format(self.env_pattern)] = self.non_critical_timeout
388
+ if upd_env:
389
+ with self.env.delta_context(** upd_env):
390
+ yield wrapp_rc
391
+ else:
392
+ yield wrapp_rc
393
+
394
+ def _actual_child(self, cmd, options, critical=True):
395
+ """Miscellaneous ecFlow sub-command."""
396
+ args = [self.path(), ]
397
+ if options:
398
+ args.append('--{:s}={!s}'.format(cmd, options[0]))
399
+ if len(options) > 1:
400
+ args.extend(options[1:])
401
+ else:
402
+ args.append('--{:s}'.format(cmd))
403
+ args = [str(a) for a in args]
404
+ logger.info('Issuing the ecFlow command: %s', ' '.join(args[1:]))
405
+ return self.sh.spawn(args, output=False, fatal=critical)
406
+
407
+ def abort(self, *opts):
408
+ """Gateway to :meth:`child` abort method."""
409
+ actual_opts = list(opts)
410
+ if not actual_opts:
411
+ # For backward compatibility with SMS
412
+ actual_opts.append("No abort reason provided")
413
+ return self.child('abort', *actual_opts)