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

atlanticwave-sdx / sdx-controller / 14251495654

03 Apr 2025 07:48PM UTC coverage: 56.072%. First build
14251495654

Pull #445

github

web-flow
Merge 13ec994a4 into 218252c3b
Pull Request #445: Fix PATCH L2VPN

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

1159 of 2067 relevant lines covered (56.07%)

1.12 hits per line

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

56.3
/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") is None:
2✔
70
            connection["status"] = str(ConnectionStateMachine.State.DELETED)
2✔
71
        else:
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
    # used in lc_message_handler to count the oxp success response
154
    body["oxp_success_count"] = 0
2✔
155

156
    body, _ = connection_state_machine(
2✔
157
        body, ConnectionStateMachine.State.UNDER_PROVISIONING
158
    )
159

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

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

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

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

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

182
    response = {
2✔
183
        "service_id": service_id,
184
        "status": body["status"],
185
        "reason": reason,
186
    }
187

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

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

199
    return response, code
2✔
200

201

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

205
     # noqa: E501
206

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

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

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

NEW
221
    new_body = connexion.request.get_json()
×
222

NEW
223
    logger.info(f"Gathered connexion JSON: {new_body}")
×
224

NEW
225
    body = json.loads(value[service_id])
×
NEW
226
    body.update(new_body)
×
227

228
    body, _ = connection_state_machine(body, ConnectionStateMachine.State.MODIFYING)
×
NEW
229
    body["oxp_success_count"] = 0
×
NEW
230
    db_instance.add_key_value_pair_to_db(
×
231
        MongoCollections.CONNECTIONS, service_id, json.dumps(body)
232
    )
233

234
    try:
×
235
        logger.info("Removing connection")
×
236
        # Get roll back connection before removing connection
NEW
237
        rollback_conn_body = value
×
238
        remove_conn_reason, remove_conn_code = connection_handler.remove_connection(
×
239
            current_app.te_manager, service_id
240
        )
241

242
        if remove_conn_code // 100 != 2:
×
243
            response = {
×
244
                "service_id": service_id,
245
                "status": "Failure",
246
                "reason": remove_conn_reason,
247
            }
248
            return response, remove_conn_code
×
249

250
        logger.info(f"Removed connection: {service_id}")
×
251
    except Exception as e:
×
252
        logger.info(f"Delete failed (connection id: {service_id}): {e}")
×
253
        return f"Failed, reason: {e}", 500
×
254

255
    logger.info(
×
256
        f"Placing new connection {service_id} with te_manager: {current_app.te_manager}"
257
    )
258

NEW
259
    body, _ = connection_state_machine(
×
260
        body, ConnectionStateMachine.State.UNDER_PROVISIONING
261
    )
NEW
262
    db_instance.add_key_value_pair_to_db(
×
263
        MongoCollections.CONNECTIONS, service_id, json.dumps(body)
264
    )
265
    reason, code = connection_handler.place_connection(current_app.te_manager, body)
×
266

267
    if code // 100 == 2:
×
268
        # Service created successfully
269
        code = 201
×
270
        logger.info(f"Placed: ID: {service_id} reason='{reason}', code={code}")
×
271
        response = {
×
272
            "service_id": service_id,
273
            "status": body["status"],
274
            "reason": reason,
275
        }
276
        return response, code
×
277
    else:
278
        body, _ = connection_state_machine(body, ConnectionStateMachine.State.DOWN)
×
279

280
    logger.info(
×
281
        f"Failed to place new connection. ID: {service_id} reason='{reason}', code={code}"
282
    )
283
    logger.info("Rolling back to old connection.")
×
284

285
    if not rollback_conn_body:
×
286
        response = {
×
287
            "service_id": service_id,
288
            "status": "Failure, unable to rollback to last successful L2VPN connection",
289
            "reason": reason,
290
        }
291
        return response, code
×
292

293
    # because above placement failed, so re-place the original connection request.
294
    conn_request = json.loads(rollback_conn_body[service_id])
×
295
    conn_request["id"] = service_id
×
296

297
    try:
×
298
        rollback_conn_reason, rollback_conn_code = connection_handler.place_connection(
×
299
            current_app.te_manager, conn_request
300
        )
301
        if rollback_conn_code // 100 == 2:
×
302
            db_instance.add_key_value_pair_to_db(
×
303
                MongoCollections.CONNECTIONS, service_id, json.dumps(conn_request)
304
            )
305
        logger.info(
×
306
            f"Roll back connection result: ID: {service_id} reason='{rollback_conn_reason}', code={rollback_conn_code}"
307
        )
308
    except Exception as e:
×
309
        logger.info(f"Rollback failed (connection id: {service_id}): {e}")
×
310
        return f"Rollback failed, reason: {e}", 500
×
311

312
    response = {
×
313
        "service_id": service_id,
314
        "status": "Failure, rolled back to last successful L2VPN connection",
315
        "reason": reason,
316
    }
317
    return response, code
×
318

319

320
def get_archived_connections_by_id(service_id):
2✔
321
    """
322
    List archived connection by ID.
323

324
    :param service_id: ID of connection that needs to be fetched
325
    :type service_id: str
326

327
    :rtype: Connection
328
    """
329

330
    value = get_connection_status(db_instance, service_id)
×
331

332
    if not value:
×
333
        return "Connection not found", 404
×
334

335
    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