From b9641af2dcd207a7104c93a9f643748d70125afd Mon Sep 17 00:00:00 2001 From: Yufeng Xin Date: Mon, 19 Aug 2024 12:28:12 -0400 Subject: [PATCH 01/13] spec v2:connection->l2vpn --- ...tion_controller.py => l2vpn_controller.py} | 2 +- .../models/connection_qos_metrics.py | 271 +++++++++++ sdx_controller/models/connection_qos_unit.py | 88 ++++ .../models/connection_scheduling.py | 88 ++++ sdx_controller/models/connection_v2.py | 449 ++++++++++++++++++ .../models/connection_v2_endpoints.py | 88 ++++ .../models/connection_v2_notifications.py | 62 +++ sdx_controller/models/l2vpn_body.py | 36 ++ sdx_controller/swagger/swagger.yaml | 177 +++---- .../test/test_connection_controller.py | 422 ---------------- sdx_controller/test/test_l2vpn_controller.py | 66 +++ 11 files changed, 1240 insertions(+), 509 deletions(-) rename sdx_controller/controllers/{connection_controller.py => l2vpn_controller.py} (98%) create mode 100644 sdx_controller/models/connection_qos_metrics.py create mode 100644 sdx_controller/models/connection_qos_unit.py create mode 100644 sdx_controller/models/connection_scheduling.py create mode 100644 sdx_controller/models/connection_v2.py create mode 100644 sdx_controller/models/connection_v2_endpoints.py create mode 100644 sdx_controller/models/connection_v2_notifications.py create mode 100644 sdx_controller/models/l2vpn_body.py delete mode 100644 sdx_controller/test/test_connection_controller.py create mode 100644 sdx_controller/test/test_l2vpn_controller.py diff --git a/sdx_controller/controllers/connection_controller.py b/sdx_controller/controllers/l2vpn_controller.py similarity index 98% rename from sdx_controller/controllers/connection_controller.py rename to sdx_controller/controllers/l2vpn_controller.py index e453ac0..f64f56f 100644 --- a/sdx_controller/controllers/connection_controller.py +++ b/sdx_controller/controllers/l2vpn_controller.py @@ -33,7 +33,7 @@ def delete_connection(connection_id): :rtype: None """ logger.info( - f"Handling delete (connecton id: {connection_id}) " + f"Handling delete (service id: {connection_id}) " f"with te_manager: {current_app.te_manager}" ) diff --git a/sdx_controller/models/connection_qos_metrics.py b/sdx_controller/models/connection_qos_metrics.py new file mode 100644 index 0000000..bc39b83 --- /dev/null +++ b/sdx_controller/models/connection_qos_metrics.py @@ -0,0 +1,271 @@ +# coding: utf-8 + +from __future__ import absolute_import +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from sdx_controller.models.base_model_ import Model +from sdx_controller.models.connection_qos_unit import ConnectionQosUnit # noqa: F401,E501 +from sdx_controller import util + + +class ConnectionQosMetrics(Model): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + def __init__(self, min_bw: ConnectionQosUnit=None, max_delay: ConnectionQosUnit=None, max_number_oxps: ConnectionQosUnit=None, bandwidth_measured: float=None, latency_measured: float=None, packetloss_required: float=None, packetloss_measured: float=None, availability_required: float=None, availability_measured: float=None): # noqa: E501 + """ConnectionQosMetrics - a model defined in Swagger + + :param min_bw: The min_bw of this ConnectionQosMetrics. # noqa: E501 + :type min_bw: ConnectionQosUnit + :param max_delay: The max_delay of this ConnectionQosMetrics. # noqa: E501 + :type max_delay: ConnectionQosUnit + :param max_number_oxps: The max_number_oxps of this ConnectionQosMetrics. # noqa: E501 + :type max_number_oxps: ConnectionQosUnit + :param bandwidth_measured: The bandwidth_measured of this ConnectionQosMetrics. # noqa: E501 + :type bandwidth_measured: float + :param latency_measured: The latency_measured of this ConnectionQosMetrics. # noqa: E501 + :type latency_measured: float + :param packetloss_required: The packetloss_required of this ConnectionQosMetrics. # noqa: E501 + :type packetloss_required: float + :param packetloss_measured: The packetloss_measured of this ConnectionQosMetrics. # noqa: E501 + :type packetloss_measured: float + :param availability_required: The availability_required of this ConnectionQosMetrics. # noqa: E501 + :type availability_required: float + :param availability_measured: The availability_measured of this ConnectionQosMetrics. # noqa: E501 + :type availability_measured: float + """ + self.swagger_types = { + 'min_bw': ConnectionQosUnit, + 'max_delay': ConnectionQosUnit, + 'max_number_oxps': ConnectionQosUnit, + 'bandwidth_measured': float, + 'latency_measured': float, + 'packetloss_required': float, + 'packetloss_measured': float, + 'availability_required': float, + 'availability_measured': float + } + + self.attribute_map = { + 'min_bw': 'min_bw', + 'max_delay': 'max_delay', + 'max_number_oxps': 'max_number_oxps', + 'bandwidth_measured': 'bandwidth_measured', + 'latency_measured': 'latency_measured', + 'packetloss_required': 'packetloss_required', + 'packetloss_measured': 'packetloss_measured', + 'availability_required': 'availability_required', + 'availability_measured': 'availability_measured' + } + self._min_bw = min_bw + self._max_delay = max_delay + self._max_number_oxps = max_number_oxps + self._bandwidth_measured = bandwidth_measured + self._latency_measured = latency_measured + self._packetloss_required = packetloss_required + self._packetloss_measured = packetloss_measured + self._availability_required = availability_required + self._availability_measured = availability_measured + + @classmethod + def from_dict(cls, dikt) -> 'ConnectionQosMetrics': + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The connection_qos_metrics of this ConnectionQosMetrics. # noqa: E501 + :rtype: ConnectionQosMetrics + """ + return util.deserialize_model(dikt, cls) + + @property + def min_bw(self) -> ConnectionQosUnit: + """Gets the min_bw of this ConnectionQosMetrics. + + + :return: The min_bw of this ConnectionQosMetrics. + :rtype: ConnectionQosUnit + """ + return self._min_bw + + @min_bw.setter + def min_bw(self, min_bw: ConnectionQosUnit): + """Sets the min_bw of this ConnectionQosMetrics. + + + :param min_bw: The min_bw of this ConnectionQosMetrics. + :type min_bw: ConnectionQosUnit + """ + + self._min_bw = min_bw + + @property + def max_delay(self) -> ConnectionQosUnit: + """Gets the max_delay of this ConnectionQosMetrics. + + + :return: The max_delay of this ConnectionQosMetrics. + :rtype: ConnectionQosUnit + """ + return self._max_delay + + @max_delay.setter + def max_delay(self, max_delay: ConnectionQosUnit): + """Sets the max_delay of this ConnectionQosMetrics. + + + :param max_delay: The max_delay of this ConnectionQosMetrics. + :type max_delay: ConnectionQosUnit + """ + + self._max_delay = max_delay + + @property + def max_number_oxps(self) -> ConnectionQosUnit: + """Gets the max_number_oxps of this ConnectionQosMetrics. + + + :return: The max_number_oxps of this ConnectionQosMetrics. + :rtype: ConnectionQosUnit + """ + return self._max_number_oxps + + @max_number_oxps.setter + def max_number_oxps(self, max_number_oxps: ConnectionQosUnit): + """Sets the max_number_oxps of this ConnectionQosMetrics. + + + :param max_number_oxps: The max_number_oxps of this ConnectionQosMetrics. + :type max_number_oxps: ConnectionQosUnit + """ + + self._max_number_oxps = max_number_oxps + + @property + def bandwidth_measured(self) -> float: + """Gets the bandwidth_measured of this ConnectionQosMetrics. + + + :return: The bandwidth_measured of this ConnectionQosMetrics. + :rtype: float + """ + return self._bandwidth_measured + + @bandwidth_measured.setter + def bandwidth_measured(self, bandwidth_measured: float): + """Sets the bandwidth_measured of this ConnectionQosMetrics. + + + :param bandwidth_measured: The bandwidth_measured of this ConnectionQosMetrics. + :type bandwidth_measured: float + """ + + self._bandwidth_measured = bandwidth_measured + + @property + def latency_measured(self) -> float: + """Gets the latency_measured of this ConnectionQosMetrics. + + + :return: The latency_measured of this ConnectionQosMetrics. + :rtype: float + """ + return self._latency_measured + + @latency_measured.setter + def latency_measured(self, latency_measured: float): + """Sets the latency_measured of this ConnectionQosMetrics. + + + :param latency_measured: The latency_measured of this ConnectionQosMetrics. + :type latency_measured: float + """ + + self._latency_measured = latency_measured + + @property + def packetloss_required(self) -> float: + """Gets the packetloss_required of this ConnectionQosMetrics. + + + :return: The packetloss_required of this ConnectionQosMetrics. + :rtype: float + """ + return self._packetloss_required + + @packetloss_required.setter + def packetloss_required(self, packetloss_required: float): + """Sets the packetloss_required of this ConnectionQosMetrics. + + + :param packetloss_required: The packetloss_required of this ConnectionQosMetrics. + :type packetloss_required: float + """ + + self._packetloss_required = packetloss_required + + @property + def packetloss_measured(self) -> float: + """Gets the packetloss_measured of this ConnectionQosMetrics. + + + :return: The packetloss_measured of this ConnectionQosMetrics. + :rtype: float + """ + return self._packetloss_measured + + @packetloss_measured.setter + def packetloss_measured(self, packetloss_measured: float): + """Sets the packetloss_measured of this ConnectionQosMetrics. + + + :param packetloss_measured: The packetloss_measured of this ConnectionQosMetrics. + :type packetloss_measured: float + """ + + self._packetloss_measured = packetloss_measured + + @property + def availability_required(self) -> float: + """Gets the availability_required of this ConnectionQosMetrics. + + + :return: The availability_required of this ConnectionQosMetrics. + :rtype: float + """ + return self._availability_required + + @availability_required.setter + def availability_required(self, availability_required: float): + """Sets the availability_required of this ConnectionQosMetrics. + + + :param availability_required: The availability_required of this ConnectionQosMetrics. + :type availability_required: float + """ + + self._availability_required = availability_required + + @property + def availability_measured(self) -> float: + """Gets the availability_measured of this ConnectionQosMetrics. + + + :return: The availability_measured of this ConnectionQosMetrics. + :rtype: float + """ + return self._availability_measured + + @availability_measured.setter + def availability_measured(self, availability_measured: float): + """Sets the availability_measured of this ConnectionQosMetrics. + + + :param availability_measured: The availability_measured of this ConnectionQosMetrics. + :type availability_measured: float + """ + + self._availability_measured = availability_measured diff --git a/sdx_controller/models/connection_qos_unit.py b/sdx_controller/models/connection_qos_unit.py new file mode 100644 index 0000000..fd0eda0 --- /dev/null +++ b/sdx_controller/models/connection_qos_unit.py @@ -0,0 +1,88 @@ +# coding: utf-8 + +from __future__ import absolute_import +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from sdx_controller.models.base_model_ import Model +from sdx_controller import util + + +class ConnectionQosUnit(Model): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + def __init__(self, value: int=None, strict: bool=None): # noqa: E501 + """ConnectionQosUnit - a model defined in Swagger + + :param value: The value of this ConnectionQosUnit. # noqa: E501 + :type value: int + :param strict: The strict of this ConnectionQosUnit. # noqa: E501 + :type strict: bool + """ + self.swagger_types = { + 'value': int, + 'strict': bool + } + + self.attribute_map = { + 'value': 'value', + 'strict': 'strict' + } + self._value = value + self._strict = strict + + @classmethod + def from_dict(cls, dikt) -> 'ConnectionQosUnit': + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The connection_qos_unit of this ConnectionQosUnit. # noqa: E501 + :rtype: ConnectionQosUnit + """ + return util.deserialize_model(dikt, cls) + + @property + def value(self) -> int: + """Gets the value of this ConnectionQosUnit. + + + :return: The value of this ConnectionQosUnit. + :rtype: int + """ + return self._value + + @value.setter + def value(self, value: int): + """Sets the value of this ConnectionQosUnit. + + + :param value: The value of this ConnectionQosUnit. + :type value: int + """ + + self._value = value + + @property + def strict(self) -> bool: + """Gets the strict of this ConnectionQosUnit. + + + :return: The strict of this ConnectionQosUnit. + :rtype: bool + """ + return self._strict + + @strict.setter + def strict(self, strict: bool): + """Sets the strict of this ConnectionQosUnit. + + + :param strict: The strict of this ConnectionQosUnit. + :type strict: bool + """ + + self._strict = strict diff --git a/sdx_controller/models/connection_scheduling.py b/sdx_controller/models/connection_scheduling.py new file mode 100644 index 0000000..1824104 --- /dev/null +++ b/sdx_controller/models/connection_scheduling.py @@ -0,0 +1,88 @@ +# coding: utf-8 + +from __future__ import absolute_import +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from sdx_controller.models.base_model_ import Model +from sdx_controller import util + + +class ConnectionScheduling(Model): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + def __init__(self, start_time: datetime=None, end_time: datetime=None): # noqa: E501 + """ConnectionScheduling - a model defined in Swagger + + :param start_time: The start_time of this ConnectionScheduling. # noqa: E501 + :type start_time: datetime + :param end_time: The end_time of this ConnectionScheduling. # noqa: E501 + :type end_time: datetime + """ + self.swagger_types = { + 'start_time': datetime, + 'end_time': datetime + } + + self.attribute_map = { + 'start_time': 'start_time', + 'end_time': 'end_time' + } + self._start_time = start_time + self._end_time = end_time + + @classmethod + def from_dict(cls, dikt) -> 'ConnectionScheduling': + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The connection_scheduling of this ConnectionScheduling. # noqa: E501 + :rtype: ConnectionScheduling + """ + return util.deserialize_model(dikt, cls) + + @property + def start_time(self) -> datetime: + """Gets the start_time of this ConnectionScheduling. + + + :return: The start_time of this ConnectionScheduling. + :rtype: datetime + """ + return self._start_time + + @start_time.setter + def start_time(self, start_time: datetime): + """Sets the start_time of this ConnectionScheduling. + + + :param start_time: The start_time of this ConnectionScheduling. + :type start_time: datetime + """ + + self._start_time = start_time + + @property + def end_time(self) -> datetime: + """Gets the end_time of this ConnectionScheduling. + + + :return: The end_time of this ConnectionScheduling. + :rtype: datetime + """ + return self._end_time + + @end_time.setter + def end_time(self, end_time: datetime): + """Sets the end_time of this ConnectionScheduling. + + + :param end_time: The end_time of this ConnectionScheduling. + :type end_time: datetime + """ + + self._end_time = end_time diff --git a/sdx_controller/models/connection_v2.py b/sdx_controller/models/connection_v2.py new file mode 100644 index 0000000..46a5c02 --- /dev/null +++ b/sdx_controller/models/connection_v2.py @@ -0,0 +1,449 @@ +# coding: utf-8 + +from __future__ import absolute_import +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from sdx_controller.models.base_model_ import Model +from sdx_controller.models.connection_qos_metrics import ConnectionQosMetrics # noqa: F401,E501 +from sdx_controller.models.connection_scheduling import ConnectionScheduling # noqa: F401,E501 +from sdx_controller.models.connection_v2_endpoints import ConnectionV2Endpoints # noqa: F401,E501 +from sdx_controller.models.connection_v2_notifications import ConnectionV2Notifications # noqa: F401,E501 +from sdx_controller.models.link import Link # noqa: F401,E501 +from sdx_controller import util + + +class ConnectionV2(Model): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + def __init__(self, name: str=None, endpoints: List[ConnectionV2Endpoints]=None, description: str=None, notifications: List[ConnectionV2Notifications]=None, scheduling: ConnectionScheduling=None, qos_metrics: Dict[str, ConnectionQosMetrics]=None, paths: List[str]=None, status: str=None, complete: bool=False, quantity: int=None, multi_path: bool=None, preempt: bool=None, backup_path_type: str=None, exclusive_links: List[Link]=None, inclusive_links: List[Link]=None): # noqa: E501 + """ConnectionV2 - a model defined in Swagger + + :param name: The name of this ConnectionV2. # noqa: E501 + :type name: str + :param endpoints: The endpoints of this ConnectionV2. # noqa: E501 + :type endpoints: List[ConnectionV2Endpoints] + :param description: The description of this ConnectionV2. # noqa: E501 + :type description: str + :param notifications: The notifications of this ConnectionV2. # noqa: E501 + :type notifications: List[ConnectionV2Notifications] + :param scheduling: The scheduling of this ConnectionV2. # noqa: E501 + :type scheduling: ConnectionScheduling + :param qos_metrics: The qos_metrics of this ConnectionV2. # noqa: E501 + :type qos_metrics: Dict[str, ConnectionQosMetrics] + :param paths: The paths of this ConnectionV2. # noqa: E501 + :type paths: List[str] + :param status: The status of this ConnectionV2. # noqa: E501 + :type status: str + :param complete: The complete of this ConnectionV2. # noqa: E501 + :type complete: bool + :param quantity: The quantity of this ConnectionV2. # noqa: E501 + :type quantity: int + :param multi_path: The multi_path of this ConnectionV2. # noqa: E501 + :type multi_path: bool + :param preempt: The preempt of this ConnectionV2. # noqa: E501 + :type preempt: bool + :param backup_path_type: The backup_path_type of this ConnectionV2. # noqa: E501 + :type backup_path_type: str + :param exclusive_links: The exclusive_links of this ConnectionV2. # noqa: E501 + :type exclusive_links: List[Link] + :param inclusive_links: The inclusive_links of this ConnectionV2. # noqa: E501 + :type inclusive_links: List[Link] + """ + self.swagger_types = { + 'name': str, + 'endpoints': List[ConnectionV2Endpoints], + 'description': str, + 'notifications': List[ConnectionV2Notifications], + 'scheduling': ConnectionScheduling, + 'qos_metrics': Dict[str, ConnectionQosMetrics], + 'paths': List[str], + 'status': str, + 'complete': bool, + 'quantity': int, + 'multi_path': bool, + 'preempt': bool, + 'backup_path_type': str, + 'exclusive_links': List[Link], + 'inclusive_links': List[Link] + } + + self.attribute_map = { + 'name': 'name', + 'endpoints': 'endpoints', + 'description': 'description', + 'notifications': 'notifications', + 'scheduling': 'scheduling', + 'qos_metrics': 'qos_metrics', + 'paths': 'paths', + 'status': 'status', + 'complete': 'complete', + 'quantity': 'quantity', + 'multi_path': 'multi_path', + 'preempt': 'preempt', + 'backup_path_type': 'backup_path_type', + 'exclusive_links': 'exclusive_links', + 'inclusive_links': 'inclusive_links' + } + self._name = name + self._endpoints = endpoints + self._description = description + self._notifications = notifications + self._scheduling = scheduling + self._qos_metrics = qos_metrics + self._paths = paths + self._status = status + self._complete = complete + self._quantity = quantity + self._multi_path = multi_path + self._preempt = preempt + self._backup_path_type = backup_path_type + self._exclusive_links = exclusive_links + self._inclusive_links = inclusive_links + + @classmethod + def from_dict(cls, dikt) -> 'ConnectionV2': + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The connection_v2 of this ConnectionV2. # noqa: E501 + :rtype: ConnectionV2 + """ + return util.deserialize_model(dikt, cls) + + @property + def name(self) -> str: + """Gets the name of this ConnectionV2. + + + :return: The name of this ConnectionV2. + :rtype: str + """ + return self._name + + @name.setter + def name(self, name: str): + """Sets the name of this ConnectionV2. + + + :param name: The name of this ConnectionV2. + :type name: str + """ + if name is None: + raise ValueError("Invalid value for `name`, must not be `None`") # noqa: E501 + + self._name = name + + @property + def endpoints(self) -> List[ConnectionV2Endpoints]: + """Gets the endpoints of this ConnectionV2. + + + :return: The endpoints of this ConnectionV2. + :rtype: List[ConnectionV2Endpoints] + """ + return self._endpoints + + @endpoints.setter + def endpoints(self, endpoints: List[ConnectionV2Endpoints]): + """Sets the endpoints of this ConnectionV2. + + + :param endpoints: The endpoints of this ConnectionV2. + :type endpoints: List[ConnectionV2Endpoints] + """ + if endpoints is None: + raise ValueError("Invalid value for `endpoints`, must not be `None`") # noqa: E501 + + self._endpoints = endpoints + + @property + def description(self) -> str: + """Gets the description of this ConnectionV2. + + + :return: The description of this ConnectionV2. + :rtype: str + """ + return self._description + + @description.setter + def description(self, description: str): + """Sets the description of this ConnectionV2. + + + :param description: The description of this ConnectionV2. + :type description: str + """ + + self._description = description + + @property + def notifications(self) -> List[ConnectionV2Notifications]: + """Gets the notifications of this ConnectionV2. + + + :return: The notifications of this ConnectionV2. + :rtype: List[ConnectionV2Notifications] + """ + return self._notifications + + @notifications.setter + def notifications(self, notifications: List[ConnectionV2Notifications]): + """Sets the notifications of this ConnectionV2. + + + :param notifications: The notifications of this ConnectionV2. + :type notifications: List[ConnectionV2Notifications] + """ + + self._notifications = notifications + + @property + def scheduling(self) -> ConnectionScheduling: + """Gets the scheduling of this ConnectionV2. + + + :return: The scheduling of this ConnectionV2. + :rtype: ConnectionScheduling + """ + return self._scheduling + + @scheduling.setter + def scheduling(self, scheduling: ConnectionScheduling): + """Sets the scheduling of this ConnectionV2. + + + :param scheduling: The scheduling of this ConnectionV2. + :type scheduling: ConnectionScheduling + """ + + self._scheduling = scheduling + + @property + def qos_metrics(self) -> Dict[str, ConnectionQosMetrics]: + """Gets the qos_metrics of this ConnectionV2. + + + :return: The qos_metrics of this ConnectionV2. + :rtype: Dict[str, ConnectionQosMetrics] + """ + return self._qos_metrics + + @qos_metrics.setter + def qos_metrics(self, qos_metrics: Dict[str, ConnectionQosMetrics]): + """Sets the qos_metrics of this ConnectionV2. + + + :param qos_metrics: The qos_metrics of this ConnectionV2. + :type qos_metrics: Dict[str, ConnectionQosMetrics] + """ + + self._qos_metrics = qos_metrics + + @property + def paths(self) -> List[str]: + """Gets the paths of this ConnectionV2. + + + :return: The paths of this ConnectionV2. + :rtype: List[str] + """ + return self._paths + + @paths.setter + def paths(self, paths: List[str]): + """Sets the paths of this ConnectionV2. + + + :param paths: The paths of this ConnectionV2. + :type paths: List[str] + """ + + self._paths = paths + + @property + def status(self) -> str: + """Gets the status of this ConnectionV2. + + Connection Status # noqa: E501 + + :return: The status of this ConnectionV2. + :rtype: str + """ + return self._status + + @status.setter + def status(self, status: str): + """Sets the status of this ConnectionV2. + + Connection Status # noqa: E501 + + :param status: The status of this ConnectionV2. + :type status: str + """ + allowed_values = ["success", "fail", "scheduled", "provisioining"] # noqa: E501 + if status not in allowed_values: + raise ValueError( + "Invalid value for `status` ({0}), must be one of {1}" + .format(status, allowed_values) + ) + + self._status = status + + @property + def complete(self) -> bool: + """Gets the complete of this ConnectionV2. + + + :return: The complete of this ConnectionV2. + :rtype: bool + """ + return self._complete + + @complete.setter + def complete(self, complete: bool): + """Sets the complete of this ConnectionV2. + + + :param complete: The complete of this ConnectionV2. + :type complete: bool + """ + + self._complete = complete + + @property + def quantity(self) -> int: + """Gets the quantity of this ConnectionV2. + + + :return: The quantity of this ConnectionV2. + :rtype: int + """ + return self._quantity + + @quantity.setter + def quantity(self, quantity: int): + """Sets the quantity of this ConnectionV2. + + + :param quantity: The quantity of this ConnectionV2. + :type quantity: int + """ + + self._quantity = quantity + + @property + def multi_path(self) -> bool: + """Gets the multi_path of this ConnectionV2. + + + :return: The multi_path of this ConnectionV2. + :rtype: bool + """ + return self._multi_path + + @multi_path.setter + def multi_path(self, multi_path: bool): + """Sets the multi_path of this ConnectionV2. + + + :param multi_path: The multi_path of this ConnectionV2. + :type multi_path: bool + """ + + self._multi_path = multi_path + + @property + def preempt(self) -> bool: + """Gets the preempt of this ConnectionV2. + + + :return: The preempt of this ConnectionV2. + :rtype: bool + """ + return self._preempt + + @preempt.setter + def preempt(self, preempt: bool): + """Sets the preempt of this ConnectionV2. + + + :param preempt: The preempt of this ConnectionV2. + :type preempt: bool + """ + + self._preempt = preempt + + @property + def backup_path_type(self) -> str: + """Gets the backup_path_type of this ConnectionV2. + + + :return: The backup_path_type of this ConnectionV2. + :rtype: str + """ + return self._backup_path_type + + @backup_path_type.setter + def backup_path_type(self, backup_path_type: str): + """Sets the backup_path_type of this ConnectionV2. + + + :param backup_path_type: The backup_path_type of this ConnectionV2. + :type backup_path_type: str + """ + allowed_values = ["0", "1", "2", "3"] # noqa: E501 + if backup_path_type not in allowed_values: + raise ValueError( + "Invalid value for `backup_path_type` ({0}), must be one of {1}" + .format(backup_path_type, allowed_values) + ) + + self._backup_path_type = backup_path_type + + @property + def exclusive_links(self) -> List[Link]: + """Gets the exclusive_links of this ConnectionV2. + + + :return: The exclusive_links of this ConnectionV2. + :rtype: List[Link] + """ + return self._exclusive_links + + @exclusive_links.setter + def exclusive_links(self, exclusive_links: List[Link]): + """Sets the exclusive_links of this ConnectionV2. + + + :param exclusive_links: The exclusive_links of this ConnectionV2. + :type exclusive_links: List[Link] + """ + + self._exclusive_links = exclusive_links + + @property + def inclusive_links(self) -> List[Link]: + """Gets the inclusive_links of this ConnectionV2. + + + :return: The inclusive_links of this ConnectionV2. + :rtype: List[Link] + """ + return self._inclusive_links + + @inclusive_links.setter + def inclusive_links(self, inclusive_links: List[Link]): + """Sets the inclusive_links of this ConnectionV2. + + + :param inclusive_links: The inclusive_links of this ConnectionV2. + :type inclusive_links: List[Link] + """ + + self._inclusive_links = inclusive_links diff --git a/sdx_controller/models/connection_v2_endpoints.py b/sdx_controller/models/connection_v2_endpoints.py new file mode 100644 index 0000000..8f69f2e --- /dev/null +++ b/sdx_controller/models/connection_v2_endpoints.py @@ -0,0 +1,88 @@ +# coding: utf-8 + +from __future__ import absolute_import +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from sdx_controller.models.base_model_ import Model +from sdx_controller import util + + +class ConnectionV2Endpoints(Model): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + def __init__(self, port_id: str=None, vlan: str=None): # noqa: E501 + """ConnectionV2Endpoints - a model defined in Swagger + + :param port_id: The port_id of this ConnectionV2Endpoints. # noqa: E501 + :type port_id: str + :param vlan: The vlan of this ConnectionV2Endpoints. # noqa: E501 + :type vlan: str + """ + self.swagger_types = { + 'port_id': str, + 'vlan': str + } + + self.attribute_map = { + 'port_id': 'port_id', + 'vlan': 'vlan' + } + self._port_id = port_id + self._vlan = vlan + + @classmethod + def from_dict(cls, dikt) -> 'ConnectionV2Endpoints': + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The connection_v2_endpoints of this ConnectionV2Endpoints. # noqa: E501 + :rtype: ConnectionV2Endpoints + """ + return util.deserialize_model(dikt, cls) + + @property + def port_id(self) -> str: + """Gets the port_id of this ConnectionV2Endpoints. + + + :return: The port_id of this ConnectionV2Endpoints. + :rtype: str + """ + return self._port_id + + @port_id.setter + def port_id(self, port_id: str): + """Sets the port_id of this ConnectionV2Endpoints. + + + :param port_id: The port_id of this ConnectionV2Endpoints. + :type port_id: str + """ + + self._port_id = port_id + + @property + def vlan(self) -> str: + """Gets the vlan of this ConnectionV2Endpoints. + + + :return: The vlan of this ConnectionV2Endpoints. + :rtype: str + """ + return self._vlan + + @vlan.setter + def vlan(self, vlan: str): + """Sets the vlan of this ConnectionV2Endpoints. + + + :param vlan: The vlan of this ConnectionV2Endpoints. + :type vlan: str + """ + + self._vlan = vlan diff --git a/sdx_controller/models/connection_v2_notifications.py b/sdx_controller/models/connection_v2_notifications.py new file mode 100644 index 0000000..07b6ed7 --- /dev/null +++ b/sdx_controller/models/connection_v2_notifications.py @@ -0,0 +1,62 @@ +# coding: utf-8 + +from __future__ import absolute_import +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from sdx_controller.models.base_model_ import Model +from sdx_controller import util + + +class ConnectionV2Notifications(Model): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + def __init__(self, email: str=None): # noqa: E501 + """ConnectionV2Notifications - a model defined in Swagger + + :param email: The email of this ConnectionV2Notifications. # noqa: E501 + :type email: str + """ + self.swagger_types = { + 'email': str + } + + self.attribute_map = { + 'email': 'email' + } + self._email = email + + @classmethod + def from_dict(cls, dikt) -> 'ConnectionV2Notifications': + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The connection_v2_notifications of this ConnectionV2Notifications. # noqa: E501 + :rtype: ConnectionV2Notifications + """ + return util.deserialize_model(dikt, cls) + + @property + def email(self) -> str: + """Gets the email of this ConnectionV2Notifications. + + + :return: The email of this ConnectionV2Notifications. + :rtype: str + """ + return self._email + + @email.setter + def email(self, email: str): + """Sets the email of this ConnectionV2Notifications. + + + :param email: The email of this ConnectionV2Notifications. + :type email: str + """ + + self._email = email diff --git a/sdx_controller/models/l2vpn_body.py b/sdx_controller/models/l2vpn_body.py new file mode 100644 index 0000000..f569c9c --- /dev/null +++ b/sdx_controller/models/l2vpn_body.py @@ -0,0 +1,36 @@ +# coding: utf-8 + +from __future__ import absolute_import +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from sdx_controller.models.base_model_ import Model +from sdx_controller import util + + +class L2vpnBody(Model): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + def __init__(self): # noqa: E501 + """L2vpnBody - a model defined in Swagger + + """ + self.swagger_types = { + } + + self.attribute_map = { + } + + @classmethod + def from_dict(cls, dikt) -> 'L2vpnBody': + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The l2vpn_body of this L2vpnBody. # noqa: E501 + :rtype: L2vpnBody + """ + return util.deserialize_model(dikt, cls) diff --git a/sdx_controller/swagger/swagger.yaml b/sdx_controller/swagger/swagger.yaml index d345845..7076e0f 100644 --- a/sdx_controller/swagger/swagger.yaml +++ b/sdx_controller/swagger/swagger.yaml @@ -1,15 +1,18 @@ openapi: 3.0.0 info: title: SDX-Controller - description: "You can find\nout more about Swagger at\n[http://swagger.io](http://swagger.io)\ - \ or on\n[irc.freenode.net, #swagger](http://swagger.io/irc/).\n" + description: | + You can find + out more about Swagger at + [http://swagger.io](http://swagger.io) or on + [irc.freenode.net, #swagger](http://swagger.io/irc/). termsOfService: http://swagger.io/terms/ contact: email: yxin@renci.org license: name: Apache 2.0 url: http://www.apache.org/licenses/LICENSE-2.0.html - version: 1.0.0 + version: 2.0.0-oas3 externalDocs: description: Find out more about Swagger url: http://swagger.io @@ -79,10 +82,6 @@ paths: $ref: '#/components/schemas/topology' "400": description: Invalid id value - # security: - # - topology_auth: - # - write:topology - # - read:topology x-openapi-router-controller: sdx_controller.controllers.topology_controller /topology/grenml: get: @@ -134,20 +133,40 @@ paths: "404": description: Link not found x-openapi-router-controller: sdx_controller.controllers.link_controller - /connection: + /l2vpn: + get: + tags: + - l2vpn + summary: List all l2vpn connections + description: connection details + operationId: getconnections + responses: + "200": + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/connection' + x-content-type: application/json + application/xml: + schema: + $ref: '#/components/schemas/connection' + "404": + description: no connections found + x-openapi-router-controller: sdx_controller.controllers.connection_controller post: tags: - - connection - summary: Place an connection request from the SDX-Controller + - l2vpn + summary: Place an L2vpn connection request from the SDX-Controller operationId: place_connection requestBody: description: order placed for creating a connection content: application/json: schema: - oneOf: - - $ref: '#/components/schemas/connection' - - $ref: '#/components/schemas/connection_v2' + $ref: '#/components/schemas/l2vpn_body' required: true responses: "200": @@ -159,39 +178,17 @@ paths: "400": description: Invalid Connection x-openapi-router-controller: sdx_controller.controllers.connection_controller - /connections: - get: - tags: - - connection - summary: List all connections - description: connection details - operationId: getconnections - responses: - "200": - description: successful operation - content: - application/json: - schema: - type: object - items: - $ref: '#/components/schemas/connection' - application/xml: - schema: - $ref: '#/components/schemas/connection' - "404": - description: no connections found - x-openapi-router-controller: sdx_controller.controllers.connection_controller - /connection/{connection_id}: + /l2vpn/{service_id}: get: tags: - - connection - summary: Find connection by ID + - l2vpn + summary: Find l2vpn connection by ID description: connection details operationId: getconnection_by_id parameters: - - name: connection_id + - name: service_id in: path - description: ID of connection that needs to be fetched + description: ID of l2vpn connection that needs to be fetched required: true style: simple explode: false @@ -215,12 +212,12 @@ paths: x-openapi-router-controller: sdx_controller.controllers.connection_controller delete: tags: - - connection + - l2vpn summary: Delete connection order by ID description: delete a connection operationId: delete_connection parameters: - - name: connection_id + - name: service_id in: path description: ID of the connection that needs to be deleted required: true @@ -459,12 +456,12 @@ components: type: boolean default: false example: - start_time: 2000-01-23T04:56:07.000+00:00 + start_time: 2000-01-23T04:56:07.000Z quantity: 0 name: name - end_time: 2000-01-23T04:56:07.000+00:00 + end_time: 2000-01-23T04:56:07.000Z id: id - egress_port: + egress_port: node: urn:sdx:port:amlight.net:Novi06 name: urn:sdx:port:amlight.net:Novi06:10 short_name: "10" @@ -480,7 +477,7 @@ components: status: up complete: false status: success - timestamp: 2000-01-23T04:56:07+00:00 + timestamp: 2000-01-23T04:56:07.000Z version: 1 xml: name: Connection @@ -495,21 +492,13 @@ components: endpoints: type: array items: - type: object - properties: - port_id: - type: string - vlan: - type: string + $ref: '#/components/schemas/connection_v2_endpoints' description: type: string notifications: type: array items: - type: object - properties: - email: - type: string + $ref: '#/components/schemas/connection_v2_notifications' scheduling: $ref: '#/components/schemas/connection_scheduling' qos_metrics: @@ -628,7 +617,7 @@ components: availability_measured: maximum: 100 minimum: 0 - type: number + type: number User: type: object properties: @@ -698,9 +687,9 @@ components: short_name: Novi06 location: address: address - latitude: 0.80082819046101150206595775671303272247314453125 - longitude: 6.02745618307040320615897144307382404804229736328125 - iso3166_2_lvl4: "US-MIA" + latitude: 0.8008281904610115 + longitude: 6.027456183070403 + iso3166_2_lvl4: US-MIA id: id ports: - node: urn:sdx:port:amlight.net:Novi06 @@ -719,9 +708,9 @@ components: short_name: Novi06 location: address: address - latitude: 0.80082819046101150206595775671303272247314453125 - longitude: 6.02745618307040320615897144307382404804229736328125 - iso3166_2_lvl4: "US-MIA" + latitude: 0.8008281904610115 + longitude: 6.027456183070403 + iso3166_2_lvl4: US-MIA id: id ports: - node: urn:sdx:port:amlight.net:Novi06 @@ -736,30 +725,30 @@ components: id: id state: enabled status: up - timestamp: 2000-01-23T04:56:07.000+00:00 + timestamp: 2000-01-23T04:56:07.000Z name: amLight links: - - residual_bandwidth: 596213.79545492655597627162933349609375 - packet_loss: 23.0213588693476509661195450462400913238525390625 - bandwidth: 146582.15146899645333178341388702392578125 - latency: 563738.10192566714249551296234130859375 + - residual_bandwidth: 596213.7954549266 + packet_loss: 23.02135886934765 + bandwidth: 146582.15146899645 + latency: 563738.1019256671 name: miami-Boca.amLight.sdx short_name: Miami-BocaRaton id: id - availability: 70.61401241503108394681476056575775146484375 + availability: 70.61401241503108 state: enabled ports: - null - null status: up - - residual_bandwidth: 596213.79545492655597627162933349609375 - packet_loss: 23.0213588693476509661195450462400913238525390625 - bandwidth: 146582.15146899645333178341388702392578125 - latency: 563738.10192566714249551296234130859375 + - residual_bandwidth: 596213.7954549266 + packet_loss: 23.02135886934765 + bandwidth: 146582.15146899645 + latency: 563738.1019256671 name: miami-Boca.amLight.sdx short_name: Miami-BocaRaton id: id - availability: 70.61401241503108394681476056575775146484375 + availability: 70.61401241503108 state: enabled ports: - null @@ -796,9 +785,9 @@ components: short_name: Novi06 location: address: address - latitude: 0.80082819046101150206595775671303272247314453125 - longitude: 6.02745618307040320615897144307382404804229736328125 - iso3166_2_lvl4: "US-MIA" + latitude: 0.8008281904610115 + longitude: 6.027456183070403 + iso3166_2_lvl4: US-MIA id: id ports: - node: urn:sdx:port:amlight.net:Novi06 @@ -861,14 +850,14 @@ components: type: string example: up example: - residual_bandwidth: 596213.79545492655597627162933349609375 - packet_loss: 23.0213588693476509661195450462400913238525390625 - bandwidth: 146582.15146899645333178341388702392578125 - latency: 563738.10192566714249551296234130859375 + residual_bandwidth: 596213.7954549266 + packet_loss: 23.02135886934765 + bandwidth: 146582.15146899645 + latency: 563738.1019256671 name: miami-Boca.amLight.sdx short_name: Miami-BocaRaton id: id - availability: 70.61401241503108394681476056575775146484375 + availability: 70.61401241503108 state: enabled ports: - null @@ -919,13 +908,13 @@ components: type: number longitude: type: number - iso3166_2_lvl4: + iso3166_2_lvl4: type: string example: address: address - latitude: 0.80082819046101150206595775671303272247314453125 - longitude: 6.02745618307040320615897144307382404804229736328125 - iso3166_2_lvl4: "US-MIA" + latitude: 0.8008281904610115 + longitude: 6.027456183070403 + iso3166_2_lvl4: US-MIA ApiResponse: type: object properties: @@ -936,6 +925,22 @@ components: type: string message: type: string + l2vpn_body: + oneOf: + - $ref: '#/components/schemas/connection' + - $ref: '#/components/schemas/connection_v2' + connection_v2_endpoints: + type: object + properties: + port_id: + type: string + vlan: + type: string + connection_v2_notifications: + type: object + properties: + email: + type: string requestBodies: topology: description: Inter-domain topology object that the SDX-Controller keeps diff --git a/sdx_controller/test/test_connection_controller.py b/sdx_controller/test/test_connection_controller.py deleted file mode 100644 index 25561f5..0000000 --- a/sdx_controller/test/test_connection_controller.py +++ /dev/null @@ -1,422 +0,0 @@ -# coding: utf-8 - -from __future__ import absolute_import - -import unittest -import uuid -from unittest.mock import patch - -from flask import json - -from sdx_controller.models.connection import Connection -from sdx_controller.test import BaseTestCase, TestData - -BASE_PATH = "/SDX-Controller/1.0.0" - - -class TestConnectionController(BaseTestCase): - """ConnectionController integration test stubs""" - - def test_delete_connection_no_setup(self): - """ - Test case for delete_connection(). - - Delete connection order by ID. - """ - connection_id = 2 - response = self.client.open( - f"{BASE_PATH}/connection/{connection_id}", - method="DELETE", - ) - self.assert404(response, f"Response body is : {response.data.decode('utf-8')}") - - def __add_the_three_topologies(self): - """ - A helper to add the three known topologies. - """ - for idx, topology_file in enumerate( - [ - 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): - """ - 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 - self.__add_the_three_topologies() - - 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("service_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. - - Find connection by ID. - """ - connection_id = 10 - response = self.client.open( - f"{BASE_PATH}/connection/{connection_id}", - method="GET", - ) - - # The connection_id we've supplied above should not exist. - # TODO: test for existing connection_id. See - # https://github.com/atlanticwave-sdx/sdx-controller/issues/34. - self.assertStatus(response, 404) - - def test_place_connection_no_topology(self): - """ - Test case for place_connection. - - Place a connection request with no topology present. - """ - body = Connection() - - response = self.client.open( - f"{BASE_PATH}/connection", - method="POST", - data=json.dumps(body), - content_type="application/json", - ) - print(f"Response body is : {response.data.decode('utf-8')}") - - # Expect 400 failure because the request is incomplete: the - # bare minimum connection request we sent does not have - # ingress port data, etc., for example. - self.assertStatus(response, 400) - - def __test_with_one_topology(self, topology_file): - """ - A helper method to test place_connection() with just one topology. - """ - topology = json.loads(topology_file.read_text()) - self.te_manager.add_topology(topology) - - request = TestData.CONNECTION_REQ.read_text() - - response = self.client.open( - f"{BASE_PATH}/connection", - method="POST", - data=request, - content_type="application/json", - ) - - print(f"Response body is : {response.data.decode('utf-8')}") - - # Expect 400 failure, because TEManager do not have enough - # topology data. - self.assertStatus(response, 400) - - def test_place_connection_with_amlight(self): - """ - Test place_connection() with just Amlight topology. - """ - self.__test_with_one_topology(TestData.TOPOLOGY_FILE_AMLIGHT) - - def test_place_connection_with_sax(self): - """ - Test place_connection() with just SAX topology. - """ - self.__test_with_one_topology(TestData.TOPOLOGY_FILE_SAX) - - def test_place_connection_with_zaoxi(self): - """ - Test place_connection() with just ZAOXI topology. - """ - 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.assertIn("is not valid under any of the given schemas", response["detail"]) - - def test_place_connection_with_three_topologies(self): - """ - Test case for place_connection. - - Place a connection request when some topologies are known. - """ - self.__add_the_three_topologies() - - request = TestData.CONNECTION_REQ.read_text() - - response = self.client.open( - f"{BASE_PATH}/connection", - method="POST", - data=request, - content_type="application/json", - ) - - print(f"Response body is : {response.data.decode('utf-8')}") - - # Expect 200 success because TEManager now should be properly - # set up with all the expected topology data. - self.assertStatus(response, 200) - - def test_place_connection_with_three_topologies_added_in_sequence(self): - """ - Test case for place_connection. - - 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( - [ - 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 = TestData.CONNECTION_REQ.read_text() - - response = self.client.open( - f"{BASE_PATH}/connection", - method="POST", - data=request, - content_type="application/json", - ) - - print(f"Response body is : {response.data.decode('utf-8')}") - - if idx in [0, 1]: - # Expect 400 failure because TEManager do not have all - # the topologies yet. - self.assertStatus(response, 400) - if idx == 200: - # Expect 200 success now that TEManager should be set - # 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("service_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("service_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. - """ - # Generate a random ID. - connection_id = uuid.uuid4() - response = self.client.open( - f"{BASE_PATH}/connection/{connection_id}", - method="GET", - ) - - print(f"Response body is : {response.data.decode('utf-8')}") - - self.assertStatus(response, 404) - - def test_z100_getconnection_by_id_expect_200(self): - """ - Test getconnection_by_id with a non-existent connection ID. - """ - - self.__add_the_three_topologies() - - request_body = TestData.CONNECTION_REQ.read_text() - - post_response = self.client.open( - f"{BASE_PATH}/connection", - method="POST", - data=request_body, - content_type="application/json", - ) - - print(f"Response body: {post_response.data.decode('utf-8')}") - - self.assertStatus(post_response, 200) - - connection_id = post_response.get_json().get("service_id") - print(f"Got connection_id: {connection_id}") - - # Now try `GET /connection/{connection_id}` - get_response = self.client.open( - f"{BASE_PATH}/connection/{connection_id}", - method="GET", - ) - - print(f"Response body: {get_response.data.decode('utf-8')}") - - self.assertStatus(get_response, 200) - - @patch("sdx_controller.utils.db_utils.DbUtils.get_all_entries_in_collection") - def test_z105_getconnections_fail(self, mock_get_all_entries): - """Test case for getconnections.""" - mock_get_all_entries.return_value = {} - response = self.client.open( - f"{BASE_PATH}/connections", - method="GET", - ) - self.assertStatus(response, 404) - - def test_z105_getconnections_success(self): - """Test case for getconnections.""" - response = self.client.open( - f"{BASE_PATH}/connections", - method="GET", - ) - - print(f"Response body is : {response.data.decode('utf-8')}") - self.assertStatus(response, 200) - - assert len(response.get_json()) != 0 - - -if __name__ == "__main__": - unittest.main() diff --git a/sdx_controller/test/test_l2vpn_controller.py b/sdx_controller/test/test_l2vpn_controller.py new file mode 100644 index 0000000..1c071ce --- /dev/null +++ b/sdx_controller/test/test_l2vpn_controller.py @@ -0,0 +1,66 @@ +# coding: utf-8 + +from __future__ import absolute_import + +from flask import json +from six import BytesIO + +from sdx_controller.models.connection import Connection # noqa: E501 +from sdx_controller.models.l2vpn_body import L2vpnBody # noqa: E501 +from sdx_controller.test import BaseTestCase + + +class TestL2vpnController(BaseTestCase): + """L2vpnController integration test stubs""" + + def test_delete_connection(self): + """Test case for delete_connection + + Delete connection order by ID + """ + response = self.client.open( + '/SDX-Controller/1.0.0/l2vpn/{service_id}'.format(service_id='38400000-8cf0-11bd-b23e-10b96e4ef00d'), + method='DELETE') + self.assert200(response, + 'Response body is : ' + response.data.decode('utf-8')) + + def test_getconnection_by_id(self): + """Test case for getconnection_by_id + + Find l2vpn connection by ID + """ + response = self.client.open( + '/SDX-Controller/1.0.0/l2vpn/{service_id}'.format(service_id='38400000-8cf0-11bd-b23e-10b96e4ef00d'), + method='GET') + self.assert200(response, + 'Response body is : ' + response.data.decode('utf-8')) + + def test_getconnections(self): + """Test case for getconnections + + List all l2vpn connections + """ + response = self.client.open( + '/SDX-Controller/1.0.0/l2vpn', + method='GET') + self.assert200(response, + 'Response body is : ' + response.data.decode('utf-8')) + + def test_place_connection(self): + """Test case for place_connection + + Place an L2vpn connection request from the SDX-Controller + """ + body = L2vpnBody() + response = self.client.open( + '/SDX-Controller/1.0.0/l2vpn', + method='POST', + data=json.dumps(body), + content_type='application/json') + self.assert200(response, + 'Response body is : ' + response.data.decode('utf-8')) + + +if __name__ == '__main__': + import unittest + unittest.main() From 3b09b178fb01886ac9fbe8a83a823c390812ccde Mon Sep 17 00:00:00 2001 From: Yufeng Xin Date: Mon, 19 Aug 2024 12:32:06 -0400 Subject: [PATCH 02/13] lint --- README.md | 2 +- .../models/connection_qos_metrics.py | 62 +++++---- sdx_controller/models/connection_qos_unit.py | 23 ++-- .../models/connection_scheduling.py | 25 ++-- sdx_controller/models/connection_v2.py | 122 +++++++++++------- .../models/connection_v2_endpoints.py | 23 ++-- .../models/connection_v2_notifications.py | 19 ++- sdx_controller/models/l2vpn_body.py | 19 ++- sdx_controller/test/test_l2vpn_controller.py | 40 +++--- 9 files changed, 182 insertions(+), 153 deletions(-) diff --git a/README.md b/README.md index 95899db..0259151 100644 --- a/README.md +++ b/README.md @@ -182,7 +182,7 @@ You can also run a single test, and optionally, print logs on the console: ```console -$ tox -- -s --log-cli-level=INFO sdx_controller/test/test_connection_controller.py::TestConnectionController::test_getconnection_by_id +$ tox -- -s --log-cli-level=INFO sdx_controller/test/test_l2vpn_controller.py::TestL2vpnController::test_getconnection_by_id ``` If you want to examine Docker logs after the test suite has exited, diff --git a/sdx_controller/models/connection_qos_metrics.py b/sdx_controller/models/connection_qos_metrics.py index bc39b83..ec47c9e 100644 --- a/sdx_controller/models/connection_qos_metrics.py +++ b/sdx_controller/models/connection_qos_metrics.py @@ -1,13 +1,15 @@ # coding: utf-8 from __future__ import absolute_import -from datetime import date, datetime # noqa: F401 -from typing import List, Dict # noqa: F401 +from datetime import date, datetime # noqa: F401 +from typing import Dict, List # noqa: F401 -from sdx_controller.models.base_model_ import Model -from sdx_controller.models.connection_qos_unit import ConnectionQosUnit # noqa: F401,E501 from sdx_controller import util +from sdx_controller.models.base_model_ import Model +from sdx_controller.models.connection_qos_unit import ( # noqa: F401,E501 + ConnectionQosUnit, +) class ConnectionQosMetrics(Model): @@ -15,7 +17,19 @@ class ConnectionQosMetrics(Model): Do not edit the class manually. """ - def __init__(self, min_bw: ConnectionQosUnit=None, max_delay: ConnectionQosUnit=None, max_number_oxps: ConnectionQosUnit=None, bandwidth_measured: float=None, latency_measured: float=None, packetloss_required: float=None, packetloss_measured: float=None, availability_required: float=None, availability_measured: float=None): # noqa: E501 + + def __init__( + self, + min_bw: ConnectionQosUnit = None, + max_delay: ConnectionQosUnit = None, + max_number_oxps: ConnectionQosUnit = None, + bandwidth_measured: float = None, + latency_measured: float = None, + packetloss_required: float = None, + packetloss_measured: float = None, + availability_required: float = None, + availability_measured: float = None, + ): # noqa: E501 """ConnectionQosMetrics - a model defined in Swagger :param min_bw: The min_bw of this ConnectionQosMetrics. # noqa: E501 @@ -38,27 +52,27 @@ def __init__(self, min_bw: ConnectionQosUnit=None, max_delay: ConnectionQosUnit= :type availability_measured: float """ self.swagger_types = { - 'min_bw': ConnectionQosUnit, - 'max_delay': ConnectionQosUnit, - 'max_number_oxps': ConnectionQosUnit, - 'bandwidth_measured': float, - 'latency_measured': float, - 'packetloss_required': float, - 'packetloss_measured': float, - 'availability_required': float, - 'availability_measured': float + "min_bw": ConnectionQosUnit, + "max_delay": ConnectionQosUnit, + "max_number_oxps": ConnectionQosUnit, + "bandwidth_measured": float, + "latency_measured": float, + "packetloss_required": float, + "packetloss_measured": float, + "availability_required": float, + "availability_measured": float, } self.attribute_map = { - 'min_bw': 'min_bw', - 'max_delay': 'max_delay', - 'max_number_oxps': 'max_number_oxps', - 'bandwidth_measured': 'bandwidth_measured', - 'latency_measured': 'latency_measured', - 'packetloss_required': 'packetloss_required', - 'packetloss_measured': 'packetloss_measured', - 'availability_required': 'availability_required', - 'availability_measured': 'availability_measured' + "min_bw": "min_bw", + "max_delay": "max_delay", + "max_number_oxps": "max_number_oxps", + "bandwidth_measured": "bandwidth_measured", + "latency_measured": "latency_measured", + "packetloss_required": "packetloss_required", + "packetloss_measured": "packetloss_measured", + "availability_required": "availability_required", + "availability_measured": "availability_measured", } self._min_bw = min_bw self._max_delay = max_delay @@ -71,7 +85,7 @@ def __init__(self, min_bw: ConnectionQosUnit=None, max_delay: ConnectionQosUnit= self._availability_measured = availability_measured @classmethod - def from_dict(cls, dikt) -> 'ConnectionQosMetrics': + def from_dict(cls, dikt) -> "ConnectionQosMetrics": """Returns the dict as a model :param dikt: A dict. diff --git a/sdx_controller/models/connection_qos_unit.py b/sdx_controller/models/connection_qos_unit.py index fd0eda0..fd141bd 100644 --- a/sdx_controller/models/connection_qos_unit.py +++ b/sdx_controller/models/connection_qos_unit.py @@ -1,12 +1,12 @@ # coding: utf-8 from __future__ import absolute_import -from datetime import date, datetime # noqa: F401 -from typing import List, Dict # noqa: F401 +from datetime import date, datetime # noqa: F401 +from typing import Dict, List # noqa: F401 -from sdx_controller.models.base_model_ import Model from sdx_controller import util +from sdx_controller.models.base_model_ import Model class ConnectionQosUnit(Model): @@ -14,7 +14,8 @@ class ConnectionQosUnit(Model): Do not edit the class manually. """ - def __init__(self, value: int=None, strict: bool=None): # noqa: E501 + + def __init__(self, value: int = None, strict: bool = None): # noqa: E501 """ConnectionQosUnit - a model defined in Swagger :param value: The value of this ConnectionQosUnit. # noqa: E501 @@ -22,20 +23,14 @@ def __init__(self, value: int=None, strict: bool=None): # noqa: E501 :param strict: The strict of this ConnectionQosUnit. # noqa: E501 :type strict: bool """ - self.swagger_types = { - 'value': int, - 'strict': bool - } - - self.attribute_map = { - 'value': 'value', - 'strict': 'strict' - } + self.swagger_types = {"value": int, "strict": bool} + + self.attribute_map = {"value": "value", "strict": "strict"} self._value = value self._strict = strict @classmethod - def from_dict(cls, dikt) -> 'ConnectionQosUnit': + def from_dict(cls, dikt) -> "ConnectionQosUnit": """Returns the dict as a model :param dikt: A dict. diff --git a/sdx_controller/models/connection_scheduling.py b/sdx_controller/models/connection_scheduling.py index 1824104..0e88d17 100644 --- a/sdx_controller/models/connection_scheduling.py +++ b/sdx_controller/models/connection_scheduling.py @@ -1,12 +1,12 @@ # coding: utf-8 from __future__ import absolute_import -from datetime import date, datetime # noqa: F401 -from typing import List, Dict # noqa: F401 +from datetime import date, datetime # noqa: F401 +from typing import Dict, List # noqa: F401 -from sdx_controller.models.base_model_ import Model from sdx_controller import util +from sdx_controller.models.base_model_ import Model class ConnectionScheduling(Model): @@ -14,7 +14,10 @@ class ConnectionScheduling(Model): Do not edit the class manually. """ - def __init__(self, start_time: datetime=None, end_time: datetime=None): # noqa: E501 + + def __init__( + self, start_time: datetime = None, end_time: datetime = None + ): # noqa: E501 """ConnectionScheduling - a model defined in Swagger :param start_time: The start_time of this ConnectionScheduling. # noqa: E501 @@ -22,20 +25,14 @@ def __init__(self, start_time: datetime=None, end_time: datetime=None): # noqa: :param end_time: The end_time of this ConnectionScheduling. # noqa: E501 :type end_time: datetime """ - self.swagger_types = { - 'start_time': datetime, - 'end_time': datetime - } - - self.attribute_map = { - 'start_time': 'start_time', - 'end_time': 'end_time' - } + self.swagger_types = {"start_time": datetime, "end_time": datetime} + + self.attribute_map = {"start_time": "start_time", "end_time": "end_time"} self._start_time = start_time self._end_time = end_time @classmethod - def from_dict(cls, dikt) -> 'ConnectionScheduling': + def from_dict(cls, dikt) -> "ConnectionScheduling": """Returns the dict as a model :param dikt: A dict. diff --git a/sdx_controller/models/connection_v2.py b/sdx_controller/models/connection_v2.py index 46a5c02..12830fc 100644 --- a/sdx_controller/models/connection_v2.py +++ b/sdx_controller/models/connection_v2.py @@ -1,17 +1,25 @@ # coding: utf-8 from __future__ import absolute_import -from datetime import date, datetime # noqa: F401 -from typing import List, Dict # noqa: F401 +from datetime import date, datetime # noqa: F401 +from typing import Dict, List # noqa: F401 +from sdx_controller import util from sdx_controller.models.base_model_ import Model -from sdx_controller.models.connection_qos_metrics import ConnectionQosMetrics # noqa: F401,E501 -from sdx_controller.models.connection_scheduling import ConnectionScheduling # noqa: F401,E501 -from sdx_controller.models.connection_v2_endpoints import ConnectionV2Endpoints # noqa: F401,E501 -from sdx_controller.models.connection_v2_notifications import ConnectionV2Notifications # noqa: F401,E501 +from sdx_controller.models.connection_qos_metrics import ( # noqa: F401,E501 + ConnectionQosMetrics, +) +from sdx_controller.models.connection_scheduling import ( # noqa: F401,E501 + ConnectionScheduling, +) +from sdx_controller.models.connection_v2_endpoints import ( # noqa: F401,E501 + ConnectionV2Endpoints, +) +from sdx_controller.models.connection_v2_notifications import ( # noqa: F401,E501 + ConnectionV2Notifications, +) from sdx_controller.models.link import Link # noqa: F401,E501 -from sdx_controller import util class ConnectionV2(Model): @@ -19,7 +27,25 @@ class ConnectionV2(Model): Do not edit the class manually. """ - def __init__(self, name: str=None, endpoints: List[ConnectionV2Endpoints]=None, description: str=None, notifications: List[ConnectionV2Notifications]=None, scheduling: ConnectionScheduling=None, qos_metrics: Dict[str, ConnectionQosMetrics]=None, paths: List[str]=None, status: str=None, complete: bool=False, quantity: int=None, multi_path: bool=None, preempt: bool=None, backup_path_type: str=None, exclusive_links: List[Link]=None, inclusive_links: List[Link]=None): # noqa: E501 + + def __init__( + self, + name: str = None, + endpoints: List[ConnectionV2Endpoints] = None, + description: str = None, + notifications: List[ConnectionV2Notifications] = None, + scheduling: ConnectionScheduling = None, + qos_metrics: Dict[str, ConnectionQosMetrics] = None, + paths: List[str] = None, + status: str = None, + complete: bool = False, + quantity: int = None, + multi_path: bool = None, + preempt: bool = None, + backup_path_type: str = None, + exclusive_links: List[Link] = None, + inclusive_links: List[Link] = None, + ): # noqa: E501 """ConnectionV2 - a model defined in Swagger :param name: The name of this ConnectionV2. # noqa: E501 @@ -54,39 +80,39 @@ def __init__(self, name: str=None, endpoints: List[ConnectionV2Endpoints]=None, :type inclusive_links: List[Link] """ self.swagger_types = { - 'name': str, - 'endpoints': List[ConnectionV2Endpoints], - 'description': str, - 'notifications': List[ConnectionV2Notifications], - 'scheduling': ConnectionScheduling, - 'qos_metrics': Dict[str, ConnectionQosMetrics], - 'paths': List[str], - 'status': str, - 'complete': bool, - 'quantity': int, - 'multi_path': bool, - 'preempt': bool, - 'backup_path_type': str, - 'exclusive_links': List[Link], - 'inclusive_links': List[Link] + "name": str, + "endpoints": List[ConnectionV2Endpoints], + "description": str, + "notifications": List[ConnectionV2Notifications], + "scheduling": ConnectionScheduling, + "qos_metrics": Dict[str, ConnectionQosMetrics], + "paths": List[str], + "status": str, + "complete": bool, + "quantity": int, + "multi_path": bool, + "preempt": bool, + "backup_path_type": str, + "exclusive_links": List[Link], + "inclusive_links": List[Link], } self.attribute_map = { - 'name': 'name', - 'endpoints': 'endpoints', - 'description': 'description', - 'notifications': 'notifications', - 'scheduling': 'scheduling', - 'qos_metrics': 'qos_metrics', - 'paths': 'paths', - 'status': 'status', - 'complete': 'complete', - 'quantity': 'quantity', - 'multi_path': 'multi_path', - 'preempt': 'preempt', - 'backup_path_type': 'backup_path_type', - 'exclusive_links': 'exclusive_links', - 'inclusive_links': 'inclusive_links' + "name": "name", + "endpoints": "endpoints", + "description": "description", + "notifications": "notifications", + "scheduling": "scheduling", + "qos_metrics": "qos_metrics", + "paths": "paths", + "status": "status", + "complete": "complete", + "quantity": "quantity", + "multi_path": "multi_path", + "preempt": "preempt", + "backup_path_type": "backup_path_type", + "exclusive_links": "exclusive_links", + "inclusive_links": "inclusive_links", } self._name = name self._endpoints = endpoints @@ -105,7 +131,7 @@ def __init__(self, name: str=None, endpoints: List[ConnectionV2Endpoints]=None, self._inclusive_links = inclusive_links @classmethod - def from_dict(cls, dikt) -> 'ConnectionV2': + def from_dict(cls, dikt) -> "ConnectionV2": """Returns the dict as a model :param dikt: A dict. @@ -134,7 +160,9 @@ def name(self, name: str): :type name: str """ if name is None: - raise ValueError("Invalid value for `name`, must not be `None`") # noqa: E501 + raise ValueError( + "Invalid value for `name`, must not be `None`" + ) # noqa: E501 self._name = name @@ -157,7 +185,9 @@ def endpoints(self, endpoints: List[ConnectionV2Endpoints]): :type endpoints: List[ConnectionV2Endpoints] """ if endpoints is None: - raise ValueError("Invalid value for `endpoints`, must not be `None`") # noqa: E501 + raise ValueError( + "Invalid value for `endpoints`, must not be `None`" + ) # noqa: E501 self._endpoints = endpoints @@ -289,8 +319,9 @@ def status(self, status: str): allowed_values = ["success", "fail", "scheduled", "provisioining"] # noqa: E501 if status not in allowed_values: raise ValueError( - "Invalid value for `status` ({0}), must be one of {1}" - .format(status, allowed_values) + "Invalid value for `status` ({0}), must be one of {1}".format( + status, allowed_values + ) ) self._status = status @@ -400,8 +431,9 @@ def backup_path_type(self, backup_path_type: str): allowed_values = ["0", "1", "2", "3"] # noqa: E501 if backup_path_type not in allowed_values: raise ValueError( - "Invalid value for `backup_path_type` ({0}), must be one of {1}" - .format(backup_path_type, allowed_values) + "Invalid value for `backup_path_type` ({0}), must be one of {1}".format( + backup_path_type, allowed_values + ) ) self._backup_path_type = backup_path_type diff --git a/sdx_controller/models/connection_v2_endpoints.py b/sdx_controller/models/connection_v2_endpoints.py index 8f69f2e..8f48643 100644 --- a/sdx_controller/models/connection_v2_endpoints.py +++ b/sdx_controller/models/connection_v2_endpoints.py @@ -1,12 +1,12 @@ # coding: utf-8 from __future__ import absolute_import -from datetime import date, datetime # noqa: F401 -from typing import List, Dict # noqa: F401 +from datetime import date, datetime # noqa: F401 +from typing import Dict, List # noqa: F401 -from sdx_controller.models.base_model_ import Model from sdx_controller import util +from sdx_controller.models.base_model_ import Model class ConnectionV2Endpoints(Model): @@ -14,7 +14,8 @@ class ConnectionV2Endpoints(Model): Do not edit the class manually. """ - def __init__(self, port_id: str=None, vlan: str=None): # noqa: E501 + + def __init__(self, port_id: str = None, vlan: str = None): # noqa: E501 """ConnectionV2Endpoints - a model defined in Swagger :param port_id: The port_id of this ConnectionV2Endpoints. # noqa: E501 @@ -22,20 +23,14 @@ def __init__(self, port_id: str=None, vlan: str=None): # noqa: E501 :param vlan: The vlan of this ConnectionV2Endpoints. # noqa: E501 :type vlan: str """ - self.swagger_types = { - 'port_id': str, - 'vlan': str - } - - self.attribute_map = { - 'port_id': 'port_id', - 'vlan': 'vlan' - } + self.swagger_types = {"port_id": str, "vlan": str} + + self.attribute_map = {"port_id": "port_id", "vlan": "vlan"} self._port_id = port_id self._vlan = vlan @classmethod - def from_dict(cls, dikt) -> 'ConnectionV2Endpoints': + def from_dict(cls, dikt) -> "ConnectionV2Endpoints": """Returns the dict as a model :param dikt: A dict. diff --git a/sdx_controller/models/connection_v2_notifications.py b/sdx_controller/models/connection_v2_notifications.py index 07b6ed7..49bf42e 100644 --- a/sdx_controller/models/connection_v2_notifications.py +++ b/sdx_controller/models/connection_v2_notifications.py @@ -1,12 +1,12 @@ # coding: utf-8 from __future__ import absolute_import -from datetime import date, datetime # noqa: F401 -from typing import List, Dict # noqa: F401 +from datetime import date, datetime # noqa: F401 +from typing import Dict, List # noqa: F401 -from sdx_controller.models.base_model_ import Model from sdx_controller import util +from sdx_controller.models.base_model_ import Model class ConnectionV2Notifications(Model): @@ -14,23 +14,20 @@ class ConnectionV2Notifications(Model): Do not edit the class manually. """ - def __init__(self, email: str=None): # noqa: E501 + + def __init__(self, email: str = None): # noqa: E501 """ConnectionV2Notifications - a model defined in Swagger :param email: The email of this ConnectionV2Notifications. # noqa: E501 :type email: str """ - self.swagger_types = { - 'email': str - } + self.swagger_types = {"email": str} - self.attribute_map = { - 'email': 'email' - } + self.attribute_map = {"email": "email"} self._email = email @classmethod - def from_dict(cls, dikt) -> 'ConnectionV2Notifications': + def from_dict(cls, dikt) -> "ConnectionV2Notifications": """Returns the dict as a model :param dikt: A dict. diff --git a/sdx_controller/models/l2vpn_body.py b/sdx_controller/models/l2vpn_body.py index f569c9c..84b7ec8 100644 --- a/sdx_controller/models/l2vpn_body.py +++ b/sdx_controller/models/l2vpn_body.py @@ -1,12 +1,12 @@ # coding: utf-8 from __future__ import absolute_import -from datetime import date, datetime # noqa: F401 -from typing import List, Dict # noqa: F401 +from datetime import date, datetime # noqa: F401 +from typing import Dict, List # noqa: F401 -from sdx_controller.models.base_model_ import Model from sdx_controller import util +from sdx_controller.models.base_model_ import Model class L2vpnBody(Model): @@ -14,18 +14,15 @@ class L2vpnBody(Model): Do not edit the class manually. """ - def __init__(self): # noqa: E501 - """L2vpnBody - a model defined in Swagger - """ - self.swagger_types = { - } + def __init__(self): # noqa: E501 + """L2vpnBody - a model defined in Swagger""" + self.swagger_types = {} - self.attribute_map = { - } + self.attribute_map = {} @classmethod - def from_dict(cls, dikt) -> 'L2vpnBody': + def from_dict(cls, dikt) -> "L2vpnBody": """Returns the dict as a model :param dikt: A dict. diff --git a/sdx_controller/test/test_l2vpn_controller.py b/sdx_controller/test/test_l2vpn_controller.py index 1c071ce..2ea8233 100644 --- a/sdx_controller/test/test_l2vpn_controller.py +++ b/sdx_controller/test/test_l2vpn_controller.py @@ -19,10 +19,12 @@ def test_delete_connection(self): Delete connection order by ID """ response = self.client.open( - '/SDX-Controller/1.0.0/l2vpn/{service_id}'.format(service_id='38400000-8cf0-11bd-b23e-10b96e4ef00d'), - method='DELETE') - self.assert200(response, - 'Response body is : ' + response.data.decode('utf-8')) + "/SDX-Controller/1.0.0/l2vpn/{service_id}".format( + service_id="38400000-8cf0-11bd-b23e-10b96e4ef00d" + ), + method="DELETE", + ) + self.assert200(response, "Response body is : " + response.data.decode("utf-8")) def test_getconnection_by_id(self): """Test case for getconnection_by_id @@ -30,21 +32,20 @@ def test_getconnection_by_id(self): Find l2vpn connection by ID """ response = self.client.open( - '/SDX-Controller/1.0.0/l2vpn/{service_id}'.format(service_id='38400000-8cf0-11bd-b23e-10b96e4ef00d'), - method='GET') - self.assert200(response, - 'Response body is : ' + response.data.decode('utf-8')) + "/SDX-Controller/1.0.0/l2vpn/{service_id}".format( + service_id="38400000-8cf0-11bd-b23e-10b96e4ef00d" + ), + method="GET", + ) + self.assert200(response, "Response body is : " + response.data.decode("utf-8")) def test_getconnections(self): """Test case for getconnections List all l2vpn connections """ - response = self.client.open( - '/SDX-Controller/1.0.0/l2vpn', - method='GET') - self.assert200(response, - 'Response body is : ' + response.data.decode('utf-8')) + response = self.client.open("/SDX-Controller/1.0.0/l2vpn", method="GET") + self.assert200(response, "Response body is : " + response.data.decode("utf-8")) def test_place_connection(self): """Test case for place_connection @@ -53,14 +54,15 @@ def test_place_connection(self): """ body = L2vpnBody() response = self.client.open( - '/SDX-Controller/1.0.0/l2vpn', - method='POST', + "/SDX-Controller/1.0.0/l2vpn", + method="POST", data=json.dumps(body), - content_type='application/json') - self.assert200(response, - 'Response body is : ' + response.data.decode('utf-8')) + content_type="application/json", + ) + self.assert200(response, "Response body is : " + response.data.decode("utf-8")) -if __name__ == '__main__': +if __name__ == "__main__": import unittest + unittest.main() From d467d73659882ae17c191c607f680b0c94e9ad86 Mon Sep 17 00:00:00 2001 From: Yufeng Xin Date: Mon, 19 Aug 2024 13:13:17 -0400 Subject: [PATCH 03/13] adding the patch method --- .../controllers/l2vpn_controller.py | 15 ++++++++ sdx_controller/swagger/swagger.yaml | 35 ++++++++++++++++--- sdx_controller/test/test_l2vpn_controller.py | 11 ++++++ 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/sdx_controller/controllers/l2vpn_controller.py b/sdx_controller/controllers/l2vpn_controller.py index f64f56f..d4c2407 100644 --- a/sdx_controller/controllers/l2vpn_controller.py +++ b/sdx_controller/controllers/l2vpn_controller.py @@ -155,3 +155,18 @@ def place_connection(body): # response["reason"] = reason # `reason` is not present in schema though. return response, code + + +def patch_connection(service_id): # noqa: E501 + """Edit and change an existing L2vpn connection by ID from the SDX-Controller + + # noqa: E501 + + :param service_id: ID of l2vpn connection that needs to be changed + :type service_id: dict | bytes + + :rtype: Connection + """ + if connexion.request.is_json: + service_id = Object.from_dict(connexion.request.get_json()) # noqa: E501 + return "do some magic!" diff --git a/sdx_controller/swagger/swagger.yaml b/sdx_controller/swagger/swagger.yaml index 7076e0f..0149d74 100644 --- a/sdx_controller/swagger/swagger.yaml +++ b/sdx_controller/swagger/swagger.yaml @@ -155,7 +155,7 @@ paths: $ref: '#/components/schemas/connection' "404": description: no connections found - x-openapi-router-controller: sdx_controller.controllers.connection_controller + x-openapi-router-controller: sdx_controller.controllers.l2vpn_controller post: tags: - l2vpn @@ -177,7 +177,7 @@ paths: $ref: '#/components/schemas/connection' "400": description: Invalid Connection - x-openapi-router-controller: sdx_controller.controllers.connection_controller + x-openapi-router-controller: sdx_controller.controllers.l2vpn_controller /l2vpn/{service_id}: get: tags: @@ -209,7 +209,7 @@ paths: description: Invalid ID supplied "404": description: connection not found - x-openapi-router-controller: sdx_controller.controllers.connection_controller + x-openapi-router-controller: sdx_controller.controllers.l2vpn_controller delete: tags: - l2vpn @@ -233,7 +233,34 @@ paths: description: Invalid ID supplied "404": description: connection not found - x-openapi-router-controller: sdx_controller.controllers.connection_controller + x-openapi-router-controller: sdx_controller.controllers.l2vpn_controller + patch: + tags: + - l2vpn + summary: Edit and change an existing L2vpn connection by ID from the SDX-Controller + operationId: patch_connection + parameters: + - name: service_id + in: path + description: ID of l2vpn connection that needs to be changed + required: true + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/connection' + - $ref: '#/components/schemas/connection_v2' + x-content-type: application/json + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/connection' + "400": + description: Invalid Connection + x-openapi-router-controller: sdx_controller.controllers.l2vpn_controller /user: post: tags: diff --git a/sdx_controller/test/test_l2vpn_controller.py b/sdx_controller/test/test_l2vpn_controller.py index 2ea8233..eb23fb8 100644 --- a/sdx_controller/test/test_l2vpn_controller.py +++ b/sdx_controller/test/test_l2vpn_controller.py @@ -61,6 +61,17 @@ def test_place_connection(self): ) self.assert200(response, "Response body is : " + response.data.decode("utf-8")) + def test_patch_connection(self): + """Test case for patch_connection + + Edit and change an existing L2vpn connection by ID from the SDX-Controller + """ + response = self.client.open( + "/SDX-Controller/1.0.0/l2vpn/{service_id}".format(service_id=Object()), + method="PATCH", + ) + self.assert200(response, "Response body is : " + response.data.decode("utf-8")) + if __name__ == "__main__": import unittest From b3ef1c91b07326b5376b781d07ab63aa0d6a8d1e Mon Sep 17 00:00:00 2001 From: Yufeng Xin Date: Mon, 19 Aug 2024 13:18:40 -0400 Subject: [PATCH 04/13] patch --- sdx_controller/controllers/l2vpn_controller.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sdx_controller/controllers/l2vpn_controller.py b/sdx_controller/controllers/l2vpn_controller.py index d4c2407..c3f02d4 100644 --- a/sdx_controller/controllers/l2vpn_controller.py +++ b/sdx_controller/controllers/l2vpn_controller.py @@ -157,7 +157,7 @@ def place_connection(body): return response, code -def patch_connection(service_id): # noqa: E501 +def patch_connection(connection_id): # noqa: E501 """Edit and change an existing L2vpn connection by ID from the SDX-Controller # noqa: E501 @@ -167,6 +167,8 @@ def patch_connection(service_id): # noqa: E501 :rtype: Connection """ - if connexion.request.is_json: - service_id = Object.from_dict(connexion.request.get_json()) # noqa: E501 + value = db_instance.read_from_db("connections", f"{connection_id}") + if not value: + return "Connection not found", 404 + # return json.loads(value[connection_id]) return "do some magic!" From 4b3b303faad91e98a94acd70e9fec974f9a6c95a Mon Sep 17 00:00:00 2001 From: Yufeng Xin Date: Mon, 19 Aug 2024 13:23:21 -0400 Subject: [PATCH 05/13] patch --- sdx_controller/controllers/l2vpn_controller.py | 3 +-- sdx_controller/test/test_l2vpn_controller.py | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/sdx_controller/controllers/l2vpn_controller.py b/sdx_controller/controllers/l2vpn_controller.py index c3f02d4..1588f5c 100644 --- a/sdx_controller/controllers/l2vpn_controller.py +++ b/sdx_controller/controllers/l2vpn_controller.py @@ -170,5 +170,4 @@ def patch_connection(connection_id): # noqa: E501 value = db_instance.read_from_db("connections", f"{connection_id}") if not value: return "Connection not found", 404 - # return json.loads(value[connection_id]) - return "do some magic!" + return json.loads(value[connection_id]) diff --git a/sdx_controller/test/test_l2vpn_controller.py b/sdx_controller/test/test_l2vpn_controller.py index eb23fb8..fc1e43e 100644 --- a/sdx_controller/test/test_l2vpn_controller.py +++ b/sdx_controller/test/test_l2vpn_controller.py @@ -67,7 +67,9 @@ def test_patch_connection(self): Edit and change an existing L2vpn connection by ID from the SDX-Controller """ response = self.client.open( - "/SDX-Controller/1.0.0/l2vpn/{service_id}".format(service_id=Object()), + "/SDX-Controller/1.0.0/l2vpn/{service_id}".format( + service_id="38400000-8cf0-11bd-b23e-10b96e4ef00d" + ), method="PATCH", ) self.assert200(response, "Response body is : " + response.data.decode("utf-8")) From 38f51d94b4c6395e655943338006833760a2ccc7 Mon Sep 17 00:00:00 2001 From: Yufeng Xin Date: Mon, 19 Aug 2024 13:52:11 -0400 Subject: [PATCH 06/13] patch test --- .../controllers/l2vpn_controller.py | 15 +++++++-- .../models/l2vpn_service_id_body.py | 33 +++++++++++++++++++ sdx_controller/swagger/swagger.yaml | 15 ++++++--- 3 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 sdx_controller/models/l2vpn_service_id_body.py diff --git a/sdx_controller/controllers/l2vpn_controller.py b/sdx_controller/controllers/l2vpn_controller.py index 1588f5c..d619915 100644 --- a/sdx_controller/controllers/l2vpn_controller.py +++ b/sdx_controller/controllers/l2vpn_controller.py @@ -157,17 +157,28 @@ def place_connection(body): return response, code -def patch_connection(connection_id): # noqa: E501 +def patch_connection(connection_id, body=None): # noqa: E501 """Edit and change an existing L2vpn connection by ID from the SDX-Controller # noqa: E501 :param service_id: ID of l2vpn connection that needs to be changed - :type service_id: dict | bytes + :type service_id: dict | bytes' + :param body: + :type body: dict | bytes :rtype: Connection """ value = db_instance.read_from_db("connections", f"{connection_id}") if not value: return "Connection not found", 404 + + logger.info(f"Changed connection: {body}") + if not connexion.request.is_json: + return "Request body must be JSON", 400 + + body = L2vpnServiceIdBody.from_dict(connexion.request.get_json()) # noqa: E501 + + logger.info(f"Gathered connexion JSON: {body}") + return json.loads(value[connection_id]) diff --git a/sdx_controller/models/l2vpn_service_id_body.py b/sdx_controller/models/l2vpn_service_id_body.py new file mode 100644 index 0000000..e317c93 --- /dev/null +++ b/sdx_controller/models/l2vpn_service_id_body.py @@ -0,0 +1,33 @@ +# coding: utf-8 + +from __future__ import absolute_import + +from datetime import date, datetime # noqa: F401 +from typing import Dict, List # noqa: F401 + +from sdx_controller import util +from sdx_controller.models.base_model_ import Model + + +class L2vpnServiceIdBody(Model): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + + def __init__(self): # noqa: E501 + """L2vpnServiceIdBody - a model defined in Swagger""" + self.swagger_types = {} + + self.attribute_map = {} + + @classmethod + def from_dict(cls, dikt) -> "L2vpnServiceIdBody": + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The l2vpn_service_id_body of this L2vpnServiceIdBody. # noqa: E501 + :rtype: L2vpnServiceIdBody + """ + return util.deserialize_model(dikt, cls) diff --git a/sdx_controller/swagger/swagger.yaml b/sdx_controller/swagger/swagger.yaml index 0149d74..e186240 100644 --- a/sdx_controller/swagger/swagger.yaml +++ b/sdx_controller/swagger/swagger.yaml @@ -244,13 +244,16 @@ paths: in: path description: ID of l2vpn connection that needs to be changed required: true + style: simple + explode: false + schema: + type: string + format: uuid + requestBody: content: application/json: schema: - oneOf: - - $ref: '#/components/schemas/connection' - - $ref: '#/components/schemas/connection_v2' - x-content-type: application/json + $ref: '#/components/schemas/l2vpn_service_id_body' responses: "200": description: successful operation @@ -956,6 +959,10 @@ components: oneOf: - $ref: '#/components/schemas/connection' - $ref: '#/components/schemas/connection_v2' + l2vpn_service_id_body: + oneOf: + - $ref: '#/components/schemas/connection' + - $ref: '#/components/schemas/connection_v2' connection_v2_endpoints: type: object properties: From d1d15293e5a0e16a330223677c2922d6553d5dc2 Mon Sep 17 00:00:00 2001 From: Yufeng Xin Date: Mon, 19 Aug 2024 14:00:12 -0400 Subject: [PATCH 07/13] serviceidbody --- sdx_controller/controllers/l2vpn_controller.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sdx_controller/controllers/l2vpn_controller.py b/sdx_controller/controllers/l2vpn_controller.py index d619915..a974449 100644 --- a/sdx_controller/controllers/l2vpn_controller.py +++ b/sdx_controller/controllers/l2vpn_controller.py @@ -5,6 +5,11 @@ import connexion from flask import current_app +from sdx_controller.models.connection import Connection # noqa: E501 +from sdx_controller.models.l2vpn_body import L2vpnBody # noqa: E501 +from sdx_controller.models.l2vpn_service_id_body import L2vpnServiceIdBody # noqa: E501 +from sdx_controller import util + from sdx_controller.handlers.connection_handler import ConnectionHandler from sdx_controller.utils.db_utils import DbUtils From 7e0985bc8e8aabd76a207d47c0a8018ebd4f4e3d Mon Sep 17 00:00:00 2001 From: Yufeng Xin Date: Mon, 19 Aug 2024 14:01:13 -0400 Subject: [PATCH 08/13] isort --- sdx_controller/controllers/l2vpn_controller.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sdx_controller/controllers/l2vpn_controller.py b/sdx_controller/controllers/l2vpn_controller.py index a974449..6fda5bc 100644 --- a/sdx_controller/controllers/l2vpn_controller.py +++ b/sdx_controller/controllers/l2vpn_controller.py @@ -5,12 +5,11 @@ import connexion from flask import current_app +from sdx_controller import util +from sdx_controller.handlers.connection_handler import ConnectionHandler from sdx_controller.models.connection import Connection # noqa: E501 from sdx_controller.models.l2vpn_body import L2vpnBody # noqa: E501 from sdx_controller.models.l2vpn_service_id_body import L2vpnServiceIdBody # noqa: E501 -from sdx_controller import util - -from sdx_controller.handlers.connection_handler import ConnectionHandler from sdx_controller.utils.db_utils import DbUtils LOG_FORMAT = ( From 0412ffc9e82678f742852748834f3345e2868d9a Mon Sep 17 00:00:00 2001 From: Yufeng Xin Date: Mon, 19 Aug 2024 22:17:53 -0400 Subject: [PATCH 09/13] service_id --- sdx_controller/controllers/l2vpn_controller.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/sdx_controller/controllers/l2vpn_controller.py b/sdx_controller/controllers/l2vpn_controller.py index 6fda5bc..65a8037 100644 --- a/sdx_controller/controllers/l2vpn_controller.py +++ b/sdx_controller/controllers/l2vpn_controller.py @@ -26,16 +26,18 @@ connection_handler = ConnectionHandler(db_instance) -def delete_connection(connection_id): +def delete_connection(service_id): """ Delete connection order by ID. - :param connection_id: ID of the connection that needs to be + :param service_id: ID of the connection that needs to be deleted - :type connection_id: int + :type service_id: str :rtype: None """ + connection_id = service_id + logger.info( f"Handling delete (service id: {connection_id}) " f"with te_manager: {current_app.te_manager}" @@ -71,11 +73,13 @@ def getconnection_by_id(connection_id): """ Find connection by ID. - :param connection_id: ID of connection that needs to be fetched - :type connection_id: int + :param service_id: ID of connection that needs to be fetched + :type service_id: str :rtype: Connection """ + + connection_id = service_id value = db_instance.read_from_db("connections", f"{connection_id}") if not value: return "Connection not found", 404 From 3f9ffb04cbbc18895f40e952d69e147d5d92dc1f Mon Sep 17 00:00:00 2001 From: Yufeng Xin Date: Mon, 19 Aug 2024 22:22:18 -0400 Subject: [PATCH 10/13] service_id --- sdx_controller/controllers/l2vpn_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdx_controller/controllers/l2vpn_controller.py b/sdx_controller/controllers/l2vpn_controller.py index 65a8037..3d81eba 100644 --- a/sdx_controller/controllers/l2vpn_controller.py +++ b/sdx_controller/controllers/l2vpn_controller.py @@ -69,7 +69,7 @@ def delete_connection(service_id): return "OK", 200 -def getconnection_by_id(connection_id): +def getconnection_by_id(service_id): """ Find connection by ID. From 8c3f74309c263e4d4fcd8ef0dd0749a2cf4d9a73 Mon Sep 17 00:00:00 2001 From: Yufeng Xin Date: Mon, 19 Aug 2024 22:43:54 -0400 Subject: [PATCH 11/13] back to existing tests --- sdx_controller/test/test_l2vpn_controller.py | 417 +++++++++++++++++-- 1 file changed, 380 insertions(+), 37 deletions(-) diff --git a/sdx_controller/test/test_l2vpn_controller.py b/sdx_controller/test/test_l2vpn_controller.py index fc1e43e..16d0968 100644 --- a/sdx_controller/test/test_l2vpn_controller.py +++ b/sdx_controller/test/test_l2vpn_controller.py @@ -2,80 +2,423 @@ from __future__ import absolute_import +import unittest +import uuid +from unittest.mock import patch + from flask import json from six import BytesIO -from sdx_controller.models.connection import Connection # noqa: E501 +from sdx_controller.models.connection import Connection from sdx_controller.models.l2vpn_body import L2vpnBody # noqa: E501 -from sdx_controller.test import BaseTestCase +from sdx_controller.test import BaseTestCase, TestData + +BASE_PATH = "/SDX-Controller/1.0.0" class TestL2vpnController(BaseTestCase): """L2vpnController integration test stubs""" - def test_delete_connection(self): - """Test case for delete_connection + def test_delete_connection_no_setup(self): + """ + Test case for delete_connection(). - Delete connection order by ID + Delete connection order by ID. """ + connection_id = 2 response = self.client.open( - "/SDX-Controller/1.0.0/l2vpn/{service_id}".format( - service_id="38400000-8cf0-11bd-b23e-10b96e4ef00d" - ), + f"{BASE_PATH}/l2vpn/{connection_id}", + method="DELETE", + ) + self.assert404(response, f"Response body is : {response.data.decode('utf-8')}") + + def __add_the_three_topologies(self): + """ + A helper to add the three known topologies. + """ + for idx, topology_file in enumerate( + [ + 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): + """ + Test case for delete_connection() + + Set up a connection request, get the connection ID from the + response, and then do `DELETE /l2vpn/:connection_id` + """ + # set up temanager connection first + self.__add_the_three_topologies() + + request_body = TestData.CONNECTION_REQ.read_text() + + connection_response = self.client.open( + f"{BASE_PATH}/l2vpn", + 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("service_id") + print(f"Deleting request_id: {connection_id}") + + delete_response = self.client.open( + f"{BASE_PATH}/l2vpn/{connection_id}", method="DELETE", ) - self.assert200(response, "Response body is : " + response.data.decode("utf-8")) + + 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 + """ + Test case for getconnection_by_id. - Find l2vpn connection by ID + Find connection by ID. """ + connection_id = 10 response = self.client.open( - "/SDX-Controller/1.0.0/l2vpn/{service_id}".format( - service_id="38400000-8cf0-11bd-b23e-10b96e4ef00d" - ), + f"{BASE_PATH}/l2vpn/{connection_id}", method="GET", ) - self.assert200(response, "Response body is : " + response.data.decode("utf-8")) - def test_getconnections(self): - """Test case for getconnections + # The connection_id we've supplied above should not exist. + # TODO: test for existing connection_id. See + # https://github.com/atlanticwave-sdx/sdx-controller/issues/34. + self.assertStatus(response, 404) - List all l2vpn connections + def test_place_connection_no_topology(self): """ - response = self.client.open("/SDX-Controller/1.0.0/l2vpn", method="GET") - self.assert200(response, "Response body is : " + response.data.decode("utf-8")) - - def test_place_connection(self): - """Test case for place_connection + Test case for place_connection. - Place an L2vpn connection request from the SDX-Controller + Place a connection request with no topology present. """ - body = L2vpnBody() + body = Connection() + response = self.client.open( - "/SDX-Controller/1.0.0/l2vpn", + f"{BASE_PATH}/l2vpn", method="POST", data=json.dumps(body), content_type="application/json", ) - self.assert200(response, "Response body is : " + response.data.decode("utf-8")) + print(f"Response body is : {response.data.decode('utf-8')}") - def test_patch_connection(self): - """Test case for patch_connection + # Expect 400 failure because the request is incomplete: the + # bare minimum connection request we sent does not have + # ingress port data, etc., for example. + self.assertStatus(response, 400) - Edit and change an existing L2vpn connection by ID from the SDX-Controller + def __test_with_one_topology(self, topology_file): + """ + A helper method to test place_connection() with just one topology. """ + topology = json.loads(topology_file.read_text()) + self.te_manager.add_topology(topology) + + request = TestData.CONNECTION_REQ.read_text() + response = self.client.open( - "/SDX-Controller/1.0.0/l2vpn/{service_id}".format( - service_id="38400000-8cf0-11bd-b23e-10b96e4ef00d" - ), - method="PATCH", + f"{BASE_PATH}/l2vpn", + method="POST", + data=request, + content_type="application/json", ) - self.assert200(response, "Response body is : " + response.data.decode("utf-8")) + print(f"Response body is : {response.data.decode('utf-8')}") -if __name__ == "__main__": - import unittest + # Expect 400 failure, because TEManager do not have enough + # topology data. + self.assertStatus(response, 400) + + def test_place_connection_with_amlight(self): + """ + Test place_connection() with just Amlight topology. + """ + self.__test_with_one_topology(TestData.TOPOLOGY_FILE_AMLIGHT) + + def test_place_connection_with_sax(self): + """ + Test place_connection() with just SAX topology. + """ + self.__test_with_one_topology(TestData.TOPOLOGY_FILE_SAX) + + def test_place_connection_with_zaoxi(self): + """ + Test place_connection() with just ZAOXI topology. + """ + 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}/l2vpn", + 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.assertIn("is not valid under any of the given schemas", response["detail"]) + + def test_place_connection_with_three_topologies(self): + """ + Test case for place_connection. + + Place a connection request when some topologies are known. + """ + self.__add_the_three_topologies() + + request = TestData.CONNECTION_REQ.read_text() + + response = self.client.open( + f"{BASE_PATH}/l2vpn", + method="POST", + data=request, + content_type="application/json", + ) + + print(f"Response body is : {response.data.decode('utf-8')}") + + # Expect 200 success because TEManager now should be properly + # set up with all the expected topology data. + self.assertStatus(response, 200) + + def test_place_connection_with_three_topologies_added_in_sequence(self): + """ + Test case for place_connection. + + 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( + [ + 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 = TestData.CONNECTION_REQ.read_text() + + response = self.client.open( + f"{BASE_PATH}/l2vpn", + method="POST", + data=request, + content_type="application/json", + ) + print(f"Response body is : {response.data.decode('utf-8')}") + + if idx in [0, 1]: + # Expect 400 failure because TEManager do not have all + # the topologies yet. + self.assertStatus(response, 400) + if idx == 200: + # Expect 200 success now that TEManager should be set + # 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}/l2vpn", + 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("service_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}/l2vpn", + 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("service_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. + """ + # Generate a random ID. + connection_id = uuid.uuid4() + response = self.client.open( + f"{BASE_PATH}/l2vpn/{connection_id}", + method="GET", + ) + + print(f"Response body is : {response.data.decode('utf-8')}") + + self.assertStatus(response, 404) + + def test_z100_getconnection_by_id_expect_200(self): + """ + Test getconnection_by_id with a non-existent connection ID. + """ + + self.__add_the_three_topologies() + + request_body = TestData.CONNECTION_REQ.read_text() + + post_response = self.client.open( + f"{BASE_PATH}/l2vpn", + method="POST", + data=request_body, + content_type="application/json", + ) + + print(f"Response body: {post_response.data.decode('utf-8')}") + + self.assertStatus(post_response, 200) + + connection_id = post_response.get_json().get("service_id") + print(f"Got connection_id: {connection_id}") + + # Now try `GET /l2vpn/{connection_id}` + get_response = self.client.open( + f"{BASE_PATH}/l2vpn/{connection_id}", + method="GET", + ) + + print(f"Response body: {get_response.data.decode('utf-8')}") + + self.assertStatus(get_response, 200) + + @patch("sdx_controller.utils.db_utils.DbUtils.get_all_entries_in_collection") + def test_z105_getconnections_fail(self, mock_get_all_entries): + """Test case for getconnections.""" + mock_get_all_entries.return_value = {} + response = self.client.open( + f"{BASE_PATH}/l2vpn", + method="GET", + ) + self.assertStatus(response, 404) + + def test_z105_getconnections_success(self): + """Test case for getconnections.""" + response = self.client.open( + f"{BASE_PATH}/l2vpn", + method="GET", + ) + + print(f"Response body is : {response.data.decode('utf-8')}") + self.assertStatus(response, 200) + + assert len(response.get_json()) != 0 + + +if __name__ == "__main__": unittest.main() From 007d2bff120366404d6f066077aa2e05f1d104a7 Mon Sep 17 00:00:00 2001 From: Yufeng Xin Date: Mon, 19 Aug 2024 23:08:59 -0400 Subject: [PATCH 12/13] connectionv2 --- sdx_controller/test/test_l2vpn_controller.py | 21 ++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/sdx_controller/test/test_l2vpn_controller.py b/sdx_controller/test/test_l2vpn_controller.py index 16d0968..5d83784 100644 --- a/sdx_controller/test/test_l2vpn_controller.py +++ b/sdx_controller/test/test_l2vpn_controller.py @@ -121,6 +121,27 @@ def test_place_connection_no_topology(self): # ingress port data, etc., for example. self.assertStatus(response, 400) + def test_place_connection_v2_no_topology(self): + """ + Test case for place_connection. + + Place a connection request with no topology present. + """ + body = ConnectionV2() + + response = self.client.open( + f"{BASE_PATH}/l2vpn", + method="POST", + data=json.dumps(body), + content_type="application/json", + ) + print(f"Response body is : {response.data.decode('utf-8')}") + + # Expect 400 failure because the request is incomplete: the + # bare minimum connection request we sent does not have + # ingress port data, etc., for example. + self.assertStatus(response, 400) + def __test_with_one_topology(self, topology_file): """ A helper method to test place_connection() with just one topology. From df4acef297d28fd8f71e9f7401199969472988c5 Mon Sep 17 00:00:00 2001 From: Yufeng Xin Date: Mon, 19 Aug 2024 23:12:42 -0400 Subject: [PATCH 13/13] connectionv2 import --- sdx_controller/test/test_l2vpn_controller.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sdx_controller/test/test_l2vpn_controller.py b/sdx_controller/test/test_l2vpn_controller.py index 5d83784..1daf30d 100644 --- a/sdx_controller/test/test_l2vpn_controller.py +++ b/sdx_controller/test/test_l2vpn_controller.py @@ -10,6 +10,7 @@ from six import BytesIO from sdx_controller.models.connection import Connection +from sdx_controller.models.connection_v2 import ConnectionV2 from sdx_controller.models.l2vpn_body import L2vpnBody # noqa: E501 from sdx_controller.test import BaseTestCase, TestData