OpenOrchestrator 1.3.0__py3-none-any.whl → 2.0.0rc1__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.
- OpenOrchestrator/__main__.py +66 -12
- OpenOrchestrator/common/connection_frame.py +10 -4
- OpenOrchestrator/database/base.py +8 -0
- OpenOrchestrator/database/constants.py +3 -15
- OpenOrchestrator/database/db_util.py +112 -39
- OpenOrchestrator/database/logs.py +3 -15
- OpenOrchestrator/database/queues.py +3 -15
- OpenOrchestrator/database/schedulers.py +32 -0
- OpenOrchestrator/database/triggers.py +7 -16
- OpenOrchestrator/orchestrator/application.py +13 -8
- OpenOrchestrator/orchestrator/datetime_input.py +2 -2
- OpenOrchestrator/orchestrator/popups/constant_popup.py +8 -6
- OpenOrchestrator/orchestrator/popups/credential_popup.py +10 -8
- OpenOrchestrator/orchestrator/popups/generic_popups.py +15 -2
- OpenOrchestrator/orchestrator/popups/trigger_popup.py +30 -19
- OpenOrchestrator/orchestrator/tabs/constants_tab.py +5 -2
- OpenOrchestrator/orchestrator/tabs/logging_tab.py +3 -0
- OpenOrchestrator/orchestrator/tabs/queue_tab.py +4 -1
- OpenOrchestrator/orchestrator/tabs/schedulers_tab.py +45 -0
- OpenOrchestrator/orchestrator/tabs/settings_tab.py +2 -5
- OpenOrchestrator/orchestrator/tabs/trigger_tab.py +9 -5
- OpenOrchestrator/orchestrator/test_helper.py +17 -0
- OpenOrchestrator/orchestrator_connection/connection.py +21 -4
- OpenOrchestrator/scheduler/application.py +3 -2
- OpenOrchestrator/scheduler/run_tab.py +16 -6
- OpenOrchestrator/scheduler/runner.py +51 -63
- OpenOrchestrator/scheduler/settings_tab.py +90 -14
- OpenOrchestrator/scheduler/util.py +8 -0
- OpenOrchestrator/tests/__init__.py +0 -0
- OpenOrchestrator/tests/db_test_util.py +40 -0
- OpenOrchestrator/tests/test_db_util.py +372 -0
- OpenOrchestrator/tests/test_orchestrator_connection.py +142 -0
- OpenOrchestrator/tests/test_trigger_polling.py +143 -0
- OpenOrchestrator/tests/ui_tests/__init__.py +0 -0
- OpenOrchestrator/tests/ui_tests/test_constants_tab.py +167 -0
- OpenOrchestrator/tests/ui_tests/test_logging_tab.py +180 -0
- OpenOrchestrator/tests/ui_tests/test_queues_tab.py +126 -0
- OpenOrchestrator/tests/ui_tests/test_schedulers_tab.py +47 -0
- OpenOrchestrator/tests/ui_tests/test_trigger_tab.py +243 -0
- OpenOrchestrator/tests/ui_tests/ui_util.py +151 -0
- openorchestrator-2.0.0rc1.dist-info/METADATA +158 -0
- openorchestrator-2.0.0rc1.dist-info/RECORD +55 -0
- {OpenOrchestrator-1.3.0.dist-info → openorchestrator-2.0.0rc1.dist-info}/WHEEL +1 -1
- OpenOrchestrator/scheduler/connection_frame.py +0 -96
- OpenOrchestrator-1.3.0.dist-info/METADATA +0 -60
- OpenOrchestrator-1.3.0.dist-info/RECORD +0 -39
- {OpenOrchestrator-1.3.0.dist-info → openorchestrator-2.0.0rc1.dist-info/licenses}/LICENSE +0 -0
- {OpenOrchestrator-1.3.0.dist-info → openorchestrator-2.0.0rc1.dist-info}/top_level.txt +0 -0
OpenOrchestrator/__main__.py
CHANGED
@@ -1,20 +1,74 @@
|
|
1
|
-
"""This module is used to run Orchestrator or Scheduler from the command line.
|
2
|
-
|
1
|
+
"""This module is used to run Orchestrator or Scheduler from the command line."""
|
2
|
+
|
3
|
+
import argparse
|
4
|
+
import subprocess
|
3
5
|
|
4
|
-
import sys
|
5
6
|
from OpenOrchestrator.scheduler.application import Application as s_app
|
6
7
|
from OpenOrchestrator.orchestrator.application import Application as o_app
|
7
8
|
|
8
9
|
|
9
|
-
def
|
10
|
-
|
10
|
+
def main():
|
11
|
+
"""The main entry point of the CLI."""
|
12
|
+
parser = argparse.ArgumentParser(
|
13
|
+
prog="OpenOrchestrator",
|
14
|
+
description="OpenOrchestrator is used to orchestrate, monitor and run Python-based automation scripts in Windows."
|
15
|
+
)
|
16
|
+
|
17
|
+
subparsers = parser.add_subparsers(title="Subcommands", required=True)
|
18
|
+
|
19
|
+
o_parser = subparsers.add_parser("orchestrator", aliases=["o"], help="Start the Orchestrator application.")
|
20
|
+
o_parser.add_argument("-p", "--port", type=int, help="Set the desired port for Orchestrator.")
|
21
|
+
o_parser.add_argument("-d", "--dont_show", action="store_false", help="Set if you don't want Orchestrator to open in the browser automatically.")
|
22
|
+
o_parser.set_defaults(func=orchestrator_command)
|
23
|
+
|
24
|
+
s_parser = subparsers.add_parser("scheduler", aliases=["s"], help="Start the Scheduler application.")
|
25
|
+
s_parser.set_defaults(func=scheduler_command)
|
26
|
+
|
27
|
+
u_parser = subparsers.add_parser("upgrade", aliases=["u"], help="Upgrade the database to the newest revision or create a new database from scratch.")
|
28
|
+
u_parser.add_argument("connection_string", type=str, help="The connection string to the database")
|
29
|
+
u_parser.set_defaults(func=upgrade_command)
|
30
|
+
|
31
|
+
args = parser.parse_args()
|
32
|
+
args.func(args)
|
33
|
+
|
34
|
+
|
35
|
+
def orchestrator_command(args: argparse.Namespace):
|
36
|
+
"""Start the Orchestrator app.
|
11
37
|
|
38
|
+
Args:
|
39
|
+
args: The arguments Namespace object.
|
40
|
+
"""
|
41
|
+
o_app(port=args.port, show=args.dont_show)
|
12
42
|
|
13
|
-
|
14
|
-
|
15
|
-
|
43
|
+
|
44
|
+
def scheduler_command(args: argparse.Namespace): # pylint: disable=unused-argument
|
45
|
+
"""Start the Scheduler app."""
|
16
46
|
s_app()
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
47
|
+
|
48
|
+
|
49
|
+
def upgrade_command(args: argparse.Namespace):
|
50
|
+
"""Upgrade the database to the newest revision using alembic.
|
51
|
+
|
52
|
+
Args:
|
53
|
+
args: The arguments Namespace object.
|
54
|
+
"""
|
55
|
+
confirmation = input("Are you sure you want to upgrade the database to the newest revision? This cannot be undone. (y/n)").strip()
|
56
|
+
if confirmation == "y":
|
57
|
+
# Check if database is already stamped
|
58
|
+
result = subprocess.run(["alembic", "-x", args.connection_string, "current"], check=True, capture_output=True)
|
59
|
+
if result.stdout == b'':
|
60
|
+
response = input("It seems like you are either creating a new database or trying to upgrade a legacy database. Please choose an option: (new/legacy/cancel)").strip()
|
61
|
+
if response == "legacy":
|
62
|
+
subprocess.run(["alembic", "-x", args.connection_string, "stamp", "b67b7649b282"], check=True)
|
63
|
+
elif response != "new":
|
64
|
+
print("Upgrade canceled")
|
65
|
+
return
|
66
|
+
|
67
|
+
subprocess.run(["alembic", "-x", args.connection_string, "upgrade", "head"], check=True)
|
68
|
+
print("Database upgraded to the newest revision!")
|
69
|
+
else:
|
70
|
+
print("Upgrade canceled")
|
71
|
+
|
72
|
+
|
73
|
+
if __name__ == '__main__':
|
74
|
+
main()
|
@@ -6,6 +6,8 @@ from nicegui import ui
|
|
6
6
|
|
7
7
|
from OpenOrchestrator.common import crypto_util
|
8
8
|
from OpenOrchestrator.database import db_util
|
9
|
+
from OpenOrchestrator.orchestrator import test_helper
|
10
|
+
from OpenOrchestrator.orchestrator.popups import generic_popups
|
9
11
|
|
10
12
|
|
11
13
|
# pylint: disable-next=too-few-public-methods
|
@@ -21,12 +23,13 @@ class ConnectionFrame():
|
|
21
23
|
self.disconn_button.disable()
|
22
24
|
|
23
25
|
self._initial_connect()
|
26
|
+
test_helper.set_automation_ids(self, "connection_frame")
|
24
27
|
|
25
28
|
def _define_validation(self):
|
26
|
-
self.conn_input.
|
27
|
-
self.key_input.
|
29
|
+
self.conn_input._validation = {"Please enter a connection string": bool} # pylint: disable=protected-access
|
30
|
+
self.key_input._validation = {"Invalid AES key": crypto_util.validate_key} # pylint: disable=protected-access
|
28
31
|
|
29
|
-
def _connect(self) -> None:
|
32
|
+
async def _connect(self) -> None:
|
30
33
|
"""Validate the connection string and encryption key
|
31
34
|
and connect to the database.
|
32
35
|
"""
|
@@ -40,7 +43,10 @@ class ConnectionFrame():
|
|
40
43
|
if db_util.connect(conn_string):
|
41
44
|
crypto_util.set_key(crypto_key)
|
42
45
|
self._set_state(True)
|
43
|
-
ui.notify("Connected!", type='positive')
|
46
|
+
ui.notify("Connected!", type='positive', timeout=1000)
|
47
|
+
|
48
|
+
if not os.getenv("ORCHESTRATOR_TEST") and not db_util.check_database_revision():
|
49
|
+
await generic_popups.info_popup("This version of Orchestrator doesn't match the version of the connected database. Unexpected errors might occur.")
|
44
50
|
|
45
51
|
def _disconnect(self) -> None:
|
46
52
|
db_util.disconnect()
|
@@ -2,19 +2,16 @@
|
|
2
2
|
|
3
3
|
from datetime import datetime
|
4
4
|
|
5
|
-
from sqlalchemy import String
|
6
|
-
from sqlalchemy.orm import Mapped,
|
5
|
+
from sqlalchemy import String
|
6
|
+
from sqlalchemy.orm import Mapped, mapped_column
|
7
7
|
|
8
8
|
from OpenOrchestrator.common import datetime_util
|
9
|
+
from OpenOrchestrator.database.base import Base
|
9
10
|
|
10
11
|
# All classes in this module are effectively dataclasses without methods.
|
11
12
|
# pylint: disable=too-few-public-methods
|
12
13
|
|
13
14
|
|
14
|
-
class Base(DeclarativeBase):
|
15
|
-
"""SqlAlchemy base class for all ORM classes in this module."""
|
16
|
-
|
17
|
-
|
18
15
|
class Constant(Base):
|
19
16
|
"""A class representing a constant object in the ORM."""
|
20
17
|
__tablename__ = "Constants"
|
@@ -56,12 +53,3 @@ class Credential(Base):
|
|
56
53
|
lower_length = int(((length-100)/20)*16)
|
57
54
|
upper_length = lower_length + 15
|
58
55
|
return f"{length} encrypted bytes. {lower_length}-{upper_length} decrypted bytes."
|
59
|
-
|
60
|
-
|
61
|
-
def create_tables(engine: Engine):
|
62
|
-
"""Create all SQL tables related to ORM classes in this module.
|
63
|
-
|
64
|
-
Args:
|
65
|
-
engine: The SqlAlchemy connection engine used to create the tables.
|
66
|
-
"""
|
67
|
-
Base.metadata.create_all(engine)
|
@@ -3,19 +3,21 @@
|
|
3
3
|
from datetime import datetime
|
4
4
|
from typing import TypeVar, ParamSpec
|
5
5
|
from uuid import UUID
|
6
|
+
import json
|
7
|
+
|
8
|
+
from cronsim import CronSim
|
9
|
+
from sqlalchemy import Engine, create_engine, select, insert, desc, text
|
6
10
|
|
7
|
-
from croniter import croniter # type: ignore
|
8
|
-
from sqlalchemy import Engine, create_engine, select, insert, desc
|
9
11
|
from sqlalchemy import exc as alc_exc
|
10
12
|
from sqlalchemy import func as alc_func
|
11
13
|
from sqlalchemy.orm import Session, selectin_polymorphic
|
12
14
|
|
13
15
|
from OpenOrchestrator.common import crypto_util
|
14
|
-
from OpenOrchestrator.database import logs, triggers, constants, queues
|
15
16
|
from OpenOrchestrator.database.logs import Log, LogLevel
|
16
17
|
from OpenOrchestrator.database.constants import Constant, Credential
|
17
18
|
from OpenOrchestrator.database.triggers import Trigger, SingleTrigger, ScheduledTrigger, QueueTrigger, TriggerStatus
|
18
19
|
from OpenOrchestrator.database.queues import QueueElement, QueueStatus
|
20
|
+
from OpenOrchestrator.database.schedulers import Scheduler
|
19
21
|
from OpenOrchestrator.database.truncated_string import truncate_message
|
20
22
|
|
21
23
|
# Type hint helpers for decorators
|
@@ -55,6 +57,19 @@ def disconnect() -> None:
|
|
55
57
|
_connection_engine = None
|
56
58
|
|
57
59
|
|
60
|
+
def check_database_revision() -> bool:
|
61
|
+
"""Check if the revision number of the connected database matches the expected revision."""
|
62
|
+
try:
|
63
|
+
with _get_session() as session:
|
64
|
+
version = session.execute(text(
|
65
|
+
"""SELECT version_num FROM alembic_version"""
|
66
|
+
)).scalar()
|
67
|
+
except alc_exc.ProgrammingError:
|
68
|
+
return False
|
69
|
+
|
70
|
+
return version == "90d46abd44a3"
|
71
|
+
|
72
|
+
|
58
73
|
def _get_session() -> Session:
|
59
74
|
"""Check if theres a database connection and return a
|
60
75
|
session to it.
|
@@ -83,17 +98,6 @@ def get_conn_string() -> str:
|
|
83
98
|
return str(_connection_engine.url)
|
84
99
|
|
85
100
|
|
86
|
-
def initialize_database() -> None:
|
87
|
-
"""Initializes the database with all the needed tables."""
|
88
|
-
if not _connection_engine:
|
89
|
-
raise RuntimeError("Not connected to database.")
|
90
|
-
|
91
|
-
logs.create_tables(_connection_engine)
|
92
|
-
triggers.create_tables(_connection_engine)
|
93
|
-
constants.create_tables(_connection_engine)
|
94
|
-
queues.create_tables(_connection_engine)
|
95
|
-
|
96
|
-
|
97
101
|
def get_trigger(trigger_id: UUID | str) -> Trigger:
|
98
102
|
"""Get the trigger with the given id.
|
99
103
|
|
@@ -276,8 +280,10 @@ def get_unique_log_process_names() -> tuple[str, ...]:
|
|
276
280
|
return tuple(result)
|
277
281
|
|
278
282
|
|
283
|
+
# pylint: disable=too-many-positional-arguments
|
279
284
|
def create_single_trigger(trigger_name: str, process_name: str, next_run: datetime,
|
280
|
-
process_path: str, process_args: str, is_git_repo: bool, is_blocking: bool
|
285
|
+
process_path: str, process_args: str, is_git_repo: bool, is_blocking: bool,
|
286
|
+
priority: int, scheduler_whitelist: list[str]) -> str:
|
281
287
|
"""Create a new single trigger in the database.
|
282
288
|
|
283
289
|
Args:
|
@@ -288,6 +294,11 @@ def create_single_trigger(trigger_name: str, process_name: str, next_run: dateti
|
|
288
294
|
process_args: The argument string of the process.
|
289
295
|
is_git_repo: If the process_path points to a git repo.
|
290
296
|
is_blocking: If the process should be blocking.
|
297
|
+
priority: The integer priority of the trigger.
|
298
|
+
cheduler_whitelist: A list of names of schedulers the trigger may run on.
|
299
|
+
|
300
|
+
Returns:
|
301
|
+
The id of the trigger that was created.
|
291
302
|
"""
|
292
303
|
with _get_session() as session:
|
293
304
|
trigger = SingleTrigger(
|
@@ -297,15 +308,19 @@ def create_single_trigger(trigger_name: str, process_name: str, next_run: dateti
|
|
297
308
|
process_args = process_args,
|
298
309
|
is_git_repo = is_git_repo,
|
299
310
|
is_blocking = is_blocking,
|
300
|
-
next_run = next_run
|
311
|
+
next_run = next_run,
|
312
|
+
priority=priority,
|
313
|
+
scheduler_whitelist=json.dumps(scheduler_whitelist)
|
301
314
|
)
|
302
315
|
session.add(trigger)
|
303
316
|
session.commit()
|
317
|
+
return trigger.id
|
304
318
|
|
305
319
|
|
320
|
+
# pylint: disable=too-many-positional-arguments
|
306
321
|
def create_scheduled_trigger(trigger_name: str, process_name: str, cron_expr: str, next_run: datetime,
|
307
322
|
process_path: str, process_args: str, is_git_repo: bool,
|
308
|
-
is_blocking: bool) ->
|
323
|
+
is_blocking: bool, priority: int, scheduler_whitelist: list[str]) -> str:
|
309
324
|
"""Create a new scheduled trigger in the database.
|
310
325
|
|
311
326
|
Args:
|
@@ -317,6 +332,10 @@ def create_scheduled_trigger(trigger_name: str, process_name: str, cron_expr: st
|
|
317
332
|
process_args: The argument string of the process.
|
318
333
|
is_git_repo: If the process_path points to a git repo.
|
319
334
|
is_blocking: If the process should be blocking.
|
335
|
+
priority: The integer priority of the trigger.
|
336
|
+
scheduler_whitelist: A list of names of schedulers the trigger may run on.
|
337
|
+
Returns:
|
338
|
+
The id of the trigger that was created.
|
320
339
|
"""
|
321
340
|
with _get_session() as session:
|
322
341
|
trigger = ScheduledTrigger(
|
@@ -327,15 +346,19 @@ def create_scheduled_trigger(trigger_name: str, process_name: str, cron_expr: st
|
|
327
346
|
is_git_repo = is_git_repo,
|
328
347
|
is_blocking = is_blocking,
|
329
348
|
next_run = next_run,
|
330
|
-
cron_expr = cron_expr
|
349
|
+
cron_expr = cron_expr,
|
350
|
+
priority=priority,
|
351
|
+
scheduler_whitelist=json.dumps(scheduler_whitelist)
|
331
352
|
)
|
332
353
|
session.add(trigger)
|
333
354
|
session.commit()
|
355
|
+
return trigger.id
|
334
356
|
|
335
357
|
|
358
|
+
# pylint: disable=too-many-positional-arguments
|
336
359
|
def create_queue_trigger(trigger_name: str, process_name: str, queue_name: str, process_path: str,
|
337
360
|
process_args: str, is_git_repo: bool, is_blocking: bool,
|
338
|
-
min_batch_size: int) ->
|
361
|
+
min_batch_size: int, priority: int, scheduler_whitelist: list[str]) -> str:
|
339
362
|
"""Create a new queue trigger in the database.
|
340
363
|
|
341
364
|
Args:
|
@@ -347,6 +370,11 @@ def create_queue_trigger(trigger_name: str, process_name: str, queue_name: str,
|
|
347
370
|
is_git_repo: The is_git value of the process.
|
348
371
|
is_blocking: The is_blocking value of the process.
|
349
372
|
min_batch_size: The minimum number of queue elements before triggering.
|
373
|
+
priority: The integer priority of the trigger.
|
374
|
+
scheduler_whitelist: A list of names of schedulers the trigger may run on.
|
375
|
+
|
376
|
+
Returns:
|
377
|
+
The id of the trigger that was created.
|
350
378
|
"""
|
351
379
|
with _get_session() as session:
|
352
380
|
trigger = QueueTrigger(
|
@@ -357,10 +385,13 @@ def create_queue_trigger(trigger_name: str, process_name: str, queue_name: str,
|
|
357
385
|
is_git_repo = is_git_repo,
|
358
386
|
is_blocking = is_blocking,
|
359
387
|
queue_name = queue_name,
|
360
|
-
min_batch_size = min_batch_size
|
388
|
+
min_batch_size = min_batch_size,
|
389
|
+
priority=priority,
|
390
|
+
scheduler_whitelist=json.dumps(scheduler_whitelist)
|
361
391
|
)
|
362
392
|
session.add(trigger)
|
363
393
|
session.commit()
|
394
|
+
return trigger.id
|
364
395
|
|
365
396
|
|
366
397
|
def get_constant(name: str) -> Constant:
|
@@ -559,11 +590,11 @@ def begin_single_trigger(trigger_id: UUID | str) -> bool:
|
|
559
590
|
return True
|
560
591
|
|
561
592
|
|
562
|
-
def
|
563
|
-
"""Get
|
593
|
+
def get_pending_single_triggers() -> list[SingleTrigger]:
|
594
|
+
"""Get all single triggers that are ready to run.
|
564
595
|
|
565
596
|
Returns:
|
566
|
-
|
597
|
+
All single triggers ready to run if any.
|
567
598
|
"""
|
568
599
|
with _get_session() as session:
|
569
600
|
query = (
|
@@ -571,16 +602,15 @@ def get_next_single_trigger() -> SingleTrigger | None:
|
|
571
602
|
.where(SingleTrigger.process_status == TriggerStatus.IDLE)
|
572
603
|
.where(SingleTrigger.next_run <= datetime.now())
|
573
604
|
.order_by(SingleTrigger.next_run)
|
574
|
-
.limit(1)
|
575
605
|
)
|
576
|
-
return session.
|
606
|
+
return list(session.scalars(query))
|
577
607
|
|
578
608
|
|
579
|
-
def
|
580
|
-
"""Get
|
609
|
+
def get_pending_scheduled_triggers() -> list[ScheduledTrigger]:
|
610
|
+
"""Get all scheduled triggers that are ready to run.
|
581
611
|
|
582
612
|
Returns:
|
583
|
-
|
613
|
+
All scheduled triggers ready to run if any.
|
584
614
|
"""
|
585
615
|
with _get_session() as session:
|
586
616
|
query = (
|
@@ -588,9 +618,8 @@ def get_next_scheduled_trigger() -> ScheduledTrigger | None:
|
|
588
618
|
.where(ScheduledTrigger.process_status == TriggerStatus.IDLE)
|
589
619
|
.where(ScheduledTrigger.next_run <= datetime.now())
|
590
620
|
.order_by(ScheduledTrigger.next_run)
|
591
|
-
.limit(1)
|
592
621
|
)
|
593
|
-
return session.
|
622
|
+
return list(session.scalars(query))
|
594
623
|
|
595
624
|
|
596
625
|
def begin_scheduled_trigger(trigger_id: UUID | str) -> bool:
|
@@ -619,20 +648,17 @@ def begin_scheduled_trigger(trigger_id: UUID | str) -> bool:
|
|
619
648
|
|
620
649
|
trigger.process_status = TriggerStatus.RUNNING
|
621
650
|
trigger.last_run = datetime.now()
|
622
|
-
trigger.next_run =
|
651
|
+
trigger.next_run = next(CronSim(trigger.cron_expr, datetime.now()))
|
623
652
|
|
624
653
|
session.commit()
|
625
654
|
return True
|
626
655
|
|
627
656
|
|
628
|
-
def
|
629
|
-
"""Get
|
630
|
-
This functions loops through the queue triggers and checks
|
631
|
-
if the number of queue elements with status 'New' is above
|
632
|
-
the triggers min_batch_size.
|
657
|
+
def get_pending_queue_triggers() -> list[QueueTrigger]:
|
658
|
+
"""Get all queue triggers that are ready to run.
|
633
659
|
|
634
660
|
Returns:
|
635
|
-
|
661
|
+
All queue triggers that are ready to run if any.
|
636
662
|
"""
|
637
663
|
|
638
664
|
with _get_session() as session:
|
@@ -648,9 +674,8 @@ def get_next_queue_trigger() -> QueueTrigger | None:
|
|
648
674
|
select(QueueTrigger)
|
649
675
|
.where(QueueTrigger.process_status == TriggerStatus.IDLE)
|
650
676
|
.where(sub_query >= QueueTrigger.min_batch_size)
|
651
|
-
.limit(1)
|
652
677
|
)
|
653
|
-
return session.
|
678
|
+
return list(session.scalars(query))
|
654
679
|
|
655
680
|
|
656
681
|
def begin_queue_trigger(trigger_id: UUID | str) -> bool:
|
@@ -910,3 +935,51 @@ def delete_queue_element(element_id: UUID | str) -> None:
|
|
910
935
|
q_element = session.get(QueueElement, element_id)
|
911
936
|
session.delete(q_element)
|
912
937
|
session.commit()
|
938
|
+
|
939
|
+
|
940
|
+
def get_schedulers() -> tuple[Scheduler, ...]:
|
941
|
+
"""Get Schedulers from the database"""
|
942
|
+
with _get_session() as session:
|
943
|
+
query = select(Scheduler).order_by(Scheduler.machine_name)
|
944
|
+
result = session.scalars(query).all()
|
945
|
+
return tuple(result)
|
946
|
+
|
947
|
+
|
948
|
+
def send_ping_from_scheduler(machine_name: str) -> None:
|
949
|
+
"""Send a ping from a running scheduler, updating the machine in the database.
|
950
|
+
|
951
|
+
Args:
|
952
|
+
machine_name: The machine pinging the Orchestrator
|
953
|
+
"""
|
954
|
+
with _get_session() as session:
|
955
|
+
scheduler = session.get(Scheduler, machine_name)
|
956
|
+
|
957
|
+
if scheduler:
|
958
|
+
scheduler.last_update = datetime.now()
|
959
|
+
else:
|
960
|
+
scheduler = Scheduler(machine_name=machine_name, last_update=datetime.now())
|
961
|
+
session.add(scheduler)
|
962
|
+
|
963
|
+
session.commit()
|
964
|
+
|
965
|
+
|
966
|
+
def start_trigger_from_machine(machine_name: str, trigger_name: str) -> None:
|
967
|
+
"""Start a trigger from a machine running a scheduler, updating the name and time for triggers in the database.
|
968
|
+
|
969
|
+
Args:
|
970
|
+
machine_name: The machine starting the trigger
|
971
|
+
trigger_name: The trigger being started på the machine
|
972
|
+
"""
|
973
|
+
with _get_session() as session:
|
974
|
+
scheduler = session.get(Scheduler, machine_name)
|
975
|
+
now = datetime.now()
|
976
|
+
|
977
|
+
if scheduler:
|
978
|
+
scheduler.last_update = now
|
979
|
+
scheduler.latest_trigger = trigger_name
|
980
|
+
scheduler.latest_trigger_time = now
|
981
|
+
else:
|
982
|
+
scheduler = Scheduler(machine_name=machine_name, last_update=now, latest_trigger=trigger_name, latest_trigger_time=now)
|
983
|
+
session.add(scheduler)
|
984
|
+
|
985
|
+
session.commit()
|
@@ -4,10 +4,11 @@ from datetime import datetime
|
|
4
4
|
import enum
|
5
5
|
import uuid
|
6
6
|
|
7
|
-
from sqlalchemy import String
|
8
|
-
from sqlalchemy.orm import Mapped,
|
7
|
+
from sqlalchemy import String
|
8
|
+
from sqlalchemy.orm import Mapped, mapped_column
|
9
9
|
|
10
10
|
from OpenOrchestrator.common import datetime_util
|
11
|
+
from OpenOrchestrator.database.base import Base
|
11
12
|
|
12
13
|
# All classes in this module are effectively dataclasses without methods.
|
13
14
|
# pylint: disable=too-few-public-methods
|
@@ -20,10 +21,6 @@ class LogLevel(enum.Enum):
|
|
20
21
|
ERROR = "Error"
|
21
22
|
|
22
23
|
|
23
|
-
class Base(DeclarativeBase):
|
24
|
-
"""SqlAlchemy base class for all ORM classes in this module."""
|
25
|
-
|
26
|
-
|
27
24
|
class Log(Base):
|
28
25
|
"""A class representing log objects in the ORM."""
|
29
26
|
__tablename__ = "Logs"
|
@@ -43,12 +40,3 @@ class Log(Base):
|
|
43
40
|
"Message": self.log_message,
|
44
41
|
"ID": str(self.id)
|
45
42
|
}
|
46
|
-
|
47
|
-
|
48
|
-
def create_tables(engine: Engine):
|
49
|
-
"""Create all SQL tables related to ORM classes in this module.
|
50
|
-
|
51
|
-
Args:
|
52
|
-
engine: The SqlAlchemy connection engine used to create the tables.
|
53
|
-
"""
|
54
|
-
Base.metadata.create_all(engine)
|
@@ -5,10 +5,11 @@ import enum
|
|
5
5
|
from typing import Optional
|
6
6
|
import uuid
|
7
7
|
|
8
|
-
from sqlalchemy import String
|
9
|
-
from sqlalchemy.orm import Mapped,
|
8
|
+
from sqlalchemy import String
|
9
|
+
from sqlalchemy.orm import Mapped, mapped_column
|
10
10
|
|
11
11
|
from OpenOrchestrator.common import datetime_util
|
12
|
+
from OpenOrchestrator.database.base import Base
|
12
13
|
|
13
14
|
# All classes in this module are effectively dataclasses without methods.
|
14
15
|
# pylint: disable=too-few-public-methods
|
@@ -23,10 +24,6 @@ class QueueStatus(enum.Enum):
|
|
23
24
|
ABANDONED = 'Abandoned'
|
24
25
|
|
25
26
|
|
26
|
-
class Base(DeclarativeBase):
|
27
|
-
"""SqlAlchemy base class for all ORM classes in this module."""
|
28
|
-
|
29
|
-
|
30
27
|
class QueueElement(Base):
|
31
28
|
"""A class representing a queue element in the ORM."""
|
32
29
|
__tablename__ = "Queues"
|
@@ -55,12 +52,3 @@ class QueueElement(Base):
|
|
55
52
|
"Message": self.message,
|
56
53
|
"Created By": self.created_by
|
57
54
|
}
|
58
|
-
|
59
|
-
|
60
|
-
def create_tables(engine: Engine):
|
61
|
-
"""Create all SQL tables related to ORM classes in this module.
|
62
|
-
|
63
|
-
Args:
|
64
|
-
engine: The SqlAlchemy connection engine used to create the tables.
|
65
|
-
"""
|
66
|
-
Base.metadata.create_all(engine)
|
@@ -0,0 +1,32 @@
|
|
1
|
+
"""Module for the Scheduler ORM class"""
|
2
|
+
|
3
|
+
from datetime import datetime
|
4
|
+
from typing import Optional
|
5
|
+
|
6
|
+
from sqlalchemy import String
|
7
|
+
from sqlalchemy.orm import Mapped, mapped_column
|
8
|
+
|
9
|
+
from OpenOrchestrator.common import datetime_util
|
10
|
+
from OpenOrchestrator.database.base import Base
|
11
|
+
|
12
|
+
# All classes in this module are effectively dataclasses without methods.
|
13
|
+
# pylint: disable=too-few-public-methods
|
14
|
+
|
15
|
+
|
16
|
+
class Scheduler(Base):
|
17
|
+
"""Class containing the ORM model for schedulers"""
|
18
|
+
__tablename__ = "Schedulers"
|
19
|
+
|
20
|
+
machine_name: Mapped[str] = mapped_column(String(100), primary_key=True)
|
21
|
+
last_update: Mapped[datetime] = mapped_column(onupdate=datetime.now, default=datetime.now)
|
22
|
+
latest_trigger: Mapped[Optional[str]] = mapped_column(String(100), nullable=True, default=None)
|
23
|
+
latest_trigger_time: Mapped[Optional[datetime]] = mapped_column(nullable=True, default=None)
|
24
|
+
|
25
|
+
def to_row_dict(self) -> dict[str, str]:
|
26
|
+
"""Convert scheduler to a row dictionary for display in a table."""
|
27
|
+
return {
|
28
|
+
"Machine Name": self.machine_name,
|
29
|
+
"Last Connection": datetime_util.format_datetime(self.last_update),
|
30
|
+
"Latest Trigger": self.latest_trigger if self.latest_trigger else "None yet",
|
31
|
+
"Latest Trigger Time": datetime_util.format_datetime(self.latest_trigger_time) if self.latest_trigger_time else ""
|
32
|
+
}
|
@@ -5,10 +5,11 @@ import enum
|
|
5
5
|
from typing import Optional
|
6
6
|
import uuid
|
7
7
|
|
8
|
-
from sqlalchemy import String, ForeignKey
|
9
|
-
from sqlalchemy.orm import Mapped,
|
8
|
+
from sqlalchemy import String, ForeignKey
|
9
|
+
from sqlalchemy.orm import Mapped, mapped_column
|
10
10
|
|
11
11
|
from OpenOrchestrator.common import datetime_util
|
12
|
+
from OpenOrchestrator.database.base import Base
|
12
13
|
|
13
14
|
# All classes in this module are effectively dataclasses without methods.
|
14
15
|
# pylint: disable=too-few-public-methods
|
@@ -31,10 +32,6 @@ class TriggerType(enum.Enum):
|
|
31
32
|
QUEUE = "Queue"
|
32
33
|
|
33
34
|
|
34
|
-
class Base(DeclarativeBase):
|
35
|
-
"""SqlAlchemy base class for all ORM classes in this module."""
|
36
|
-
|
37
|
-
|
38
35
|
class Trigger(Base):
|
39
36
|
"""A base class for all triggers in the ORM."""
|
40
37
|
__tablename__ = "Triggers"
|
@@ -48,6 +45,8 @@ class Trigger(Base):
|
|
48
45
|
process_status: Mapped[TriggerStatus] = mapped_column(default=TriggerStatus.IDLE)
|
49
46
|
is_git_repo: Mapped[bool]
|
50
47
|
is_blocking: Mapped[bool]
|
48
|
+
scheduler_whitelist: Mapped[str] = mapped_column(String(250))
|
49
|
+
priority: Mapped[int] = mapped_column(default=0)
|
51
50
|
type: Mapped[TriggerType]
|
52
51
|
|
53
52
|
__mapper_args__ = {
|
@@ -66,7 +65,8 @@ class Trigger(Base):
|
|
66
65
|
"Status": self.process_status.value,
|
67
66
|
"Process Name": self.process_name,
|
68
67
|
"Last Run": datetime_util.format_datetime(self.last_run, "Never"),
|
69
|
-
"ID": str(self.id)
|
68
|
+
"ID": str(self.id),
|
69
|
+
"Priority": str(self.priority)
|
70
70
|
}
|
71
71
|
|
72
72
|
|
@@ -115,12 +115,3 @@ class QueueTrigger(Trigger):
|
|
115
115
|
row_dict = super().to_row_dict()
|
116
116
|
row_dict["Next Run"] = "N/A"
|
117
117
|
return row_dict
|
118
|
-
|
119
|
-
|
120
|
-
def create_tables(engine: Engine):
|
121
|
-
"""Create all SQL tables related to ORM classes in this module.
|
122
|
-
|
123
|
-
Args:
|
124
|
-
engine: The SqlAlchemy connection engine used to create the tables.
|
125
|
-
"""
|
126
|
-
Base.metadata.create_all(engine)
|