Skip to content

Commit 5a34ae5

Browse files
authored
Merge pull request #524 from atlanticwave-sdx/rollback-on-failed-oxp-post
Rollback on failed OXP POST
2 parents 26d01cb + f97dea5 commit 5a34ae5

7 files changed

Lines changed: 751 additions & 157 deletions

File tree

compose.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ services:
5454
# MQ settings
5555
- MQ_HOST=${MQ_HOST}
5656
- MQ_PORT=${MQ_PORT:-5672}
57+
- OXP_RESPONSE_TIMEOUT=${OXP_RESPONSE_TIMEOUT:-60}
58+
- PROVISIONING_MONITOR_INTERVAL=${PROVISIONING_MONITOR_INTERVAL:-5}
5759

5860
volumes:
5961
mongodb:

env.template

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ SDX_PORT=8080
55
SDX_NAME=sdx-controller-test
66
HEARTBEAT_INTERVAL=30
77
HEARTBEAT_TOLERANCE=3
8+
OXP_RESPONSE_TIMEOUT=60
9+
PROVISIONING_MONITOR_INTERVAL=5
810

911
# Message queue settings for SDX Controller.
1012
MQ_HOST=aw-sdx-monitor.renci.org

sdx_controller/__init__.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,28 @@
11
import logging
22
import os
33
import threading
4+
import time
45
from queue import Queue
56

67
import connexion
8+
from sdx_datamodel.connection_sm import ConnectionStateMachine
9+
from sdx_datamodel.constants import MongoCollections
710
from sdx_pce.topology.temanager import TEManager
811

912
from sdx_controller import encoder
13+
from sdx_controller.handlers.connection_handler import (
14+
ConnectionHandler,
15+
connection_state_machine,
16+
)
1017
from sdx_controller.messaging.rpc_queue_consumer import RpcConsumer
1118
from sdx_controller.utils.db_utils import DbUtils
1219

1320
logger = logging.getLogger(__name__)
1421
logging.getLogger("pika").setLevel(logging.WARNING)
1522
LOG_FILE = os.environ.get("LOG_FILE")
1623
LOG_LEVEL = os.getenv("LOG_LEVEL", "DEBUG")
24+
OXP_RESPONSE_TIMEOUT = int(os.getenv("OXP_RESPONSE_TIMEOUT", 60))
25+
PROVISIONING_MONITOR_INTERVAL = int(os.getenv("PROVISIONING_MONITOR_INTERVAL", 5))
1726

1827

1928
def create_rpc_thread(app):
@@ -32,6 +41,82 @@ def create_rpc_thread(app):
3241
rpc_thread.start()
3342

3443

44+
def create_provisioning_timeout_thread(app):
45+
"""
46+
Start a background monitor for connections stuck in
47+
UNDER_PROVISIONING longer than the configured timeout.
48+
"""
49+
if OXP_RESPONSE_TIMEOUT <= 0:
50+
logger.info("[ProvisioningTimeout] Disabled.")
51+
app.provisioning_timeout_thread = None
52+
return
53+
54+
connection_handler = ConnectionHandler(app.db_instance)
55+
56+
def monitor_loop():
57+
logger.info(
58+
f"[ProvisioningTimeout] Started monitoring with timeout={OXP_RESPONSE_TIMEOUT}s interval={PROVISIONING_MONITOR_INTERVAL}s."
59+
)
60+
while True:
61+
try:
62+
now = time.time()
63+
connections = app.db_instance.get_all_entries_in_collection(
64+
MongoCollections.CONNECTIONS
65+
)
66+
for connection_entry in connections:
67+
service_id = next(iter(connection_entry), None)
68+
connection = (
69+
connection_entry.get(service_id) if service_id else None
70+
)
71+
if not isinstance(connection, dict):
72+
continue
73+
if connection.get("status") != str(
74+
ConnectionStateMachine.State.UNDER_PROVISIONING
75+
):
76+
continue
77+
78+
started_at = connection.get("provisioning_started_at")
79+
if not isinstance(started_at, (int, float)):
80+
continue
81+
if connection.get("provisioning_timeout_handled"):
82+
continue
83+
if now - started_at < OXP_RESPONSE_TIMEOUT:
84+
continue
85+
86+
logger.warning(
87+
f"[ProvisioningTimeout] Connection {service_id} timed out after {int(now - started_at)}s waiting for OXP responses."
88+
)
89+
90+
connection["provisioning_timeout_handled"] = True
91+
connection["partial_cleanup_requested"] = True
92+
connection["timeout_reason"] = (
93+
f"OXP response timeout after {OXP_RESPONSE_TIMEOUT} seconds"
94+
)
95+
connection, _ = connection_state_machine(
96+
connection, ConnectionStateMachine.State.DOWN
97+
)
98+
app.db_instance.add_key_value_pair_to_db(
99+
MongoCollections.CONNECTIONS, service_id, connection
100+
)
101+
cleanup_status, cleanup_code = (
102+
connection_handler.cleanup_partial_connection(
103+
app.te_manager, service_id, connection
104+
)
105+
)
106+
logger.info(
107+
f"[ProvisioningTimeout] Cleanup result for {service_id}: {cleanup_status}, code={cleanup_code}"
108+
)
109+
except Exception as e:
110+
logger.exception(
111+
f"[ProvisioningTimeout] Error while monitoring connections: {e}"
112+
)
113+
time.sleep(PROVISIONING_MONITOR_INTERVAL)
114+
115+
provisioning_thread = threading.Thread(target=monitor_loop, daemon=True)
116+
provisioning_thread.start()
117+
app.provisioning_timeout_thread = provisioning_thread
118+
119+
35120
def create_app(run_listener: bool = True):
36121
"""
37122
Create a connexion app.
@@ -74,6 +159,8 @@ def create_app(run_listener: bool = True):
74159
# pass this around.
75160
app.app.te_manager = app.te_manager
76161

162+
create_provisioning_timeout_thread(app)
163+
77164
if run_listener:
78165
create_rpc_thread(app)
79166
else:

0 commit comments

Comments
 (0)