Skip to content

Commit 37b4843

Browse files
committed
adding triggers
1 parent 19bc778 commit 37b4843

7 files changed

Lines changed: 97 additions & 4 deletions

File tree

state-manager/app/config/settings.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class Settings(BaseModel):
1212
mongo_database_name: str = Field(default="exosphere-state-manager", description="MongoDB database name")
1313
state_manager_secret: str = Field(..., description="Secret key for API authentication")
1414
secrets_encryption_key: str = Field(..., description="Key for encrypting secrets")
15+
trigger_ahead_time: int = Field(default=10, description="Time in minutes to trigger the graph ahead of the current time")
1516

1617
@classmethod
1718
def from_env(cls) -> "Settings":
@@ -20,6 +21,7 @@ def from_env(cls) -> "Settings":
2021
mongo_database_name=os.getenv("MONGO_DATABASE_NAME", "exosphere-state-manager"), # type: ignore
2122
state_manager_secret=os.getenv("STATE_MANAGER_SECRET"), # type: ignore
2223
secrets_encryption_key=os.getenv("SECRETS_ENCRYPTION_KEY"), # type: ignore
24+
trigger_ahead_time=int(os.getenv("TRIGGER_AHEAD_TIME", 10)) # type: ignore
2325
)
2426

2527

state-manager/app/controller/upsert_graph_template.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010

