diff --git a/sdx_controller/controllers/connection_controller.py b/sdx_controller/controllers/connection_controller.py index f096dd99..b7b3d889 100644 --- a/sdx_controller/controllers/connection_controller.py +++ b/sdx_controller/controllers/connection_controller.py @@ -1,6 +1,5 @@ import json import logging -import uuid import connexion from flask import current_app @@ -32,7 +31,30 @@ def delete_connection(connection_id): :rtype: None """ - return "do some magic!" + logger.info( + f"Handling delete (connecton id: {connection_id}) " + f"with te_manager: {current_app.te_manager}" + ) + + # # Looking up by UUID do not seem work yet. Will address in + # # https://github.com/atlanticwave-sdx/sdx-controller/issues/252. + # + # value = db_instance.read_from_db(f"{connection_id}") + # print(f"value: {value}") + # if not value: + # return "Not found", 404 + + try: + # TODO: pce's unreserve_vlan() method silently returns even if the + # connection_id is not found. This should in fact be an error. + # + # https://github.com/atlanticwave-sdx/pce/issues/180 + current_app.te_manager.unreserve_vlan(connection_id) + except Exception as e: + logger.info(f"Delete failed (connection id: {connection_id}): {e}") + return "Failed, reason: {e}", 500 + + return "OK", 200 def getconnection_by_id(connection_id): @@ -66,18 +88,35 @@ def place_connection(body): logger.info("Placing connection. Saving to database.") - if "id" in body: - connection_id = body["id"] - else: - connection_id = uuid.uuid4() - body["id"] = connection_id + connection_id = body["id"] db_instance.add_key_value_pair_to_db(connection_id, json.dumps(body)) logger.info("Saving to database complete.") - logger.info(f"Handling request with te_manager: {current_app.te_manager}") + logger.info( + f"Handling request {connection_id} with te_manager: {current_app.te_manager}" + ) reason, code = connection_handler.place_connection(current_app.te_manager, body) - logger.info(f"place_connection result: reason='{reason}', code={code}") - - return reason, code + logger.info( + f"place_connection result: ID: {connection_id} reason='{reason}', code={code}" + ) + + response = { + "connection_id": connection_id, + "status": "OK" if code == 200 else "Failure", + "reason": reason, + } + + # # TODO: our response is supposed to be shaped just like request + # # ('#/components/schemas/connection'), and in that case the below + # # code would be a quick implementation. + # # + # # https://github.com/atlanticwave-sdx/sdx-controller/issues/251 + # response = body + + # response["id"] = connection_id + # response["status"] = "success" if code == 200 else "failure" + # response["reason"] = reason # `reason` is not present in schema though. + + return response, code diff --git a/sdx_controller/swagger/swagger.yaml b/sdx_controller/swagger/swagger.yaml index a0b8283b..c50788ad 100644 --- a/sdx_controller/swagger/swagger.yaml +++ b/sdx_controller/swagger/swagger.yaml @@ -172,10 +172,8 @@ paths: style: simple explode: false schema: - maximum: 10 - minimum: 1 - type: integer - format: int64 + type: string + format: uuid responses: "200": description: successful operation @@ -205,10 +203,11 @@ paths: style: simple explode: false schema: - minimum: 1 - type: integer - format: int64 + type: string + format: uuid responses: + "200": + description: successful operation "400": description: Invalid ID supplied "404": diff --git a/sdx_controller/test/test_connection_controller.py b/sdx_controller/test/test_connection_controller.py index d4ad8952..d47bc1bb 100644 --- a/sdx_controller/test/test_connection_controller.py +++ b/sdx_controller/test/test_connection_controller.py @@ -15,9 +15,9 @@ class TestConnectionController(BaseTestCase): """ConnectionController integration test stubs""" - def test_delete_connection(self): + def test_delete_connection_no_setup(self): """ - Test case for delete_connection. + Test case for delete_connection(). Delete connection order by ID. """ @@ -28,6 +28,50 @@ def test_delete_connection(self): ) self.assert200(response, f"Response body is : {response.data.decode('utf-8')}") + def test_delete_connection_with_setup(self): + """ + Test case for delete_connection() + + Set up a connection request, get the connection ID from the + response, and then do `DELETE /connection/:connection_id` + """ + # set up temanager connection first + for idx, topology_file in enumerate( + [ + TestData.TOPOLOGY_FILE_AMLIGHT, + TestData.TOPOLOGY_FILE_SAX, + TestData.TOPOLOGY_FILE_ZAOXI, + ] + ): + topology = json.loads(topology_file.read_text()) + self.te_manager.add_topology(topology) + + request_body = TestData.CONNECTION_REQ.read_text() + + connection_response = self.client.open( + f"{BASE_PATH}/connection", + method="POST", + data=request_body, + content_type="application/json", + ) + + print(f"Response body: {connection_response.data.decode('utf-8')}") + + self.assertStatus(connection_response, 200) + + connection_id = connection_response.get_json().get("connection_id") + print(f"Deleting request_id: {connection_id}") + + delete_response = self.client.open( + f"{BASE_PATH}/connection/{connection_id}", + method="DELETE", + ) + + self.assert200( + delete_response, + f"Response body is : {delete_response.data.decode('utf-8')}", + ) + def test_getconnection_by_id(self): """ Test case for getconnection_by_id. @@ -106,6 +150,44 @@ def test_place_connection_with_zaoxi(self): """ self.__test_with_one_topology(TestData.TOPOLOGY_FILE_ZAOXI) + def test_place_connection_no_id(self): + """ + Test place_connection() with a request that has no ID field. + """ + # Remove ID + request = json.loads(TestData.CONNECTION_REQ.read_text()) + request.pop("id") + request = json.dumps(request) + + print(f"request: {request} {type(request)}") + + response = self.client.open( + f"{BASE_PATH}/connection", + method="POST", + data=request, + content_type="application/json", + ) + + print(f"response: {response}") + print(f"Response body is : {response.data.decode('utf-8')}") + + # Expect a 400 response because the required ID field is + # missing from the request. + self.assertStatus(response, 400) + + # JSON response should have a body like: + # + # { + # "detail": "'id' is a required property", + # "status": 400, + # "title": "Bad Request", + # "type": "about:blank" + # } + + response = response.get_json() + self.assertEqual(response["status"], 400) + self.assertEqual(response["detail"], "'id' is a required property") + def test_place_connection_with_three_topologies(self): """ Test case for place_connection. @@ -139,7 +221,9 @@ def test_place_connection_with_three_topologies_added_in_sequence(self): """ Test case for place_connection. - Place the same connection request while adding topologies. + Keep placing the same connection request while adding + topologies. The first few requests should fail, and the final + one eventually succeed. """ for idx, topology_file in enumerate( [