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

atlanticwave-sdx / sdx-controller / 14564879303

21 Apr 2025 12:38AM UTC coverage: 56.139% (-0.006%) from 56.145%
14564879303

Pull #453

github

web-flow
Merge 07fd356d2 into 0e62a4c6d
Pull Request #453: Assign oxp_success_count to 0 only if it does not exist

2 of 4 new or added lines in 1 file covered. (50.0%)

1 existing line in 1 file now uncovered.

1166 of 2077 relevant lines covered (56.14%)

1.12 hits per line

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

55.4
/sdx_controller/controllers/l2vpn_controller.py
1
import json
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
from sdx_controller.models.l2vpn_service_id_body import L2vpnServiceIdBody  # noqa: E501
2✔
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.read_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
            connection["status"] = str(ConnectionStateMachine.State.DELETED)
2✔
72
        else:
73
            connection, _ = connection_state_machine(
×
74
                connection, ConnectionStateMachine.State.DELETED
75
            )
76

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

79
        connection_handler.remove_connection(current_app.te_manager, service_id)
2✔
80
        db_instance.mark_deleted(MongoCollections.CONNECTIONS, f"{service_id}")
2✔
81
        db_instance.mark_deleted(MongoCollections.BREAKDOWNS, f"{service_id}")
2✔
82
    except Exception as e:
×
83
        logger.info(f"Delete failed (connection id: {service_id}): {e}")
×
84
        return f"Failed, reason: {e}", 500
×
85

86
    return "OK", 200
2✔
87

88

89
def get_connection_by_id(service_id):
2✔
90
    """
91
    Find connection by ID.
92

93
    :param service_id: ID of connection that needs to be fetched
94
    :type service_id: str
95

96
    :rtype: Connection
97
    """
98

99
    value = get_connection_status(db_instance, service_id)
2✔
100

101
    if not value:
2✔
102
        return "Connection not found", 404
2✔
103

104
    return value
2✔
105

106

107
def get_connections():  # noqa: E501
2✔
108
    """List all connections
109

110
    connection details # noqa: E501
111

112
    :rtype: Connection
113
    """
114
    values = db_instance.get_all_entries_in_collection(MongoCollections.CONNECTIONS)
2✔
115
    if not values:
2✔
116
        return "No connection was found", 404
2✔
117
    return_values = {}
2✔
118
    for connection in values:
2✔
119
        service_id = next(iter(connection))
2✔
120
        logger.info(f"service_id: {service_id}")
2✔
121
        connection_status = get_connection_status(db_instance, service_id)
2✔
122
        if connection_status:
2✔
123
            return_values[service_id] = connection_status.get(service_id)
2✔
124
    return return_values
2✔
125

126

127
def place_connection(body):
2✔
128
    """
129
    Place an connection request from the SDX-Controller.
130

131
    :param body: order placed for creating a connection
132
    :type body: dict | bytes
133

134
    :rtype: Connection
135
    """
136
    logger.info(f"Placing connection: {body}")
2✔
137
    if not connexion.request.is_json:
2✔
138
        return "Request body must be JSON", 400
×
139

140
    body = connexion.request.get_json()
2✔
141
    logger.info(f"Gathered connexion JSON: {body}")
2✔
142

143
    logger.info("Placing connection. Saving to database.")
2✔
144

145
    service_id = body.get("id")
2✔
146

147
    if service_id is None:
2✔
148
        service_id = str(uuid.uuid4())
2✔
149
        body["id"] = service_id
2✔
150
        logger.info(f"Request has no ID. Generated ID: {service_id}")
2✔
151

152
    body["status"] = str(ConnectionStateMachine.State.REQUESTED)
2✔
153

154
    # used in lc_message_handler to count the oxp success response
155
    if "oxp_success_count" not in body:
2✔
156
        body["oxp_success_count"] = 0
2✔
157

158
    body, _ = connection_state_machine(
2✔
159
        body, ConnectionStateMachine.State.UNDER_PROVISIONING
160
    )
161

162
    db_instance.add_key_value_pair_to_db(
2✔
163
        MongoCollections.CONNECTIONS, service_id, json.dumps(body)
164
    )
165

166
    logger.info(
2✔
167
        f"Handling request {service_id} with te_manager: {current_app.te_manager}"
168
    )
169
    reason, code = connection_handler.place_connection(current_app.te_manager, body)
2✔
170

171
    value = db_instance.read_from_db(MongoCollections.CONNECTIONS, service_id)
2✔
172
    body = json.loads(value[service_id]) if value else body
2✔
173

174
    if code // 100 != 2:
2✔
175
        body["status"] = str(ConnectionStateMachine.State.REJECTED)
2✔
176

177
    db_instance.add_key_value_pair_to_db(
2✔
178
        MongoCollections.CONNECTIONS, service_id, json.dumps(body)
179
    )
180
    logger.info(
2✔
181
        f"place_connection result: ID: {service_id} reason='{reason}', code={code}"
182
    )
183

184
    response = {
2✔
185
        "service_id": service_id,
186
        "status": parse_conn_status(body["status"]),
187
        "reason": reason,
188
    }
189

190
    # # TODO: our response is supposed to be shaped just like request
