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

atlanticwave-sdx / sdx-controller / 23596626709

26 Mar 2026 01:23PM UTC coverage: 53.373% (-0.5%) from 53.895%
23596626709

Pull #516

github

web-flow
Merge 537ea66f4 into 043d77e9e
Pull Request #516: Oxp conn status change

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

100 existing lines in 2 files now uncovered.

1266 of 2372 relevant lines covered (53.37%)

1.07 hits per line

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

52.08
/sdx_controller/controllers/l2vpn_controller.py
1
import copy
2✔
2
import logging
2✔
3
import os
2✔
4
import uuid
2✔
5

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

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

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

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

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

34

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

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

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

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

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

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

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

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

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

101
    return "OK", 200
2✔
102

103

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

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

111
    :rtype: Connection
112
    """
113

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

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

119
    return value
2✔
120

121

122
def get_connections():  # noqa: E501
2✔
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 place_connection(body):
2✔
143
    """
144
    Place an connection request from the SDX-Controller.
145

146
    :param body: order placed for creating a connection
147
    :type body: dict | bytes
148

149
    :rtype: Connection
150
    """
151
    logger.info(f"Placing connection: {body}")
2✔
152
    if not connexion.request.is_json:
2✔
UNCOV
153
        return "Request body must be JSON", 400
×
154

155
    body = connexion.request.get_json()
2✔
156
    logger.info(f"Gathered connexion JSON: {body}")
2✔
157

158
    logger.info("Placing connection. Saving to database.")
2✔
159

160
    service_id = body.get("id")
2✔
161

162
    if service_id is None:
2✔
163
        service_id = str(uuid.uuid4())
2✔
164
        body["id"] = service_id
2✔
165
        logger.info(f"Request has no ID. Generated ID: {service_id}")
2✔
166

167
    body["status"] = str(ConnectionStateMachine.State.REQUESTED)
2✔
168

169
    # used in lc_message_handler to count the oxp success response
170
    body["oxp_success_count"] = 0
2✔
171

172
    conn_status = ConnectionStateMachine.State.UNDER_PROVISIONING
2✔
173
    body, _ = connection_state_machine(body, conn_status)
2✔
174

175
    db_instance.add_key_value_pair_to_db(MongoCollections.CONNECTIONS, service_id, body)
2✔
176

177
    logger.info(
2✔
178
        f"Handling request {service_id} with te_manager: {current_app.te_manager}"
179
    )
180
    reason, code = connection_handler.place_connection(current_app.te_manager, body)
2✔
181

182
    if code // 100 != 2:
2✔
183
        conn_status = ConnectionStateMachine.State.REJECTED
2✔
184
        db_instance.update_field_in_json(
2✔
185
            MongoCollections.CONNECTIONS,
186
            service_id,
187
            "status",
188
            str(conn_status),
189
        )
190
    logger.info(
2✔
191
        f"place_connection result: ID: {service_id} reason='{reason}', code={code}"
192
    )
193

194
    response = {
2✔
195
        "service_id": service_id,
196
        "status": parse_conn_status(str(conn_status)),
197
        "reason": reason,
198
    }
199

200
    # # TODO: our response is supposed to be shaped just like request
201
    # # ('#/components/schemas/connection'), and in that case the below
202
    # # code would be a quick implementation.
203
    # #
204
    # # https://github.com/atlanticwave-sdx/sdx-controller/issues/251
205
    # response = body
206

207
    # response["id"] = service_id
208
    # response["status"] = "success" if code == 2xx else "failure"
209
    # response["reason"] = reason # `reason` is not present in schema though.
210

211
    return response, code
2✔
212

213

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

217
     # noqa: E501
218

219
    :param service_id: ID of l2vpn connection that needs to be changed
220
    :type service_id: dict | bytes'
221
    :param body:
222
    :type body: dict | bytes
223

224
    :rtype: Connection
225
    """
226
    body = db_instance.get_value_from_db(MongoCollections.CONNECTIONS, f"{service_id}")
×
227
    if not body:
×
UNCOV
228
        return "Connection not found", 404
×
229

230
    if not connexion.request.is_json:
×
UNCOV
231
        return "Request body must be JSON", 400
×
232

UNCOV
233
    new_body = connexion.request.get_json()
×
234

UNCOV
235
    logger.info(f"Gathered connexion JSON: {new_body}")
×
236

237
    # Get roll back connection before removing connection
238
    rollback_conn_body = copy.deepcopy(body)
×
UNCOV
239
    body.update(new_body)
×
240

UNCOV
241
    body, _ = connection_state_machine(body, ConnectionStateMachine.State.MODIFYING)
×
242

UNCOV
243
    body["oxp_success_count"] = 0
×
244

245
    db_instance.add_key_value_pair_to_db(MongoCollections.CONNECTIONS, service_id, body)
×
246

247
    try:
×
248
        logger.info("Removing connection")
