• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

atlanticwave-sdx / sdx-controller / 24409993215

14 Apr 2026 04:12PM UTC coverage: 53.992% (+0.07%) from 53.92%
24409993215

push

github

web-flow
Merge pull request #525 from atlanticwave-sdx/revert-520-518-l2vpn-rollback-deletes-endpoints-object-when-vlan-assigment-is-not-found

Revert "clean up the patch_connection state transition"

0 of 8 new or added lines in 1 file covered. (0.0%)

2 existing lines in 1 file now uncovered.

1285 of 2380 relevant lines covered (53.99%)

1.08 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

59.21
/sdx_controller/controllers/l2vpn_controller.py
1
import logging
2✔
2
import os
2✔
3
import uuid
2✔
4

5
import connexion
2✔
6
from flask import current_app
2✔
7
from sdx_datamodel.connection_sm import ConnectionStateMachine
2✔
8
from sdx_datamodel.constants import MongoCollections
2✔
9

10
from sdx_controller.handlers.connection_handler import (
2✔
11
    ConnectionHandler,
12
    connection_state_machine,
13
    get_connection_status,
14
    parse_conn_status,
15
)
16

17
# from sdx_controller.models.l2vpn_service_id_body import L2vpnServiceIdBody  # noqa: E501
18
from sdx_controller.utils.db_utils import DbUtils
2✔
19

20
LOG_FORMAT = (
2✔
21
    "%(levelname) -10s %(asctime)s %(name) -30s %(funcName) "
22
    "-35s %(lineno) -5d: %(message)s"
23
)
24
logger = logging.getLogger(__name__)
2✔
25
logging.getLogger("pika").setLevel(logging.WARNING)
2✔
26
logger.setLevel(logging.getLevelName(os.getenv("LOG_LEVEL", "DEBUG")))
2✔
27

28
# Get DB connection and tables set up.
29
db_instance = DbUtils()
2✔
30
db_instance.initialize_db()
2✔
31
connection_handler = ConnectionHandler(db_instance)
2✔
32

33

34
def delete_connection(service_id):
2✔
35
    """
36
    Delete connection order by ID.
37

38
    :param service_id: ID of the connection that needs to be
39
        deleted
40
    :type service_id: str
41

42
    :rtype: None
43
    """
44
    logger.info(
2✔
45
        f"Handling delete (service id: {service_id}) "
46
        f"with te_manager: {current_app.te_manager}"
47
    )
48

49
    # # Looking up by UUID do not seem work yet.  Will address in
50
    # # https://github.com/atlanticwave-sdx/sdx-controller/issues/252.
51
    #
52
    # value = db_instance.read_from_db(f"{service_id}")
53
    # print(f"value: {value}")
54
    # if not value:
55
    #     return "Not found", 404
56

57
    try:
2✔
58
        # TODO: pce's unreserve_vlan() method silently returns even if the
59
        # service_id is not found.  This should in fact be an error.
60
        #
61
        # https://github.com/atlanticwave-sdx/pce/issues/180
62
        connection = db_instance.get_value_from_db(
2✔
63
            MongoCollections.CONNECTIONS, f"{service_id}"
64
        )
65

66
        if not connection:
2✔
67
            return "Did not find connection", 404
2✔
68

69
        logger.info(f"connection: {connection} {type(connection)}")
2✔
70
        if connection.get("status") is None:
2✔
71
            logger.error("Missing field: status is not in connection.")
×
72
            connection["status"] = str(ConnectionStateMachine.State.DELETED)
×
73
        elif connection["status"] == str(ConnectionStateMachine.State.UP):
2✔
74
            connection, _ = connection_state_machine(
2✔
75
                connection, ConnectionStateMachine.State.DELETED
76
            )
77
        elif connection["status"] == str(
×
78
            ConnectionStateMachine.State.UNDER_PROVISIONING
79
        ):
80
            connection, _ = connection_state_machine(
×
81
                connection, ConnectionStateMachine.State.DOWN
82
            )
83
            connection, _ = connection_state_machine(
×
84
                connection, ConnectionStateMachine.State.DELETED
85
            )
86
        else:
87
            connection, _ = connection_state_machine(
×
88
                connection, ConnectionStateMachine.State.DELETED
89
            )
90

91
        logger.info(f"Removing connection: {service_id} {connection.get('status')}")
2✔
92

93
        connection_handler.remove_connection(current_app.te_manager, service_id, "API")
2✔
94
        db_instance.mark_deleted(MongoCollections.CONNECTIONS, f"{service_id}")
2✔
95
        db_instance.mark_deleted(MongoCollections.BREAKDOWNS, f"{service_id}")
2✔
96
    except Exception as e:
×
97
        logger.info(f"Delete failed (connection id: {service_id}): {e}")
×
98
        return f"Failed, reason: {e}", 500
×
99

100
    return "OK", 200
2✔
101

102