191
    # # ('#/components/schemas/connection'), and in that case the below
192
    # # code would be a quick implementation.
193
    # #
194
    # # https://github.com/atlanticwave-sdx/sdx-controller/issues/251
195
    # response = body
196

197
    # response["id"] = service_id
198
    # response["status"] = "success" if code == 2xx else "failure"
199
    # response["reason"] = reason # `reason` is not present in schema though.
200

201
    return response, code
2✔
202

203

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

207
     # noqa: E501
208

209
    :param service_id: ID of l2vpn connection that needs to be changed
210
    :type service_id: dict | bytes'
211
    :param body:
212
    :type body: dict | bytes
213

214
    :rtype: Connection
215
    """
216
    value = db_instance.read_from_db(MongoCollections.CONNECTIONS, f"{service_id}")
×
217
    if not value:
×
218
        return "Connection not found", 404
×
219

220
    if not connexion.request.is_json:
×
221
        return "Request body must be JSON", 400
×
222

223
    new_body = connexion.request.get_json()
×
224

225
    logger.info(f"Gathered connexion JSON: {new_body}")
×
226

227
    body = json.loads(value[service_id])
×
228
    body.update(new_body)
×
229

230
    body, _ = connection_state_machine(body, ConnectionStateMachine.State.MODIFYING)
×
231

NEW
232
    if "oxp_success_count" not in body:
×
NEW
233
        body["oxp_success_count"] = 0
×
234

UNCOV
235
    db_instance.add_key_value_pair_to_db(
×
236
        MongoCollections.CONNECTIONS, service_id, json.dumps(body)
237
    )
238

239
    try:
×
240
        logger.info("Removing connection")
×
241
        # Get roll back connection before removing connection
242
        rollback_conn_body = value
×
243
        remove_conn_reason, remove_conn_code = connection_handler.remove_connection(
×
244
            current_app.te_manager, service_id
245
        )
246

247
        if remove_conn_code // 100 != 2:
×
248
            body, _ = connection_state_machine(body, ConnectionStateMachine.State.DOWN)
×
249
            db_instance.add_key_value_pair_to_db(
×
250
                MongoCollections.CONNECTIONS, service_id, json.dumps(body)
251
            )
252
            response = {
×
253
                "service_id": service_id,
254
                "status": parse_conn_status(body["status"]),
255
                "reason": f"Failure to modify L2VPN during removal: {remove_conn_reason}",
256
            }
257
            return response, remove_conn_code
×
258

259
        logger.info(f"Removed connection: {service_id}")
×
260
    except Exception as e:
×
261
        logger.info(f"Delete failed (connection id: {service_id}): {e}")
×
262
        return f"Failed, reason: {e}", 500
×
263

264
    logger.info(
×
265
        f"Placing new connection {service_id} with te_manager: {current_app.te_manager}"
266
    )
267

268
    body, _ = connection_state_machine(
×
269
        body, ConnectionStateMachine.State.UNDER_PROVISIONING
270
    )
271
    db_instance.add_key_value_pair_to_db(
×
272
        MongoCollections.CONNECTIONS, service_id, json.dumps(body)
273
    )
274
    reason, code = connection_handler.place_connection(current_app.te_manager, body)
×
275

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

289
    logger.info(
×
290
        f"Failed to place new connection. ID: {service_id} reason='{reason}', code={code}"
291
    )
292
    logger.info("Rolling back to old connection.")
×
293

294
    if not rollback_conn_body:
×
295
        response = {
×
296
            "service_id": service_id,
297
            "status": parse_conn_status(body["status"]),
298
            "reason": f"Failure, unable to rollback to last successful L2VPN: {reason}",
299
        }
300
        return response, code
×
301

302
    # because above placement failed, so re-place the original connection request.
303
    conn_request = json.loads(rollback_conn_body[service_id])
×
304
    conn_request["id"] = service_id
×
305

306
    try:
×
307
        rollback_conn_reason, rollback_conn_code = connection_handler.place_connection(
×
308
            current_app.te_manager, conn_request
309
        )
310
        if rollback_conn_code // 100 == 2:
×
311
            db_instance.add_key_value_pair_to_db(
×
312
                MongoCollections.CONNECTIONS, service_id, json.dumps(conn_request)
313
            )
314
        logger.info(
×
315
            f"Roll back connection result: ID: {service_id} reason='{rollback_conn_reason}', code={rollback_conn_code}"
316
        )
317
    except Exception as e:
×
318
        logger.info(f"Rollback failed (connection id: {service_id}): {e}")
×
319
        return f"Rollback failed, reason: {e}", 500
×
320

321
    response = {
×
322
        "service_id": service_id,
323
        "reason": f"Failure, rolled back to last successful L2VPN: {reason}",
324
        "status": parse_conn_status(conn_request["status"]),
325
    }
326
    return response, code
×
327

328

329
def get_archived_connections_by_id(service_id):
2✔
330
    """
331
    List archived connection by ID.
332

333
    :param service_id: ID of connection that needs to be fetched
334
    :type service_id: str
335

336
    :rtype: Connection
337
    """
338

339
    value = get_connection_status(db_instance, service_id)
×
340

341
    if not value:
×
342
        return "Connection not found", 404
×
343

344
    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