1111
async def upsert_graph_template(namespace_name: str, graph_name: str, body: UpsertGraphTemplateRequest, x_exosphere_request_id: str, background_tasks: BackgroundTasks) -> UpsertGraphTemplateResponse:
1212
try:
13+
14+
old_triggers = []
15+
1316
graph_template = await GraphTemplate.find_one(
1417
GraphTemplate.name == graph_name,
1518
GraphTemplate.namespace == namespace_name
@@ -21,6 +24,7 @@ async def upsert_graph_template(namespace_name: str, graph_name: str, body: Upse
2124
"Graph template already exists in namespace", graph_template=graph_template,
2225
namespace_name=namespace_name,
2326
x_exosphere_request_id=x_exosphere_request_id)
27+
old_triggers = graph_template.triggers
2428

2529
graph_template.set_secrets(body.secrets)
2630
graph_template.validation_status = GraphTemplateValidationStatus.PENDING
@@ -54,7 +58,7 @@ async def upsert_graph_template(namespace_name: str, graph_name: str, body: Upse
5458
logger.error("Error validating graph template", error=e, x_exosphere_request_id=x_exosphere_request_id)
5559
raise HTTPException(status_code=400, detail=f"Error validating graph template: {str(e)}")
5660

57-
background_tasks.add_task(verify_graph, graph_template)
61+
background_tasks.add_task(verify_graph, graph_template, old_triggers)
5862

5963
return UpsertGraphTemplateResponse(
6064
nodes=graph_template.nodes,
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from pydantic import Field
2+
from beanie import Document
3+
from typing import Optional
4+
5+
from pymongo import IndexModel
6+
from ..trigger_models import TriggerTypeEnum, TriggerStatusEnum
7+
from datetime import datetime
8+
9+
class Triggers(Document):
10+
type: TriggerTypeEnum = Field(..., description="Type of the trigger")
11+
expression: Optional[str] = Field(default=None, description="Expression of the trigger")
12+
graph_name: str = Field(..., description="Name of the graph")
13+
trigger_time: datetime = Field(..., description="Trigger time of the trigger")
14+
trigger_status: TriggerStatusEnum = Field(..., description="Status of the trigger")
15+
16+
class Settings:
17+
indexes = [
18+
IndexModel(
19+
[
20+
("trigger_time", -1),
21+
],
22+
name="idx_trigger_time"
23+
)
24+
]
File renamed without changes.

state-manager/app/models/trigger_models.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66
class TriggerTypeEnum(str, Enum):
77
CRON = "CRON"
88

9+
class TriggerStatusEnum(str, Enum):
10+
PENDING = "PENDING"
11+
FAILED = "FAILED"
12+
CANCELLED = "CANCELLED"
13+
TRIGGERED = "TRIGGERED"
14+
915
class CronTrigger(BaseModel):
1016
expression: str = Field(..., description="Cron expression for the trigger")
1117

state-manager/app/routes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from .models.enqueue_request import EnqueueRequestModel
1010
from .controller.enqueue_states import enqueue_states
1111

12-
from .models.trigger_model import TriggerGraphRequestModel, TriggerGraphResponseModel
12+
from .models.trigger_graph_model import TriggerGraphRequestModel, TriggerGraphResponseModel
1313
from .controller.trigger_graph import trigger_graph
1414

1515
from .models.executed_models import ExecutedRequestModel, ExecutedResponseModel

state-manager/app/tasks/verify_graph.py

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
import asyncio
2+
import croniter
3+
4+
from datetime import datetime, timedelta
5+
from beanie.operators import In
6+
from json_schema_to_pydantic import create_model
27

38
from app.models.db.graph_template_model import GraphTemplate
49
from app.models.graph_template_validation_status import GraphTemplateValidationStatus
510
from app.models.db.registered_node import RegisteredNode
611
from app.singletons.logs_manager import LogsManager
7-
from json_schema_to_pydantic import create_model
12+
from app.models.trigger_models import Trigger, CronTrigger, TriggerStatusEnum, TriggerTypeEnum
13+
from app.models.db.trigger import Triggers as DatabaseTriggers
14+
from config.settings import get_settings
815

916
logger = LogsManager().get_logger()
1017

@@ -95,7 +102,54 @@ async def verify_inputs(graph_template: GraphTemplate, registered_nodes: list[Re
95102

96103
return errors
97104

98-
async def verify_graph(graph_template: GraphTemplate):
105+
async def cancel_crons(graph_template: GraphTemplate, old_triggers: list[Trigger]):
106+
old_crons = set([CronTrigger(**trigger.value) for trigger in old_triggers if trigger.type == TriggerTypeEnum.CRON])
107+
new_crons = set([CronTrigger(**trigger.value) for trigger in graph_template.triggers if trigger.type == TriggerTypeEnum.CRON])
108+
109+
removed = old_crons - new_crons
110+
111+
await DatabaseTriggers.find(
112+
DatabaseTriggers.graph_name == graph_template.name,
113+
DatabaseTriggers.trigger_status == TriggerStatusEnum.PENDING,
114+
DatabaseTriggers.type == TriggerTypeEnum.CRON,
115+
In(DatabaseTriggers.expression, [cron.expression for cron in removed])
116+
).update(
117+
{
118+
"$set": {
119+
"trigger_status": TriggerStatusEnum.CANCELLED
120+
}
121+
}
122+
) # type: ignore
123+
124+
async def create_crons(graph_template: GraphTemplate, old_triggers: list[Trigger]):
125+
old_crons = set([CronTrigger(**trigger.value) for trigger in old_triggers if trigger.type == TriggerTypeEnum.CRON])
126+
new_crons = set([CronTrigger(**trigger.value) for trigger in graph_template.triggers if trigger.type == TriggerTypeEnum.CRON])
127+
128+
crons_to_create = new_crons - old_crons
129+
130+
trigger_ahead_time = get_settings().trigger_ahead_time
131+
current_time = datetime.now()
132+
limit_time = datetime.now() + timedelta(minutes=trigger_ahead_time)
133+
134+
new_db_triggers = []
135+
for cron in crons_to_create:
136+
iter = croniter.croniter(cron.expression, current_time)
137+
next_trigger_time = iter.get_next(datetime)
138+
139+
while (next_trigger_time < limit_time):
140+
new_db_triggers.append(
141+
DatabaseTriggers(
142+
type=TriggerTypeEnum.CRON,
143+
expression=cron.expression,
144+
graph_name=graph_template.name,
145+
trigger_status=TriggerStatusEnum.PENDING,
146+
trigger_time=next_trigger_time
147+
)
148+
)
149+
next_trigger_time = iter.get_next(datetime)
150+
await DatabaseTriggers.insert_many(new_db_triggers)
151+
152+
async def verify_graph(graph_template: GraphTemplate, old_triggers: list[Trigger]):
99153
try:
100154
errors = []
101155
registered_nodes = await RegisteredNode.list_nodes_by_templates(graph_template.nodes)
@@ -118,6 +172,9 @@ async def verify_graph(graph_template: GraphTemplate):
118172

119173
graph_template.validation_status = GraphTemplateValidationStatus.VALID
120174
graph_template.validation_errors = []
175+
176+
await asyncio.gather(*[cancel_crons(graph_template, old_triggers), create_crons(graph_template, old_triggers)])
177+
121178
await graph_template.save()
122179

123180
except Exception as e:

0 commit comments

Comments
 (0)