103
def get_connection_by_id(service_id):
2✔
104
    """
105
    Find connection by ID.
106

107
    :param service_id: ID of connection that needs to be fetched
108
    :type service_id: str
109

110
    :rtype: Connection
111
    """
112

113
    value = get_connection_status(db_instance, service_id)
2✔
114

115
    if not value:
2✔
116
        return "Connection not found", 404
2✔
117

118
    return value
2✔
119

120

121
def get_connections():  # noqa: E501
2✔
122
    """
123
    List all connections
124

125
    connection details # noqa: E501
126

127
    :rtype: Connection
128
    """
129
    values = db_instance.get_all_entries_in_collection(MongoCollections.CONNECTIONS)
2✔
130
    if not values:
2✔
131
        return "No connection was found", 404
2✔
132
    return_values = {}
2✔
133
    for connection in values:
2✔
134
        service_id = next(iter(connection))
2✔
135
        logger.info(f"service_id: {service_id}")
2✔
136
        connection_status = get_connection_status(db_instance, service_id)
2✔
137
        if connection_status:
2✔
138
            return_values[service_id] = connection_status.get(service_id)
2✔
139
    return return_values
2✔
140

141

142
def get_archived_connections():
2✔
143
    """
144
    List all archived connections.
145

146
    :rtype: dict
147
    """
148
    values = db_instance.get_all_entries_in_collection(
2✔
149
        MongoCollections.HISTORICAL_CONNECTIONS
150
    )
151
    if not values:
2✔
152
        return "No archived connection was found", 404
2✔
153

154
    return_values = {}
2✔
155
    for archived_connection in values:
2✔
156
        service_id = next(iter(archived_connection))
2✔
157
        archived_events = connection_handler.get_archived_connections(service_id)
2✔
158
        if archived_events:
2✔
159
            return_values[service_id] = archived_events
2✔
160

161
    if not return_values:
2✔
162
        return "No archived connection was found", 404
×
163
    return return_values
2✔
164

165

166
def place_connection(body):
2✔
167
    """
168
    Place an connection request from the SDX-Controller.
169

170
    :param body: order placed for creating a connection
171
    :type body: dict | bytes
172

173
    :rtype: Connection
174
    """
175
    logger.info(f"Placing connection: {body}")
2✔
176
    if not connexion.request.is_json:
2✔
177
        return "Request body must be JSON", 400
×
178

179
    body = connexion.request.get_json()
2✔
180
    logger.info(f"Gathered connexion JSON: {body}")
2✔
181

182
    logger.info("Placing connection. Saving to database.")
2✔
183

184
    service_id = body.get("id")
2✔
185

186
    if service_id is None:
2✔
187
        service_id = str(uuid.uuid4())
2✔
188
        body["id"] = service_id
2✔
189
        logger.info(f"Request has no ID. Generated ID: {service_id}")
2✔
190

191
    body["status"] = str(ConnectionStateMachine.State.REQUESTED)
2✔
192

193
    # used in lc_message_handler to count the oxp success response
194
    body["oxp_success_count"] = 0
2✔
195

196
    conn_status = ConnectionStateMachine.State.UNDER_PROVISIONING
2✔
197
    body, _ = connection_state_machine(body, conn_status)
2✔
198

199
    db_instance.add_key_value_pair_to_db(MongoCollections.CONNECTIONS, service_id, body)
2✔
200

201
    logger.info(
2✔
202
        f"Handling request {service_id} with te_manager: {current_app.te_manager}"
203
    )
204
    reason, code = connection_handler.place_connection(current_app.te_manager, body)
2✔
205

206
    if code // 100 != 2:
2✔
207
        conn_status = ConnectionStateMachine.State.REJECTED
2✔
208
        db_instance.update_field_in_json(
2✔
209
            MongoCollections.CONNECTIONS,
210
            service_id,
211
            "status",
212
            str(conn_status),
213
        )
214
    logger.info(
2✔
215
        f"place_connection result: ID: {service_id} reason='{reason}', code={code}"
216
    )
217

218
    response = {
2✔
219
        "service_id": service_id,
220
        "status": parse_conn_status(str(conn_status)),
221
        "reason": reason,
222
    }
223

224
    # # TODO: our response is supposed to be shaped just like request
225
    # # ('#/components/schemas/connection'), and in that case the below
226
    # # code would be a quick implementation.
227
    # #
228
    # # https://github.com/atlanticwave-sdx/sdx-controller/issues/251
229
    # response = body
230

231
    # response["id"] = service_id
232
    # response["status"] = "success" if code == 2xx else "failure"
233
    # response["reason"] = reason # `reason` is not present in schema though.
234

235
    return response, code
2✔
236

237

238
def patch_connection(service_id, body=None):  # noqa: E501
2✔
239
    """Edit and change an existing L2vpn connection by ID from the SDX-Controller
240

241
     # noqa: E501
242

243
    :param service_id: ID of l2vpn connection that needs to be changed
244
    :type service_id: dict | bytes'
245
    :param body:
246
    :type body: dict | bytes
247

248
    :rtype: Connection
249
    """
