Skip to content

Commit

Permalink
Merge pull request #297 from atlanticwave-sdx/296.connection-request-…
Browse files Browse the repository at this point in the history
…v2-spec

Use connection request v2 spec
  • Loading branch information
YufengXin authored Jul 10, 2024
2 parents a0e394c + 4185b10 commit 2b2775a
Show file tree
Hide file tree
Showing 5 changed files with 258 additions and 6 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ dependencies = [
"pika >= 1.2.0",
"dataset",
"pymongo > 3.0",
"sdx-pce @ git+https://github.com/atlanticwave-sdx/[email protected].rc1",
"sdx-pce @ git+https://github.com/atlanticwave-sdx/[email protected].rc2",
]

[project.optional-dependencies]
Expand Down
8 changes: 7 additions & 1 deletion sdx_controller/controllers/connection_controller.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
import logging
import uuid

import connexion
from flask import current_app
Expand Down Expand Up @@ -111,7 +112,12 @@ def place_connection(body):

logger.info("Placing connection. Saving to database.")

connection_id = body["id"]
connection_id = body.get("id")

if connection_id is None:
connection_id = str(uuid.uuid4())
body["id"] = connection_id
logger.info(f"Request has no ID. Generated ID: {connection_id}")

db_instance.add_key_value_pair_to_db("connections", connection_id, json.dumps(body))
logger.info("Saving to database complete.")
Expand Down
153 changes: 152 additions & 1 deletion sdx_controller/swagger/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,9 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/connection'
oneOf:
- $ref: '#/components/schemas/connection'
- $ref: '#/components/schemas/connection_v2'
required: true
responses:
"200":
Expand Down Expand Up @@ -482,6 +484,155 @@ components:
version: 1
xml:
name: Connection
connection_v2:
required:
- description
- endpoints
- name
- notifications
- qos_metrics
- scheduling
type: object
properties:
name:
type: string
endpoints:
type: array
items:
type: object
properties:
port_id:
type: string
vlan:
type: string
description:
type: string
notifications:
type: array
items:
type: object
properties:
email:
type: string
scheduling:
$ref: '#/components/schemas/connection_scheduling'
qos_metrics:
type: object
additionalProperties:
$ref: '#/components/schemas/connection_qos_metrics'
paths:
type: array
items:
type: string
status:
type: string
description: Connection Status
enum:
- success
- fail
- scheduled
- provisioining
complete:
type: boolean
default: false
quantity:
type: integer
format: int32
multi_path:
type: boolean
preempt:
type: boolean
backup_path_type:
type: string
enum:
- "0"
- "1"
- "2"
- "3"
exclusive_links:
type: array
items:
$ref: '#/components/schemas/link'
inclusive_links:
type: array
items:
$ref: '#/components/schemas/link'
example:
name: new-connection
endpoints:
- port_id: urn:sdx:port:ampath.net:Ampath3:50
vlan: "777"
- port_id: urn:sdx:port:sax.net:Sax01:41
vlan: 55:90
- port_id: urn:sdx:port:ampath.net:Ampath3:50
vlan: untagged
description: a test circuit
scheduling:
start_time: 2024-06-24T01:00:00.000Z
end_time: 2024-06-26T01:00:00.000Z
qos_metrics:
min_bw:
value: 12
strict: true
max_delay:
value: 4
strict: false
max_number_oxps:
value: 7
strict: true
notifications:
- email: [email protected]
xml:
name: Connection
connection_scheduling:
type: object
properties:
start_time:
type: string
format: date-time
end_time:
type: string
format: date-time
connection_qos_unit:
type: object
properties:
value:
type: integer
strict:
type: boolean
connection_qos_metrics:
type: object
properties:
min_bw:
$ref: '#/components/schemas/connection_qos_unit'
max_delay:
$ref: '#/components/schemas/connection_qos_unit'
max_number_oxps:
$ref: '#/components/schemas/connection_qos_unit'
bandwidth_measured:
maximum: 1000000
minimum: 1
type: number
latency_measured:
maximum: 1000000
minimum: 1
type: number
packetloss_required:
maximum: 100
minimum: 0
type: number
packetloss_measured:
maximum: 100
minimum: 0
type: number
availability_required:
maximum: 100
minimum: 0
type: number
availability_measured:
maximum: 100
minimum: 0
type: number
User:
type: object
properties:
Expand Down
5 changes: 5 additions & 0 deletions sdx_controller/test/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ class TestData:
TOPOLOGY_FILE_ZAOXI = TOPOLOGY_DIR / "zaoxi.json"
TOPOLOGY_FILE_SAX = TOPOLOGY_DIR / "sax.json"
TOPOLOGY_FILE_AMLIGHT = TOPOLOGY_DIR / "amlight.json"
TOPOLOGY_FILE_AMLIGHT_USER_PORT = TOPOLOGY_DIR / "amlight_user_port.json"

REQUESTS_DIR = files("sdx_datamodel") / "data" / "requests"
CONNECTION_REQ = REQUESTS_DIR / "test_request.json"
CONNECTION_REQ_V2_L2VPN_P2P = REQUESTS_DIR / "test-l2vpn-p2p-v2.json"
CONNECTION_REQ_V2_AMLIGHT_ZAOXI = (
REQUESTS_DIR / "test_request-amlight_zaoxi-p2p-v2.json"
)
96 changes: 93 additions & 3 deletions sdx_controller/test/test_connection_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,13 @@ def __add_the_three_topologies(self):
"""
for idx, topology_file in enumerate(
[
TestData.TOPOLOGY_FILE_AMLIGHT,
TestData.TOPOLOGY_FILE_AMLIGHT_USER_PORT,
TestData.TOPOLOGY_FILE_SAX,
TestData.TOPOLOGY_FILE_ZAOXI,
]
):
topology = json.loads(topology_file.read_text())
print(f"Adding topology: {topology.get('id')}")
self.te_manager.add_topology(topology)

def test_delete_connection_with_setup(self):
Expand Down Expand Up @@ -194,7 +195,7 @@ def test_place_connection_no_id(self):

response = response.get_json()
self.assertEqual(response["status"], 400)
self.assertEqual(response["detail"], "'id' is a required property")
self.assertIn("is not valid under any of the given schemas", response["detail"])

def test_place_connection_with_three_topologies(self):
"""
Expand Down Expand Up @@ -257,6 +258,95 @@ def test_place_connection_with_three_topologies_added_in_sequence(self):
# up with all the expected topology data.
self.assertStatus(response, 200)

def test_place_connection_v2_with_three_topologies_400_response(self):
"""
Test case for connection request format v2.
"""
self.__add_the_three_topologies()

# No solution for this request.
request = TestData.CONNECTION_REQ_V2_L2VPN_P2P.read_text()

# The example connection request ("test-l2vpn-p2p-v2.json")
# carries an ID field for testing purposes, but the actual v2
# format does not have an ID field. So we remove the ID from
# the request.
request_json = json.loads(request)
original_request_id = request_json.pop("id")
print(f"original_request_id: {original_request_id}")

new_request = json.dumps(request_json)
print(f"new_request: {new_request}")

response = self.client.open(
f"{BASE_PATH}/connection",
method="POST",
data=new_request,
content_type="application/json",
)

print(f"Response body is : {response.data.decode('utf-8')}")

# Expect a 400 response because PCE would not be able to find
# a solution for the connection request.
self.assertStatus(response, 400)
self.assertEqual(
response.get_json().get("status"),
"Failure",
)
self.assertEqual(
response.get_json().get("reason"), "Could not generate a traffic matrix"
)

# Returned connection ID should be different from the original
# request ID.
connection_id = response.get_json().get("connection_id")
self.assertNotEqual(connection_id, original_request_id)

def test_place_connection_v2_with_three_topologies_200_response(self):
"""
Test case for connection request format v2. This request
should be able to find a path.
"""
self.__add_the_three_topologies()

# There should be solution for this request.
request = TestData.CONNECTION_REQ_V2_AMLIGHT_ZAOXI.read_text()

# Remove any existing request ID.
request_json = json.loads(request)
original_request_id = request_json.pop("id")
print(f"original_request_id: {original_request_id}")

new_request = json.dumps(request_json)
print(f"new_request: {new_request}")

response = self.client.open(
f"{BASE_PATH}/connection",
method="POST",
data=new_request,
content_type="application/json",
)

print(f"Response body is : {response.data.decode('utf-8')}")

# Expect a 200 response because PCE should be able to find a
# solution for the connection request.
self.assertStatus(response, 200)
self.assertEqual(
response.get_json().get("status"),
"OK",
)
self.assertEqual(
response.get_json().get("reason"),
"Connection published",
)

# Returned connection ID should be different from the original
# request ID.
connection_id = response.get_json().get("connection_id")
self.assertNotEqual(connection_id, original_request_id)

def test_z100_getconnection_by_id_expect_404(self):
"""
Test getconnection_by_id with a non-existent connection ID.
Expand Down Expand Up @@ -325,7 +415,7 @@ def test_z105_getconnections_success(self):
print(f"Response body is : {response.data.decode('utf-8')}")
self.assertStatus(response, 200)

assert len(response.json) == 1
assert len(response.get_json()) != 0


if __name__ == "__main__":
Expand Down

0 comments on commit 2b2775a

Please sign in to comment.