×
UNCOV
249
        remove_conn_reason, remove_conn_code = connection_handler.remove_connection(
×
250
            current_app.te_manager, service_id, "API"
251
        )
252

253
        if remove_conn_code // 100 != 2:
×
254
            body, _ = connection_state_machine(body, ConnectionStateMachine.State.DOWN)
×
UNCOV
255
            db_instance.add_key_value_pair_to_db(
×
256
                MongoCollections.CONNECTIONS, service_id, body
257
            )
UNCOV
258
            response = {
×
259
                "service_id": service_id,
260
                "status": parse_conn_status(body["status"]),
261
                "reason": f"Failure to modify L2VPN during removal: {remove_conn_reason}",
262
            }
UNCOV
263
            return response, remove_conn_code
×
264

265
        logger.info(f"Removed connection: {service_id}")
×
266
    except Exception as e:
×
267
        logger.info(f"Delete failed (connection id: {service_id}): {e}")
×
UNCOV
268
        return f"Failed, reason: {e}", 500
×
269

UNCOV
270
    logger.info(
×
271
        f"Placing new connection {service_id} with te_manager: {current_app.te_manager}"
272
    )
273

UNCOV
274
    body, _ = connection_state_machine(
×
275
        body, ConnectionStateMachine.State.UNDER_PROVISIONING
276
    )
277
    db_instance.add_key_value_pair_to_db(MongoCollections.CONNECTIONS, service_id, body)
×
UNCOV
278
    reason, code = connection_handler.place_connection(current_app.te_manager, body)
×
279

UNCOV
280
    if code // 100 == 2:
×
281
        # Service created successfully
282
        code = 201
×
283
        logger.info(f"Placed: ID: {service_id} reason='{reason}', code={code}")
×
UNCOV
284
        response = {
×
285
            "service_id": service_id,
286
            "status": parse_conn_status(body["status"]),
287
            "reason": reason,
288
        }
UNCOV
289
        return response, code
×
290
    else:
UNCOV
291
        body, _ = connection_state_machine(body, ConnectionStateMachine.State.DOWN)
×
292

UNCOV
293
    logger.info(
×
294
        f"Failed to place new connection. ID: {service_id} reason='{reason}', code={code}"
295
    )
UNCOV
296
    logger.info("Rolling back to old connection.")
×
297

298
    # because above placement failed, so re-place the original connection request.
299

UNCOV
300
    rollback_conn_body["status"] = str(ConnectionStateMachine.State.REQUESTED)
×
301
    # used in lc_message_handler to count the oxp success response
UNCOV
302
    rollback_conn_body["oxp_success_count"] = 0
×
303
    conn_status = ConnectionStateMachine.State.UNDER_PROVISIONING
×
UNCOV
304
    rollback_conn_body, _ = connection_state_machine(rollback_conn_body, conn_status)
×
305

306
    conn_request = rollback_conn_body
×
307
    conn_request["id"] = service_id
×
308

309
    try:
×
310
        rollback_conn_reason, rollback_conn_code = connection_handler.place_connection(
×
311
            current_app.te_manager, conn_request
312
        )
313
        if rollback_conn_code // 100 != 2:
×
314
            conn_status = ConnectionStateMachine.State.REJECTED
×
UNCOV
315
            db_instance.update_field_in_json(
×
316
                MongoCollections.CONNECTIONS,
317
                service_id,
318
                "status",
319
                str(conn_status),
320
            )
321
        logger.info(
×
322
            f"Roll back connection result: ID: {service_id} reason='{rollback_conn_reason}', code={rollback_conn_code}"
323
        )
324
    except Exception as e:
×
UNCOV
325
        conn_status = ConnectionStateMachine.State.REJECTED
×
UNCOV
326
        db_instance.update_field_in_json(
×
327
            MongoCollections.CONNECTIONS,
328
            service_id,
329
            "status",
330
            str(conn_status),
331
        )
UNCOV
332
        logger.info(f"Rollback failed (connection id: {service_id}): {e}")
×
UNCOV
333
        rollback_conn_code = 500
×
334

UNCOV
335
    response = {
×
336
        "service_id": service_id,
337
        "reason": f"Patch Failure,rolled back to last successful L2VPN: {rollback_conn_reason}",
338
        "status": parse_conn_status(str(conn_status)),
339
    }
UNCOV
340
    return response, rollback_conn_code
×
341

342

343
def get_archived_connections_by_id(service_id):
2✔
344
    """
345
    List archived connection by ID.
346

347
    :param service_id: ID of connection that needs to be fetched
348
    :type service_id: str
349

350
    :rtype: Connection
351
    """
352

UNCOV
353
    value = get_connection_status(db_instance, service_id)
×
354

UNCOV
355
    if not value:
×
UNCOV
356
        return "Connection not found", 404
×
357

UNCOV
358
    return value
×
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