250
    body = db_instance.get_value_from_db(MongoCollections.CONNECTIONS, f"{service_id}")
×
251
    if not body:
×
252
        return "Connection not found", 404
×
253

254
    if not connexion.request.is_json:
×
255
        return "Request body must be JSON", 400
×
256

257
    new_body = connexion.request.get_json()
×
258

259
    logger.info(f"Gathered connexion JSON: {new_body}")
×
260

UNCOV
261
    body.update(new_body)
×
262

263
    body, _ = connection_state_machine(body, ConnectionStateMachine.State.MODIFYING)
×
264

265
    body["oxp_success_count"] = 0
×
266

267
    db_instance.add_key_value_pair_to_db(MongoCollections.CONNECTIONS, service_id, body)
×
268

269
    try:
×
270
        logger.info("Removing connection")
×
271
        # Get roll back connection before removing connection
NEW
272
        rollback_conn_body = body
×
UNCOV
273
        remove_conn_reason, remove_conn_code = connection_handler.remove_connection(
×
274
            current_app.te_manager, service_id, "API"
275
        )
276

277
        if remove_conn_code // 100 != 2:
×
278
            body, _ = connection_state_machine(body, ConnectionStateMachine.State.DOWN)
×
279
            db_instance.add_key_value_pair_to_db(
×
280
                MongoCollections.CONNECTIONS, service_id, body
281
            )
282
            response = {
×
283
                "service_id": service_id,
284
                "status": parse_conn_status(body["status"]),
285
                "reason": f"Failure to modify L2VPN during removal: {remove_conn_reason}",
286
            }
287
            return response, remove_conn_code
×
288

289
        logger.info(f"Removed connection: {service_id}")
×
290
    except Exception as e:
×
291
        logger.info(f"Delete failed (connection id: {service_id}): {e}")
×
292
        return f"Failed, reason: {e}", 500
×
293

294
    logger.info(
×
295
        f"Placing new connection {service_id} with te_manager: {current_app.te_manager}"
296
    )
297

298
    body, _ = connection_state_machine(
×
299
        body, ConnectionStateMachine.State.UNDER_PROVISIONING
300
    )
301
    db_instance.add_key_value_pair_to_db(MongoCollections.CONNECTIONS, service_id, body)
×
302
    reason, code = connection_handler.place_connection(current_app.te_manager, body)
×
303

304
    if code // 100 == 2:
×
305
        # Service created successfully
306
        code = 201
×
307
        logger.info(f"Placed: ID: {service_id} reason='{reason}', code={code}")
×
308
        response = {
×
309
            "service_id": service_id,
310
            "status": parse_conn_status(body["status"]),
311
            "reason": reason,
312
        }
313
        return response, code
×
314
    else:
315
        body, _ = connection_state_machine(body, ConnectionStateMachine.State.DOWN)
×
316

317
    logger.info(
×
318
        f"Failed to place new connection. ID: {service_id} reason='{reason}', code={code}"
319
    )
320
    logger.info("Rolling back to old connection.")
×
321

NEW
322
    if not rollback_conn_body:
×
NEW
323
        response = {
×
324
            "service_id": service_id,
325
            "status": parse_conn_status(body["status"]),
326
            "reason": f"Failure, unable to rollback to last successful L2VPN: {reason}",
327
        }
NEW
328
        return response, code
×
329

330
    # because above placement failed, so re-place the original connection request.
331
    conn_request = rollback_conn_body
×
332
    conn_request["id"] = service_id
×
333

334
    try:
×
335
        rollback_conn_reason, rollback_conn_code = connection_handler.place_connection(
×
336
            current_app.te_manager, conn_request
337
        )
NEW
338
        if rollback_conn_code // 100 == 2:
×
NEW
339
            db_instance.add_key_value_pair_to_db(
×
340
                MongoCollections.CONNECTIONS, service_id, conn_request
341
            )
342
        logger.info(
×
343
            f"Roll back connection result: ID: {service_id} reason='{rollback_conn_reason}', code={rollback_conn_code}"
344
        )
345
    except Exception as e:
×
346
        logger.info(f"Rollback failed (connection id: {service_id}): {e}")
×
NEW
347
        return f"Rollback failed, reason: {e}", 500
×
348

349
    response = {
×
350
        "service_id": service_id,
351
        "reason": f"Failure, rolled back to last successful L2VPN: {reason}",
352
        "status": parse_conn_status(conn_request["status"]),
353
    }
NEW
354
    return response, code
×
355

356

357
def get_archived_connections_by_id(service_id):
2✔
358
    """
359
    List archived connection by ID.
360

361
    :param service_id: ID of connection that needs to be fetched
362
    :type service_id: str
363

364
    :rtype: Connection
365
    """
366

367
    value = connection_handler.get_archived_connections(service_id)
2✔
368

369
    if not value:
2✔
370
        return "Archived connection not found", 404
2✔
371

372
    return {service_id: value}
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc