nost-tools 2.2.0__tar.gz → 2.4.0__tar.gz
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.
Potentially problematic release.
This version of nost-tools might be problematic. Click here for more details.
- {nost_tools-2.2.0 → nost_tools-2.4.0}/PKG-INFO +1 -1
- {nost_tools-2.2.0 → nost_tools-2.4.0}/nost_tools/__init__.py +1 -1
- {nost_tools-2.2.0 → nost_tools-2.4.0}/nost_tools/application.py +191 -51
- {nost_tools-2.2.0 → nost_tools-2.4.0}/nost_tools/configuration.py +9 -7
- {nost_tools-2.2.0 → nost_tools-2.4.0}/nost_tools/managed_application.py +50 -41
- {nost_tools-2.2.0 → nost_tools-2.4.0}/nost_tools/manager.py +78 -69
- {nost_tools-2.2.0 → nost_tools-2.4.0}/nost_tools/schemas.py +73 -10
- {nost_tools-2.2.0 → nost_tools-2.4.0}/nost_tools/simulator.py +1 -3
- {nost_tools-2.2.0 → nost_tools-2.4.0}/nost_tools.egg-info/PKG-INFO +1 -1
- {nost_tools-2.2.0 → nost_tools-2.4.0}/LICENSE +0 -0
- {nost_tools-2.2.0 → nost_tools-2.4.0}/README.md +0 -0
- {nost_tools-2.2.0 → nost_tools-2.4.0}/nost_tools/application_utils.py +0 -0
- {nost_tools-2.2.0 → nost_tools-2.4.0}/nost_tools/entity.py +0 -0
- {nost_tools-2.2.0 → nost_tools-2.4.0}/nost_tools/errors.py +0 -0
- {nost_tools-2.2.0 → nost_tools-2.4.0}/nost_tools/logger_application.py +0 -0
- {nost_tools-2.2.0 → nost_tools-2.4.0}/nost_tools/observer.py +0 -0
- {nost_tools-2.2.0 → nost_tools-2.4.0}/nost_tools/publisher.py +0 -0
- {nost_tools-2.2.0 → nost_tools-2.4.0}/nost_tools.egg-info/SOURCES.txt +0 -0
- {nost_tools-2.2.0 → nost_tools-2.4.0}/nost_tools.egg-info/dependency_links.txt +0 -0
- {nost_tools-2.2.0 → nost_tools-2.4.0}/nost_tools.egg-info/requires.txt +0 -0
- {nost_tools-2.2.0 → nost_tools-2.4.0}/nost_tools.egg-info/top_level.txt +0 -0
- {nost_tools-2.2.0 → nost_tools-2.4.0}/pyproject.toml +0 -0
- {nost_tools-2.2.0 → nost_tools-2.4.0}/setup.cfg +0 -0
- {nost_tools-2.2.0 → nost_tools-2.4.0}/tests/test_entity.py +0 -0
- {nost_tools-2.2.0 → nost_tools-2.4.0}/tests/test_observer.py +0 -0
- {nost_tools-2.2.0 → nost_tools-2.4.0}/tests/test_simulator.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nost_tools
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: Tools for Novel Observing Strategies Testbed (NOS-T) Applications
|
|
5
5
|
Author-email: "Paul T. Grogan" <paul.grogan@asu.edu>, "Emmanuel M. Gonzalez" <emmanuelgonzalez@asu.edu>
|
|
6
6
|
License-Expression: BSD-3-Clause
|
|
@@ -4,6 +4,7 @@ Provides a base application that publishes messages from a simulator to a broker
|
|
|
4
4
|
|
|
5
5
|
import functools
|
|
6
6
|
import logging
|
|
7
|
+
import logging.handlers
|
|
7
8
|
import os
|
|
8
9
|
import signal
|
|
9
10
|
import ssl
|
|
@@ -48,13 +49,19 @@ class Application:
|
|
|
48
49
|
time_status_init (:obj:`datetime`): Scenario time of first time status message
|
|
49
50
|
"""
|
|
50
51
|
|
|
51
|
-
def __init__(
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
app_name: str,
|
|
55
|
+
app_description: str = None,
|
|
56
|
+
setup_signal_handlers: bool = True,
|
|
57
|
+
):
|
|
52
58
|
"""
|
|
53
59
|
Initializes a new application.
|
|
54
60
|
|
|
55
61
|
Args:
|
|
56
62
|
app_name (str): application name
|
|
57
63
|
app_description (str): application description (optional)
|
|
64
|
+
setup_signal_handlers (bool): whether to set up signal handlers (default: True)
|
|
58
65
|
"""
|
|
59
66
|
self.simulator = Simulator()
|
|
60
67
|
self.connection = None
|
|
@@ -85,8 +92,12 @@ class Application:
|
|
|
85
92
|
self._token_refresh_thread = None
|
|
86
93
|
self.token_refresh_interval = None
|
|
87
94
|
self._reconnect_delay = None
|
|
95
|
+
# Offset
|
|
96
|
+
self._wallclock_refresh_thread = None
|
|
97
|
+
self.wallclock_offset_refresh_interval = None
|
|
88
98
|
# Set up signal handlers for graceful shutdown
|
|
89
|
-
|
|
99
|
+
if setup_signal_handlers:
|
|
100
|
+
self._setup_signal_handlers()
|
|
90
101
|
|
|
91
102
|
def _setup_signal_handlers(self):
|
|
92
103
|
"""
|
|
@@ -194,6 +205,42 @@ class Application:
|
|
|
194
205
|
self._token_refresh_thread.start()
|
|
195
206
|
logger.debug("Starting refresh token thread successfully completed.")
|
|
196
207
|
|
|
208
|
+
def start_wallclock_refresh_thread(self): # , interval=30, host="pool.ntp.org"):
|
|
209
|
+
"""
|
|
210
|
+
Starts a background thread to refresh the wallclock offset periodically.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
interval (int): Seconds between wallclock offset refreshes (default: 3600 seconds/1 hour)
|
|
214
|
+
host (str): NTP host to query (default: 'pool.ntp.org')
|
|
215
|
+
"""
|
|
216
|
+
logger.debug("Starting wallclock offset refresh thread.")
|
|
217
|
+
|
|
218
|
+
def refresh_wallclock_periodically():
|
|
219
|
+
while not self._should_stop.wait(
|
|
220
|
+
timeout=self.config.rc.wallclock_offset_properties.wallclock_offset_refresh_interval
|
|
221
|
+
):
|
|
222
|
+
logger.debug("Wallclock refresh thread is running.")
|
|
223
|
+
try:
|
|
224
|
+
logger.info(
|
|
225
|
+
f"Contacting {self.config.rc.wallclock_offset_properties.ntp_host} to retrieve wallclock offset."
|
|
226
|
+
)
|
|
227
|
+
response = ntplib.NTPClient().request(
|
|
228
|
+
self.config.rc.wallclock_offset_properties.ntp_host,
|
|
229
|
+
version=3,
|
|
230
|
+
timeout=2,
|
|
231
|
+
)
|
|
232
|
+
offset = timedelta(seconds=response.offset)
|
|
233
|
+
self.simulator.set_wallclock_offset(offset)
|
|
234
|
+
logger.info(f"Wallclock offset updated to {offset}.")
|
|
235
|
+
except Exception as e:
|
|
236
|
+
logger.debug(f"Failed to refresh wallclock offset: {e}")
|
|
237
|
+
|
|
238
|
+
self._wallclock_refresh_thread = threading.Thread(
|
|
239
|
+
target=refresh_wallclock_periodically
|
|
240
|
+
)
|
|
241
|
+
self._wallclock_refresh_thread.start()
|
|
242
|
+
logger.debug("Starting wallclock offset refresh thread successfully completed.")
|
|
243
|
+
|
|
197
244
|
def update_connection_credentials(self, access_token):
|
|
198
245
|
"""
|
|
199
246
|
Updates the connection credentials with the new access token.
|
|
@@ -203,14 +250,33 @@ class Application:
|
|
|
203
250
|
"""
|
|
204
251
|
self.connection.update_secret(access_token, "secret")
|
|
205
252
|
|
|
253
|
+
def _get_parameters_from_config(self):
|
|
254
|
+
"""
|
|
255
|
+
Gets application parameters from configuration or returns None if not available.
|
|
256
|
+
This method can be overridden by subclasses to customize parameter retrieval.
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
object: Configuration parameters object or None
|
|
260
|
+
"""
|
|
261
|
+
if self.config and self.config.rc.yaml_file:
|
|
262
|
+
try:
|
|
263
|
+
return getattr(
|
|
264
|
+
self.config.rc.simulation_configuration.execution_parameters,
|
|
265
|
+
"application",
|
|
266
|
+
None,
|
|
267
|
+
)
|
|
268
|
+
except (AttributeError, KeyError):
|
|
269
|
+
return None
|
|
270
|
+
return None
|
|
271
|
+
|
|
206
272
|
def start_up(
|
|
207
273
|
self,
|
|
208
274
|
prefix: str,
|
|
209
275
|
config: ConnectionConfig,
|
|
210
|
-
set_offset: bool =
|
|
276
|
+
set_offset: bool = True,
|
|
211
277
|
time_status_step: timedelta = None,
|
|
212
278
|
time_status_init: datetime = None,
|
|
213
|
-
shut_down_when_terminated: bool =
|
|
279
|
+
shut_down_when_terminated: bool = False,
|
|
214
280
|
) -> None:
|
|
215
281
|
"""
|
|
216
282
|
Starts up the application to prepare for scenario execution.
|
|
@@ -225,31 +291,56 @@ class Application:
|
|
|
225
291
|
time_status_init (:obj:`datetime`): scenario time for first time status message
|
|
226
292
|
shut_down_when_terminated (bool): True, if the application should shut down when the simulation is terminated
|
|
227
293
|
"""
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
294
|
+
self.config = config
|
|
295
|
+
|
|
296
|
+
if self.config.rc.yaml_file:
|
|
297
|
+
logger.info(
|
|
298
|
+
f"Collecting start up parameters from YAML configuration file: {self.config.rc.yaml_file}"
|
|
299
|
+
)
|
|
300
|
+
parameters = self._get_parameters_from_config()
|
|
301
|
+
if parameters:
|
|
302
|
+
self.set_offset = getattr(parameters, "set_offset", set_offset)
|
|
303
|
+
self.time_status_step = getattr(
|
|
304
|
+
parameters, "time_status_step", time_status_step
|
|
305
|
+
)
|
|
306
|
+
self.time_status_init = getattr(
|
|
307
|
+
parameters, "time_status_init", time_status_init
|
|
308
|
+
)
|
|
309
|
+
self.shut_down_when_terminated = getattr(
|
|
310
|
+
parameters, "shut_down_when_terminated", shut_down_when_terminated
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
# Configure file logging if requested
|
|
314
|
+
if getattr(parameters, "enable_file_logging", False):
|
|
315
|
+
self.configure_file_logging(
|
|
316
|
+
log_dir=getattr(parameters, "log_dir", None),
|
|
317
|
+
log_filename=getattr(parameters, "log_filename", None),
|
|
318
|
+
log_level=getattr(parameters, "log_level", None),
|
|
319
|
+
max_bytes=getattr(parameters, "max_bytes", None),
|
|
320
|
+
backup_count=getattr(parameters, "backup_count", None),
|
|
321
|
+
log_format=getattr(parameters, "log_format", None),
|
|
322
|
+
)
|
|
323
|
+
else:
|
|
324
|
+
logger.warning("No parameters found in configuration, using defaults")
|
|
325
|
+
self.set_offset = set_offset
|
|
326
|
+
self.time_status_step = time_status_step
|
|
327
|
+
self.time_status_init = time_status_init
|
|
328
|
+
self.shut_down_when_terminated = shut_down_when_terminated
|
|
329
|
+
else:
|
|
330
|
+
logger.info(
|
|
331
|
+
f"Collecting start up parameters from user input or default values."
|
|
332
|
+
)
|
|
234
333
|
self.set_offset = set_offset
|
|
235
334
|
self.time_status_step = time_status_step
|
|
236
335
|
self.time_status_init = time_status_init
|
|
237
336
|
self.shut_down_when_terminated = shut_down_when_terminated
|
|
238
|
-
else:
|
|
239
|
-
self.config = config
|
|
240
|
-
parameters = getattr(
|
|
241
|
-
self.config.rc.simulation_configuration.execution_parameters,
|
|
242
|
-
"application",
|
|
243
|
-
None,
|
|
244
|
-
)
|
|
245
|
-
self.set_offset = parameters.set_offset
|
|
246
|
-
self.time_status_step = parameters.time_status_step
|
|
247
|
-
self.time_status_init = parameters.time_status_init
|
|
248
|
-
self.shut_down_when_terminated = parameters.shut_down_when_terminated
|
|
249
337
|
|
|
250
338
|
if self.set_offset:
|
|
251
|
-
#
|
|
252
|
-
|
|
339
|
+
# Start periodic wallclock offset updates instead of one-time call
|
|
340
|
+
logger.info(
|
|
341
|
+
f"Wallclock offset will be set every {self.config.rc.wallclock_offset_properties.wallclock_offset_refresh_interval} seconds using {self.config.rc.wallclock_offset_properties.ntp_host}."
|
|
342
|
+
)
|
|
343
|
+
self.start_wallclock_refresh_thread()
|
|
253
344
|
|
|
254
345
|
# Set the prefix and configuration parameters
|
|
255
346
|
self.prefix = prefix
|
|
@@ -290,7 +381,6 @@ class Application:
|
|
|
290
381
|
locale=config.rc.server_configuration.servers.rabbitmq.locale,
|
|
291
382
|
blocked_connection_timeout=config.rc.server_configuration.servers.rabbitmq.blocked_connection_timeout,
|
|
292
383
|
)
|
|
293
|
-
logger.info(parameters)
|
|
294
384
|
|
|
295
385
|
# Configure transport layer security (TLS) if needed
|
|
296
386
|
if self.config.rc.server_configuration.servers.rabbitmq.tls:
|
|
@@ -1348,34 +1438,26 @@ class Application:
|
|
|
1348
1438
|
)
|
|
1349
1439
|
else:
|
|
1350
1440
|
logger.info("Closing token refresh thread completed successfully")
|
|
1351
|
-
|
|
1441
|
+
# Also stop wallclock refresh thread if it exists
|
|
1442
|
+
if (
|
|
1443
|
+
hasattr(self, "_wallclock_refresh_thread")
|
|
1444
|
+
and self._wallclock_refresh_thread
|
|
1445
|
+
and self._wallclock_refresh_thread.is_alive()
|
|
1446
|
+
):
|
|
1447
|
+
logger.info("Closing wallclock refresh thread.")
|
|
1448
|
+
# Set a timeout to avoid hanging indefinitely
|
|
1449
|
+
self._wallclock_refresh_thread.join(timeout=60.0)
|
|
1450
|
+
# Check if it's still alive after timeout
|
|
1451
|
+
if self._wallclock_refresh_thread.is_alive():
|
|
1452
|
+
logger.warning(
|
|
1453
|
+
"Closing wallclock refresh thread timed out after 60 seconds. "
|
|
1454
|
+
)
|
|
1455
|
+
else:
|
|
1456
|
+
logger.info(
|
|
1457
|
+
"Closing wallclock refresh thread completed successfully"
|
|
1458
|
+
)
|
|
1352
1459
|
logger.debug("Stop_application completed successfully.")
|
|
1353
1460
|
|
|
1354
|
-
def set_wallclock_offset(
|
|
1355
|
-
self, host="pool.ntp.org", retry_delay_s: int = 5, max_retry: int = 5
|
|
1356
|
-
) -> None:
|
|
1357
|
-
"""
|
|
1358
|
-
Issues a Network Time Protocol (NTP) request to determine the system clock offset.
|
|
1359
|
-
|
|
1360
|
-
Args:
|
|
1361
|
-
host (str): NTP host (default: 'pool.ntp.org')
|
|
1362
|
-
retry_delay_s (int): number of seconds to wait before retrying
|
|
1363
|
-
max_retry (int): maximum number of retries allowed
|
|
1364
|
-
"""
|
|
1365
|
-
for i in range(max_retry):
|
|
1366
|
-
try:
|
|
1367
|
-
logger.info(f"Contacting {host} to retrieve wallclock offset.")
|
|
1368
|
-
response = ntplib.NTPClient().request(host, version=3, timeout=2)
|
|
1369
|
-
offset = timedelta(seconds=response.offset)
|
|
1370
|
-
self.simulator.set_wallclock_offset(offset)
|
|
1371
|
-
logger.info(f"Wallclock offset updated to {offset}.")
|
|
1372
|
-
return
|
|
1373
|
-
except ntplib.NTPException:
|
|
1374
|
-
logger.warning(
|
|
1375
|
-
f"Could not connect to {host}, attempt #{i+1}/{max_retry} in {retry_delay_s} s."
|
|
1376
|
-
)
|
|
1377
|
-
time.sleep(retry_delay_s)
|
|
1378
|
-
|
|
1379
1461
|
def _create_time_status_publisher(
|
|
1380
1462
|
self, time_status_step: timedelta, time_status_init: datetime
|
|
1381
1463
|
) -> None:
|
|
@@ -1405,9 +1487,67 @@ class Application:
|
|
|
1405
1487
|
|
|
1406
1488
|
def _create_shut_down_observer(self) -> None:
|
|
1407
1489
|
"""
|
|
1408
|
-
Creates
|
|
1490
|
+
Creates a shut down observer to close the application when the simulator is terminated.
|
|
1409
1491
|
"""
|
|
1410
1492
|
if self._shut_down_observer is not None:
|
|
1411
1493
|
self.simulator.remove_observer(self._shut_down_observer)
|
|
1412
1494
|
self._shut_down_observer = ShutDownObserver(self)
|
|
1413
1495
|
self.simulator.add_observer(self._shut_down_observer)
|
|
1496
|
+
|
|
1497
|
+
def configure_file_logging(
|
|
1498
|
+
self,
|
|
1499
|
+
log_dir: str = None,
|
|
1500
|
+
log_filename: str = None,
|
|
1501
|
+
log_level: str = None,
|
|
1502
|
+
max_bytes: int = None,
|
|
1503
|
+
backup_count: int = None,
|
|
1504
|
+
log_format: str = None,
|
|
1505
|
+
):
|
|
1506
|
+
"""
|
|
1507
|
+
Configures file logging for the application.
|
|
1508
|
+
|
|
1509
|
+
Args:
|
|
1510
|
+
log_dir (str): Directory where log files will be stored
|
|
1511
|
+
log_filename (str): Name of the log file. If None, a timestamped filename will be used
|
|
1512
|
+
log_level (str): Logging level (e.g., 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL')
|
|
1513
|
+
max_bytes (int): Maximum file size in bytes before rotating
|
|
1514
|
+
backup_count (int): Number of backup files to keep
|
|
1515
|
+
log_format (str): Log message format
|
|
1516
|
+
"""
|
|
1517
|
+
try:
|
|
1518
|
+
if log_filename is None:
|
|
1519
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
1520
|
+
log_filename = os.path.join(log_dir, f"{self.app_name}_{timestamp}.log")
|
|
1521
|
+
else:
|
|
1522
|
+
log_filename = os.path.join(log_dir, log_filename)
|
|
1523
|
+
|
|
1524
|
+
# Create log directory if it doesn't exist
|
|
1525
|
+
if log_dir and not os.path.exists(log_dir):
|
|
1526
|
+
os.makedirs(log_dir)
|
|
1527
|
+
logger.info(f"Log directory {log_dir} successfully created.")
|
|
1528
|
+
|
|
1529
|
+
# Configure rotating file handler
|
|
1530
|
+
handler = logging.handlers.RotatingFileHandler(
|
|
1531
|
+
log_filename, maxBytes=max_bytes, backupCount=backup_count
|
|
1532
|
+
)
|
|
1533
|
+
|
|
1534
|
+
# Set log level
|
|
1535
|
+
level = getattr(logging, log_level.upper(), logging.INFO)
|
|
1536
|
+
handler.setLevel(level)
|
|
1537
|
+
|
|
1538
|
+
# Set log format
|
|
1539
|
+
if log_format is not None:
|
|
1540
|
+
formatter = logging.Formatter(log_format)
|
|
1541
|
+
handler.setFormatter(formatter)
|
|
1542
|
+
else:
|
|
1543
|
+
# Default format
|
|
1544
|
+
formatter = logging.Formatter(
|
|
1545
|
+
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
1546
|
+
)
|
|
1547
|
+
handler.setFormatter(formatter)
|
|
1548
|
+
|
|
1549
|
+
# Add the handler to the root logger
|
|
1550
|
+
logging.getLogger().addHandler(handler)
|
|
1551
|
+
logger.info(f"File logging configured: {log_filename} (level: {log_level})")
|
|
1552
|
+
except Exception as e:
|
|
1553
|
+
logger.error(f"Error configuring file logging: {e}")
|
|
@@ -21,6 +21,7 @@ from .schemas import (
|
|
|
21
21
|
RuntimeConfig,
|
|
22
22
|
ServersConfig,
|
|
23
23
|
SimulationConfig,
|
|
24
|
+
WallclockOffsetProperties,
|
|
24
25
|
)
|
|
25
26
|
|
|
26
27
|
logger = logging.getLogger(__name__)
|
|
@@ -52,6 +53,7 @@ class ConnectionConfig:
|
|
|
52
53
|
password: str = None,
|
|
53
54
|
rabbitmq_host: str = None,
|
|
54
55
|
rabbitmq_port: int = None,
|
|
56
|
+
keycloak_authentication: bool = False,
|
|
55
57
|
keycloak_host: str = None,
|
|
56
58
|
keycloak_port: int = None,
|
|
57
59
|
keycloak_realm: str = None,
|
|
@@ -70,6 +72,7 @@ class ConnectionConfig:
|
|
|
70
72
|
password (str): client password, provided by NOS-T operator
|
|
71
73
|
host (str): broker hostname
|
|
72
74
|
rabbitmq_port (int): RabbitMQ broker port number
|
|
75
|
+
keycloak_authentication (bool): True, if Keycloak IAM authentication is used
|
|
73
76
|
keycloak_port (int): Keycloak IAM port number
|
|
74
77
|
keycloak_realm (str): Keycloak realm name
|
|
75
78
|
client_id (str): Keycloak client ID
|
|
@@ -84,6 +87,7 @@ class ConnectionConfig:
|
|
|
84
87
|
self.rabbitmq_host = rabbitmq_host
|
|
85
88
|
self.keycloak_host = keycloak_host
|
|
86
89
|
self.rabbitmq_port = rabbitmq_port
|
|
90
|
+
self.keycloak_authentication = keycloak_authentication
|
|
87
91
|
self.keycloak_port = keycloak_port
|
|
88
92
|
self.keycloak_realm = keycloak_realm
|
|
89
93
|
self.client_id = client_id
|
|
@@ -283,6 +287,7 @@ class ConnectionConfig:
|
|
|
283
287
|
port=self.rabbitmq_port,
|
|
284
288
|
virtual_host=self.virtual_host,
|
|
285
289
|
tls=self.is_tls,
|
|
290
|
+
keycloak_authentication=self.keycloak_authentication,
|
|
286
291
|
),
|
|
287
292
|
keycloak=KeycloakConfig(
|
|
288
293
|
host=self.keycloak_host,
|
|
@@ -312,13 +317,8 @@ class ConnectionConfig:
|
|
|
312
317
|
del server_config.execution
|
|
313
318
|
self.server_config = server_config
|
|
314
319
|
|
|
315
|
-
if
|
|
316
|
-
|
|
317
|
-
and self.password is not None
|
|
318
|
-
and self.client_id is not None
|
|
319
|
-
and self.client_secret_key is not None
|
|
320
|
-
):
|
|
321
|
-
logger.info("Using provided credentials.")
|
|
320
|
+
if self.username is not None and self.password is not None:
|
|
321
|
+
logger.info("Using user-provided credentials.")
|
|
322
322
|
self.credentials_config = Credentials(
|
|
323
323
|
username=self.username,
|
|
324
324
|
password=self.password,
|
|
@@ -329,8 +329,10 @@ class ConnectionConfig:
|
|
|
329
329
|
self.load_environment_variables()
|
|
330
330
|
|
|
331
331
|
self.rc = RuntimeConfig(
|
|
332
|
+
wallclock_offset_properties=WallclockOffsetProperties(),
|
|
332
333
|
credentials=self.credentials_config,
|
|
333
334
|
server_configuration=server_config,
|
|
334
335
|
simulation_configuration=self.simulation_config,
|
|
335
336
|
application_configuration=self.app_specific,
|
|
337
|
+
yaml_file=self.yaml_file,
|
|
336
338
|
)
|
|
@@ -32,27 +32,57 @@ class ManagedApplication(Application):
|
|
|
32
32
|
time_step (:obj:`timedelta`): scenario time step used in execution
|
|
33
33
|
"""
|
|
34
34
|
|
|
35
|
-
def __init__(
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
app_name: str,
|
|
38
|
+
app_description: str = None,
|
|
39
|
+
setup_signal_handlers: bool = True,
|
|
40
|
+
):
|
|
36
41
|
"""
|
|
37
42
|
Initializes a new managed application.
|
|
38
43
|
|
|
39
44
|
Args:
|
|
40
45
|
app_name (str): application name
|
|
41
46
|
app_description (str): application description
|
|
47
|
+
setup_signal_handlers (bool): whether to set up signal handlers (default: True)
|
|
42
48
|
"""
|
|
43
|
-
super().__init__(
|
|
49
|
+
super().__init__(
|
|
50
|
+
app_name, app_description, setup_signal_handlers=setup_signal_handlers
|
|
51
|
+
)
|
|
44
52
|
self.time_step = None
|
|
45
53
|
self._sim_start_time = None
|
|
46
54
|
self._sim_stop_time = None
|
|
47
55
|
|
|
56
|
+
def _get_parameters_from_config(self):
|
|
57
|
+
"""
|
|
58
|
+
Override to get parameters specific to managed applications
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
object: Configuration parameters for this managed application
|
|
62
|
+
"""
|
|
63
|
+
if self.config and self.config.rc.yaml_file:
|
|
64
|
+
try:
|
|
65
|
+
parameters = (
|
|
66
|
+
self.config.rc.simulation_configuration.execution_parameters.managed_applications
|
|
67
|
+
)
|
|
68
|
+
try:
|
|
69
|
+
# Try to get app-specific parameters
|
|
70
|
+
return parameters[self.app_name]
|
|
71
|
+
except KeyError:
|
|
72
|
+
# Fall back to default parameters
|
|
73
|
+
return parameters.get("default")
|
|
74
|
+
except (AttributeError, KeyError):
|
|
75
|
+
return None
|
|
76
|
+
return None
|
|
77
|
+
|
|
48
78
|
def start_up(
|
|
49
79
|
self,
|
|
50
80
|
prefix: str,
|
|
51
81
|
config: ConnectionConfig,
|
|
52
|
-
set_offset: bool =
|
|
82
|
+
set_offset: bool = True,
|
|
53
83
|
time_status_step: timedelta = None,
|
|
54
84
|
time_status_init: datetime = None,
|
|
55
|
-
shut_down_when_terminated: bool =
|
|
85
|
+
shut_down_when_terminated: bool = False,
|
|
56
86
|
time_step: timedelta = None,
|
|
57
87
|
manager_app_name: str = None,
|
|
58
88
|
) -> None:
|
|
@@ -70,48 +100,27 @@ class ManagedApplication(Application):
|
|
|
70
100
|
time_step (:obj:`timedelta`): scenario time step used in execution (Default: 1 second)
|
|
71
101
|
manager_app_name (str): manager application name (Default: manager)
|
|
72
102
|
"""
|
|
73
|
-
|
|
74
|
-
set_offset is not None
|
|
75
|
-
and time_status_step is not None
|
|
76
|
-
and time_status_init is not None
|
|
77
|
-
and shut_down_when_terminated is not None
|
|
78
|
-
and time_step is not None
|
|
79
|
-
and manager_app_name is not None
|
|
80
|
-
):
|
|
81
|
-
self.set_offset = set_offset
|
|
82
|
-
self.time_status_step = time_status_step
|
|
83
|
-
self.time_status_init = time_status_init
|
|
84
|
-
self.shut_down_when_terminated = shut_down_when_terminated
|
|
85
|
-
self.time_step = time_step
|
|
86
|
-
self.manager_app_name = manager_app_name
|
|
87
|
-
else:
|
|
88
|
-
self.config = config
|
|
89
|
-
parameters = (
|
|
90
|
-
self.config.rc.simulation_configuration.execution_parameters.managed_applications
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
try:
|
|
94
|
-
parameters = parameters[self.app_name]
|
|
95
|
-
except KeyError:
|
|
96
|
-
parameters = parameters["default"]
|
|
97
|
-
self.set_offset = parameters.set_offset
|
|
98
|
-
self.time_status_step = parameters.time_status_step
|
|
99
|
-
self.time_status_init = parameters.time_status_init
|
|
100
|
-
self.shut_down_when_terminated = parameters.shut_down_when_terminated
|
|
101
|
-
self.time_step = parameters.time_step
|
|
102
|
-
self.manager_app_name = parameters.manager_app_name
|
|
103
|
+
self.config = config
|
|
103
104
|
|
|
104
|
-
#
|
|
105
|
+
# Call base start_up to handle common parameters
|
|
105
106
|
super().start_up(
|
|
106
107
|
prefix,
|
|
107
108
|
config,
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
set_offset,
|
|
110
|
+
time_status_step,
|
|
111
|
+
time_status_init,
|
|
112
|
+
shut_down_when_terminated,
|
|
112
113
|
)
|
|
113
|
-
|
|
114
|
-
|
|
114
|
+
|
|
115
|
+
# Get additional parameters specific to managed applications
|
|
116
|
+
if self.config.rc.yaml_file:
|
|
117
|
+
parameters = self._get_parameters_from_config()
|
|
118
|
+
if parameters:
|
|
119
|
+
self.time_step = parameters.time_step
|
|
120
|
+
self.manager_app_name = parameters.manager_app_name
|
|
121
|
+
else:
|
|
122
|
+
self.time_step = time_step
|
|
123
|
+
self.manager_app_name = manager_app_name
|
|
115
124
|
|
|
116
125
|
# Register callback functions
|
|
117
126
|
self.add_message_callback(
|
|
@@ -67,12 +67,22 @@ class Manager(Application):
|
|
|
67
67
|
required_apps_status (dict): Ready status for all required applications
|
|
68
68
|
"""
|
|
69
69
|
|
|
70
|
-
def __init__(
|
|
70
|
+
def __init__(
|
|
71
|
+
self,
|
|
72
|
+
app_name: str = "manager",
|
|
73
|
+
app_description: str = None,
|
|
74
|
+
setup_signal_handlers: bool = True,
|
|
75
|
+
):
|
|
71
76
|
"""
|
|
72
77
|
Initializes a new manager.
|
|
78
|
+
|
|
79
|
+
Attributes:
|
|
80
|
+
setup_signal_handlers (bool): whether to set up signal handlers (default: True)
|
|
73
81
|
"""
|
|
74
82
|
# call super class constructor
|
|
75
|
-
super().__init__(
|
|
83
|
+
super().__init__(
|
|
84
|
+
app_name, app_description, setup_signal_handlers=setup_signal_handlers
|
|
85
|
+
)
|
|
76
86
|
self.required_apps_status = {}
|
|
77
87
|
|
|
78
88
|
self.sim_start_time = None
|
|
@@ -129,16 +139,32 @@ class Manager(Application):
|
|
|
129
139
|
f"Heartbeat check: {remaining:.2f} seconds remaining in sleep"
|
|
130
140
|
)
|
|
131
141
|
|
|
142
|
+
def _get_parameters_from_config(self):
|
|
143
|
+
"""
|
|
144
|
+
Override to get parameters specific to manager application
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
object: Configuration parameters for the manager application
|
|
148
|
+
"""
|
|
149
|
+
if self.config and self.config.rc.yaml_file:
|
|
150
|
+
try:
|
|
151
|
+
return getattr(
|
|
152
|
+
self.config.rc.simulation_configuration.execution_parameters,
|
|
153
|
+
"manager",
|
|
154
|
+
None,
|
|
155
|
+
)
|
|
156
|
+
except (AttributeError, KeyError):
|
|
157
|
+
return None
|
|
158
|
+
return None
|
|
159
|
+
|
|
132
160
|
def start_up(
|
|
133
161
|
self,
|
|
134
162
|
prefix: str,
|
|
135
163
|
config: ConnectionConfig,
|
|
136
|
-
set_offset: bool =
|
|
164
|
+
set_offset: bool = True,
|
|
137
165
|
time_status_step: timedelta = None,
|
|
138
166
|
time_status_init: datetime = None,
|
|
139
|
-
shut_down_when_terminated: bool =
|
|
140
|
-
time_step: timedelta = None,
|
|
141
|
-
manager_app_name: str = None,
|
|
167
|
+
shut_down_when_terminated: bool = False
|
|
142
168
|
) -> None:
|
|
143
169
|
"""
|
|
144
170
|
Starts up the application by connecting to message broker, starting a background event loop,
|
|
@@ -151,45 +177,20 @@ class Manager(Application):
|
|
|
151
177
|
time_status_step (:obj:`timedelta`): scenario duration between time status messages
|
|
152
178
|
time_status_init (:obj:`datetime`): scenario time for first time status message
|
|
153
179
|
shut_down_when_terminated (bool): True, if the application should shut down when the simulation is terminated
|
|
154
|
-
time_step (:obj:`timedelta`): scenario time step used in execution (Default: 1 second)
|
|
155
|
-
manager_app_name (str): manager application name (Default: manager)
|
|
156
180
|
"""
|
|
157
|
-
|
|
158
|
-
set_offset is not None
|
|
159
|
-
and time_status_step is not None
|
|
160
|
-
and time_status_init is not None
|
|
161
|
-
and shut_down_when_terminated is not None
|
|
162
|
-
and time_step is not None
|
|
163
|
-
and manager_app_name is not None
|
|
164
|
-
):
|
|
165
|
-
self.set_offset = set_offset
|
|
166
|
-
self.time_status_step = time_status_step
|
|
167
|
-
self.time_status_init = time_status_init
|
|
168
|
-
self.shut_down_when_terminated = shut_down_when_terminated
|
|
169
|
-
self.time_step = time_step
|
|
170
|
-
self.manager_app_name = manager_app_name
|
|
171
|
-
else:
|
|
172
|
-
self.config = config
|
|
173
|
-
parameters = (
|
|
174
|
-
self.config.rc.simulation_configuration.execution_parameters.manager
|
|
175
|
-
)
|
|
176
|
-
self.set_offset = parameters.set_offset
|
|
177
|
-
self.time_status_step = parameters.time_status_step
|
|
178
|
-
self.time_status_init = parameters.time_status_init
|
|
179
|
-
self.shut_down_when_terminated = parameters.shut_down_when_terminated
|
|
180
|
-
self.time_step = parameters.time_step
|
|
181
|
+
self.config = config
|
|
181
182
|
|
|
182
|
-
#
|
|
183
|
+
# Call base start_up to handle common parameters
|
|
183
184
|
super().start_up(
|
|
184
185
|
prefix,
|
|
185
186
|
config,
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
187
|
+
set_offset,
|
|
188
|
+
time_status_step,
|
|
189
|
+
time_status_init,
|
|
190
|
+
shut_down_when_terminated,
|
|
190
191
|
)
|
|
191
192
|
|
|
192
|
-
#
|
|
193
|
+
# Additional manager-specific setup: establish the exchange
|
|
193
194
|
self.establish_exchange()
|
|
194
195
|
|
|
195
196
|
def execute_test_plan(self, *args, **kwargs) -> None:
|
|
@@ -241,8 +242,33 @@ class Manager(Application):
|
|
|
241
242
|
init_retry_delay_s (float): number of seconds to wait between initialization commands while waiting for required applications
|
|
242
243
|
init_max_retry (int): number of initialization commands while waiting for required applications before continuing to execution
|
|
243
244
|
"""
|
|
244
|
-
|
|
245
|
-
|
|
245
|
+
if self.config.rc.yaml_file:
|
|
246
|
+
logger.info(
|
|
247
|
+
f"Collecting execution parameters from YAML configuration file: {self.config.rc.yaml_file}"
|
|
248
|
+
)
|
|
249
|
+
parameters = getattr(
|
|
250
|
+
self.config.rc.simulation_configuration.execution_parameters,
|
|
251
|
+
self.app_name,
|
|
252
|
+
None,
|
|
253
|
+
)
|
|
254
|
+
self.sim_start_time = parameters.sim_start_time
|
|
255
|
+
self.sim_stop_time = parameters.sim_stop_time
|
|
256
|
+
self.start_time = parameters.start_time
|
|
257
|
+
self.time_step = parameters.time_step
|
|
258
|
+
self.time_scale_factor = parameters.time_scale_factor
|
|
259
|
+
self.time_scale_updates = parameters.time_scale_updates
|
|
260
|
+
self.time_status_step = parameters.time_status_step
|
|
261
|
+
self.time_status_init = parameters.time_status_init
|
|
262
|
+
self.command_lead = parameters.command_lead
|
|
263
|
+
self.required_apps = [
|
|
264
|
+
app for app in parameters.required_apps if app != self.app_name
|
|
265
|
+
]
|
|
266
|
+
self.init_retry_delay_s = parameters.init_retry_delay_s
|
|
267
|
+
self.init_max_retry = parameters.init_max_retry
|
|
268
|
+
else:
|
|
269
|
+
logger.info(
|
|
270
|
+
f"Collecting execution parameters from user input or default values."
|
|
271
|
+
)
|
|
246
272
|
self.sim_start_time = sim_start_time
|
|
247
273
|
self.sim_stop_time = sim_stop_time
|
|
248
274
|
self.start_time = start_time
|
|
@@ -255,36 +281,10 @@ class Manager(Application):
|
|
|
255
281
|
self.required_apps = required_apps
|
|
256
282
|
self.init_retry_delay_s = init_retry_delay_s
|
|
257
283
|
self.init_max_retry = init_max_retry
|
|
258
|
-
else:
|
|
259
|
-
if self.config.rc:
|
|
260
|
-
logger.info("Retrieving execution parameters from YAML file.")
|
|
261
|
-
parameters = getattr(
|
|
262
|
-
self.config.rc.simulation_configuration.execution_parameters,
|
|
263
|
-
self.app_name,
|
|
264
|
-
None,
|
|
265
|
-
)
|
|
266
|
-
self.sim_start_time = parameters.sim_start_time
|
|
267
|
-
self.sim_stop_time = parameters.sim_stop_time
|
|
268
|
-
self.start_time = parameters.start_time
|
|
269
|
-
self.time_step = parameters.time_step
|
|
270
|
-
self.time_scale_factor = parameters.time_scale_factor
|
|
271
|
-
self.time_scale_updates = parameters.time_scale_updates
|
|
272
|
-
self.time_status_step = parameters.time_status_step
|
|
273
|
-
self.time_status_init = parameters.time_status_init
|
|
274
|
-
self.command_lead = parameters.command_lead
|
|
275
|
-
self.required_apps = [
|
|
276
|
-
app for app in parameters.required_apps if app != self.app_name
|
|
277
|
-
]
|
|
278
|
-
self.init_retry_delay_s = parameters.init_retry_delay_s
|
|
279
|
-
self.init_max_retry = parameters.init_max_retry
|
|
280
|
-
else:
|
|
281
|
-
raise ValueError(
|
|
282
|
-
"No configuration runtime. Please provide simulation start and stop times."
|
|
283
|
-
)
|
|
284
284
|
|
|
285
285
|
# Convert TimeScaleUpdateSchema objects to TimeScaleUpdate objects
|
|
286
286
|
converted_updates = []
|
|
287
|
-
for update_schema in
|
|
287
|
+
for update_schema in self.time_scale_updates:
|
|
288
288
|
converted_updates.append(
|
|
289
289
|
TimeScaleUpdate(
|
|
290
290
|
time_scale_factor=update_schema.time_scale_factor,
|
|
@@ -563,8 +563,17 @@ class Manager(Application):
|
|
|
563
563
|
app_topics="stop",
|
|
564
564
|
payload=command.model_dump_json(by_alias=True),
|
|
565
565
|
)
|
|
566
|
-
|
|
567
|
-
|
|
566
|
+
|
|
567
|
+
# Update the execution end time if simulator is in EXECUTING mode
|
|
568
|
+
if self.simulator.get_mode() == Mode.EXECUTING:
|
|
569
|
+
try:
|
|
570
|
+
self.simulator.set_end_time(sim_stop_time)
|
|
571
|
+
except RuntimeError as e:
|
|
572
|
+
logger.warning(f"Could not set simulator end time: {e}")
|
|
573
|
+
else:
|
|
574
|
+
logger.debug(
|
|
575
|
+
"Skipping setting simulator end time as simulator is not in EXECUTING mode"
|
|
576
|
+
)
|
|
568
577
|
|
|
569
578
|
def update(self, time_scale_factor: float, sim_update_time: datetime) -> None:
|
|
570
579
|
"""
|
|
@@ -280,7 +280,7 @@ class KeycloakConfig(BaseModel):
|
|
|
280
280
|
realm: str = Field("master", description="Keycloak realm.")
|
|
281
281
|
tls: bool = Field(False, description="Keycloak TLS/SSL.")
|
|
282
282
|
token_refresh_interval: int = Field(
|
|
283
|
-
|
|
283
|
+
240, description="Keycloak token refresh interval, in seconds."
|
|
284
284
|
)
|
|
285
285
|
|
|
286
286
|
|
|
@@ -307,8 +307,21 @@ class ServersConfig(BaseModel):
|
|
|
307
307
|
return values
|
|
308
308
|
|
|
309
309
|
|
|
310
|
+
class WallclockOffsetProperties(BaseModel):
|
|
311
|
+
"""
|
|
312
|
+
Properties to report wallclock offset.
|
|
313
|
+
"""
|
|
314
|
+
|
|
315
|
+
wallclock_offset_refresh_interval: Optional[int] = Field(
|
|
316
|
+
10800, description="Wallclock offset refresh interval, in seconds."
|
|
317
|
+
)
|
|
318
|
+
ntp_host: Optional[str] = Field(
|
|
319
|
+
"pool.ntp.org", description="NTP host for wallclock offset synchronization."
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
|
|
310
323
|
class GeneralConfig(BaseModel):
|
|
311
|
-
prefix: str = Field("nost", description="Execution prefix.")
|
|
324
|
+
prefix: Optional[str] = Field("nost", description="Execution prefix.")
|
|
312
325
|
|
|
313
326
|
|
|
314
327
|
class TimeScaleUpdateSchema(BaseModel):
|
|
@@ -324,7 +337,35 @@ class TimeScaleUpdateSchema(BaseModel):
|
|
|
324
337
|
)
|
|
325
338
|
|
|
326
339
|
|
|
327
|
-
class
|
|
340
|
+
class LoggingConfig(BaseModel):
|
|
341
|
+
"""
|
|
342
|
+
Configuration for logging.
|
|
343
|
+
"""
|
|
344
|
+
|
|
345
|
+
enable_file_logging: Optional[bool] = Field(
|
|
346
|
+
False, description="Enable file logging."
|
|
347
|
+
)
|
|
348
|
+
log_dir: Optional[str] = Field(
|
|
349
|
+
"logs", description="Directory path for log files."
|
|
350
|
+
)
|
|
351
|
+
log_filename: Optional[str] = Field(None, description="Path to the log file.")
|
|
352
|
+
log_level: Optional[str] = Field(
|
|
353
|
+
"INFO", description="Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)."
|
|
354
|
+
)
|
|
355
|
+
max_bytes: Optional[int] = Field(
|
|
356
|
+
10 * 1024 * 1024,
|
|
357
|
+
description="Maximum size of the log file in bytes. Default is 10MB.",
|
|
358
|
+
)
|
|
359
|
+
backup_count: Optional[int] = Field(
|
|
360
|
+
5, description="Number of backup log files to keep."
|
|
361
|
+
)
|
|
362
|
+
log_format: Optional[str] = Field(
|
|
363
|
+
"%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
|
364
|
+
description="Format of the log messages.",
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
class ManagerConfig(LoggingConfig):
|
|
328
369
|
sim_start_time: Optional[datetime] = Field(
|
|
329
370
|
None, description="Simulation start time."
|
|
330
371
|
)
|
|
@@ -352,6 +393,10 @@ class ManagerConfig(BaseModel):
|
|
|
352
393
|
shut_down_when_terminated: bool = Field(
|
|
353
394
|
False, description="Shut down when terminated."
|
|
354
395
|
)
|
|
396
|
+
is_scenario_time_step: bool = Field(
|
|
397
|
+
True,
|
|
398
|
+
description="If True, time_step is in scenario time and won't be scaled. If False, time_step is in wallclock time and will be scaled by the time scale factor.",
|
|
399
|
+
)
|
|
355
400
|
is_scenario_time_status_step: bool = Field(
|
|
356
401
|
True,
|
|
357
402
|
description="If True, time_status_step is in scenario time and won't be scaled. If False, time_status_step is in wallclock time and will be scaled by the time scale factor.",
|
|
@@ -361,6 +406,16 @@ class ManagerConfig(BaseModel):
|
|
|
361
406
|
def scale_time(cls, values):
|
|
362
407
|
time_scale_factor = values.get("time_scale_factor", 1.0)
|
|
363
408
|
|
|
409
|
+
if "time_step" in values and not values.get("is_scenario_time_step", True):
|
|
410
|
+
time_step = values["time_step"]
|
|
411
|
+
if isinstance(time_step, str):
|
|
412
|
+
hours, minutes, seconds = map(int, time_step.split(":"))
|
|
413
|
+
time_step = timedelta(hours=hours, minutes=minutes, seconds=seconds)
|
|
414
|
+
if isinstance(time_step, timedelta):
|
|
415
|
+
values["time_step"] = timedelta(
|
|
416
|
+
seconds=time_step.total_seconds() * time_scale_factor
|
|
417
|
+
)
|
|
418
|
+
|
|
364
419
|
if "time_status_step" in values and not values.get(
|
|
365
420
|
"is_scenario_time_status_step", True
|
|
366
421
|
):
|
|
@@ -378,7 +433,7 @@ class ManagerConfig(BaseModel):
|
|
|
378
433
|
return values
|
|
379
434
|
|
|
380
435
|
|
|
381
|
-
class ManagedApplicationConfig(
|
|
436
|
+
class ManagedApplicationConfig(LoggingConfig):
|
|
382
437
|
time_scale_factor: float = Field(1.0, description="Time scale factor.")
|
|
383
438
|
time_step: timedelta = Field(
|
|
384
439
|
timedelta(seconds=1), description="Time step for swe_change."
|
|
@@ -440,7 +495,7 @@ class LoggerApplicationConfig(BaseModel):
|
|
|
440
495
|
timedelta(seconds=10), description="Time status step."
|
|
441
496
|
)
|
|
442
497
|
time_status_init: Optional[datetime] = Field(
|
|
443
|
-
datetime(
|
|
498
|
+
datetime.now(), description="Time status init."
|
|
444
499
|
)
|
|
445
500
|
shut_down_when_terminated: Optional[bool] = Field(
|
|
446
501
|
False, description="Shut down when terminated."
|
|
@@ -471,7 +526,9 @@ class ApplicationConfig(BaseModel):
|
|
|
471
526
|
|
|
472
527
|
|
|
473
528
|
class ExecConfig(BaseModel):
|
|
474
|
-
general: GeneralConfig
|
|
529
|
+
general: Optional[GeneralConfig] = Field(
|
|
530
|
+
None, description="General configuration for the execution."
|
|
531
|
+
)
|
|
475
532
|
manager: Optional[ManagerConfig] = Field(None, description="Manager configuration.")
|
|
476
533
|
managed_applications: Optional[Dict[str, ManagedApplicationConfig]] = Field(
|
|
477
534
|
default_factory=lambda: {"default": ManagedApplicationConfig()},
|
|
@@ -509,11 +566,11 @@ class ChannelConfig(BaseModel):
|
|
|
509
566
|
|
|
510
567
|
|
|
511
568
|
class Credentials(BaseModel):
|
|
512
|
-
username: str = Field(
|
|
513
|
-
password: str = Field(
|
|
514
|
-
client_id: Optional[str] = Field(
|
|
569
|
+
username: Optional[str] = Field("admin", description="Username for authentication.")
|
|
570
|
+
password: Optional[str] = Field("admin", description="Password for authentication.")
|
|
571
|
+
client_id: Optional[str] = Field(None, description="Client ID for authentication.")
|
|
515
572
|
client_secret_key: Optional[str] = Field(
|
|
516
|
-
|
|
573
|
+
None, description="Client secret key for authentication."
|
|
517
574
|
)
|
|
518
575
|
|
|
519
576
|
|
|
@@ -529,6 +586,9 @@ class SimulationConfig(BaseModel):
|
|
|
529
586
|
|
|
530
587
|
|
|
531
588
|
class RuntimeConfig(BaseModel):
|
|
589
|
+
wallclock_offset_properties: WallclockOffsetProperties = Field(
|
|
590
|
+
..., description="Properties for wallclock offset."
|
|
591
|
+
)
|
|
532
592
|
credentials: Credentials = Field(..., description="Credentials for authentication.")
|
|
533
593
|
server_configuration: Config = (
|
|
534
594
|
Field(..., description="Simulation configuration."),
|
|
@@ -539,3 +599,6 @@ class RuntimeConfig(BaseModel):
|
|
|
539
599
|
application_configuration: Optional[Dict] = Field(
|
|
540
600
|
None, description="Application-specific, user-provided configuration."
|
|
541
601
|
)
|
|
602
|
+
yaml_file: Optional[str] = Field(
|
|
603
|
+
None, description="Path to the YAML file containing the configuration."
|
|
604
|
+
)
|
|
@@ -516,9 +516,7 @@ class Simulator(Observable):
|
|
|
516
516
|
Args:
|
|
517
517
|
wallclock_offset(:obj:`timedelta`): difference between system clock and trusted wallclock source
|
|
518
518
|
"""
|
|
519
|
-
if self._mode == Mode.
|
|
520
|
-
raise RuntimeError("Cannot set wallclock offset: simulator is executing")
|
|
521
|
-
elif self._mode == Mode.TERMINATING:
|
|
519
|
+
if self._mode == Mode.TERMINATING:
|
|
522
520
|
raise RuntimeError("Cannot set wallclock offset: simulator is terminating")
|
|
523
521
|
self._wallclock_offset = wallclock_offset
|
|
524
522
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nost_tools
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: Tools for Novel Observing Strategies Testbed (NOS-T) Applications
|
|
5
5
|
Author-email: "Paul T. Grogan" <paul.grogan@asu.edu>, "Emmanuel M. Gonzalez" <emmanuelgonzalez@asu.edu>
|
|
6
6
|
License-Expression: BSD-3-Clause
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|