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

atlanticwave-sdx / sdx-controller / 13702713197

06 Mar 2025 03:52PM UTC coverage: 56.869% (-0.2%) from 57.064%
13702713197

Pull #421

github

web-flow
Merge d1e28cb95 into 6ea197037
Pull Request #421: 420 aggregate issue for connection status formalization

26 of 51 new or added lines in 3 files covered. (50.98%)

4 existing lines in 3 files now uncovered.

1155 of 2031 relevant lines covered (56.87%)

1.14 hits per line

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

56.15
/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
)
16
from sdx_controller.models.l2vpn_service_id_body import L2vpnServiceIdBody  # noqa: E501
2✔
17
from sdx_controller.utils.db_utils import DbUtils
2✔
18

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

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

32

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

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

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

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

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

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

68
        logger.info(f"connection: {connection} {type(connection)}")
2✔
69
        if connection.get("status") == None:
2✔
70
            connection["status"] = str(ConnectionStateMachine.State.DELETED)
2✔
71
        else:
NEW
72
            connection, _ = connection_state_machine(
×
73
                connection, ConnectionStateMachine.State.DELETED
74
            )
75

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

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

85
    return "OK", 200
2✔
86

87

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

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

95
    :rtype: Connection
96
    """
97

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

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

103
    return value
2✔
104

105

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

109
    connection details # noqa: E501
110

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

125

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

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

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

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

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

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

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

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

153
    logger.info(
2✔
154
        f"Handling request {service_id} with te_manager: {current_app.te_manager}"
155
    )
156
    reason, code = connection_handler.place_connection(current_app.te_manager, body)
2✔
157

158
    if code // 100 == 2:
2✔
159
        body, _ = connection_state_machine(
2✔
160
            body, ConnectionStateMachine.State.UNDER_PROVISIONING
161
        )
162
    else:
163
        body, _ = connection_state_machine(body, ConnectionStateMachine.State.REJECTED)
2✔
164

165
    # used in lc_message_handler to count the oxp success response
166
    body["oxp_success_count"] = 0
2✔
167

168
    db_instance.add_key_value_pair_to_db(
2✔
169
        MongoCollections.CONNECTIONS, service_id, json.dumps(body)
170
    )
171
    logger.info(
2✔
172
        f"place_connection result: ID: {service_id} reason='{reason}', code={code}"
173
    )
174

175
    response = {
2✔
176
        "service_id": service_id,
177
        "status": body["status"],
178
        "reason": reason,
179
    }
180

181
    # # TODO: our response is supposed to be shaped just like request
182
    # # ('#/components/schemas/connection'), and in that case the below
183
    # # code would be a quick implementation.
184
    # #
185
    # # https://github.com/atlanticwave-sdx/sdx-controller/issues/251
186
    # response = body
187

188
    # response["id"] = service_id
189
    # response["status"] = "success" if code == 2xx else "failure"
190
    # response["reason"] = reason # `reason` is not present in schema though.
191

192
    return response, code
2✔
193

194

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

198
     # noqa: E501
199

200
    :param service_id: ID of l2vpn connection that needs to be changed
201
    :type service_id: dict | bytes'
202
    :param body:
203
    :type body: dict | bytes
204

205
    :rtype: Connection
206
    """
207
    value = db_instance.read_from_db(MongoCollections.CONNECTIONS, f"{service_id}")
×
208
    if not value:
×
209
        return "Connection not found", 404
×
210

211
    if not connexion.request.is_json:
×
212
        return "Request body must be JSON", 400
×
213

214
    body = L2vpnServiceIdBody.from_dict(connexion.request.get_json())  # noqa: E501
×
215

216
    logger.info(f"Gathered connexion JSON: {body}")
×
217

218
    body["id"] = service_id
×
219
    logger.info(f"Request has no ID. Generated ID: {service_id}")
×
220

NEW
221
    body, _ = connection_state_machine(body, ConnectionStateMachine.State.MODIFYING)
×
222
    try:
×
223
        logger.info("Removing connection")
×
224
        # Get roll back connection before removing connection
225
        rollback_conn_body = db_instance.read_from_db(
×
226
            MongoCollections.CONNECTIONS, service_id
227
        )
228
        remove_conn_reason, remove_conn_code = connection_handler.remove_connection(
×
229
            current_app.te_manager, service_id
230
        )
231

232
        if remove_conn_code // 100 != 2:
×
233
            response = {
×
234
                "service_id": service_id,
235
                "status": "Failure",
236
                "reason": remove_conn_reason,
237
            }
238
            return response, remove_conn_code
×
239

240
        logger.info(f"Removed connection: {service_id}")
×
241
    except Exception as e:
×
242
        logger.info(f"Delete failed (connection id: {service_id}): {e}")
×
243
        return f"Failed, reason: {e}", 500
×
244

245
    logger.info(
×
246
        f"Placing new connection {service_id} with te_manager: {current_app.te_manager}"
247
    )
248

249
    reason, code = connection_handler.place_connection(current_app.te_manager, body)
×
250

251
    if code // 100 == 2:
×
252
        db_instance.add_key_value_pair_to_db(
×
253
            MongoCollections.CONNECTIONS, service_id, json.dumps(body)
254
        )
255
        # Service created successfully
256
        code = 201
×
257
        logger.info(f"Placed: ID: {service_id} reason='{reason}', code={code}")
×
NEW
258
        body, _ = connection_state_machine(
×
259
            body, ConnectionStateMachine.State.UNDER_PROVISIONING
260
        )
UNCOV
261
        response = {
×
262
            "service_id": service_id,
263
            "status": body["status"],
264
            "reason": reason,
265
        }
266
        return response, code
×
267
    else:
NEW
268
        body, _ = connection_state_machine(body, ConnectionStateMachine.State.DOWN)
×
269

270
    logger.info(
×
271
        f"Failed to place new connection. ID: {service_id} reason='{reason}', code={code}"
272
    )
273
    logger.info("Rolling back to old connection.")
×
274

275
    if not rollback_conn_body:
×
276
        response = {
×
277
            "service_id": service_id,
278
            "status": "Failure, unable to rollback to last successful L2VPN connection",
279
            "reason": reason,
280
        }
281
        return response, code
×
282

283
    # because above placement failed, so re-place the original connection request.
284
    conn_request = json.loads(rollback_conn_body[service_id])
×
285
    conn_request["id"] = service_id
×
286

287
    try:
×
288
        rollback_conn_reason, rollback_conn_code = connection_handler.place_connection(
×
289
            current_app.te_manager, conn_request
290
        )
291
        if rollback_conn_code // 100 == 2:
×
292
            db_instance.add_key_value_pair_to_db(
×
293
                MongoCollections.CONNECTIONS, service_id, json.dumps(conn_request)
294
            )
295
        logger.info(
×
296
            f"Roll back connection result: ID: {service_id} reason='{rollback_conn_reason}', code={rollback_conn_code}"
297
        )
298
    except Exception as e:
×
299
        logger.info(f"Rollback failed (connection id: {service_id}): {e}")
×
300
        return f"Rollback failed, reason: {e}", 500
×
301

302
    response = {
×
303
        "service_id": service_id,
304
        "status": "Failure, rolled back to last successful L2VPN connection",
305
        "reason": reason,
306
    }
307
    return response, code
×
308

309

310
def get_archived_connections_by_id(service_id):
2✔
311
    """
312
    List archived connection by ID.
313

314
    :param service_id: ID of connection that needs to be fetched
315
    :type service_id: str
316

317
    :rtype: Connection
318
    """
319

320
    value = get_connection_status(db_instance, service_id)
×
321

322
    if not value:
×
323
        return "Connection not found", 404
×
324

325
    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