ert 19.0.0rc1__py3-none-any.whl → 19.0.0rc3__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.
- ert/__main__.py +63 -94
- ert/analysis/_es_update.py +14 -11
- ert/config/_create_observation_dataframes.py +262 -23
- ert/config/_observations.py +153 -181
- ert/config/_read_summary.py +5 -4
- ert/config/ert_config.py +56 -1
- ert/config/parsing/observations_parser.py +0 -6
- ert/config/rft_config.py +1 -1
- ert/dark_storage/compute/__init__.py +0 -0
- ert/dark_storage/compute/misfits.py +42 -0
- ert/dark_storage/endpoints/__init__.py +2 -0
- ert/dark_storage/endpoints/compute/__init__.py +0 -0
- ert/dark_storage/endpoints/compute/misfits.py +95 -0
- ert/dark_storage/endpoints/experiments.py +3 -0
- ert/dark_storage/json_schema/experiment.py +1 -0
- ert/gui/main_window.py +0 -2
- ert/gui/tools/manage_experiments/export_dialog.py +0 -4
- ert/gui/tools/manage_experiments/storage_info_widget.py +5 -1
- ert/gui/tools/plot/plot_api.py +10 -10
- ert/gui/tools/plot/plot_widget.py +0 -5
- ert/gui/tools/plot/plot_window.py +1 -1
- ert/services/__init__.py +3 -7
- ert/services/_base_service.py +387 -0
- ert/services/_storage_main.py +22 -59
- ert/services/ert_server.py +24 -186
- ert/services/webviz_ert_service.py +20 -0
- ert/shared/storage/command.py +38 -0
- ert/shared/storage/extraction.py +42 -0
- ert/shared/version.py +3 -3
- ert/storage/local_ensemble.py +95 -2
- ert/storage/local_experiment.py +16 -0
- ert/storage/local_storage.py +1 -3
- ert/utils/__init__.py +0 -20
- {ert-19.0.0rc1.dist-info → ert-19.0.0rc3.dist-info}/METADATA +2 -2
- {ert-19.0.0rc1.dist-info → ert-19.0.0rc3.dist-info}/RECORD +46 -41
- {ert-19.0.0rc1.dist-info → ert-19.0.0rc3.dist-info}/WHEEL +1 -1
- everest/bin/everest_script.py +5 -5
- everest/bin/kill_script.py +2 -2
- everest/bin/monitor_script.py +2 -2
- everest/bin/utils.py +4 -4
- everest/detached/everserver.py +6 -6
- everest/gui/main_window.py +2 -2
- everest/util/__init__.py +19 -1
- ert/config/observation_config_migrations.py +0 -793
- ert/storage/migration/to22.py +0 -18
- {ert-19.0.0rc1.dist-info → ert-19.0.0rc3.dist-info}/entry_points.txt +0 -0
- {ert-19.0.0rc1.dist-info → ert-19.0.0rc3.dist-info}/licenses/COPYING +0 -0
- {ert-19.0.0rc1.dist-info → ert-19.0.0rc3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This file contains a more generic version of "ert services", and
|
|
3
|
+
is scheduled for removal when WebvizErt is removed.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import contextlib
|
|
9
|
+
import io
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import signal
|
|
13
|
+
import sys
|
|
14
|
+
import threading
|
|
15
|
+
import types
|
|
16
|
+
from collections.abc import Callable, Mapping, Sequence
|
|
17
|
+
from logging import Logger, getLogger
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from select import PIPE_BUF, select
|
|
20
|
+
from subprocess import Popen, TimeoutExpired
|
|
21
|
+
from tempfile import NamedTemporaryFile
|
|
22
|
+
from time import sleep
|
|
23
|
+
from types import FrameType
|
|
24
|
+
from typing import TYPE_CHECKING, Any, Generic, Self, TypedDict, TypeVar
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
T = TypeVar("T", bound="BaseService")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ErtServerConnectionInfo(TypedDict):
|
|
33
|
+
urls: list[str]
|
|
34
|
+
authtoken: str
|
|
35
|
+
host: str
|
|
36
|
+
port: str
|
|
37
|
+
cert: str
|
|
38
|
+
auth: str
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
SERVICE_CONF_PATHS: set[str] = set()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class BaseServiceExit(OSError):
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def cleanup_service_files(signum: int, frame: FrameType | None) -> None:
|
|
49
|
+
for file_path in SERVICE_CONF_PATHS:
|
|
50
|
+
file = Path(file_path)
|
|
51
|
+
if file.exists():
|
|
52
|
+
file.unlink()
|
|
53
|
+
raise BaseServiceExit(f"Signal {signum} received.")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
if threading.current_thread() is threading.main_thread():
|
|
57
|
+
signal.signal(signal.SIGTERM, cleanup_service_files)
|
|
58
|
+
signal.signal(signal.SIGINT, cleanup_service_files)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def local_exec_args(script_args: str | list[str]) -> list[str]:
|
|
62
|
+
"""
|
|
63
|
+
Convenience function that returns the exec_args for executing a Python
|
|
64
|
+
script in the directory of '_base_service.py'.
|
|
65
|
+
|
|
66
|
+
This is done instead of using 'python -m [module path]' due to the '-m' flag
|
|
67
|
+
adding the user's current working directory to sys.path. Executing a Python
|
|
68
|
+
script by itself will add the directory of the script rather than the
|
|
69
|
+
current working directory, thus we avoid accidentally importing user's
|
|
70
|
+
directories that just happen to have the same names as the ones we use.
|
|
71
|
+
"""
|
|
72
|
+
if isinstance(script_args, str):
|
|
73
|
+
script = script_args
|
|
74
|
+
rest: list[str] = []
|
|
75
|
+
else:
|
|
76
|
+
script = script_args[0]
|
|
77
|
+
rest = script_args[1:]
|
|
78
|
+
script = f"_{script}_main.py"
|
|
79
|
+
return [sys.executable, str(Path(__file__).parent / script), *rest]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class _Context(Generic[T]):
|
|
83
|
+
def __init__(self, service: T) -> None:
|
|
84
|
+
self._service = service
|
|
85
|
+
|
|
86
|
+
def __enter__(self) -> T:
|
|
87
|
+
return self._service
|
|
88
|
+
|
|
89
|
+
def __exit__(
|
|
90
|
+
self,
|
|
91
|
+
exc_type: type[BaseException] | None,
|
|
92
|
+
exc_value: BaseException | None,
|
|
93
|
+
traceback: types.TracebackType | None,
|
|
94
|
+
) -> bool:
|
|
95
|
+
self._service.shutdown()
|
|
96
|
+
return exc_type is None
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class _Proc(threading.Thread):
|
|
100
|
+
def __init__(
|
|
101
|
+
self,
|
|
102
|
+
service_name: str,
|
|
103
|
+
exec_args: Sequence[str],
|
|
104
|
+
timeout: int,
|
|
105
|
+
on_connection_info_received: Callable[
|
|
106
|
+
[ErtServerConnectionInfo | Exception | None], None
|
|
107
|
+
],
|
|
108
|
+
project: Path,
|
|
109
|
+
) -> None:
|
|
110
|
+
super().__init__()
|
|
111
|
+
|
|
112
|
+
self._shutdown = threading.Event()
|
|
113
|
+
|
|
114
|
+
self._service_name = service_name
|
|
115
|
+
self._exec_args = exec_args
|
|
116
|
+
self._timeout = timeout
|
|
117
|
+
self._propagate_connection_info_from_childproc = on_connection_info_received
|
|
118
|
+
self._service_config_path = project / f"{self._service_name}_server.json"
|
|
119
|
+
|
|
120
|
+
fd_read, fd_write = os.pipe()
|
|
121
|
+
self._comm_pipe = os.fdopen(fd_read)
|
|
122
|
+
|
|
123
|
+
env = os.environ.copy()
|
|
124
|
+
env["ERT_COMM_FD"] = str(fd_write)
|
|
125
|
+
|
|
126
|
+
SERVICE_CONF_PATHS.add(str(self._service_config_path))
|
|
127
|
+
|
|
128
|
+
# The process is waited for in _do_shutdown()
|
|
129
|
+
self._childproc = Popen(
|
|
130
|
+
self._exec_args,
|
|
131
|
+
pass_fds=(fd_write,),
|
|
132
|
+
env=env,
|
|
133
|
+
close_fds=True,
|
|
134
|
+
)
|
|
135
|
+
os.close(fd_write)
|
|
136
|
+
|
|
137
|
+
def run(self) -> None:
|
|
138
|
+
comm = self._read_connection_info_from_process(self._childproc)
|
|
139
|
+
|
|
140
|
+
if comm is None:
|
|
141
|
+
self._propagate_connection_info_from_childproc(TimeoutError())
|
|
142
|
+
return # _read_conn_info() has already cleaned up in this case
|
|
143
|
+
|
|
144
|
+
conn_info: ErtServerConnectionInfo | Exception | None = None
|
|
145
|
+
try:
|
|
146
|
+
conn_info = json.loads(comm)
|
|
147
|
+
except json.JSONDecodeError:
|
|
148
|
+
conn_info = ServerBootFail()
|
|
149
|
+
except Exception as exc:
|
|
150
|
+
conn_info = exc
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
self._propagate_connection_info_from_childproc(conn_info)
|
|
154
|
+
|
|
155
|
+
while True:
|
|
156
|
+
if self._childproc.poll() is not None:
|
|
157
|
+
break
|
|
158
|
+
if self._shutdown.wait(1):
|
|
159
|
+
self._do_shutdown()
|
|
160
|
+
break
|
|
161
|
+
|
|
162
|
+
except Exception as e:
|
|
163
|
+
print(str(e))
|
|
164
|
+
self.logger.exception(e)
|
|
165
|
+
|
|
166
|
+
finally:
|
|
167
|
+
self._ensure_connection_info_file_is_deleted()
|
|
168
|
+
|
|
169
|
+
def shutdown(self) -> int:
|
|
170
|
+
"""Shutdown the server."""
|
|
171
|
+
self._shutdown.set()
|
|
172
|
+
self.join()
|
|
173
|
+
|
|
174
|
+
return self._childproc.returncode
|
|
175
|
+
|
|
176
|
+
def _read_connection_info_from_process(self, proc: Popen[bytes]) -> str | None:
|
|
177
|
+
comm_buf = io.StringIO()
|
|
178
|
+
first_iter = True
|
|
179
|
+
while first_iter or proc.poll() is None:
|
|
180
|
+
first_iter = False
|
|
181
|
+
ready = select([self._comm_pipe], [], [], self._timeout)
|
|
182
|
+
|
|
183
|
+
# Timeout reached, exit with a failure
|
|
184
|
+
if ready == ([], [], []):
|
|
185
|
+
self._do_shutdown()
|
|
186
|
+
self._ensure_connection_info_file_is_deleted()
|
|
187
|
+
return None
|
|
188
|
+
|
|
189
|
+
x = self._comm_pipe.read(PIPE_BUF)
|
|
190
|
+
if not x: # EOF
|
|
191
|
+
break
|
|
192
|
+
comm_buf.write(x)
|
|
193
|
+
return comm_buf.getvalue()
|
|
194
|
+
|
|
195
|
+
def _do_shutdown(self) -> None:
|
|
196
|
+
if self._childproc is None:
|
|
197
|
+
return
|
|
198
|
+
try:
|
|
199
|
+
self._childproc.terminate()
|
|
200
|
+
self._childproc.wait(10) # Give it 10s to shut down cleanly..
|
|
201
|
+
except TimeoutExpired:
|
|
202
|
+
try:
|
|
203
|
+
self._childproc.kill() # ... then kick it harder...
|
|
204
|
+
self._childproc.wait(self._timeout) # ... and wait again
|
|
205
|
+
except TimeoutExpired:
|
|
206
|
+
self.logger.error(
|
|
207
|
+
f"waiting for child-process exceeded timeout {self._timeout}s"
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
def _ensure_connection_info_file_is_deleted(self) -> None:
|
|
211
|
+
"""
|
|
212
|
+
Ensure that the JSON connection information file is deleted
|
|
213
|
+
"""
|
|
214
|
+
with contextlib.suppress(OSError):
|
|
215
|
+
if self._service_config_path.exists():
|
|
216
|
+
self._service_config_path.unlink()
|
|
217
|
+
|
|
218
|
+
@property
|
|
219
|
+
def logger(self) -> Logger:
|
|
220
|
+
return getLogger(f"ert.shared.{self._service_name}")
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class ServerBootFail(RuntimeError):
|
|
224
|
+
pass
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class BaseService:
|
|
228
|
+
"""
|
|
229
|
+
BaseService provides a block-only-when-needed mechanism for starting and
|
|
230
|
+
maintaining services as subprocesses.
|
|
231
|
+
|
|
232
|
+
This is achieved by using a POSIX communication pipe, over which the service
|
|
233
|
+
can communicate that it has started. The contents of the communication is
|
|
234
|
+
also written to a file inside of the ERT storage directory.
|
|
235
|
+
|
|
236
|
+
The service itself can implement the other side of the pipe as such::
|
|
237
|
+
|
|
238
|
+
import os
|
|
239
|
+
|
|
240
|
+
# ... perform initialisation ...
|
|
241
|
+
|
|
242
|
+
# BaseService provides this environment variable with the pipe's FD
|
|
243
|
+
comm_fd = os.environ["ERT_COMM_FD"]
|
|
244
|
+
|
|
245
|
+
# Open the pipe with Python's IO classes for ease of use
|
|
246
|
+
with os.fdopen(comm_fd, "wb") as comm:
|
|
247
|
+
# Write JSON over the pipe, which will be interpreted by a subclass
|
|
248
|
+
# of BaseService on ERT's side
|
|
249
|
+
comm.write('{"some": "json"}')
|
|
250
|
+
|
|
251
|
+
# The pipe is flushed and closed here. This tells BaseService that
|
|
252
|
+
# initialisation is finished and it will try to read the JSON data.
|
|
253
|
+
"""
|
|
254
|
+
|
|
255
|
+
_instance: BaseService | None = None
|
|
256
|
+
|
|
257
|
+
def __init__(
|
|
258
|
+
self,
|
|
259
|
+
exec_args: Sequence[str] = (),
|
|
260
|
+
timeout: int = 120,
|
|
261
|
+
conn_info: ErtServerConnectionInfo | Exception | None = None,
|
|
262
|
+
project: str | None = None,
|
|
263
|
+
) -> None:
|
|
264
|
+
self._exec_args = exec_args
|
|
265
|
+
self._timeout = timeout
|
|
266
|
+
|
|
267
|
+
self._proc: _Proc | None = None
|
|
268
|
+
self._conn_info: ErtServerConnectionInfo | Exception | None = conn_info
|
|
269
|
+
self._conn_info_event = threading.Event()
|
|
270
|
+
self._project = Path(project) if project is not None else Path.cwd()
|
|
271
|
+
|
|
272
|
+
# Flag that we have connection information
|
|
273
|
+
if self._conn_info:
|
|
274
|
+
self._conn_info_event.set()
|
|
275
|
+
else:
|
|
276
|
+
self._proc = _Proc(
|
|
277
|
+
self.service_name, exec_args, timeout, self.set_conn_info, self._project
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
@classmethod
|
|
281
|
+
def start_server(cls, *args: Any, **kwargs: Any) -> _Context[Self]:
|
|
282
|
+
if cls._instance is not None:
|
|
283
|
+
raise RuntimeError("Server already running")
|
|
284
|
+
cls._instance = obj = cls(*args, **kwargs)
|
|
285
|
+
if obj._proc is not None:
|
|
286
|
+
obj._proc.start()
|
|
287
|
+
return _Context(obj)
|
|
288
|
+
|
|
289
|
+
@classmethod
|
|
290
|
+
def connect(
|
|
291
|
+
cls,
|
|
292
|
+
*,
|
|
293
|
+
project: os.PathLike[str],
|
|
294
|
+
timeout: int | None = None,
|
|
295
|
+
) -> Self:
|
|
296
|
+
if cls._instance is not None:
|
|
297
|
+
cls._instance.wait_until_ready()
|
|
298
|
+
assert isinstance(cls._instance, cls)
|
|
299
|
+
return cls._instance
|
|
300
|
+
|
|
301
|
+
path = Path(project)
|
|
302
|
+
name = f"{cls.service_name}_server.json"
|
|
303
|
+
# Note: If the caller actually pass None, we override that here...
|
|
304
|
+
if timeout is None:
|
|
305
|
+
timeout = 240
|
|
306
|
+
t = -1
|
|
307
|
+
while t < timeout:
|
|
308
|
+
if (path / name).exists():
|
|
309
|
+
with (path / name).open() as f:
|
|
310
|
+
return cls((), conn_info=json.load(f), project=str(path))
|
|
311
|
+
|
|
312
|
+
sleep(1)
|
|
313
|
+
t += 1
|
|
314
|
+
|
|
315
|
+
raise TimeoutError("Server not started")
|
|
316
|
+
|
|
317
|
+
def wait_until_ready(self, timeout: int | None = None) -> bool:
|
|
318
|
+
if timeout is None:
|
|
319
|
+
timeout = self._timeout
|
|
320
|
+
|
|
321
|
+
if self._conn_info_event.wait(timeout):
|
|
322
|
+
return not (
|
|
323
|
+
self._conn_info is None or isinstance(self._conn_info, Exception)
|
|
324
|
+
)
|
|
325
|
+
if isinstance(self._conn_info, TimeoutError):
|
|
326
|
+
self.logger.critical(f"startup exceeded defined timeout {timeout}s")
|
|
327
|
+
return False # Timeout reached
|
|
328
|
+
|
|
329
|
+
def wait(self) -> None:
|
|
330
|
+
if self._proc is not None:
|
|
331
|
+
self._proc.join()
|
|
332
|
+
|
|
333
|
+
def set_conn_info(self, info: ErtServerConnectionInfo | Exception | None) -> None:
|
|
334
|
+
if self._conn_info is not None:
|
|
335
|
+
raise ValueError("Connection information already set")
|
|
336
|
+
if info is None:
|
|
337
|
+
raise ValueError
|
|
338
|
+
self._conn_info = info
|
|
339
|
+
|
|
340
|
+
if self._project is not None:
|
|
341
|
+
if not Path(self._project).exists():
|
|
342
|
+
raise RuntimeError(f"No storage exists at : {self._project}")
|
|
343
|
+
path = f"{self._project}/{self.service_name}_server.json"
|
|
344
|
+
else:
|
|
345
|
+
path = f"{self.service_name}_server.json"
|
|
346
|
+
|
|
347
|
+
if isinstance(info, Mapping):
|
|
348
|
+
with NamedTemporaryFile(dir=f"{self._project}", delete=False) as f:
|
|
349
|
+
f.write(json.dumps(info, indent=4).encode("utf-8"))
|
|
350
|
+
f.flush()
|
|
351
|
+
os.rename(f.name, path)
|
|
352
|
+
|
|
353
|
+
self._conn_info_event.set()
|
|
354
|
+
|
|
355
|
+
def fetch_conn_info(self) -> Mapping[str, Any]:
|
|
356
|
+
is_ready = self.wait_until_ready(self._timeout)
|
|
357
|
+
if isinstance(self._conn_info, Exception):
|
|
358
|
+
raise self._conn_info
|
|
359
|
+
if not is_ready:
|
|
360
|
+
raise TimeoutError
|
|
361
|
+
if self._conn_info is None:
|
|
362
|
+
raise ValueError("conn_info is None")
|
|
363
|
+
return self._conn_info
|
|
364
|
+
|
|
365
|
+
def shutdown(self) -> int:
|
|
366
|
+
"""Shutdown the server."""
|
|
367
|
+
if self._proc is None:
|
|
368
|
+
return -1
|
|
369
|
+
self.__class__._instance = None
|
|
370
|
+
proc, self._proc = self._proc, None
|
|
371
|
+
return proc.shutdown()
|
|
372
|
+
|
|
373
|
+
@property
|
|
374
|
+
def service_name(self) -> str:
|
|
375
|
+
"""
|
|
376
|
+
Subclass should return the name of the service, eg 'storage' for ERT Storage.
|
|
377
|
+
Used for identifying the server information JSON file.
|
|
378
|
+
"""
|
|
379
|
+
raise NotImplementedError
|
|
380
|
+
|
|
381
|
+
@property
|
|
382
|
+
def logger(self) -> Logger:
|
|
383
|
+
return getLogger(f"ert.shared.{self.service_name}")
|
|
384
|
+
|
|
385
|
+
@property
|
|
386
|
+
def _service_file(self) -> str:
|
|
387
|
+
return f"{self.service_name}_server.json"
|
ert/services/_storage_main.py
CHANGED
|
@@ -13,7 +13,6 @@ import sys
|
|
|
13
13
|
import threading
|
|
14
14
|
import time
|
|
15
15
|
import warnings
|
|
16
|
-
from argparse import ArgumentParser
|
|
17
16
|
from base64 import b64encode
|
|
18
17
|
from pathlib import Path
|
|
19
18
|
from typing import Any
|
|
@@ -30,11 +29,12 @@ from uvicorn.supervisors import ChangeReload
|
|
|
30
29
|
|
|
31
30
|
from ert.logging import STORAGE_LOG_CONFIG
|
|
32
31
|
from ert.plugins import setup_site_logging
|
|
33
|
-
from ert.services import
|
|
32
|
+
from ert.services._base_service import BaseServiceExit
|
|
34
33
|
from ert.shared import __file__ as ert_shared_path
|
|
35
34
|
from ert.shared import find_available_socket, get_machine_name
|
|
35
|
+
from ert.shared.storage.command import add_parser_options
|
|
36
36
|
from ert.trace import tracer
|
|
37
|
-
from
|
|
37
|
+
from everest.util import makedirs_if_needed
|
|
38
38
|
|
|
39
39
|
DARK_STORAGE_APP = "ert.dark_storage.app:app"
|
|
40
40
|
|
|
@@ -82,7 +82,7 @@ def _get_host_list() -> list[str]:
|
|
|
82
82
|
|
|
83
83
|
|
|
84
84
|
def _create_connection_info(
|
|
85
|
-
sock: socket.socket, authtoken: str, cert: str | os.PathLike[str]
|
|
85
|
+
sock: socket.socket, authtoken: str, cert: str | os.PathLike[str]
|
|
86
86
|
) -> dict[str, Any]:
|
|
87
87
|
connection_info = {
|
|
88
88
|
"urls": [
|
|
@@ -91,7 +91,7 @@ def _create_connection_info(
|
|
|
91
91
|
"authtoken": authtoken,
|
|
92
92
|
"host": get_machine_name(),
|
|
93
93
|
"port": sock.getsockname()[1],
|
|
94
|
-
"cert":
|
|
94
|
+
"cert": cert,
|
|
95
95
|
"auth": authtoken,
|
|
96
96
|
}
|
|
97
97
|
|
|
@@ -102,17 +102,14 @@ def _create_connection_info(
|
|
|
102
102
|
return connection_info
|
|
103
103
|
|
|
104
104
|
|
|
105
|
-
def _generate_certificate(cert_folder:
|
|
105
|
+
def _generate_certificate(cert_folder: str) -> tuple[str, str, bytes]:
|
|
106
106
|
"""Generate a private key and a certificate signed with it
|
|
107
107
|
|
|
108
108
|
Both the certificate and the key are written to files in the folder given
|
|
109
109
|
by `get_certificate_dir(config)`. The key is encrypted before being
|
|
110
110
|
stored.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
* Certificate file path
|
|
114
|
-
* Key file path
|
|
115
|
-
* Password used for encrypting the key
|
|
111
|
+
Returns the path to the certificate file, the path to the key file, and
|
|
112
|
+
the password used for encrypting the key
|
|
116
113
|
"""
|
|
117
114
|
# Generate private key
|
|
118
115
|
key = rsa.generate_private_key(
|
|
@@ -153,11 +150,11 @@ def _generate_certificate(cert_folder: Path) -> tuple[Path, Path, bytes]:
|
|
|
153
150
|
|
|
154
151
|
# Write certificate and key to disk
|
|
155
152
|
makedirs_if_needed(cert_folder)
|
|
156
|
-
cert_path = cert_folder
|
|
157
|
-
cert_path.write_bytes(cert.public_bytes(serialization.Encoding.PEM))
|
|
158
|
-
key_path = cert_folder
|
|
153
|
+
cert_path = os.path.join(cert_folder, dns_name + ".crt")
|
|
154
|
+
Path(cert_path).write_bytes(cert.public_bytes(serialization.Encoding.PEM))
|
|
155
|
+
key_path = os.path.join(cert_folder, dns_name + ".key")
|
|
159
156
|
pw = bytes(os.urandom(28))
|
|
160
|
-
key_path.write_bytes(
|
|
157
|
+
Path(key_path).write_bytes(
|
|
161
158
|
key.private_bytes(
|
|
162
159
|
encoding=serialization.Encoding.PEM,
|
|
163
160
|
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
|
@@ -187,16 +184,16 @@ def run_server(
|
|
|
187
184
|
|
|
188
185
|
config_args: dict[str, Any] = {}
|
|
189
186
|
if args.debug or debug:
|
|
190
|
-
config_args.update(reload=True, reload_dirs=[
|
|
187
|
+
config_args.update(reload=True, reload_dirs=[os.path.dirname(ert_shared_path)])
|
|
191
188
|
os.environ["ERT_STORAGE_DEBUG"] = "1"
|
|
192
189
|
|
|
193
|
-
sock
|
|
190
|
+
sock = find_available_socket(
|
|
194
191
|
host=get_machine_name(), port_range=range(51850, 51870 + 1)
|
|
195
192
|
)
|
|
196
193
|
|
|
197
194
|
# Appropriated from uvicorn.main:run
|
|
198
195
|
os.environ["ERT_STORAGE_NO_TOKEN"] = "1"
|
|
199
|
-
os.environ["ERT_STORAGE_ENS_PATH"] =
|
|
196
|
+
os.environ["ERT_STORAGE_ENS_PATH"] = os.path.abspath(args.project)
|
|
200
197
|
config = (
|
|
201
198
|
# uvicorn.Config() resets the logging config (overriding additional
|
|
202
199
|
# handlers added to loggers like e.g. the ert_azurelogger handler
|
|
@@ -256,11 +253,11 @@ def _join_terminate_thread(terminate_on_parent_death_thread: threading.Thread) -
|
|
|
256
253
|
"""Join the terminate thread, handling BaseServiceExit (which is used by Everest)"""
|
|
257
254
|
try:
|
|
258
255
|
terminate_on_parent_death_thread.join()
|
|
259
|
-
except
|
|
256
|
+
except BaseServiceExit:
|
|
260
257
|
logger = logging.getLogger("ert.shared.storage.info")
|
|
261
258
|
logger.info(
|
|
262
259
|
"Got BaseServiceExit while joining terminate thread, "
|
|
263
|
-
"as expected from
|
|
260
|
+
"as expected from _base_service.py"
|
|
264
261
|
)
|
|
265
262
|
|
|
266
263
|
|
|
@@ -268,7 +265,9 @@ def main() -> None:
|
|
|
268
265
|
args = parse_args()
|
|
269
266
|
authentication = _generate_authentication()
|
|
270
267
|
os.environ["ERT_STORAGE_TOKEN"] = authentication
|
|
271
|
-
cert_path, key_path, key_pw = _generate_certificate(
|
|
268
|
+
cert_path, key_path, key_pw = _generate_certificate(
|
|
269
|
+
os.path.join(args.project, "cert")
|
|
270
|
+
)
|
|
272
271
|
config_args: dict[str, Any] = {
|
|
273
272
|
"ssl_keyfile": key_path,
|
|
274
273
|
"ssl_certfile": cert_path,
|
|
@@ -284,7 +283,7 @@ def main() -> None:
|
|
|
284
283
|
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
|
285
284
|
|
|
286
285
|
if args.debug:
|
|
287
|
-
config_args.update(reload=True, reload_dirs=[
|
|
286
|
+
config_args.update(reload=True, reload_dirs=[os.path.dirname(ert_shared_path)])
|
|
288
287
|
|
|
289
288
|
# Need to run uvicorn.Config before entering the ErtPluginContext because
|
|
290
289
|
# uvicorn.Config overrides the configuration of existing loggers, thus removing
|
|
@@ -311,48 +310,12 @@ def main() -> None:
|
|
|
311
310
|
logger.info("Starting dark storage")
|
|
312
311
|
logger.info(f"Started dark storage with parent {args.parent_pid}")
|
|
313
312
|
run_server(args, debug=False, uvicorn_config=uvicorn_config)
|
|
314
|
-
except (SystemExit,
|
|
313
|
+
except (SystemExit, BaseServiceExit):
|
|
315
314
|
logger.info("Stopping dark storage")
|
|
316
315
|
finally:
|
|
317
316
|
stopped.set()
|
|
318
317
|
_join_terminate_thread(terminate_on_parent_death_thread)
|
|
319
318
|
|
|
320
319
|
|
|
321
|
-
def add_parser_options(ap: ArgumentParser) -> None:
|
|
322
|
-
ap.add_argument(
|
|
323
|
-
"config",
|
|
324
|
-
type=str,
|
|
325
|
-
help=("ERT config file to start the server from "),
|
|
326
|
-
nargs="?", # optional
|
|
327
|
-
)
|
|
328
|
-
ap.add_argument(
|
|
329
|
-
"--project",
|
|
330
|
-
"-p",
|
|
331
|
-
type=Path,
|
|
332
|
-
help="Path to directory in which to create storage_server.json",
|
|
333
|
-
default=Path.cwd(),
|
|
334
|
-
)
|
|
335
|
-
ap.add_argument(
|
|
336
|
-
"--traceparent",
|
|
337
|
-
type=str,
|
|
338
|
-
help="Trace parent id to be used by the storage root span",
|
|
339
|
-
default=None,
|
|
340
|
-
)
|
|
341
|
-
ap.add_argument(
|
|
342
|
-
"--parent_pid",
|
|
343
|
-
type=int,
|
|
344
|
-
help="The parent process id",
|
|
345
|
-
default=os.getppid(),
|
|
346
|
-
)
|
|
347
|
-
ap.add_argument(
|
|
348
|
-
"--host", type=str, default=os.environ.get("ERT_STORAGE_HOST", "127.0.0.1")
|
|
349
|
-
)
|
|
350
|
-
ap.add_argument("--logging-config", type=str, default=None)
|
|
351
|
-
ap.add_argument(
|
|
352
|
-
"--verbose", action="store_true", help="Show verbose output.", default=False
|
|
353
|
-
)
|
|
354
|
-
ap.add_argument("--debug", action="store_true", default=False)
|
|
355
|
-
|
|
356
|
-
|
|
357
320
|
if __name__ == "__main__":
|
|
358
321
|
main()
|