diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..2f84aa5 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,70 @@ +# Install Python dependencies, run tests and lint. +# +# For more information see: +# https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Test + +on: + push: + branches: [ "main", "develop" ] + pull_request: + branches: [ "main", "develop" ] + +permissions: + contents: read + +jobs: + test: + + runs-on: + - ubuntu-latest + + strategy: + matrix: + python-version: + - "3.7" + - "3.8" + - "3.9" + - "3.10" + + steps: + - name: Check out code + uses: actions/checkout@v3 + + # See https://github.com/marketplace/actions/setup-python + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + + # See https://github.com/marketplace/actions/cache + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + + - name: Set up pip cache + uses: actions/cache@v3 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 pytest pytest-cov + if [ -f test-requirements.txt ]; then pip install -r test-requirements.txt; fi + + - name: Lint with flake8 + run: | + # Stop the build if there are Python syntax errors or undefined names. + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. + flake8 . --count --exit-zero --max-complexity=10 --statistics + + - name: Run tests + run: | + pytest diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8cf4d52 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +# ignore some usual virtual environments. +venv* + +# Ignore files added by editors and OS. +*.swp +*.swo +*~ +*.bak +.*.kate-swp +*.DS_Store + +# Ignore files generated by build tools. +*.pyc +*.pyo +*.egg-info/ +*.egg +.pc + +# Ignore files generated by coverage tools. +/.coverage +/.coverage.* +/.coverage.el +/coverage.xml + +# Ignore files generated when running tests. +/tests/data/amlight.png +/tests/data/sdx.png +/tests/data/sdx-out.json diff --git a/README.md b/README.md index 97b1825..2f483de 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,16 @@ - [How to Contribute](#contrib) - [AW-SDX Data Model](#datamodel) +- [How to test and use](#usage) - [AW-SDX Accompanying Projectsl](#accompany) ## How to Contribute 1. Ensure you're able to run the existing code in your own [development environment](#setup). 2. Create a descriptive [GitHub issue](https://github.com/atlanticwave-sdx/datamodel/issues) that outlines what feature you plan to contribute. -3. Clone the repository, and start from the most recent version of the [develop branch](https://github.com/atlanticwave-sdx/datamodel.git). +3. Clone the repository, and start from the most recent version of the [develop branch](https://github.com/atlanticwave-sdx/datamodel/tree/develop). 4. Name your branch using the Github issue number as a prefix along with a brief name that corresponds to your feature (e.g., `8-how-to-contribute`). -5. Once satisfied with your completed and tested work, submit a [pull request](https://github.com/atlanticwave-sdx/datamodel.git) against the **develop** branch so that your code can be reviewed by the team. +5. Once satisfied with your completed and tested work, submit a [pull request](https://github.com/atlanticwave-sdx/datamodel/pulls) against the **develop** branch so that your code can be reviewed by the team. Notes: @@ -43,4 +44,31 @@ This set of updates mainly come from the domain monitoring system which is suppo ## Topology description schemas There are defined in the *schema* subfolder. Some attributes of each objects are requied (Can be found in the API definition) while some are optional. Two attributes are worth of mentioning: (1) In the *service* object, there is a *vender* attribute for the domain to list device vendors that are NOT in its domain, (2) in the topology, link, node, and port objects, there is an *private* attibute for the domain to list attributes that need to kept private.:wq +## Usage + +### Running tests + +Run tests with: + +``` +python -m pip install -r test-requirements.txt +python -m unittest +``` + +If you want to run some specific tests: + +``` +python -m unittest -v tests.test_topology_handler +python -m unittest -v tests.test_topology_validator +``` + +## Install +``` +pip install -r requirements.txt +``` +``` +pip install -e . +``` + + ## Accompanying AW-SDX Projects diff --git a/doc/requirements.txt b/doc/requirements.txt new file mode 100644 index 0000000..9b65feb --- /dev/null +++ b/doc/requirements.txt @@ -0,0 +1,101 @@ +1. topology.json +{ + "type": "object", + "properties": { + "id": { // must be provided by SDX, not by OXPO// source IP of the OXPO? + "type": "string" + }, + "links": ["repository://"], // urn:sdx:link:amlight:novi01_2_novi02_2 + "name": "AmLight", + "nodes": ["repository://Node_novi01", "repository://Node_novi02", "repository://Node_novi03"], + "time_stamp": "06-31-2021-11:23:59", // is residual bandwidth going to change the timestamp? + "version": "1", // is residual bandwidth going to change the version? + "domain_service": { + "$ref": "repository://Service" + } + }, + "title": "Topology" +} + +Are "Links" for inter-domain as well? +how to specify the inter-domain connections? + +Each attribute must have a qualifier for private or public +how to push for updates (full topology, just the change, or just a notification of a change) + +Future: +topology capabilities: INT/JTI, counters + + +2. port.json +{ + "type": "object", + "properties": { + "id": "2", // ID used by the equipment (of_port or ifIndex) maybe URN? + "short_name": "???? What fields are mandatory or optional", + "name": "2", // "Examples: Juniper: xe-1/1/0, Brocade: eth2/5, Dell Hu-1/2" + "node": "repository://Node_novi01", + "type": "100GbpsE", + "encapsulation": "VLAN", // what's the difference between label and encapsulation? + "label": "vlan", + "swapping_capability": { + "type": "string", + "enum": [ + "vlan" // ? + ] + }, + "label_range": [[1, 5], [100, 200], [34, 34]], + // mtu is desired because of INT and VXLAN + "inter-domain": "SAX:Switch1:Port2", // full URN + "inter-domain": "sfsadfsadfdsfgsdfgfdsgfsdgfdsgfsd", // shared string + // description + "status": "?????? // state and status could be multivalue. Boolean not enough [provisioning|decom|maintenance|up|down|optimal|suboptimal]" + }, + "title": "Port" +} + +3. link.json +{ + "type": "object", + "properties": { + "id": "urn:sdx:link:amlight:novi01_2_novi02_2", + "short_name": "", + "name": "novi01_2_novi02_2", + "ports": ["repository://Port_novi01_02", "repository://Port_novi02_02"], + "total_bandwidth": // no an SDX issue. I suggest removing it. + { //unit bps? minimum and maximum must be conditioned to the interface.type (10GE for instance) + "type": "number", + "minimum": 1, + "maximum": 1000000 + }, + "bandwidth": { // 10Mbps - 1Tbps + "type": "number", + "minimum": 0, + "maximum": 1000000 // no max, just > 0 + }, + "latency": { // ns or ms? + "type": "number", + "minimum": 0, + "maximum": 1000000 // no max, just > 0 + }, + "packet loss": { // % + "type": "number", + "minimum": 0, + "maximum": 100 + }, + "availability": { // % + "type": "number", + "minimum": 0, + "maximum": 100 + }, + "residual_bw": { // based on the last X time? what is X? // array with last 30s, last 5min, last hour, last day, last week + "type": "number", + "minimum": 0, + "maximum": 100 + } + }, + "title": "Link" +} + +4. node.json + diff --git a/models/__init__.py b/models/__init__.py deleted file mode 100644 index 3e33fe0..0000000 --- a/models/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -# coding: utf-8 - -# flake8: noqa -""" - SDX LC - - You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). # noqa: E501 - - OpenAPI spec version: 1.0.0 - Contact: yxin@renci.org - Generated by: https://github.com/swagger-api/swagger-codegen.git -""" - -from __future__ import absolute_import - -# import models into model package -from datamodel.models.connection import Connection -from datamodel.models.link import Link -from datamodel.models.link_measurement_period import LinkMeasurementPeriod -from datamodel.models.location import Location -from datamodel.models.node import Node -from datamodel.models.port import Port -from datamodel.models.service import Service -from datamodel.models.topology import Topology diff --git a/parsing/exceptions.py b/parsing/exceptions.py deleted file mode 100644 index 8920de0..0000000 --- a/parsing/exceptions.py +++ /dev/null @@ -1,21 +0,0 @@ -class DataModelException(Exception): - """ - Base exception for GRENML parsing - """ - pass - -class MissingAttributeException(DataModelException): - """ - A required attribute could not be found when parsing an element - """ - - def __init__(self, attribute_name, expected_attribute): - self.element_name = attribute_name - self.expected_attribute = expected_attribute - - def __str__(self): - return ("Missing attribute {} while parsing <{}>").format( - self.expected_attribute, - self.attribute_name, - ) - diff --git a/parsing/topologyhandler.py b/parsing/topologyhandler.py deleted file mode 100644 index feda46b..0000000 --- a/parsing/topologyhandler.py +++ /dev/null @@ -1,36 +0,0 @@ -from models import topology - - -class TopologyHandler(): - - """" - Handler for parsing the topology descritpion in json - """ - - import json - from datamodel.models.topology import Topology - from .exception import MissingAttributeException - - def __init__(self): - super().__init__() - self.topology_file=MANIFEST_FILE=None - self.topology = None - - def _import_topology(self, topology_filename): - with open(topology_filename) as data_file: - data = json.load(data_file) - - try: - id = data['id'] - name=data['name'] - version=data['version'] - time_stamp=data['time_stamp'] - nodes=data['nodes'] - links=data['links'] - except: - raise MissingAttributeException() - - topology=Topology(id=id, name=name, version=version, time_stamp=time_stamp, nodes=nodes, links=links) - - def get_topology(): - return self.topology diff --git a/requirements.txt b/requirements.txt index 9b65feb..55aa08c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,101 +1,2 @@ -1. topology.json -{ - "type": "object", - "properties": { - "id": { // must be provided by SDX, not by OXPO// source IP of the OXPO? - "type": "string" - }, - "links": ["repository://"], // urn:sdx:link:amlight:novi01_2_novi02_2 - "name": "AmLight", - "nodes": ["repository://Node_novi01", "repository://Node_novi02", "repository://Node_novi03"], - "time_stamp": "06-31-2021-11:23:59", // is residual bandwidth going to change the timestamp? - "version": "1", // is residual bandwidth going to change the version? - "domain_service": { - "$ref": "repository://Service" - } - }, - "title": "Topology" -} - -Are "Links" for inter-domain as well? -how to specify the inter-domain connections? - -Each attribute must have a qualifier for private or public -how to push for updates (full topology, just the change, or just a notification of a change) - -Future: -topology capabilities: INT/JTI, counters - - -2. port.json -{ - "type": "object", - "properties": { - "id": "2", // ID used by the equipment (of_port or ifIndex) maybe URN? - "short_name": "???? What fields are mandatory or optional", - "name": "2", // "Examples: Juniper: xe-1/1/0, Brocade: eth2/5, Dell Hu-1/2" - "node": "repository://Node_novi01", - "type": "100GbpsE", - "encapsulation": "VLAN", // what's the difference between label and encapsulation? - "label": "vlan", - "swapping_capability": { - "type": "string", - "enum": [ - "vlan" // ? - ] - }, - "label_range": [[1, 5], [100, 200], [34, 34]], - // mtu is desired because of INT and VXLAN - "inter-domain": "SAX:Switch1:Port2", // full URN - "inter-domain": "sfsadfsadfdsfgsdfgfdsgfsdgfdsgfsd", // shared string - // description - "status": "?????? // state and status could be multivalue. Boolean not enough [provisioning|decom|maintenance|up|down|optimal|suboptimal]" - }, - "title": "Port" -} - -3. link.json -{ - "type": "object", - "properties": { - "id": "urn:sdx:link:amlight:novi01_2_novi02_2", - "short_name": "", - "name": "novi01_2_novi02_2", - "ports": ["repository://Port_novi01_02", "repository://Port_novi02_02"], - "total_bandwidth": // no an SDX issue. I suggest removing it. - { //unit bps? minimum and maximum must be conditioned to the interface.type (10GE for instance) - "type": "number", - "minimum": 1, - "maximum": 1000000 - }, - "bandwidth": { // 10Mbps - 1Tbps - "type": "number", - "minimum": 0, - "maximum": 1000000 // no max, just > 0 - }, - "latency": { // ns or ms? - "type": "number", - "minimum": 0, - "maximum": 1000000 // no max, just > 0 - }, - "packet loss": { // % - "type": "number", - "minimum": 0, - "maximum": 100 - }, - "availability": { // % - "type": "number", - "minimum": 0, - "maximum": 100 - }, - "residual_bw": { // based on the last X time? what is X? // array with last 30s, last 5min, last hour, last day, last week - "type": "number", - "minimum": 0, - "maximum": 100 - } - }, - "title": "Link" -} - -4. node.json - +grenml +networkx diff --git a/schemas/Connection.json b/schemas/Connection.json index 3771b0e..2644d0c 100644 --- a/schemas/Connection.json +++ b/schemas/Connection.json @@ -107,7 +107,11 @@ }, "status": { "type": "string", - "enum": [] + "enum": [ + "up", + "down", + "error" + ] } }, "required": ["id", "name", "ingress_port", "egress_port"] diff --git a/schemas/Link.json b/schemas/Link.json index a4178f1..725e95a 100644 --- a/schemas/Link.json +++ b/schemas/Link.json @@ -27,12 +27,12 @@ "type": "string", "format": "date-time" }, - "total_bandwidth": { + "bandwidth": { "type": "number", "minimum": 1, "maximum": 1000000 }, - "available_bandwidth": { + "residual_bandwidth": { "type": "number", "minimum": 1, "maximum": 1000000 @@ -52,6 +52,22 @@ "minimum": 0, "maximum": 100 }, + "status": { + "type": "string", + "enum": [ + "up", + "down", + "error" + ] + }, + "state": { + "type": "string", + "enum": [ + "enabled", + "disabled", + "maintenance" + ] + }, "private_attributes": { "description": "This property is used to define a list of private properties that will not be exposed to outside users of SDX", "type": "array", diff --git a/schemas/Node.json b/schemas/Node.json index 937ec9a..26acd2c 100644 --- a/schemas/Node.json +++ b/schemas/Node.json @@ -22,12 +22,6 @@ "$ref": "port.json" } ] - }, - "private_attibutes": { - "type": "array", - "items": { - "type": "string" - } } }, "required": ["id", "location", "name", "ports"] diff --git a/schemas/Port.json b/schemas/Port.json index 4c55586..7f3f733 100644 --- a/schemas/Port.json +++ b/schemas/Port.json @@ -12,24 +12,26 @@ "type": "string" }, "name": { - "description": "This is supposed to be an urn", + "description": "This is supposed to be a full name", "type": "string" }, "node": { "type": "string" }, - "inter_domain": { + "nni": { "type": "boolean" }, - "link_to": { - "$ref": "port.json" - }, "type": { "type": "string", "enum": [ - "10GE", + "100FE", "1GE", - "100GE" + "10GE", + "25GE", + "40GE", + "100GE", + "400GE", + "Other" ] }, "encapsulation": { @@ -73,7 +75,16 @@ "type": "string", "enum": [ "up", - "down" + "down", + "error" + ] + }, + "state": { + "type": "string", + "enum": [ + "enabled", + "disabled", + "maintenance" ] }, "private_attributes": { diff --git a/schemas/Service.json b/schemas/Service.json index b886226..2f18041 100644 --- a/schemas/Service.json +++ b/schemas/Service.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Port", + "title": "Service", "type": "object", "properties": { "owner": { @@ -22,12 +22,6 @@ } ] }, - "private_attributes": { - "type": "array", - "items": { - "type": "string" - } - }, "monitoring_capability": { "anyOf": [ { diff --git a/schemas/Topology.json b/schemas/Topology.json index 80dfb4c..bf4603c 100644 --- a/schemas/Topology.json +++ b/schemas/Topology.json @@ -4,9 +4,11 @@ "type": "object", "properties": { "id": { + "description": "This is supposed to be an unique identifier: urn:sdx:topology:", "type": "string" }, "name": { + "description": "This is supposed to be a readable name", "type": "string" }, "time_stamp": { @@ -14,18 +16,18 @@ "format": "date-time" }, "version": { + "description": "This is supposed to be in ISO format", "type": "number", "minimum": 0 }, + "model_version": { + "description": "This is supposed to be 1.0.0", + "type": "string", + "minimum": 0 + }, "domain_service": { "$ref": "Service" }, - "private_attributes": { - "type": "array", - "items": { - "type": "string" - } - }, "nodes": { "type": "array", "items": [ @@ -43,5 +45,5 @@ ] } }, - "required": ["id", "name", "version","time_stamp","nodes", "links"] + "required": ["id", "name", "version","model_version","time_stamp","nodes", "links"] } \ No newline at end of file diff --git a/sdxdatamodel/__init__.py b/sdxdatamodel/__init__.py new file mode 100644 index 0000000..3339748 --- /dev/null +++ b/sdxdatamodel/__init__.py @@ -0,0 +1,6 @@ +from __future__ import absolute_import + +from os.path import dirname, basename, isfile, join +import glob +modules = glob.glob(join(dirname(__file__), "*.py")) +__all__ = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')] diff --git a/sdxdatamodel/__pycache__/__init__.cpython-38.pyc b/sdxdatamodel/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..f4bb233 Binary files /dev/null and b/sdxdatamodel/__pycache__/__init__.cpython-38.pyc differ diff --git a/sdxdatamodel/models/__init__.py b/sdxdatamodel/models/__init__.py new file mode 100644 index 0000000..329323c --- /dev/null +++ b/sdxdatamodel/models/__init__.py @@ -0,0 +1,8 @@ +# coding: utf-8 + +from __future__ import absolute_import + +from os.path import dirname, basename, isfile, join +import glob +modules = glob.glob(join(dirname(__file__), "*.py")) +__all__ = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')] diff --git a/models/connection.py b/sdxdatamodel/models/connection.py similarity index 78% rename from models/connection.py rename to sdxdatamodel/models/connection.py index d335ebb..41aaec8 100644 --- a/models/connection.py +++ b/sdxdatamodel/models/connection.py @@ -12,9 +12,11 @@ import pprint import re # noqa: F401 - import six +from .port import Port +from sdxdatamodel.parsing.porthandler import PortHandler + class Connection(object): """NOTE: This class is auto generated by the swagger code generator program. @@ -36,7 +38,6 @@ class Connection(object): 'start_time': 'datetime', 'end_time': 'datetime', 'status': 'str', - 'complete': 'bool' } attribute_map = { @@ -47,36 +48,36 @@ class Connection(object): 'quantity': 'quantity', 'start_time': 'start_time', 'end_time': 'end_time', + 'bandwidth': 'float', + 'latency': 'float', + 'latency': 'float', 'status': 'status', - 'complete': 'complete' } - def __init__(self, id=None, name=None, ingress_port=None, egress_port=None, quantity=None, start_time=None, end_time=None, status=None, complete=False): # noqa: E501 - """Connection - a model defined in Swagger""" # noqa: E501 - self._id = None - self._name = None - self._ingress_port = None - self._egress_port = None + def __init__(self, id=None, name=None, ingress_port=None, egress_port=None, bandwidth=None, latency=None, quantity=None, start_time=None, end_time=None, status=None, complete=False): # noqa: E501 + + self._id = id + self._name = name self._quantity = None self._start_time = None self._end_time = None self._status = None - self._complete = None - self.discriminator = None - self.id = id - self.name = name - self.ingress_port = ingress_port - self.egress_port = egress_port + self._bandwidth = None, + self._latency = None, + self.set_ingress_port(ingress_port) + self.set_egress_port(egress_port) + if bandwidth is not None: + self._bandwidth = bandwidth + if latency is not None: + self._latency = latency if quantity is not None: - self.quantity = quantity + self._quantity = quantity if start_time is not None: - self.start_time = start_time + self._start_time = start_time if end_time is not None: - self.end_time = end_time + self._end_time = end_time if status is not None: - self.status = status - if complete is not None: - self.complete = complete + self._status = status @property def id(self): @@ -134,8 +135,8 @@ def ingress_port(self): """ return self._ingress_port - @ingress_port.setter - def ingress_port(self, ingress_port): + #setter + def set_ingress_port(self, ingress_port): """Sets the ingress_port of this Connection. @@ -145,7 +146,10 @@ def ingress_port(self, ingress_port): if ingress_port is None: raise ValueError("Invalid value for `ingress_port`, must not be `None`") # noqa: E501 - self._ingress_port = ingress_port + port_handler = PortHandler() + self._ingress_port = port_handler.import_port_data(ingress_port) + + return self.ingress_port @property def egress_port(self): @@ -157,8 +161,8 @@ def egress_port(self): """ return self._egress_port - @egress_port.setter - def egress_port(self, egress_port): + #setter + def set_egress_port(self, egress_port): """Sets the egress_port of this Connection. @@ -168,7 +172,11 @@ def egress_port(self, egress_port): if egress_port is None: raise ValueError("Invalid value for `egress_port`, must not be `None`") # noqa: E501 - self._egress_port = egress_port + port_handler = PortHandler() + self._egress_port = port_handler.import_port_data(egress_port) + + return self.egress_port + @property def quantity(self): @@ -191,6 +199,46 @@ def quantity(self, quantity): self._quantity = quantity + @property + def bandwidth(self): + """Gets the quantity of this Connection. # noqa: E501 + + + :return: The quantity of this Connection. # noqa: E501 + :rtype: float + """ + return self._bandwidth + + def set_bandwidth(self, bw): + """Sets the bw of this Connection. + + + :param bw: The bw of this Connection. # noqa: E501 + :type: float + """ + + self._bandwidth = bw + + @property + def latency(self): + """Gets the latency of this Connection. # noqa: E501 + + + :return: The latency of this Connection. # noqa: E501 + :rtype: float + """ + return self._latency + + def set_latency(self, latency): + """Sets the latency of this Connection. + + + :param bw: The latency of this Connection. # noqa: E501 + :type: float + """ + + self._latency = latency + @property def start_time(self): """Gets the start_time of this Connection. # noqa: E501 @@ -262,27 +310,6 @@ def status(self, status): self._status = status - @property - def complete(self): - """Gets the complete of this Connection. # noqa: E501 - - - :return: The complete of this Connection. # noqa: E501 - :rtype: bool - """ - return self._complete - - @complete.setter - def complete(self, complete): - """Sets the complete of this Connection. - - - :param complete: The complete of this Connection. # noqa: E501 - :type: bool - """ - - self._complete = complete - def to_dict(self): """Returns the model properties as a dict""" result = {} diff --git a/models/link.py b/sdxdatamodel/models/link.py similarity index 67% rename from models/link.py rename to sdxdatamodel/models/link.py index 55464e1..10210a1 100644 --- a/models/link.py +++ b/sdxdatamodel/models/link.py @@ -16,10 +16,6 @@ import six class Link(object): - """NOTE: This class is auto generated by the swagger code generator program. - - Do not edit the class manually. - """ """ Attributes: swagger_types (dict): The key is attribute name @@ -31,12 +27,15 @@ class Link(object): 'id': 'str', 'name': 'str', 'short_name': 'str', + 'nni': 'bool', 'ports': 'list[Port]', - 'total_bandwidth': 'float', - 'available_bandwidth': 'float', + 'bandwidth': 'float', + 'residual_bandwidth': 'float', 'latency': 'float', 'packet_loss': 'float', 'availability': 'float', + 'status': 'str', + 'state': 'str', 'private_attributes': 'list[str]', 'time_stamp': 'datetime', 'measurement_period': 'LinkMeasurementPeriod' @@ -46,53 +45,61 @@ class Link(object): 'id': 'id', 'name': 'name', 'short_name': 'short_name', + 'nni': 'nni', 'ports': 'ports', - 'total_bandwidth': 'total_bandwidth', - 'available_bandwidth': 'available_bandwidth', + 'bandwidth': 'bandwidth', + 'residual_bandwidth': 'residual_bandwidth', 'latency': 'latency', 'packet_loss': 'packet_loss', 'availability': 'availability', + 'status': 'status', + 'state': 'state', 'private_attributes': 'private_attributes', 'time_stamp': 'time_stamp', 'measurement_period': 'measurement_period' } - def __init__(self, id=None, name=None, short_name=None, ports=None, total_bandwidth=None, available_bandwidth=None, latency=None, packet_loss=None, availability=None, private_attributes=None, time_stamp=None, measurement_period=None): # noqa: E501 + def __init__(self, id=None, name=None, short_name=None, nni=None, ports=None, bandwidth=None, residual_bandwidth=None, latency=None, packet_loss=None, availability=None, private_attributes=None, time_stamp=None, measurement_period=None): # noqa: E501 """Link - a model defined in Swagger""" # noqa: E501 self._id = None self._name = None self._short_name = None + self._nni = None self._ports = None - self._total_bandwidth = None - self._available_bandwidth = None + self._bandwidth = None + self._residual_bandwidth = None self._latency = None self._packet_loss = None self._availability = None + self._state = None + self._status = None self._private_attributes = None self._time_stamp = None self._measurement_period = None - self.discriminator = None - self.id = id - self.name = name + self._id = id + self._name = name if short_name is not None: - self.short_name = short_name - self.ports = ports - if total_bandwidth is not None: - self.total_bandwidth = total_bandwidth - if available_bandwidth is not None: - self.available_bandwidth = available_bandwidth + self._short_name = short_name + if nni is not None: + self._nni = nni + if ports is not None: + self._ports = self.set_ports(ports) + if bandwidth is not None: + self._bandwidth = bandwidth + if residual_bandwidth is not None: + self._residual_bandwidth = residual_bandwidth if latency is not None: - self.latency = latency + self._latency = latency if packet_loss is not None: - self.packet_loss = packet_loss + self._packet_loss = packet_loss if availability is not None: - self.availability = availability + self._availability = availability if private_attributes is not None: - self.private_attributes = private_attributes + self._private_attributes = private_attributes if time_stamp is not None: - self.time_stamp = time_stamp + self._time_stamp = time_stamp if measurement_period is not None: - self.measurement_period = measurement_period + self._measurement_period = measurement_period @property def id(self): @@ -161,6 +168,27 @@ def short_name(self, short_name): self._short_name = short_name + @property + def nni(self): + """Gets the short_name of this Link. # noqa: E501 + + + :return: The short_name of this Link. # noqa: E501 + :rtype: str + """ + return self._nni + + @nni.setter + def nni(self, nni): + """Sets the short_name of this Link. + + + :param short_name: The short_name of this Link. # noqa: E501 + :type: str + """ + + self._nni = nni + @property def ports(self): """Gets the ports of this Link. # noqa: E501 @@ -171,6 +199,23 @@ def ports(self): """ return self._ports + def set_ports(self, ports): + """Sets the ports of this Node. + + + :param ports: The ports of this Node. # noqa: E501 + :type: list[port] + """ + if ports is None: + raise ValueError("Invalid value for `ports`, must not be `None`") # noqa: E501 + + if self._ports is None: + self._ports=[] + + for port in ports: + self._ports.append(port) + + return self.ports @ports.setter def ports(self, ports): """Sets the ports of this Link. @@ -185,46 +230,46 @@ def ports(self, ports): self._ports = ports @property - def total_bandwidth(self): - """Gets the total_bandwidth of this Link. # noqa: E501 + def bandwidth(self): + """Gets the bandwidth of this Link. # noqa: E501 - :return: The total_bandwidth of this Link. # noqa: E501 + :return: The bandwidth of this Link. # noqa: E501 :rtype: float """ - return self._total_bandwidth + return self._bandwidth - @total_bandwidth.setter - def total_bandwidth(self, total_bandwidth): - """Sets the total_bandwidth of this Link. + @bandwidth.setter + def bandwidth(self, bandwidth): + """Sets the bandwidth of this Link. - :param total_bandwidth: The total_bandwidth of this Link. # noqa: E501 + :param bandwidth: The bandwidth of this Link. # noqa: E501 :type: float """ - self._total_bandwidth = total_bandwidth + self._bandwidth = bandwidth @property - def available_bandwidth(self): - """Gets the available_bandwidth of this Link. # noqa: E501 + def residual_bandwidth(self): + """Gets the residual_bandwidth of this Link. # noqa: E501 - :return: The available_bandwidth of this Link. # noqa: E501 + :return: The residual_bandwidth of this Link. # noqa: E501 :rtype: float """ - return self._available_bandwidth + return self._residual_bandwidth - @available_bandwidth.setter - def available_bandwidth(self, available_bandwidth): - """Sets the available_bandwidth of this Link. + @residual_bandwidth.setter + def residual_bandwidth(self, residual_bandwidth): + """Sets the residual_bandwidth of this Link. - :param available_bandwidth: The available_bandwidth of this Link. # noqa: E501 + :param residual_bandwidth: The residual_bandwidth of this Link. # noqa: E501 :type: float """ - self._available_bandwidth = available_bandwidth + self._residual_bandwidth = residual_bandwidth @property def latency(self): @@ -289,6 +334,52 @@ def availability(self, availability): self._availability = availability + @property + def status(self): + """Gets the status of this Link. # noqa: E501 + + + :return: The status of this Link. # noqa: E501 + :rtype: str + """ + return self._status + + @status.setter + def status(self, status): + """Sets the status of this Link. + + + :param status: The status of this Link. # noqa: E501 + :type: str + """ + if status is None: + raise ValueError("Invalid value for `status`, must not be `None`") # noqa: E501 + + self._status = status + + @property + def state(self): + """Gets the state of this Link. # noqa: E501 + + + :return: The status of this Link. # noqa: E501 + :rtype: str + """ + return self._state + + @status.setter + def state(self, state): + """Sets the status of this Link. + + + :param status: The status of this Link. # noqa: E501 + :type: str + """ + if state is None: + raise ValueError("Invalid value for `state`, must not be `None`") # noqa: E501 + + self._state = state + @property def private_attributes(self): """Gets the private_attributes of this Link. # noqa: E501 diff --git a/models/link_measurement_period.py b/sdxdatamodel/models/link_measurement_period.py similarity index 100% rename from models/link_measurement_period.py rename to sdxdatamodel/models/link_measurement_period.py diff --git a/models/location.py b/sdxdatamodel/models/location.py similarity index 96% rename from models/location.py rename to sdxdatamodel/models/location.py index a9667df..e44556e 100644 --- a/models/location.py +++ b/sdxdatamodel/models/location.py @@ -41,16 +41,18 @@ class Location(object): def __init__(self, address=None, latitude=None, longitude=None): # noqa: E501 """Location - a model defined in Swagger""" # noqa: E501 + self._id="location" + self._name="location" self._address = None self._latitude = None self._longitude = None self.discriminator = None if address is not None: - self.address = address + self._address = address if latitude is not None: - self.latitude = latitude + self._latitude = latitude if longitude is not None: - self.longitude = longitude + self._longitude = longitude @property def address(self): diff --git a/models/node.py b/sdxdatamodel/models/node.py similarity index 82% rename from models/node.py rename to sdxdatamodel/models/node.py index 2dd8025..195b9a7 100644 --- a/models/node.py +++ b/sdxdatamodel/models/node.py @@ -12,9 +12,11 @@ import pprint import re # noqa: F401 - import six +from sdxdatamodel.parsing.locationhandler import LocationHandler +from sdxdatamodel.parsing.porthandler import PortHandler + class Node(object): """NOTE: This class is auto generated by the swagger code generator program. @@ -50,18 +52,16 @@ def __init__(self, id=None, name=None, short_name=None, location=None, ports=Non self._id = None self._name = None self._short_name = None - self._location = None self._ports = None self._private_attributes = None - self.discriminator = None - self.id = id - self.name = name + self._id = id + self._name = name if short_name is not None: - self.short_name = short_name - self.location = location - self.ports = ports + self._short_name = short_name + self._location = self.set_location(location) + self._ports = self.set_ports(ports) if private_attributes is not None: - self.private_attributes = private_attributes + self._private_attributes = private_attributes @property def id(self): @@ -132,6 +132,15 @@ def short_name(self, short_name): @property def location(self): + """Gets the name of this Node. # noqa: E501 + + + :return: The name of this Node. # noqa: E501 + :rtype: str + """ + return self._location + + def get_location(self): """Gets the location of this Node. # noqa: E501 @@ -140,18 +149,19 @@ def location(self): """ return self._location - @location.setter - def location(self, location): + def set_location(self, location): """Sets the location of this Node. - :param location: The location of this Node. # noqa: E501 :type: Location """ if location is None: raise ValueError("Invalid value for `location`, must not be `None`") # noqa: E501 - self._location = location + location_handler = LocationHandler() + self._location = location_handler.import_location_data(location) + + return self.get_location() @property def ports(self): @@ -163,6 +173,26 @@ def ports(self): """ return self._ports + def set_ports(self, ports): + """Sets the ports of this Node. + + + :param ports: The ports of this Node. # noqa: E501 + :type: list[port] + """ + if ports is None: + raise ValueError("Invalid value for `ports`, must not be `None`") # noqa: E501 + + if self._ports is None: + self._ports=[] + + for port in ports: + port_handler = PortHandler() + port_obj = port_handler.import_port_data(port) + self._ports.append(port_obj) + + return self._ports + @ports.setter def ports(self, ports): """Sets the ports of this Node. @@ -176,6 +206,7 @@ def ports(self, ports): self._ports = ports + @property def private_attributes(self): """Gets the private_attributes of this Node. # noqa: E501 diff --git a/models/port.py b/sdxdatamodel/models/port.py similarity index 86% rename from models/port.py rename to sdxdatamodel/models/port.py index c2697aa..8f34e84 100644 --- a/models/port.py +++ b/sdxdatamodel/models/port.py @@ -34,6 +34,7 @@ class Port(object): 'node': 'str', 'label_range': 'list[str]', 'status': 'str', + 'state': 'str', 'private_attributes': 'list[str]' } @@ -44,6 +45,7 @@ class Port(object): 'node': 'node', 'label_range': 'label_range', 'status': 'status', + 'state': 'state', 'private_attributes': 'private_attributes' } @@ -56,17 +58,20 @@ def __init__(self, id=None, name=None, short_name=None, node=None, label_range=N self._label_range = None self._status = None self._private_attributes = None - self.discriminator = None - self.id = id - self.name = name + self._id = id + self._name = name if short_name is not None: - self.short_name = short_name - self.node = node + self._short_name = short_name + self._node = node if label_range is not None: - self.label_range = label_range - self.status = status + self._label_range = label_range + self._status = status if private_attributes is not None: - self.private_attributes = private_attributes + self._private_attributes = private_attributes + + #def __init__(self, port:dict): + # self.__init__(id=id, name=port['name'],short_name=port['short_name'], node=port['node'], + # label_range=port['label_range'], status=None, private_attributes=port['private_attributes']) @property def id(self): @@ -202,6 +207,29 @@ def status(self, status): self._status = status + @property + def state(self): + """Gets the state of this Port. # noqa: E501 + + + :return: The state of this Port. # noqa: E501 + :rtype: str + """ + return self._state + + @status.setter + def state(self, state): + """Sets the state of this Port. + + + :param state: The state of this Port. # noqa: E501 + :type: str + """ + if state is None: + raise ValueError("Invalid value for `state`, must not be `None`") # noqa: E501 + + self._state = state + @property def private_attributes(self): """Gets the private_attributes of this Port. # noqa: E501 diff --git a/models/service.py b/sdxdatamodel/models/service.py similarity index 94% rename from models/service.py rename to sdxdatamodel/models/service.py index 9663c9f..ed6fcd8 100644 --- a/models/service.py +++ b/sdxdatamodel/models/service.py @@ -45,8 +45,10 @@ class Service(object): 'vendor': 'vendor' } - def __init__(self, monitoring_capability=None, owner=None, private_attributes=None, provisioning_system=None, provisioning_url=None, vendor=None): # noqa: E501 + def __init__(self, monitoring_capability=None, owner=None, private_attributes=None, provisioning_system=None, provisioning_url=None, vendor=None): """Service - a model defined in Swagger""" # noqa: E501 + self._id="service" + self._name="service" self._monitoring_capability = None self._owner = None self._private_attributes = None @@ -55,17 +57,17 @@ def __init__(self, monitoring_capability=None, owner=None, private_attributes=No self._vendor = None self.discriminator = None if monitoring_capability is not None: - self.monitoring_capability = monitoring_capability + self._monitoring_capability = monitoring_capability if owner is not None: - self.owner = owner + self._owner = owner if private_attributes is not None: - self.private_attributes = private_attributes + self._private_attributes = private_attributes if provisioning_system is not None: - self.provisioning_system = provisioning_system + self._provisioning_system = provisioning_system if provisioning_url is not None: - self.provisioning_url = provisioning_url + self._provisioning_url = provisioning_url if vendor is not None: - self.vendor = vendor + self._vendor = vendor @property def monitoring_capability(self): diff --git a/models/topology.py b/sdxdatamodel/models/topology.py similarity index 64% rename from models/topology.py rename to sdxdatamodel/models/topology.py index e7e0a9b..a8dd9d2 100644 --- a/models/topology.py +++ b/sdxdatamodel/models/topology.py @@ -12,21 +12,19 @@ import pprint import re # noqa: F401 - import six +import datetime + +from sdxdatamodel.parsing.servicehandler import ServiceHandler +from sdxdatamodel.parsing.nodehandler import NodeHandler +from sdxdatamodel.parsing.linkhandler import LinkHandler + +SDX_INSTITUTION_ID = 'urn:ogf:network:sdx' +SDX_TOPOLOGY_ID_prefix = "urn:ogf:network:sdx" +TOPOLOGY_INITIAL_VERSION="0.0" class Topology(object): - """NOTE: This class is auto generated by the swagger code generator program. - - Do not edit the class manually. - """ - """ - Attributes: - swagger_types (dict): The key is attribute name - and the value is attribute type. - attribute_map (dict): The key is attribute name - and the value is json key in definition. - """ + swagger_types = { 'id': 'str', 'name': 'str', @@ -51,25 +49,21 @@ class Topology(object): def __init__(self, id=None, name=None, domain_service=None, version=None, time_stamp=None, nodes=None, links=None, private_attributes=None): # noqa: E501 """Topology - a model defined in Swagger""" # noqa: E501 - self._id = None - self._name = None + self._domain_service = None - self._version = None - self._time_stamp = None - self._nodes = None - self._links = None self._private_attributes = None - self.discriminator = None - self.id = id - self.name = name + self._id = id + self._name = name if domain_service is not None: - self.domain_service = domain_service - self.version = version - self.time_stamp = time_stamp - self.nodes = nodes - self.links = links + self._domain_service = self.set_domain_service(domain_service) + self._version = version + self._time_stamp = time_stamp + self._nodes=[] + self._links=[] + self._nodes = self.set_nodes(nodes) + self._links = self.set_links(links) if private_attributes is not None: - self.private_attributes = private_attributes + self._private_attributes = private_attributes @property def id(self): @@ -81,8 +75,7 @@ def id(self): """ return self._id - @id.setter - def id(self, id): + def set_id(self, id): """Sets the id of this Topology. @@ -104,8 +97,7 @@ def name(self): """ return self._name - @name.setter - def name(self, name): + def set_name(self, name): """Sets the name of this Topology. @@ -119,6 +111,15 @@ def name(self, name): @property def domain_service(self): + """Gets the name of this Topology. # noqa: E501 + + + :return: The name of this Topology. # noqa: E501 + :rtype: str + """ + return self._domain_service + + def get_domain_service(self): """Gets the domain_service of this Topology. # noqa: E501 @@ -127,16 +128,21 @@ def domain_service(self): """ return self._domain_service - @domain_service.setter - def domain_service(self, domain_service): + def set_domain_service(self, domain_service): """Sets the domain_service of this Topology. :param domain_service: The domain_service of this Topology. # noqa: E501 :type: Service """ + if domain_service is None: + raise ValueError("Invalid value for `domain_service`, must not be `None`") # noqa: E501 + + service_handler = ServiceHandler() + self._domain_service = service_handler.import_service_data(domain_service) + + return self.get_domain_service() - self._domain_service = domain_service @property def version(self): @@ -186,6 +192,15 @@ def time_stamp(self, time_stamp): @property def nodes(self): + """Gets the name of this Topology. # noqa: E501 + + + :return: The name of this Topology. # noqa: E501 + :rtype: str + """ + return self._nodes + + def get_nodes(self): """Gets the nodes of this Topology. # noqa: E501 @@ -194,8 +209,7 @@ def nodes(self): """ return self._nodes - @nodes.setter - def nodes(self, nodes): + def set_nodes(self, nodes): """Sets the nodes of this Topology. @@ -205,10 +219,60 @@ def nodes(self, nodes): if nodes is None: raise ValueError("Invalid value for `nodes`, must not be `None`") # noqa: E501 - self._nodes = nodes + for node in nodes: + node_handler = NodeHandler() + node_obj = node_handler.import_node_data(node) + self._nodes.append(node_obj) + + return self.get_nodes() + + def remove_node(self,node_id): + for node in list(self._nodes): + if node.id == node_id: + self._nodes.remove(node) + + def add_nodes(self, node_objects): + """add the nodes to this Topology. + :param node_objects: a list of node objects + """ + + self._nodes.extend(node_objects) + + def get_node_by_port(self,aPort): + for node in self.nodes: + ports = node.ports + for port in ports: + if port.id == aPort: + return node + + return None + def get_port_by_link(self,n1_id,n2_id): + for x in self.links: + #print("--------") + #print(x.ports[0]['node']) + #print(x.ports[1]['node']) + if x.ports[0]['node']==n1_id and x.ports[1]['node']==n2_id: + return n1_id,x.ports[0],n2_id,x.ports[1] + if x.ports[0]['node']==n2_id and x.ports[1]['node']==n1_id: + return n1_id,x.ports[1],n2_id,x.ports[0] + + def has_node_by_id(self,id): + for node in self.nodes: + if id == node.id: + return True + return False @property def links(self): + """Gets the name of this Topology. # noqa: E501 + + + :return: The name of this Topology. # noqa: E501 + :rtype: str + """ + return self._links + + def get_links(self): """Gets the links of this Topology. # noqa: E501 @@ -217,18 +281,33 @@ def links(self): """ return self._links - @links.setter - def links(self, links): + def set_links(self, links): """Sets the links of this Topology. - - :param links: The links of this Topology. # noqa: E501 + :param links: The links of this Topology, in list of JSON str. # noqa: E501 :type: list[Link] """ if links is None: raise ValueError("Invalid value for `links`, must not be `None`") # noqa: E501 - self._links = links + for link in links: + link_handler = LinkHandler() + link_obj = link_handler.import_link_data(link) + self._links.append(link_obj) + + return self.get_links() + + def remove_link(self,link_id): + for link in list(self._links): + if link.id == link_id: + self._links.remove(link) + + def add_links(self, link_objects): + """add the links to this Topology. + :param link_objects: a list of link objects + """ + + self._links.extend(link_objects) @property def private_attributes(self): diff --git a/sdxdatamodel/parsing/__init__.py b/sdxdatamodel/parsing/__init__.py new file mode 100644 index 0000000..3339748 --- /dev/null +++ b/sdxdatamodel/parsing/__init__.py @@ -0,0 +1,6 @@ +from __future__ import absolute_import + +from os.path import dirname, basename, isfile, join +import glob +modules = glob.glob(join(dirname(__file__), "*.py")) +__all__ = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')] diff --git a/sdxdatamodel/parsing/connectionhandler.py b/sdxdatamodel/parsing/connectionhandler.py new file mode 100644 index 0000000..7f4d9fb --- /dev/null +++ b/sdxdatamodel/parsing/connectionhandler.py @@ -0,0 +1,42 @@ +import json +from sdxdatamodel.models.connection import Connection +from .exceptions import MissingAttributeException + +class ConnectionHandler(): + + """" + Handler for parsing the connection request descritpion in json + """ + + def __init__(self): + super().__init__() + self.connection = None + + def import_connection_data(self, data): + try: + id = data['id'] + name=data['name'] + if 'bandwidth_required' in data.keys(): + bw=data['bandwidth_required'] + if 'latency_required' in data.keys(): + la=data['latency_required'] + if 'start_time' in data.keys(): + start=data['start_time'] + if 'end_time' in data.keys(): + end=data['end_time'] + ingress=data['ingress_port'] + egress=data['egress_port'] + except KeyError as e: + raise MissingAttributeException(e.args[0],e.args[0]) + + connection=Connection(id=id, name=name, start_time = start, end_time = end, bandwidth=bw, latency=la, ingress_port = ingress, egress_port = egress) + + return connection + + def import_connection(self,file): + with open(file, 'r', encoding='utf-8') as data_file: + data = json.load(data_file) + self.connection = self.import_connection_data(data) + + def get_connection(self): + return self.connection diff --git a/sdxdatamodel/parsing/exceptions.py b/sdxdatamodel/parsing/exceptions.py new file mode 100644 index 0000000..f4146bb --- /dev/null +++ b/sdxdatamodel/parsing/exceptions.py @@ -0,0 +1,36 @@ +class DataModelException(Exception): + """ + Base exception for topology data model functions + """ + pass + +class MissingAttributeException(DataModelException): + """ + A required attribute could not be found when parsing a model element in JSON + """ + + def __init__(self, attribute_name, expected_attribute): + self.attribute_name = attribute_name + self.expected_attribute = expected_attribute + + def __str__(self): + return ("Missing attribute {} while parsing <{}>").format( + self.expected_attribute, + self.attribute_name, + ) + +class GraphNotConnectedException(DataModelException): + """ + The topology is not connected + """ + + def __init__(self,graph, connectivity): + self.graph = graph + self.connectivity = connectivity + + def __str__(self): + return ("Graph <{}> is Not {} Connected ").format( + self.graph, + self.connectivity, + ) + diff --git a/sdxdatamodel/parsing/linkhandler.py b/sdxdatamodel/parsing/linkhandler.py new file mode 100644 index 0000000..240493b --- /dev/null +++ b/sdxdatamodel/parsing/linkhandler.py @@ -0,0 +1,55 @@ +import json +from sdxdatamodel.models.link import Link +from .exceptions import MissingAttributeException + +class LinkHandler(): + + """" + Handler for parsing the connection request descritpion in json + """ + + def __init__(self): + super().__init__() + self.link = None + + def import_link_data(self, data): + try: + id = data['id'] + name=data['name'] + short_name=data['short_name'] + ports=data['ports'] + + n=None;timestamp=None;t_b=None;a_b=None;latency=None;p_l=None;p_a=None;avai=None;m_p=None + if 'nni' in data.keys(): + n=bool(data['nni']) + if 'time_stamp' in data.keys(): + timestamp=data['time_stamp'] + if 'bandwidth' in data.keys(): + t_b=data['bandwidth'] + if 'residual_bandwidth' in data.keys(): + a_b=data['residual_bandwidth'] + if 'latency' in data.keys(): + latency=data['latency'] + if 'packet_loss' in data.keys(): + p_l=data['packet_loss'] + if 'private_attributes' in data.keys(): + p_a=data['private_attributes'] + if 'availability' in data.keys(): + avai=data['availability'] + if 'measurement_period' in data.keys(): + m_p=data['measurement_period'] + except KeyError as e: + raise MissingAttributeException(e.args[0],e.args[0]) + + link=Link(id=id, name=name, short_name=short_name, ports=ports, nni=n, bandwidth=t_b, residual_bandwidth=a_b, + latency=latency, packet_loss=p_l, availability=avai, private_attributes=p_a, time_stamp=timestamp, measurement_period=m_p) + + return link + + def import_link(self,file): + with open(file, 'r', encoding='utf-8') as data_file: + data = json.load(data_file) + self.link = self.import_link_data(data) + + def get_link(self): + return self.link diff --git a/sdxdatamodel/parsing/locationhandler.py b/sdxdatamodel/parsing/locationhandler.py new file mode 100644 index 0000000..9fa5b36 --- /dev/null +++ b/sdxdatamodel/parsing/locationhandler.py @@ -0,0 +1,37 @@ +import json +from sdxdatamodel.models.location import Location +from .exceptions import MissingAttributeException + +class LocationHandler(): + + """" + Handler for parsing the connection request descritpion in json + """ + + def __init__(self): + super().__init__() + self.location = None + + def import_location_data(self, data): + try: + addr=None;long=None;lat=None + if 'address' in data.keys(): + addr = data['address'] + if 'latitude' in data.keys(): + long=data['latitude'] + if 'longitude' in data.keys(): + lat=data['longitude'] + except KeyError as e: + raise MissingAttributeException(e.args[0],e.args[0]) + + location=Location(address=addr, longitude=long, latitude=lat) + + return location + + def import_location(self,file): + with open(file, 'r', encoding='utf-8') as data_file: + data = json.load(data_file) + self.location = self.import_location_data(data) + + def get_location(self): + return self.location diff --git a/sdxdatamodel/parsing/nodehandler.py b/sdxdatamodel/parsing/nodehandler.py new file mode 100644 index 0000000..f0f49c5 --- /dev/null +++ b/sdxdatamodel/parsing/nodehandler.py @@ -0,0 +1,38 @@ +import json +from sdxdatamodel.models.node import Node +from .exceptions import MissingAttributeException + +class NodeHandler(): + + """" + Handler for parsing the connection request descritpion in json + """ + + def __init__(self): + super().__init__() + self.node = None + + def import_node_data(self, data): + try: + id = data['id'] + name=data['name'] + short_name=data['short_name'] + location=data['location'] + ports=data['ports'] + p_a=None + if 'private_attributes' in data.keys(): + p_a=data['private_attributes'] + except KeyError as e: + raise MissingAttributeException(e.args[0],e.args[0]) + + node=Node(id=id, name=name, short_name=short_name, location=location, ports=ports, private_attributes=p_a) + + return node + + def import_node(self,file): + with open(file, 'r', encoding='utf-8') as data_file: + data = json.load(data_file) + self.node = self.import_node_data(data) + + def get_node(self): + return self.node diff --git a/sdxdatamodel/parsing/porthandler.py b/sdxdatamodel/parsing/porthandler.py new file mode 100644 index 0000000..a34b058 --- /dev/null +++ b/sdxdatamodel/parsing/porthandler.py @@ -0,0 +1,44 @@ +import json +from sdxdatamodel.models.port import Port +from .exceptions import MissingAttributeException + +class PortHandler(): + + """" + Handler for parsing the connection request descritpion in json + """ + + def __init__(self): + super().__init__() + self.port = None + + def import_port_data(self, data): + try: + id = data['id'] + name=data['name'] + node=None + short_name=None + l_r=None + p_a=None + if 'node' in data.keys(): + node=data['node'] + if 'short_name' in data.keys(): + node=data['short_name'] + if 'label_range' in data.keys(): + l_r=data['label_range'] + if 'private_attributes' in data.keys(): + p_a=data['private_attributes'] + except KeyError as e: + raise MissingAttributeException(e.args[0],e.args[0]) + + self.port=Port(id=id, name=name,short_name=short_name, node=node, label_range=l_r, status=None, private_attributes=p_a) + + return self.port + + def import_port(self,file): + with open(file, 'r', encoding='utf-8') as data_file: + data = json.load(data_file) + self.port = self.import_port_data(data) + + def get_port(self): + return self.port diff --git a/sdxdatamodel/parsing/servicehandler.py b/sdxdatamodel/parsing/servicehandler.py new file mode 100644 index 0000000..189061d --- /dev/null +++ b/sdxdatamodel/parsing/servicehandler.py @@ -0,0 +1,42 @@ +import json +from sdxdatamodel.models.service import Service +from .exceptions import MissingAttributeException + +class ServiceHandler(): + + """" + Handler for parsing the connection request descritpion in json + """ + + def __init__(self): + super().__init__() + self.service = None + + def import_service_data(self, data): + try: + owner=data['owner'] + m_c=None;p_s=None;p_url=None;vendor=None;p_a=None + if 'monitoring_capability' in data.keys(): + m_c = data['monitoring_capability'] + if 'provisioning_system' in data.keys(): + p_s=data['provisioning_system'] + if 'provisioning_url' in data.keys(): + p_url=data['provisioning_url'] + if 'vendor' in data.keys(): + vendor=data['vendor'] + if 'private_attributes' in data.keys(): + p_a=data['private_attributes'] + except KeyError as e: + raise MissingAttributeException(e.args[0],e.args[0]) + + service=Service(monitoring_capability=m_c, owner=owner, private_attributes=p_a, provisioning_system=p_s, provisioning_url=p_url, vendor=vendor) + + return service + + def import_service(self,file): + with open(file, 'r', encoding='utf-8') as data_file: + data = json.load(data_file) + self.service = self.import_service_data(data) + + def get_service(self): + return self.service diff --git a/sdxdatamodel/parsing/topologyhandler.py b/sdxdatamodel/parsing/topologyhandler.py new file mode 100644 index 0000000..451fd6a --- /dev/null +++ b/sdxdatamodel/parsing/topologyhandler.py @@ -0,0 +1,48 @@ +import json +from sdxdatamodel.models.topology import Topology +from .exceptions import MissingAttributeException + +MANIFEST_FILE = None + +class TopologyHandler(): + + """" + Handler for parsing the topology descritpion in json + """ + + def __init__(self,topology_filename=None): + super().__init__() + self.topology_file = topology_filename + self.topology = None + + def topology_file_name(self,topology_filename=None): + self.topology_file = topology_filename + + def import_topology(self): + try: + with open(self.topology_file, 'r', encoding='utf-8') as data_file: + data = json.load(data_file) + self.import_topology_data(data) + except (IOError, FileNotFoundError) as e: + print(e) + + def import_topology_data(self, data): + try: + id = data['id'] + name=data['name'] + service = None + if 'domain_service' in data.keys(): + service=data['domain_service'] + version=data['version'] + time_stamp=data['time_stamp'] + nodes=data['nodes'] + links=data['links'] + except KeyError as e: + raise MissingAttributeException(e.args[0],e.args[0]) + + self.topology=Topology(id=id, name=name, domain_service = service, version=version, time_stamp=time_stamp, nodes=nodes, links=links) + + return self.topology + + def get_topology(self): + return self.topology diff --git a/sdxdatamodel/sdxdatamodel.egg-info/PKG-INFO b/sdxdatamodel/sdxdatamodel.egg-info/PKG-INFO new file mode 100644 index 0000000..65ffadd --- /dev/null +++ b/sdxdatamodel/sdxdatamodel.egg-info/PKG-INFO @@ -0,0 +1,80 @@ +Metadata-Version: 2.1 +Name: sdxdatamodel +Version: 0.0.1 +Summary: UNKNOWN +Home-page: https://github.com/atlanticwave-sdx/datamodel +Author: Y. Xin +Author-email: yxin@renci.org +License: UNKNOWN +Project-URL: Bug Tracker, https://github.com/atlanticwave-sdx/datamodel/issues +Description: ## Table of Contents + + - [How to Contribute](#contrib) + - [AW-SDX Data Model](#datamodel) + - [How to test and use](#usage) + - [AW-SDX Accompanying Projectsl](#accompany) + + ## How to Contribute + + 1. Ensure you're able to run the existing code in your own [development environment](#setup). + 2. Create a descriptive [GitHub issue](https://github.com/atlanticwave-sdx/datamodel/issues) that outlines what feature you plan to contribute. + 3. Clone the repository, and start from the most recent version of the [develop branch](https://github.com/atlanticwave-sdx/datamodel/tree/develop). + 4. Name your branch using the Github issue number as a prefix along with a brief name that corresponds to your feature (e.g., `8-how-to-contribute`). + 5. Once satisfied with your completed and tested work, submit a [pull request](https://github.com/atlanticwave-sdx/datamodel/pulls) against the **develop** branch so that your code can be reviewed by the team. + + Notes: + + - Do not create a pull request against the **master** branch. The **master** branch is considered the production branch and must always remain stable. The **master** branch is periodically updated from the contents of the **develop** branch at the conclusion of a development cycle. + - Do not put any content (css, js, images, etc.) in the main `static` directory, instead create a directory named `static` in your app that can be imported into the main `static` directory using the `manage.py collectstatic` call. + - Use clear and concise naming conventions for apps, classes, functions, variables, etc. Ideally others will be able to reuse your work, and the more clear and concise your code is, the easier it is to reuse it. + - Include easy to understand documentation and complete unit/functional tests for each new feature being introduced to the project. ([pytest](https://docs.pytest.org/en/latest/) is the recommended framework to use for testing). + + ## AW-SDX Data Model + + ## System + Each domain, proxied by the customized SDX-LC who communicates between the SDX-controller and the domain (1) provisioning system (eg, Kytos) and (2) monitoring system (BAPM). + + ## Topology Models + In the whole SDX system, two types of topology models are needed: + ### Domain substrate description model + It's used by the intra-domain prvisioning system. + ### Domain declaration/advertisement model + Based on the information from the domain provisioning system, tt's abstracted/generated/passed by the SDX-LC to the SDX-controller for inter-domain topology assembly to support (a) inter-domain path computation; and (b) inter-domain path monitoring and reconfiguration. It would consist of three types of information: (1) Topology abstraction; (2) network resources available for inter-domain connections and their QoS metrics (eg, bw, latency, packet loss, vlan ranges, etc); (3) switching capability (eg, vlan, Q-in-Q, etc). + + There is a *service* attribute in the topology object, which is an object that describes domain service meta information like owner, provisiong system, and security features. + + ## Domain topology and state update + ### Topology update: + On the events of addition, removal, and/or maintenance of ports, nodes, links, an updated domain topology with version and timestamp needs to be sent to the LC and subsequently the SDX controller. A new topology object is supposed to be generated and passed on to the SDX controller. + + ### Topology link state update + This set of updates mainly come from the domain monitoring system which is supposed to stream periodical measurement information on the links, like bandwidth, latency, and packet loss. A new link object is supposed to generated and pass on to the SDX controller. + + ## Topology description schemas + There are defined in the *schema* subfolder. Some attributes of each objects are requied (Can be found in the API definition) while some are optional. Two attributes are worth of mentioning: (1) In the *service* object, there is a *vender* attribute for the domain to list device vendors that are NOT in its domain, (2) in the topology, link, node, and port objects, there is an *private* attibute for the domain to list attributes that need to kept private.:wq + + ## Usage + ## Unittest + ``` + python -m unittest -v test.test_topology_handler + ``` + ``` + python -m unittest -v test.test_topology_validator + ``` + ## Install + ``` + pip install -r requirements.txt + ``` + ``` + python setup.py install + ``` + + + ## Accompanying AW-SDX Projects + +Platform: UNKNOWN +Classifier: Programming Language :: Python :: 3 +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Requires-Python: >=3.6 +Description-Content-Type: text/markdown diff --git a/sdxdatamodel/sdxdatamodel.egg-info/SOURCES.txt b/sdxdatamodel/sdxdatamodel.egg-info/SOURCES.txt new file mode 100644 index 0000000..ad95d98 --- /dev/null +++ b/sdxdatamodel/sdxdatamodel.egg-info/SOURCES.txt @@ -0,0 +1,44 @@ +README.md +setup.cfg +setup.py +sdxdatamodel/models/__init__.py +sdxdatamodel/models/connection.py +sdxdatamodel/models/link.py +sdxdatamodel/models/link_measurement_period.py +sdxdatamodel/models/location.py +sdxdatamodel/models/node.py +sdxdatamodel/models/port.py +sdxdatamodel/models/service.py +sdxdatamodel/models/topology.py +sdxdatamodel/parsing/__init__.py +sdxdatamodel/parsing/connectionhandler.py +sdxdatamodel/parsing/exceptions.py +sdxdatamodel/parsing/linkhandler.py +sdxdatamodel/parsing/locationhandler.py +sdxdatamodel/parsing/nodehandler.py +sdxdatamodel/parsing/porthandler.py +sdxdatamodel/parsing/servicehandler.py +sdxdatamodel/parsing/topologyhandler.py +sdxdatamodel/sdxdatamodel.egg-info/PKG-INFO +sdxdatamodel/sdxdatamodel.egg-info/SOURCES.txt +sdxdatamodel/sdxdatamodel.egg-info/dependency_links.txt +sdxdatamodel/sdxdatamodel.egg-info/requires.txt +sdxdatamodel/sdxdatamodel.egg-info/top_level.txt +sdxdatamodel/topologymanager/__init__.py +sdxdatamodel/topologymanager/grenmlconverter.py +sdxdatamodel/topologymanager/manager.py +sdxdatamodel/validation/__init__.py +sdxdatamodel/validation/connectionvalidator.py +sdxdatamodel/validation/topologyvalidator.py +test/test_connection_handler.py +test/test_connection_validator.py +test/test_link_handler.py +test/test_location_handler.py +test/test_node_handler.py +test/test_port_handler.py +test/test_service_handler.py +test/test_topology_graph.py +test/test_topology_grenmlconverter.py +test/test_topology_handler.py +test/test_topology_manager.py +test/test_topology_validator.py \ No newline at end of file diff --git a/parsing/__init__.py b/sdxdatamodel/sdxdatamodel.egg-info/dependency_links.txt similarity index 100% rename from parsing/__init__.py rename to sdxdatamodel/sdxdatamodel.egg-info/dependency_links.txt diff --git a/sdxdatamodel/sdxdatamodel.egg-info/requires.txt b/sdxdatamodel/sdxdatamodel.egg-info/requires.txt new file mode 100644 index 0000000..92b69d7 --- /dev/null +++ b/sdxdatamodel/sdxdatamodel.egg-info/requires.txt @@ -0,0 +1 @@ +python-dateutil~=2.8 diff --git a/sdxdatamodel/sdxdatamodel.egg-info/top_level.txt b/sdxdatamodel/sdxdatamodel.egg-info/top_level.txt new file mode 100644 index 0000000..9ee0aa8 --- /dev/null +++ b/sdxdatamodel/sdxdatamodel.egg-info/top_level.txt @@ -0,0 +1,4 @@ +models +parsing +topologymanager +validation diff --git a/sdxdatamodel/topologymanager/__init__.py b/sdxdatamodel/topologymanager/__init__.py new file mode 100644 index 0000000..9bc366c --- /dev/null +++ b/sdxdatamodel/topologymanager/__init__.py @@ -0,0 +1,6 @@ +from __future__ import absolute_import + +from os.path import dirname, basename, isfile, join +import glob +modules = glob.glob(join(dirname(__file__), "*.py")) +__all__ = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')] \ No newline at end of file diff --git a/sdxdatamodel/topologymanager/grenmlconverter.py b/sdxdatamodel/topologymanager/grenmlconverter.py new file mode 100644 index 0000000..7313b7d --- /dev/null +++ b/sdxdatamodel/topologymanager/grenmlconverter.py @@ -0,0 +1,57 @@ +from grenml import GRENMLManager +from grenml.models.nodes import Node +from grenml.models.links import Link + +from sdxdatamodel.models.topology import Topology +#from models.node import Node +from sdxdatamodel.models.location import Location + +class GrenmlConverter(object): + + def __init__(self, topology:Topology): + self.topology=topology + self.grenml_manager = GRENMLManager(topology.name) + + def set_topology(self, topology:Topology): + self.topology=topology + + def read_topology(self): + domain_service = self.topology.get_domain_service() + owner = domain_service.owner + self.grenml_manager.set_primary_owner(owner) + + self.grenml_manager.add_institution(owner,owner) + + self.add_nodes(self.topology.get_nodes()) + + self.add_links(self.topology.get_links()) + + self.topology_str = self.grenml_manager.write_to_string() + + #print(self.topology_str) + + def add_nodes(self,nodes): + for node in nodes: + location = node.get_location() + print("adding node:"+node.id) + self.grenml_manager.add_node(node.id, node.name,node.short_name,longitude=location.longitude,latitude=location.latitude, address=location.address) + + def add_links(self,links): + for link in links: + inter_domain_link = False + ports=link.ports + end_nodes=[] + for port in ports: + node=self.topology.get_node_by_port(port['id']) + if node is not None: + location = node.get_location() + grenml_node = Node(node.id, node.name,node.short_name,longitude=location.longitude,latitude=location.latitude, address=location.address) + end_nodes.append(grenml_node) + else: + print("This port doesn't belong to any node in the topology, likely an Interdomain port!" + port['id']) + inter_domain_link = True + if not inter_domain_link: + self.grenml_manager.add_link(link.id, link.name,link.short_name, nodes=end_nodes) + + def get_xml_str(self): + return self.topology_str \ No newline at end of file diff --git a/sdxdatamodel/topologymanager/manager.py b/sdxdatamodel/topologymanager/manager.py new file mode 100644 index 0000000..8b70a82 --- /dev/null +++ b/sdxdatamodel/topologymanager/manager.py @@ -0,0 +1,280 @@ + +import json +import copy + +import networkx as nx +import datetime + +from sdxdatamodel.models.topology import Topology, SDX_TOPOLOGY_ID_prefix,TOPOLOGY_INITIAL_VERSION +from sdxdatamodel.models.link import Link +from sdxdatamodel.models.port import Port + + +from sdxdatamodel.parsing.topologyhandler import TopologyHandler +from sdxdatamodel.parsing.exceptions import DataModelException + +from .grenmlconverter import GrenmlConverter + +class TopologyManager(): + + """" + Manager for topology operations: merge multiple topologies, convert to grenml (XML). + """ + + def __init__(self): + super().__init__() + + self.handler = TopologyHandler() + + self.topology=None + self.topology_list={} + self.port_list={} #{port, link} + + self.num_interdomain_link=0 + + def get_handler(self): + return self.handler + + def topology_id(self, id): + self.topology._id(id) + + def set_topology(self, topology): + self.topology = topology + + def get_topology(self): + return self.topology + + def clear_topology(self): + self.topology=None + self.topology_list={} + self.port_list={} + + def add_topology(self, data): + + topology = self.handler.import_topology_data(data) + self.topology_list[topology.id] = topology + + if self.topology is None: + self.topology = copy.deepcopy(topology) + ##generate a new topology id + self.generate_id() + #Addding to the port list + links = topology.get_links() + for link in links: + for port in link.ports: + self.port_list[port['id']] = link + else: + ##check the inter-domain links first. + self.num_interdomain_link+=self.inter_domain_check(topology) + if self.num_interdomain_link==0: + print("Warning: no interdomain links detected!") + ##nodes + nodes = topology.get_nodes() + self.topology.add_nodes(nodes) + ##links + links = topology.get_links() + self.topology.add_links(links) + #version + self.update_version(False) + + self.update_timestamp() + + def get_domain_name(self,node_id): + domain_id=None + #print("len of topology_list:"+str(len(self.topology_list))) + for id, topology in self.topology_list.items(): + if topology.has_node_by_id(node_id): + domain_id = id + break + return domain_id + + def generate_id(self): + self.topology.set_id(SDX_TOPOLOGY_ID_prefix) + self.topology.version=TOPOLOGY_INITIAL_VERSION + return id + + def remove_topology(self, topology_id): + self.topology_list.pop(topology_id, None) + self.update_version(False) + self.update_timestamp() + + def update_topology(self,data): + #likely adding new inter-domain links + update_handler = TopologyHandler() + topology = update_handler.import_topology_data(data) + self.topology_list[topology.id] = topology + + ##nodes + nodes = topology.get_nodes() + for node in nodes: + self.topology.remove_node(node.id) + ##links + links = topology.get_links() + for link in links: + if link.nni != True: + #print(link.id+";......."+str(link.nni)) + self.topology.remove_link(link.id) + for port in link.ports: + self.port_list.pop(port['id']) + + ##check the inter-domain links first. + num_interdomain_link=self.inter_domain_check(topology) + if num_interdomain_link==0: + print("Warning: no interdomain links detected!") + + ##nodes + nodes = topology.get_nodes() + self.topology.add_nodes(nodes) + ##links + links = topology.get_links() + self.topology.add_links(links) + + self.update_version(True) + self.update_timestamp() + + def update_version(self,sub:bool): + try: + [ver, sub_ver] = self.topology.version.split('.') + except ValueError: + ver=self.topology.version + sub_ver='0' + + self.topology.version=self.new_version(ver, sub_ver,sub) + + return self.topology.version + + def new_version(self,ver, sub_ver,sub:bool): + if not sub: + ver=str(int(ver)+1) + sub_ver='0' + else: + sub_ver=str(int(sub_ver)+1) + + return ver+"."+sub_ver + + def update_timestamp(self): + ct = datetime.datetime.now().isoformat() + self.topology.time_stamp=ct + + return ct + + def inter_domain_check(self,topology): + interdomain_port_dict={} + num_interdomain_link=0 + links = topology.get_links() + link_dict ={} + for link in links: + link_dict[link.id] = link + for port in link.ports: + interdomain_port_dict[port['id']]=link + + #ToDo: raise an warning or exception + if len(interdomain_port_dict)==0: + print("interdomain_port_dict==0") + return False + + #match any ports in the existing topology + for port_id in interdomain_port_dict: + #print("interdomain_port:") + #print(port_id) + for existing_port, existing_link in self.port_list.items(): + #print(existing_port) + if port_id == existing_port: + #print("Interdomain port:" + port_id) + #remove redundant link between two domains + self.topology.remove_link(existing_link.id) + num_interdomain_link=+1 + self.port_list[port_id] = interdomain_port_dict[port_id] + + return num_interdomain_link + + #adjacent matrix of the graph, in jason? + def generate_graph(self): + G = nx.Graph() + links = self.topology.links + for link in links: + inter_domain_link = False + ports=link.ports + end_nodes=[] + for port in ports: + node=self.topology.get_node_by_port(port['id']) + if node is None: + print("This port doesn't belong to any node in the topology, likely a Non-SDX port!" + port['id']) + inter_domain_link = True + break + else: + end_nodes.append(node) + #print("graph node:"+node.id) + if not inter_domain_link: + G.add_edge(end_nodes[0].id,end_nodes[1].id) + edge = G.edges[end_nodes[0].id,end_nodes[1].id] + edge['id'] = link.id + edge['latency'] = link.latency + edge['bandwidth'] = link.bandwidth + edge['residual_bandwidth'] = link.residual_bandwidth + edge['weight'] = 1000.0*(1.0/link.residual_bandwidth) + edge['packet_loss'] = link.packet_loss + edge['availability'] = link.availability + + return G + + def generate_grenml(self): + self.converter = GrenmlConverter(self.topology) + + return self.converter.read_topology() + + def add_domain_service(self): + pass + + #may need to read from a configuration file. + def update_private_properties(self): + pass + + # on performance properties for now + def update_link_property(self,link_id,property,value): + #1. update the individual topology + for id, topology in self.topology_list.items(): + links = topology.get_links() + for link in links: + print(link.id + ";" + id) + if link.id == link_id: + setattr(link,property,value) + print("updated the link.") + #1.2 need to change the sub_ver of the topology? + + #2. check on the inter-domain link? + #3. update the interodamin topology + links = self.topology.get_links() + for link in links: + if link.id == link_id: + setattr(link,property,value) + print("updated the link.") + #2.2 need to change the sub_ver of the topology? + + self.update_version(True) + self.update_timestamp() + #4. Signal update the (networkx) graph + + # 5. signal Reoptimization of TE? + + def update_element_property_json(self,data,element, element_id,property,value): + elements=data[element] + for element in elements: + if element['id'] == element_id: + element[property] = value + + try: + [ver, sub_ver] = data['version'].split('.') + except ValueError: + ver='0' + sub_ver='0' + + data['version'] = self.new_version(ver,sub_ver,True) + data['time_stamp'] = datetime.datetime.now().isoformat() + + + def update_node_property(self): + pass + + def update_port_property(self): + pass diff --git a/sdxdatamodel/topologymanager/temanager.py b/sdxdatamodel/topologymanager/temanager.py new file mode 100644 index 0000000..136b5ff --- /dev/null +++ b/sdxdatamodel/topologymanager/temanager.py @@ -0,0 +1,135 @@ + +import json +import copy + +import networkx as nx +from networkx.algorithms import approximation as approx + +from sdxdatamodel.models.topology import Topology, SDX_TOPOLOGY_ID_prefix,TOPOLOGY_INITIAL_VERSION +from sdxdatamodel.models.link import Link +from sdxdatamodel.models.port import Port +from sdxdatamodel.models.connection import Connection + +from sdxdatamodel.parsing.connectionhandler import ConnectionHandler +from sdxdatamodel.topologymanager.manager import TopologyManager +from sdxdatamodel.parsing.topologyhandler import TopologyHandler +from sdxdatamodel.parsing.exceptions import DataModelException + +from .manager import TopologyManager + +class TEManager(): + + """" + TE Manager for connection - topology operations: (1) generate inputs to the PCE solver; (2) converter the solver output. + """ + + + def __init__(self, topology_data, connection_data): + super().__init__() + + self.manager = TopologyManager() + self.connection_handler = ConnectionHandler() + + self.manager.topology = self.manager.get_handler().import_topology_data(topology_data) + self.connection = self.connection_handler.import_connection_data(connection_data) + + self.graph = self.generate_graph_te() + + def generate_connection_te(self): + ingress_port = self.connection.ingress_port + ingress_node = self.manager.topology.get_node_by_port(ingress_port.id) + egress_port = self.connection.egress_port + egress_node = self.manager.topology.get_node_by_port(egress_port.id) + + i_node = [x for x,y in self.graph.nodes(data=True) if y['id']==ingress_node.id] + e_node = [x for x,y in self.graph.nodes(data=True) if y['id']==egress_node.id] + + bandwidth_required=self.connection.bandwidth + latency_required=self.connection.latency + requests=[] + request=[i_node[0],e_node[0],bandwidth_required,latency_required] + requests.append(request) + + return requests + + def generate_graph_te(self): + graph = self.manager.generate_graph() + graph = nx.convert_node_labels_to_integers(graph,label_attribute='id') + self.graph=graph + #print(list(graph.nodes(data=True))) + return graph + + def graph_node_connectivity(self, source=None, dest=None): + conn = approx.node_connectivity(self.graph,source,dest) + return conn + + def requests_connectivity(self, requests): + for request in requests: + conn = self.graph_node_connectivity(request[0],request[1]) + print("Request Connectivity: {}, {} = {}".format( + request[0], + request[1], + conn, + ) + ) + return True + + + def generate_connection_breakdown(self, connection): + breakdown = {} + paths = connection[0] #p2p for now + cost=connection[1] + i_port = None + e_port = None + print("domain breakdown:") + for i,j in paths.items(): + current_link_set=[] + for count,link in enumerate(j): + node_1 = self.graph.nodes[link[0]] + node_2 = self.graph.nodes[link[1]] + domain_1 = self.manager.get_domain_name(node_1['id']) + domain_2 = self.manager.get_domain_name(node_2['id']) + current_link_set.append(link) + current_domain = domain_1 + if domain_1 == domain_2: + #current_domain = domain_1 + if count==len(j)-1: + breakdown[current_domain]=current_link_set.copy() + else: + breakdown[current_domain]=current_link_set.copy() + current_domain=None + current_link_set=[] + print(breakdown) + #now starting with the ingress_port + first = True + i=0 + domain_breakdown={} + + for domain, links in breakdown.items(): + segment={} + if first: + first=False + last_link=links[-1] + n1=self.graph.nodes[last_link[0]]['id'] + n2=self.graph.nodes[last_link[1]]['id'] + n1,p1,n2,p2=self.manager.topology.get_port_by_link(n1,n2) + i_port=self.connection.ingress_port.to_dict() + e_port=p1 + next_i = p2 + elif i==len(breakdown)-1: + i_port=next_i + e_port=self.connection.egress_port.to_dict() + else: + last_link=links[-1] + n1=self.graph.nodes[last_link[0]]['id'] + n2=self.graph.nodes[last_link[1]]['id'] + n1,p1,n2,p2=self.manager.topology.get_port_by_link(n1,n2) + i_port=next_i + e_port=p1 + next_i=p2 + segment['ingress_port'] = i_port + segment['egress_port'] = e_port + domain_breakdown[domain]=segment.copy() + i=i+1 + + return domain_breakdown diff --git a/sdxdatamodel/validation/__init__.py b/sdxdatamodel/validation/__init__.py new file mode 100644 index 0000000..3339748 --- /dev/null +++ b/sdxdatamodel/validation/__init__.py @@ -0,0 +1,6 @@ +from __future__ import absolute_import + +from os.path import dirname, basename, isfile, join +import glob +modules = glob.glob(join(dirname(__file__), "*.py")) +__all__ = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')] diff --git a/sdxdatamodel/validation/connectionvalidator.py b/sdxdatamodel/validation/connectionvalidator.py new file mode 100644 index 0000000..50f9e8c --- /dev/null +++ b/sdxdatamodel/validation/connectionvalidator.py @@ -0,0 +1,147 @@ +""""" +-------------------------------------------------------------------- +Synopsis: A validation class to evaluate that the supplied Connection object contains expected data format +""" +from collections.abc import Iterable +from datetime import * +from re import match + +from sdxdatamodel.models.port import Port +from sdxdatamodel.models.connection import Connection + +ISO_FORMAT = r'\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[-+]\d{2}:\d{2}' + +ISO_TIME_FORMAT = r"(^(?:[1-9]\d{3}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1\d|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[1-9]\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00)-02-29)T(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:Z|[+-][01]\d:[0-5]\d)$)" + +class ConnectionValidator(): + """ + The validation class made to validate a Connection request + """ + def __init__(self): + super().__init__() + self.connection = None + + def get_connection(self): + return self.connection + + def set_connection(self, conn): + if not isinstance(conn, Connection): + raise ValueError('The Validator must be passed a Connection object') + self.connection = conn + + def is_valid(self): + errors = self.validate(self.connection, raise_error=True) + for error in errors: + print(error) + return not bool(errors) + + def validate(self, conn=None, raise_error=True): + if not conn and self._connection: + conn = self.connection + errors = self._validate_connection(conn) + if errors and raise_error: + raise ValueError('\n'.join(errors)) + return errors + + def _validate_connection(self, conn: Connection): + """ + Validate that the connection provided meets the JSON schema. + A connection must have the following: + - It must meet object standard + - It must have the default fields: id, name, ingress_port, and egress_port + :param connection: The connection being evaluated + :return: A list of any issues in the data. + """ + errors = [] + errors += self._validate_object_defaults(conn) + + errors += self._validate_port(conn.ingress_port, conn) + + errors += self._validate_port(conn.egress_port, conn) + + #errors += self._validate_time(conn.start_time, conn) + + #errors += self._validate_time(conn.end_time, conn) + return errors + + def _validate_port(self, port: Port, conn: Connection): + """ + Validate that the port provided meets the XSD standards. + A port must have the following: + - It must meet object default standards. + - A port must belong to a topology + - The node is optional + :param port: The port being evaluated. + :param topology: The Topology. + :return: A list of any issues in the data. + """ + + errors = [] + if not port: + errors.append('{} must exist'.format(port.__class__.__name__)) + + errors += self._validate_object_defaults(port) + + """ + node = find_node(port,topology) + if topology and node not in topology.nodes: + errors.append( + 'listed node id {} does not exist in parent Topology {}'.format( + node.id, node, topology.id + ) + ) + """ + return errors + + def _validate_time(self, time: str, conn: Connection): + """ + Validate that the time provided meets the XSD standards. + A port must have the following: + - It must meet object default standards. + - A link can only connect to 2 nodes + - The nodes that a link is connected to must be in the parent Topology's nodes list + :param time: time being validated + :return: A list of any issues in the data. + """ + errors = [] + if not match(ISO_TIME_FORMAT, time): + errors.append( + '{} time needs to be in full ISO format'.format( + time, + ) + ) + return errors + + def _validate_object_defaults(self, sdx_object): + """ + Validate that the object provided default fields meets the XSD standards. + The object must have the following: + - The object must have an ID + - The object ID must be a string + - The object must have a name + - The object name must be a string + - If the object has a short name, it must be a string + - If the object has a version, it must be a string in ISO format + - All the additional properties on the object are proper + :param sdx_object: The sdx Model Object being evaluated. + :return: A list of any issues in the data. + """ + errors = [] + if not sdx_object._id: + errors.append('{} must have an ID'.format(sdx_object.__class__.__name__)) + if not isinstance(sdx_object._id, str): + errors.append('{} ID must be a string'.format(sdx_object.__class__.__name__)) + if not sdx_object._name: + errors.append( + '{} {} must have a name'.format( + sdx_object.__class__.__name__, sdx_object._name, + ) + ) + if not isinstance(sdx_object._name, str): + errors.append( + '{} {} name must be a String'.format( + sdx_object.__class__.__name__, sdx_object._name, + ) + ) + + return errors \ No newline at end of file diff --git a/sdxdatamodel/validation/topologyvalidator.py b/sdxdatamodel/validation/topologyvalidator.py new file mode 100644 index 0000000..6724e1d --- /dev/null +++ b/sdxdatamodel/validation/topologyvalidator.py @@ -0,0 +1,292 @@ +""""" +-------------------------------------------------------------------- +Synopsis: A validation class to evaluate that the supplied Topology object contains expected data format +""" +from collections.abc import Iterable +from re import match + +from sdxdatamodel import * +from sdxdatamodel.models import * + +from sdxdatamodel.models.topology import SDX_INSTITUTION_ID, Topology +from sdxdatamodel.models.service import Service +from sdxdatamodel.models.node import Node +from sdxdatamodel.models.link import Link +from sdxdatamodel.models.port import Port +from sdxdatamodel.models.location import Location + + +ISO_FORMAT = r'\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[-+]\d{2}:\d{2}' + +class TopologyValidator: + """ + The validation class made to validate a Topology + """ + def __init__(self): + self.topology = None + + def get_topology(self): + return self.topology + + def set_topology(self, topology): + if not isinstance(topology, Topology): + raise ValueError('The Validator must be passed a Topology object') + self.topology = topology + + def is_valid(self): + errors = self.validate(self.topology, raise_error=False) + for error in errors: + print(error) + return not bool(errors) + def validate(self, topology=None, raise_error=True): + if not topology and self.topology: + topology = self.topology + errors = self._validate_topology(topology) + if errors and raise_error: + raise ValueError('\n'.join(errors)) + return errors + def _validate_topology(self, topology: Topology): + """ + Validate that the topology provided meets the JSON schema. + A topology must have the following: + - It must meet object standard + - It must have the default fields: id, name, version, time_stamp, nodes, and links + - It must have a Primary owner assigned + - It must have its primary owner in its institution list + - It must have the global institution in the institution list + :param topology: The topology being evaluated + :return: A list of any issues in the data. + """ + errors = [] + errors += self._validate_object_defaults(topology) + + if SDX_INSTITUTION_ID not in topology.id: + errors.append('Global Institution must be in Topology {}'.format(topology.id)) + + service = topology.get_domain_service() + if service is not None: + errors += self._validate_service(service, topology) + for node in topology.get_nodes(): + errors += self._validate_node(node, topology) + for link in topology.get_links(): + errors += self._validate_link(link, topology) + + return errors + + def _validate_service(self, service: Service, topology: Topology): + """ + Validate that the institution provided meets the XSD standards. + A institution must have the following: + - It must meet object default standards. + - It's Location values are valid + - The Institution Type must be in the list of valid Institution types + :param institution: The Institution being evaluated. + :param topology: The Parent Topology. + :return: A list of any issues in the data. + """ + errors = [] + errors += self._validate_object_defaults(service) + + return errors + + def _validate_version(self, version, time_stamp, topology: Topology): + """ + Validate that the version and time_stamp provided meets the ISO standards. + - It must meet object default standards. + - It's Location values are valid + - The Institution Type must be in the list of valid Institution types + :param version: The topology version. + :param time_stamp: The topology time stamp. + :param topology: The Parent Topology. + :return: A list of any issues in the data. + """ + errors = [] + if version: + if not isinstance(version, str): + errors.append( + '{} Version must be a String'.format( + topology.id, + ) + ) + elif not match(ISO_FORMAT, version): + errors.append( + '{} Version must be datetime ISO format'.format( + topology.id, + ) + ) + + if not match(ISO_FORMAT, time_stamp): + errors.append( + '{} time_stamp needs to be in full ISO format'.format( + time_stamp, + ) + ) + + return errors + + def _validate_node(self, node: Node, topology: Topology): + """ + Validate that the node provided meets the XSD standards. + A node must have the following: + - It must meet object default standards. + - It's Location values are valid + :param node: The Node being evaluated. + :param topology: The Parent Topology. + :return: A list of any issues in the data. + """ + errors = [] + + errors += self._validate_object_defaults(node) + errors += self._validate_location(node.get_location()) + + return errors + + def _validate_link(self, link: Link, topology: Topology): + """ + Validate that the link provided meets the XSD standards. + A link must have the following: + - It must meet object default standards. + - A link can only connect to 2 nodes + - The nodes that a link is connected to must be in the parent Topology's nodes list + :param link: The Link being evaluated. + :param topology: The Parent Topology. + :return: A list of any issues in the data. + """ + errors = [] + errors += self._validate_object_defaults(link) + + if len(link._ports) != 2: + errors.append( + 'Link {} must connect between 2 ports. Currently {}'.format( + link.id, str(link._ports) + ) + ) + for port in link._ports: + if not isinstance(port, str): + errors.append( + 'Link {} Port {} should be a string. Not {}'.format( + link.id, port, port.__class__.__name__ + ) + ) + #TODO: Check ports are in the current topology + + return errors + + def _validate_object_defaults(self, sdx_object): + """ + Validate that the object provided default fields meets the XSD standards. + The object must have the following: + - The object must have an ID + - The object ID must be a string + - The object must have a name + - The object name must be a string + - If the object has a short name, it must be a string + - If the object has a version, it must be a string in ISO format + - All the additional properties on the object are proper + :param sdx_object: The sdx Model Object being evaluated. + :return: A list of any issues in the data. + """ + errors = [] + if not sdx_object._id: + errors.append('{} must have an ID'.format(sdx_object.__class__.__name__)) + if not isinstance(sdx_object._id, str): + errors.append('{} ID must be a string'.format(sdx_object.__class__.__name__)) + if not sdx_object._name: + errors.append( + '{} {} must have a name'.format( + sdx_object.__class__.__name__, sdx_object._name, + ) + ) + if not isinstance(sdx_object._name, str): + errors.append( + '{} {} name must be a String'.format( + sdx_object.__class__.__name__, sdx_object._name, + ) + ) + + return errors + + def _validate_location(self, location: Location, enforce_coordinates=True): + """ + Validate that the object location fields meets the XSD standards. + The location must have the following: + - A location must have a longitude + - A location's longitude muse be a floating point value + - A location's longitude must be between -180 and -180 + - A location must have a latitude + - A location must be a floating point value + - A location's latitude must be between -90 and 90 + - A location's altitude must be a floating point value + - A location's UN/LOCODE must be a string value + - A location's address must be a string or a list of strings + :param location: The Location Object being evaluated. + :param enforce_coordinates: A boolean determining if longitude and latitude should be enforced + :return: A list of any issues in the data. + """ + errors = [] + if location.longitude is None and enforce_coordinates: + errors.append( + '{} {} Longitude must be set to a value'.format( + location.__class__.__name__, location.id, + ) + ) + try: + if location.longitude is not None: + if not -180 <= float(location.longitude) <= 180: + errors.append( + '{} {} Longitude must be a value that is between -180 and 180'.format( + location.__class__.__name__, location.id, + ) + ) + except ValueError: + errors.append( + '{} {} Longitude must be a value that coordinates to a Floating point value'.format( + location.__class__.__name__, location.id, + ) + ) + + if location.latitude is None and enforce_coordinates: + errors.append( + '{} {} Latitude must be set to a value'.format( + location.__class__.__name__, location.id + ) + ) + try: + if location.latitude is not None: + if not -90 <= float(location.latitude) <= 90: + errors.append( + '{} {} Latitude must be a value that is between -90 and 90'.format( + location.__class__.__name__, location.id, + ) + ) + except ValueError: + errors.append( + '{} {} Latitude must be a value that coordinates to a Floating point value'.format( + location.__class__.__name__, location.id, + ) + ) + + try: + if location.latitude: + float(location.latitude) + except ValueError: + errors.append( + '{} {} Altitude must be a value that coordinates to a Floating point value'.format( + location.__class__.__name__, location.id, + ) + ) + + if not location.address: + errors.append( + '{} {} Address must exist'.format( + location.__class__.__name__, location._id + ) + ) + if not type(location.address) == str: + errors.append( + '{} {} Address {} must be a string'.format( + location.__class__.__name__, location._id, location.address + ) + ) + + return errors \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..741abeb --- /dev/null +++ b/setup.cfg @@ -0,0 +1,34 @@ +[tool:pytest] +addopts = --cov --cov-report html --cov-report term-missing + +[metadata] +name = sdxdatamodel +version = 0.0.1 +author = Y. Xin +author_email = yxin@renci.org +description = Topology and request description data model in JSON +long_description = file: README.md +long_description_content_type = text/markdown +url = https://github.com/atlanticwave-sdx/datamodel +project_urls = + Bug Tracker = https://github.com/atlanticwave-sdx/datamodel/issues +classifiers = + Programming Language :: Python :: 3 + License :: OSI Approved :: MIT License + Operating System :: OS Independent +include_package_data = True +package_dir = + = sdxdatamodel +packages = find: +#packages =setuptools.find_packages(where='./datamodel'), +install_requires = + python-dateutil~=2.8 +python_requires = >=3.6 +tests_require = + pytest-cov + +[options.packages.find] +exclude = + docs + test* +where = sdxdatamodel diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..37c8dba --- /dev/null +++ b/setup.py @@ -0,0 +1,4 @@ + +import setuptools + +setuptools.setup() diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000..3875b20 --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,3 @@ +-r requirements.txt + +matplotlib==3.5.2 diff --git a/tests/__init__.py b/tests/__init__.py index e69de29..1cb91d4 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1,4 @@ +from os.path import dirname, basename, isfile, join +import glob +modules = glob.glob(join(dirname(__file__), "*.py")) +__all__ = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')] \ No newline at end of file diff --git a/tests/data/amlight.json b/tests/data/amlight.json new file mode 100644 index 0000000..649638e --- /dev/null +++ b/tests/data/amlight.json @@ -0,0 +1,310 @@ +{ + "id": "urn:ogf:network:sdx:topology:amlight.net", + "name": "AmLight-OXP", + "model_version":"1.0.0", + "time_stamp": "2000-01-23T04:56:07+00:00", + "version": 1.0, + "links": [ + { + "availability": 56.37376656633328, + "residual_bandwidth": 100000, + "id": "urn:ogf:network:sdx:link:amlight:B1-B2", + "latency": 5, + "name": "amlight:B1-B2", + "packet_loss": 59.621339166831824, + "ports": [ + { + "id": "urn:sdx:port:amlight.net:B1:2", + "name": "Novi01:2", + "node": "urn:sdx:node:amlight.net:B1", + "short_name": "B1:2", + "label_range": [ + "100-200", + "10001" + ], + "status": "up" + }, + { + "id": "urn:sdx:port:amlight.net:B2:2", + "label_range": [ + "100-200", + "10001" + ], + "name": "Novi02:2", + "node": "urn:sdx:node:amlight.net:B2", + "short_name": "B2:2", + "status": "up" + } + ], + "short_name": "Miami-BocaRaton", + "bandwidth": 100000 + }, + { + "availability": 56.37376656633328, + "residual_bandwidth": 100000, + "id": "urn:ogf:network:sdx:link:amlight:A1-B1", + "latency": 5, + "name": "amlight:A1-B1", + "packet_loss": 59.621339166831824, + "ports": [ + { + "id": "urn:sdx:port:amlight.net:A1:1", + "label_range": [ + "100-200", + "1000" + ], + "name": "Novi100:1", + "node": "urn:sdx:node:amlight.net:A1", + "short_name": "A1:1", + "status": "up" + }, + { + "id": "urn:sdx:port:amlight.net:B1:3", + "name": "Novi01:3", + "node": "urn:sdx:node:amlight.net:B1", + "short_name": "B1:3", + "label_range": [ + "100-200", + "10001" + ], + "status": "up" + } + ], + "short_name": "redclara-miami", + "bandwidth": 100000 + }, + { + "availability": 56.37376656633328, + "residual_bandwidth": 100000, + "id": "urn:ogf:network:sdx:link:amlight:A1-B2", + "latency": 5, + "name": "amlight:A1-B2", + "packet_loss": 59.621339166831824, + "ports": [ + { + "id": "urn:sdx:port:amlight.net:A1:2", + "label_range": [ + "100-200", + "1000" + ], + "name": "Novi100:2", + "node": "urn:sdx:node:amlight.net:A1", + "short_name": "A1:2", + "status": "up" + }, + { + "id": "urn:sdx:port:amlight.net:B2:3", + "label_range": [ + "100-200", + "10001" + ], + "name": "Novi02:3", + "node": "urn:sdx:node:amlight.net:B2", + "short_name": "B2:3", + "status": "up" + } + ], + "short_name": "redclara-BocaRaton", + "bandwidth": 100000 + }, + { + "availability": 56.37376656633328, + "residual_bandwidth": 100000, + "id": "urn:ogf:network:sdx:link:nni:Miami-Sanpaolo", + "latency": 10, + "name": "nni:Miami-Sanpaolo", + "packet_loss": 59.621339166831824, + "nni": "True", + "ports": [ + { + "id": "urn:sdx:port:amlight:B1:1", + "name": "Novi01:1", + "node": "urn:sdx:node:amlight.net:B1", + "short_name": "B1:1", + "label_range": [ + "100-200", + "10001" + ], + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:sax:B1:1", + "name": "Novi01:1", + "node": "urn:ogf:network:sdx:port:sax:B1", + "short_name": "B1:1", + "label_range": [ + "100-200", + "10001" + ], + "status": "up" + } + ], + "short_name": "Miami-Sanpaolo", + "bandwidth": 100000 + }, + { + "availability": 56.37376656633328, + "residual_bandwidth": 100000, + "id": "urn:ogf:network:sdx:link:nni:BocaRaton-Fortaleza", + "latency": 10, + "name": "nni:BocaRaton-Fortaleza", + "packet_loss": 59.621339166831824, + "nni": "True", + "ports": [ + { + "id": "urn:sdx:port:amlight.net:B2:1", + "label_range": [ + "100-200", + "1000" + ], + "name": "Novi02:1", + "node": "urn:sdx:node:amlight.net:B2", + "short_name": "B2:1", + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:sax:B2:1", + "label_range": [ + "100-200", + "1000" + ], + "name": "Novi02:1", + "node": "urn:ogf:network:sdx:node:sax:B2", + "short_name": "B2:1", + "status": "up" + } + ], + "short_name": "BocaRaton-Fortaleza", + "bandwidth": 100000 + } + ], + "nodes": [ + { + "id": "urn:sdx:node:amlight.net:B1", + "location": { + "address": "Miami", + "latitude": 25.75633040531146, + "longitude": -80.37676058477908 + }, + "name": "amlight:Novi01", + "ports": [ + { + "id": "urn:sdx:port:amlight:B1:1", + "name": "Novi01:1", + "node": "urn:sdx:node:amlight.net:B1", + "short_name": "B1:1", + "label_range": [ + "100-200", + "10001" + ], + "status": "up" + }, + { + "id": "urn:sdx:port:amlight.net:B1:2", + "name": "Novi01:2", + "node": "urn:sdx:node:amlight.net:B1", + "short_name": "B1:2", + "label_range": [ + "100-200", + "10001" + ], + "status": "up" + }, + { + "id": "urn:sdx:port:amlight.net:B1:3", + "name": "Novi01:3", + "node": "urn:sdx:node:amlight.net:B1", + "short_name": "B1:3", + "label_range": [ + "100-200", + "10001" + ], + "status": "up" + } + ], + "short_name": "B1" + }, + { + "id": "urn:sdx:node:amlight.net:B2", + "location": { + "address": "BocaRaton", + "latitude": 26.381437356374075, + "longitude": -80.10225977485742 + }, + "name": "amlight:Novi02", + "ports": [ + { + "id": "urn:sdx:port:amlight.net:B2:1", + "label_range": [ + "100-200", + "1000" + ], + "name": "Novi02:1", + "node": "urn:sdx:node:amlight.net:B2", + "short_name": "B2:1", + "status": "up" + }, + { + "id": "urn:sdx:port:amlight.net:B2:2", + "label_range": [ + "100-200", + "10001" + ], + "name": "Novi02:2", + "node": "urn:sdx:node:amlight.net:B2", + "short_name": "B2:2", + "status": "up" + }, + { + "id": "urn:sdx:port:amlight.net:B2:3", + "label_range": [ + "100-200", + "10001" + ], + "name": "Novi02:3", + "node": "urn:sdx:node:amlight.net:B2", + "short_name": "B2:3", + "status": "up" + } + ], + "short_name": "B2" + }, + { + "id": "urn:sdx:node:amlight.net:A1", + "location": { + "address": "redclara", + "latitude": 30.34943181039702, + "longitude": -81.66666016473143 + }, + "name": "amlight:Novi100", + "ports": [ + { + "id": "urn:sdx:port:amlight.net:A1:1", + "label_range": [ + "100-200", + "1000" + ], + "name": "Novi100:1", + "node": "urn:sdx:node:amlight.net:A1", + "short_name": "A1:1", + "status": "up" + }, + { + "id": "urn:sdx:port:amlight.net:A1:2", + "label_range": [ + "100-200", + "1000" + ], + "name": "Novi100:2", + "node": "urn:sdx:node:amlight.net:A1", + "short_name": "A1:2", + "status": "up" + } + ], + "short_name": "A1" + } + ], + "domain_service": { + "owner":"FIU" + } + } \ No newline at end of file diff --git a/tests/data/amlight_origin.json b/tests/data/amlight_origin.json new file mode 100644 index 0000000..09ab0ed --- /dev/null +++ b/tests/data/amlight_origin.json @@ -0,0 +1,141 @@ +{ + "id": "id", + "links": [ + { + "availability": 56.37376656633328, + "residula_bandwidth": 602746.015561422, + "id": "l-1", + "latency": 146582.15146899645, + "name": "miami-Boca.amLight.sdx", + "packet_loss": 59.621339166831824, + "ports": [ + { + "id": "p-1", + "label_range": [ + "label_range", + "label_range" + ], + "name": "urn:sdx:port:amlight.net:Novi06:9", + "node": "urn:sdx:port:amlight.net:Novi06", + "short_name": "9", + "status": "up" + }, + { + "id": "p-2", + "label_range": [ + "label_range", + "label_range" + ], + "name": "urn:sdx:port:amlight.net:Novi06:10", + "node": "urn:sdx:port:amlight.net:Novi06", + "short_name": "10", + "status": "up" + } + ], + "short_name": "Miami-BocaRaton", + "bandwidth": 80083.7389632821 + }, + { + "availability": 56.37376656633328, + "residula_bandwidth": 602746.015561422, + "id": "l-2", + "latency": 146582.15146899645, + "name": "miami-Boca.amLight.sdx", + "packet_loss": 59.621339166831824, + "ports": [ + { + "id": "p-3", + "label_range": [ + "label_range", + "label_range" + ], + "name": "urn:sdx:port:amlight.net:Novi06:9", + "node": "urn:sdx:port:amlight.net:Novi06", + "short_name": "9", + "status": "up" + }, + { + "id": "p-4", + "label_range": [ + "label_range", + "label_range" + ], + "name": "urn:sdx:port:amlight.net:Novi06:10", + "node": "urn:sdx:port:amlight.net:Novi06", + "short_name": "10", + "status": "up" + } + ], + "short_name": "Miami-BocaRaton", + "bandwidth": 80083.7389632821 + } + ], + "name": "amLight", + "nodes": [ + { + "id": "1", + "location": { + "address": "Miami", + "latitude": -28.51107891831147, + "longitude": -79.57947854792273 + }, + "name": "urn:sdx:node:amlight.net:Novi06", + "ports": [ + { + "id": "p-1", + "name": "urn:sdx:port:amlight.net:Novi06:9", + "node": "urn:sdx:port:amlight.net:Novi06", + "short_name": "9", + "status": "up" + }, + { + "id": "p-3", + "name": "urn:sdx:port:amlight.net:Novi06:9", + "node": "urn:sdx:port:amlight.net:Novi06", + "short_name": "9", + "status": "up" + } + ], + "short_name": "Novi06" + }, + { + "id": "2", + "location": { + "address": "Miami", + "latitude": -28.51107891831147, + "longitude": -79.57947854792273 + }, + "name": "urn:sdx:node:amlight.net:Novi06", + "ports": [ + { + "id": "p-2", + "label_range": [ + "label_range", + "label_range" + ], + "name": "urn:sdx:port:amlight.net:Novi06:9", + "node": "urn:sdx:port:amlight.net:Novi06", + "short_name": "9", + "status": "up" + }, + { + "id": "p-4", + "label_range": [ + "label_range", + "label_range" + ], + "name": "urn:sdx:port:amlight.net:Novi06:9", + "node": "urn:sdx:port:amlight.net:Novi06", + "short_name": "9", + "status": "up" + } + ], + "short_name": "Novi06" + } + ], + "time_stamp": "2000-01-23T04:56:07+00:00", + "version": 1, + "domain_service": { + "owner":"FIU" + } + } \ No newline at end of file diff --git a/tests/data/ampath-sax-zaoxi.pdf b/tests/data/ampath-sax-zaoxi.pdf new file mode 100644 index 0000000..99c9359 Binary files /dev/null and b/tests/data/ampath-sax-zaoxi.pdf differ diff --git a/tests/data/ampath.json b/tests/data/ampath.json new file mode 100644 index 0000000..d2fbc94 --- /dev/null +++ b/tests/data/ampath.json @@ -0,0 +1,45 @@ +{ + "id": "urn:sdx:topology:", + "links":[ + { + "id":"urn:sdx:link::Ampath5/177_JAX/249", + "name":"Ampath5/177_JAX/249", + "ports":[ + { + "id" : "urn:sdx:port::Ampath5:177" + }, + { + "id" : "urn:sdx:port::JAX:249" + } + ] + } + ], + "model_version":"1.0.0", + "name":"AmLight-OXP", + "nodes":[ + { + "id":"urn:sdx:node::Ampath3", + "location":{ + "address":"", + "latitude":"0.0", + "longitude":"-20.0" + }, + "name":"Ampath3", + "ports":[ + { + "id":"urn:sdx:port::Ampath3:149", + "mtu":"1500", + "name":"Ampath3-eth149", + "nni":"False", + "node":"urn:sdx:node::Ampath3", + "services":"l2vpn", + "state":"enabled", + "status":"up", + "type":"10GE" + } + ] + } + ], + "timestamp":"20210811-214909", + "version":2 +} diff --git a/tests/data/link.json b/tests/data/link.json new file mode 100644 index 0000000..49761cd --- /dev/null +++ b/tests/data/link.json @@ -0,0 +1,34 @@ +{ + "availability": 56.37376656633328, + "residual_bandwidth": 602746.015561422, + "id": "id", + "latency": 146582.15146899645, + "name": "miami-Boca.amLight.sdx", + "packet_loss": 59.621339166831824, + "ports": [ + { + "id": "id", + "label_range": [ + "label_range", + "label_range" + ], + "name": "urn:sdx:port:amlight.net:Novi06:9", + "node": "urn:sdx:port:amlight.net:Novi06", + "short_name": "9", + "status": "up" + }, + { + "id": "id", + "label_range": [ + "label_range", + "label_range" + ], + "name": "urn:sdx:port:amlight.net:Novi06:10", + "node": "urn:sdx:port:amlight.net:Novi06", + "short_name": "10", + "status": "up" + } + ], + "short_name": "Miami-BocaRaton", + "bandwidth": 80083.7389632821 + } \ No newline at end of file diff --git a/tests/data/location.json b/tests/data/location.json new file mode 100644 index 0000000..104fbf1 --- /dev/null +++ b/tests/data/location.json @@ -0,0 +1,5 @@ +{ + "address": "Miami", + "latitude": -28.51107891831147, + "longitude": -79.57947854792273 + } \ No newline at end of file diff --git a/tests/data/node.json b/tests/data/node.json new file mode 100644 index 0000000..ffc4665 --- /dev/null +++ b/tests/data/node.json @@ -0,0 +1,26 @@ +{ + "id": "id", + "location": { + "address": "Miami", + "latitude": -28.51107891831147, + "longitude": -79.57947854792273 + }, + "name": "urn:sdx:node:amlight.net:Novi06", + "ports": [ + { + "id": "id", + "name": "urn:sdx:port:amlight.net:Novi06:9", + "node": "urn:sdx:port:amlight.net:Novi06", + "short_name": "9", + "status": "up" + }, + { + "id": "id", + "name": "urn:sdx:port:amlight.net:Novi06:9", + "node": "urn:sdx:port:amlight.net:Novi06", + "short_name": "9", + "status": "up" + } + ], + "short_name": "Novi06" + } \ No newline at end of file diff --git a/tests/data/p2p.json b/tests/data/p2p.json new file mode 100644 index 0000000..b9ee9d5 --- /dev/null +++ b/tests/data/p2p.json @@ -0,0 +1,26 @@ +{ + "id": "id", + "name": "AMLight", + "start_time": "2000-01-23T04:56:07.000Z", + "end_time": "2000-01-23T04:56:07.000Z", + "bandwidth_required": 100, + "latency_required": 20, + "egress_port": + { + "id": "urn:sdx:port:amlight.net:Novi07:10", + "name": "10", + "short_name": "Novi07:10", + "label": "vlan", + "label_range": "100", + "node": "urn:sdx:port:amlight.net:Novi07" + }, + "ingress_port": + { + "id": "urn:sdx:port:amlight.net:Novi06:9", + "name": "Novi06:9", + "short_name": "Novi06:9", + "label": "vlan", + "label_range": "100", + "node": "urn:sdx:port:amlight.net:Novi06" + } +} \ No newline at end of file diff --git a/tests/data/port.json b/tests/data/port.json new file mode 100644 index 0000000..2604a60 --- /dev/null +++ b/tests/data/port.json @@ -0,0 +1,11 @@ +{ + "id": "id", + "label_range": [ + "label_range", + "label_range" + ], + "name": "urn:sdx:port:amlight.net:Novi06:9", + "node": "urn:sdx:port:amlight.net:Novi06", + "short_name": "9", + "status": "up" + } \ No newline at end of file diff --git a/tests/data/sax.json b/tests/data/sax.json new file mode 100644 index 0000000..c627727 --- /dev/null +++ b/tests/data/sax.json @@ -0,0 +1,481 @@ +{ + "id": "urn:ogf:network:sdx:topology:sax.net", + "name": "SAX-OXP", + "time_stamp": "2000-01-23T04:56:07+00:00", + "version": 1.0, + "model_version":"1.0.0", + "links": [ + { + "availability": 56.37376656633328, + "residual_bandwidth": 100000, + "id": "urn:ogf:network:sdx:link:sax:B1-B2", + "latency": 5, + "name": "sax:B1-B2", + "packet_loss": 59.621339166831824, + "ports": [ + { + "id": "urn:ogf:network:sdx:port:sax:B1:2", + "name": "Novi01:2", + "node": "urn:ogf:network:sdx:node:sax:B1", + "short_name": "B1:2", + "label_range": [ + "100-200", + "10001" + ], + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:sax:B2:2", + "label_range": [ + "100-200", + "10001" + ], + "name": "Novi02:2", + "node": "urn:ogf:network:sdx:node:sax:B2", + "short_name": "B2:2", + "status": "up" + } + ], + "short_name": "SaoPaulo-Fortaleza", + "bandwidth": 100000 + }, + { + "availability": 56.37376656633328, + "residual_bandwidth": 100000, + "id": "urn:ogf:network:sdx:link:sax:Panama-Fortaleza", + "latency": 5, + "name": "sax:Panama-Fortaleza", + "packet_loss": 59.621339166831824, + "ports": [ + { + "id": "urn:ogf:network:sdx:port:sax:B3:2", + "label_range": [ + "100-200", + "10001" + ], + "name": "Novi02:3", + "node": "urn:ogf:network:sdx:node:sax:B3", + "short_name": "B3:2", + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:sax:B2:4", + "label_range": [ + "100-200", + "10001" + ], + "name": "Novi02:4", + "node": "urn:ogf:network:sdx:node:sax:B2", + "short_name": "B2:3", + "status": "up" + } + ], + "short_name": "Panama-Fortaleza", + "bandwidth": 100000 + }, + { + "availability": 56.37376656633328, + "residual_bandwidth": 100000, + "id": "urn:ogf:network:sdx:link:sax:SanPaolo-Fortaleza", + "latency": 5, + "name": "nni:SanPaolo-Fortaleza", + "packet_loss": 59.621339166831824, + "ports": [ + { + "id": "urn:ogf:network:sdx:port:sax:B3:3", + "label_range": [ + "100-200", + "10001" + ], + "name": "Novi03:3", + "node": "urn:ogf:network:sdx:node:sax:B3", + "short_name": "B3:3", + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:sax:B1:4", + "name": "Novi01:3", + "node": "urn:ogf:network:sdx:node:sax:B1", + "short_name": "B1:4", + "label_range": [ + "100-200", + "10001" + ], + "status": "up" + } + ], + "short_name": "BocaRaton-Fortaleza", + "bandwidth": 100000 + }, + { + "availability": 56.37376656633328, + "residual_bandwidth": 100000, + "id": "urn:ogf:network:sdx:link:sax:A1-B1", + "latency": 5, + "name": "sax:A1-B1", + "packet_loss": 59.621339166831824, + "ports": [ + { + "id": "urn:ogf:network:sdx:port:sax:A1:1", + "label_range": [ + "100-200", + "1000" + ], + "name": "Novi100:1", + "node": "urn:ogf:network:sdx:node:sax:A1", + "short_name": "A1:1", + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:sax:B1:3", + "name": "Novi01:3", + "node": "urn:ogf:network:sdx:node:sax:B1", + "short_name": "B1:3", + "label_range": [ + "100-200", + "10001" + ], + "status": "up" + } + ], + "short_name": "redclara-SaoPaulo", + "bandwidth": 100000 + }, + { + "availability": 56.37376656633328, + "residual_bandwidth": 100000, + "id": "urn:ogf:network:sdx:link:sax:A1-B2", + "latency": 5, + "name": "sax:A1-B2", + "packet_loss": 59.621339166831824, + "ports": [ + { + "id": "urn:ogf:network:sdx:port:sax:A1:2", + "label_range": [ + "100-200", + "1000" + ], + "name": "Novi100:2", + "node": "urn:ogf:network:sdx:node:sax:A1", + "short_name": "A1:2", + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:sax:B2:3", + "label_range": [ + "100-200", + "10001" + ], + "name": "Novi02:3", + "node": "urn:ogf:network:sdx:node:sax:B2", + "short_name": "B2:3", + "status": "up" + } + ], + "short_name": "redclara-Fortaleza", + "bandwidth": 100000 + }, + { + "availability": 56.37376656633328, + "residual_bandwidth": 100000, + "id": "urn:ogf:network:sdx:link:nni:Miami-Sanpaolo", + "nni": "True", + "latency": 10, + "name": "nni:Miami-Sanpaolo", + "packet_loss": 59.621339166831824, + "ports": [ + { + "id": "urn:sdx:port:amlight:B1:1", + "name": "Novi01:1", + "node": "urn:sdx:node:amlight.net:B1", + "short_name": "B1:1", + "label_range": [ + "100-200", + "10001" + ], + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:sax:B1:1", + "name": "Novi01:1", + "node": "urn:ogf:network:sdx:node:sax:B1", + "short_name": "B1:1", + "label_range": [ + "100-200", + "10001" + ], + "status": "up" + } + ], + "short_name": "Miami-Sanpaolo", + "bandwidth": 100000 + }, + { + "availability": 56.37376656633328, + "residual_bandwidth": 100000, + "id": "urn:ogf:network:sdx:link:nni:BocaRaton-Fortaleza", + "latency": 10, + "name": "nni:BocaRaton-Fortaleza", + "packet_loss": 59.621339166831824, + "nni": "True", + "ports": [ + { + "id": "urn:sdx:port:amlight.net:B2:1", + "label_range": [ + "100-200", + "1000" + ], + "name": "Novi02:1", + "node": "urn:sdx:node:amlight.net:B2", + "short_name": "B2:1", + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:sax:B2:1", + "label_range": [ + "100-200", + "1000" + ], + "name": "Novi02:1", + "node": "urn:ogf:network:sdx:node:sax:B2", + "short_name": "B2:1", + "status": "up" + } + ], + "short_name": "BocaRaton-Fortaleza", + "bandwidth": 100000 + }, + { + "availability": 56.37376656633328, + "residual_bandwidth": 100000, + "id": "urn:ogf:network:sdx:link:nni:Fortaleza-Sangano", + "latency": 5, + "name": "nni:Fortaleza-Sangano", + "packet_loss": 59.621339166831824, + "nni": "True", + "ports": [ + { + "id": "urn:ogf:network:sdx:port:sax:B3:1", + "label_range": [ + "100-200", + "1000" + ], + "name": "Novi02:3", + "node": "urn:ogf:network:sdx:node:sax:B3", + "short_name": "B3:1", + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:zaoxi:B1:1", + "name": "Novi01:1", + "node": "urn:ogf:network:sdx:node:zaoxi:B1", + "short_name": "B1:1", + "label_range": [ + "100-200", + "10001" + ], + "status": "up" + } + ], + "short_name": "Fortaleza-Sangano", + "bandwidth": 100000 + } + ], + "nodes": [ + { + "id": "urn:ogf:network:sdx:node:sax:B1", + "location": { + "address": "SaoPaulo", + "latitude": -23.5311561958366, + "longitude": -46.650271781410524 + }, + "name": "sax:Novi01", + "ports": [ + { + "id": "urn:ogf:network:sdx:port:sax:B1:1", + "name": "Novi01:1", + "node": "urn:ogf:network:sdx:port:sax:B1", + "short_name": "B1:1", + "label_range": [ + "100-200", + "10001" + ], + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:sax:B1:2", + "name": "Novi01:2", + "node": "urn:ogf:network:sdx:port:sax:B1", + "short_name": "B1:2", + "label_range": [ + "100-200", + "10001" + ], + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:sax:B1:3", + "name": "Novi01:3", + "node": "urn:ogf:network:sdx:port:sax:B1", + "short_name": "B1:3", + "label_range": [ + "100-200", + "10001" + ], + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:sax:B1:4", + "name": "Novi01:3", + "node": "urn:ogf:network:sdx:port:sax:B1", + "short_name": "B1:4", + "label_range": [ + "100-200", + "10001" + ], + "status": "up" + } + ], + "short_name": "B1" + }, + { + "id": "urn:ogf:network:sdx:node:sax:B2", + "location": { + "address": "PanamaCity", + "latitude": 8.993040465928525, + "longitude": -79.4947050137491 + }, + "name": "sax:Novi02", + "ports": [ + { + "id": "urn:ogf:network:sdx:port:sax:B2:1", + "label_range": [ + "100-200", + "1000" + ], + "name": "Novi02:1", + "node": "urn:ogf:network:sdx:node:sax:B2", + "short_name": "B2:1", + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:sax:B2:2", + "label_range": [ + "100-200", + "10001" + ], + "name": "Novi02:2", + "node": "urn:ogf:network:sdx:node:sax:B2", + "short_name": "B2:2", + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:sax:B2:3", + "label_range": [ + "100-200", + "10001" + ], + "name": "Novi02:3", + "node": "urn:ogf:network:sdx:node:sax:B2", + "short_name": "B2:3", + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:sax:B2:4", + "label_range": [ + "100-200", + "10001" + ], + "name": "Novi02:4", + "node": "urn:ogf:network:sdx:node:sax:B2", + "short_name": "B2:3", + "status": "up" + } + ], + "short_name": "B2" + }, + { + "id": "urn:ogf:network:sdx:node:sax:B3", + + "location": { + "address": "Fortaleza", + "latitude": -3.73163824920348, + "longitude": -38.52443289673026 + }, + "name": "sax:Novi03", + "ports": [ + { + "id": "urn:ogf:network:sdx:port:sax:B3:1", + "label_range": [ + "100-200", + "1000" + ], + "name": "Novi02:3", + "node": "urn:ogf:network:sdx:node:sax:B3", + "short_name": "B3:1", + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:sax:B3:2", + "label_range": [ + "100-200", + "10001" + ], + "name": "Novi02:3", + "node": "urn:ogf:network:sdx:node:sax:B3", + "short_name": "B3:2", + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:sax:B3:3", + "label_range": [ + "100-200", + "10001" + ], + "name": "Novi03:3", + "node": "urn:ogf:network:sdx:node:sax:B3", + "short_name": "B3:3", + "status": "up" + } + ], + "short_name": "B3" + }, + { + "id": "urn:ogf:network:sdx:node:sax:A1", + "location": { + "address": "Santiago", + "latitude": -33.4507049233331, + "longitude": -70.64634765264213 + }, + "name": "sax:Novi100", + "ports": [ + { + "id": "urn:ogf:network:sdx:port:sax:A1:1", + "label_range": [ + "100-200", + "1000" + ], + "name": "Novi100:1", + "node": "urn:ogf:network:sdx:node:sax:A1", + "short_name": "A1:1", + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:sax:A1:2", + "label_range": [ + "100-200", + "1000" + ], + "name": "Novi100:2", + "node": "urn:ogf:network:sdx:node:sax:A1", + "short_name": "A1:2", + "status": "up" + } + ], + "short_name": "A1" + } + ], + "domain_service": { + "owner":"RNP" + } +} \ No newline at end of file diff --git a/tests/data/sdx.json b/tests/data/sdx.json new file mode 100644 index 0000000..c791d75 --- /dev/null +++ b/tests/data/sdx.json @@ -0,0 +1,1081 @@ +{ + "id": "urn:ogf:network:sdx", + "name": "AmLight-OXP", + "domain_service": { + "monitoring_capability": null, + "owner": "FIU", + "private_attributes": null, + "provisioning_system": null, + "provisioning_url": null, + "vendor": null + }, + "version": "2.1", + "time_stamp": "1970-01-01T00:00:00.000000", + "nodes": [ + { + "id": "urn:sdx:node:amlight.net:B1", + "name": "amlight:Novi01", + "short_name": "B1", + "location": { + "address": "Miami", + "latitude": -80.37676058477908, + "longitude": 25.75633040531146 + }, + "ports": [ + { + "id": "urn:sdx:port:amlight:B1:1", + "name": "Novi01:1", + "short_name": null, + "node": "B1:1", + "label_range": [ + "100-200", + "10001" + ], + "status": null, + "state": null, + "private_attributes": null + }, + { + "id": "urn:sdx:port:amlight.net:B1:2", + "name": "Novi01:2", + "short_name": null, + "node": "B1:2", + "label_range": [ + "100-200", + "10001" + ], + "status": null, + "state": null, + "private_attributes": null + }, + { + "id": "urn:sdx:port:amlight.net:B1:3", + "name": "Novi01:3", + "short_name": null, + "node": "B1:3", + "label_range": [ + "100-200", + "10001" + ], + "status": null, + "state": null, + "private_attributes": null + } + ], + "private_attributes": null + }, + { + "id": "urn:sdx:node:amlight.net:B2", + "name": "amlight:Novi02", + "short_name": "B2", + "location": { + "address": "BocaRaton", + "latitude": -80.10225977485742, + "longitude": 26.381437356374075 + }, + "ports": [ + { + "id": "urn:sdx:port:amlight.net:B2:1", + "name": "Novi02:1", + "short_name": null, + "node": "B2:1", + "label_range": [ + "100-200", + "1000" + ], + "status": null, + "state": null, + "private_attributes": null + }, + { + "id": "urn:sdx:port:amlight.net:B2:2", + "name": "Novi02:2", + "short_name": null, + "node": "B2:2", + "label_range": [ + "100-200", + "10001" + ], + "status": null, + "state": null, + "private_attributes": null + }, + { + "id": "urn:sdx:port:amlight.net:B2:3", + "name": "Novi02:3", + "short_name": null, + "node": "B2:3", + "label_range": [ + "100-200", + "10001" + ], + "status": null, + "state": null, + "private_attributes": null + } + ], + "private_attributes": null + }, + { + "id": "urn:sdx:node:amlight.net:A1", + "name": "amlight:Novi100", + "short_name": "A1", + "location": { + "address": "redclara", + "latitude": -81.66666016473143, + "longitude": 30.34943181039702 + }, + "ports": [ + { + "id": "urn:sdx:port:amlight.net:A1:1", + "name": "Novi100:1", + "short_name": null, + "node": "A1:1", + "label_range": [ + "100-200", + "1000" + ], + "status": null, + "state": null, + "private_attributes": null + }, + { + "id": "urn:sdx:port:amlight.net:A1:2", + "name": "Novi100:2", + "short_name": null, + "node": "A1:2", + "label_range": [ + "100-200", + "1000" + ], + "status": null, + "state": null, + "private_attributes": null + } + ], + "private_attributes": null + }, + { + "id": "urn:ogf:network:sdx:node:sax:B1", + "name": "sax:Novi01", + "short_name": "B1", + "location": { + "address": "SaoPaulo", + "latitude": -46.650271781410524, + "longitude": -23.5311561958366 + }, + "ports": [ + { + "id": "urn:ogf:network:sdx:port:sax:B1:1", + "name": "Novi01:1", + "short_name": null, + "node": "B1:1", + "label_range": [ + "100-200", + "10001" + ], + "status": null, + "state": null, + "private_attributes": null + }, + { + "id": "urn:ogf:network:sdx:port:sax:B1:2", + "name": "Novi01:2", + "short_name": null, + "node": "B1:2", + "label_range": [ + "100-200", + "10001" + ], + "status": null, + "state": null, + "private_attributes": null + }, + { + "id": "urn:ogf:network:sdx:port:sax:B1:3", + "name": "Novi01:3", + "short_name": null, + "node": "B1:3", + "label_range": [ + "100-200", + "10001" + ], + "status": null, + "state": null, + "private_attributes": null + }, + { + "id": "urn:ogf:network:sdx:port:sax:B1:4", + "name": "Novi01:3", + "short_name": null, + "node": "B1:4", + "label_range": [ + "100-200", + "10001" + ], + "status": null, + "state": null, + "private_attributes": null + } + ], + "private_attributes": null + }, + { + "id": "urn:ogf:network:sdx:node:sax:B2", + "name": "sax:Novi02", + "short_name": "B2", + "location": { + "address": "PanamaCity", + "latitude": -79.4947050137491, + "longitude": 8.993040465928525 + }, + "ports": [ + { + "id": "urn:ogf:network:sdx:port:sax:B2:1", + "name": "Novi02:1", + "short_name": null, + "node": "B2:1", + "label_range": [ + "100-200", + "1000" + ], + "status": null, + "state": null, + "private_attributes": null + }, + { + "id": "urn:ogf:network:sdx:port:sax:B2:2", + "name": "Novi02:2", + "short_name": null, + "node": "B2:2", + "label_range": [ + "100-200", + "10001" + ], + "status": null, + "state": null, + "private_attributes": null + }, + { + "id": "urn:ogf:network:sdx:port:sax:B2:3", + "name": "Novi02:3", + "short_name": null, + "node": "B2:3", + "label_range": [ + "100-200", + "10001" + ], + "status": null, + "state": null, + "private_attributes": null + }, + { + "id": "urn:ogf:network:sdx:port:sax:B2:4", + "name": "Novi02:4", + "short_name": null, + "node": "B2:3", + "label_range": [ + "100-200", + "10001" + ], + "status": null, + "state": null, + "private_attributes": null + } + ], + "private_attributes": null + }, + { + "id": "urn:ogf:network:sdx:node:sax:B3", + "name": "sax:Novi03", + "short_name": "B3", + "location": { + "address": "Fortaleza", + "latitude": -38.52443289673026, + "longitude": -3.73163824920348 + }, + "ports": [ + { + "id": "urn:ogf:network:sdx:port:sax:B3:1", + "name": "Novi02:3", + "short_name": null, + "node": "B3:1", + "label_range": [ + "100-200", + "1000" + ], + "status": null, + "state": null, + "private_attributes": null + }, + { + "id": "urn:ogf:network:sdx:port:sax:B3:2", + "name": "Novi02:3", + "short_name": null, + "node": "B3:2", + "label_range": [ + "100-200", + "10001" + ], + "status": null, + "state": null, + "private_attributes": null + }, + { + "id": "urn:ogf:network:sdx:port:sax:B3:3", + "name": "Novi03:3", + "short_name": null, + "node": "B3:3", + "label_range": [ + "100-200", + "10001" + ], + "status": null, + "state": null, + "private_attributes": null + } + ], + "private_attributes": null + }, + { + "id": "urn:ogf:network:sdx:node:sax:A1", + "name": "sax:Novi100", + "short_name": "A1", + "location": { + "address": "Santiago", + "latitude": -70.64634765264213, + "longitude": -33.4507049233331 + }, + "ports": [ + { + "id": "urn:ogf:network:sdx:port:sax:A1:1", + "name": "Novi100:1", + "short_name": null, + "node": "A1:1", + "label_range": [ + "100-200", + "1000" + ], + "status": null, + "state": null, + "private_attributes": null + }, + { + "id": "urn:ogf:network:sdx:port:sax:A1:2", + "name": "Novi100:2", + "short_name": null, + "node": "A1:2", + "label_range": [ + "100-200", + "1000" + ], + "status": null, + "state": null, + "private_attributes": null + } + ], + "private_attributes": null + }, + { + "id": "urn:ogf:network:sdx:node:zaoxi:B1", + "name": "zaoxi:Novi01", + "short_name": "B1", + "location": { + "address": "Sangano", + "latitude": 13.216709879405311, + "longitude": -9.533459658700743 + }, + "ports": [ + { + "id": "urn:ogf:network:sdx:port:zaoxi:B1:1", + "name": "Novi01:1", + "short_name": null, + "node": "B1:1", + "label_range": [ + "100-200", + "10001" + ], + "status": null, + "state": null, + "private_attributes": null + }, + { + "id": "urn:ogf:network:sdx:port:zaoxi:B1:2", + "name": "Novi01:2", + "short_name": null, + "node": "B1:2", + "label_range": [ + "100-200", + "10001" + ], + "status": null, + "state": null, + "private_attributes": null + }, + { + "id": "urn:ogf:network:sdx:port:zaoxi:B1:3", + "name": "Novi01:3", + "short_name": null, + "node": "B1:3", + "label_range": [ + "100-200", + "10001" + ], + "status": null, + "state": null, + "private_attributes": null + } + ], + "private_attributes": null + }, + { + "id": "urn:ogf:network:sdx:node:zaoxi:B2", + "name": "zaoxi:Novi02", + "short_name": "B2", + "location": { + "address": "CapeTown", + "latitude": -38.52443289673026, + "longitude": -3.73163824920348 + }, + "ports": [ + { + "id": "urn:ogf:network:sdx:port:zaoxi:B2:1", + "name": "Novi02:1", + "short_name": null, + "node": "B2:1", + "label_range": [ + "100-200", + "1000" + ], + "status": null, + "state": null, + "private_attributes": null + }, + { + "id": "urn:ogf:network:sdx:port:zaoxi:B2:2", + "name": "Novi02:2", + "short_name": null, + "node": "B2:2", + "label_range": [ + "100-200", + "10001" + ], + "status": null, + "state": null, + "private_attributes": null + }, + { + "id": "urn:ogf:network:sdx:port:zaoxi:B2:3", + "name": "Novi02:3", + "short_name": null, + "node": "B2:3", + "label_range": [ + "100-200", + "10001" + ], + "status": null, + "state": null, + "private_attributes": null + } + ], + "private_attributes": null + }, + { + "id": "urn:ogf:network:sdx:node:zaoxi:A1", + "name": "zaoxi:Novi100", + "short_name": "A1", + "location": { + "address": "Karoo", + "latitude": 22.541224555821298, + "longitude": -32.3632301851245 + }, + "ports": [ + { + "id": "urn:ogf:network:sdx:port:zaoxi:A1:1", + "name": "Novi100:1", + "short_name": null, + "node": "A1:1", + "label_range": [ + "100-200", + "1000" + ], + "status": null, + "state": null, + "private_attributes": null + }, + { + "id": "urn:ogf:network:sdx:port:zaoxi:A1:2", + "name": "Novi100:2", + "short_name": null, + "node": "A1:2", + "label_range": [ + "100-200", + "1000" + ], + "status": null, + "state": null, + "private_attributes": null + } + ], + "private_attributes": null + } + ], + "links": [ + { + "id": "urn:ogf:network:sdx:link:amlight:B1-B2", + "name": "amlight:B1-B2", + "short_name": "Miami-BocaRaton", + "nni": null, + "ports": [ + { + "id": "urn:sdx:port:amlight.net:B1:2", + "name": "Novi01:2", + "node": "urn:sdx:node:amlight.net:B1", + "short_name": "B1:2", + "label_range": [ + "100-200", + "10001" + ], + "status": "up" + }, + { + "id": "urn:sdx:port:amlight.net:B2:2", + "label_range": [ + "100-200", + "10001" + ], + "name": "Novi02:2", + "node": "urn:sdx:node:amlight.net:B2", + "short_name": "B2:2", + "status": "up" + } + ], + "bandwidth": 100000, + "residual_bandwidth": 100000, + "latency": 5, + "packet_loss": 59.621339166831824, + "availability": 56.37376656633328, + "status": null, + "state": null, + "private_attributes": null, + "time_stamp": null, + "measurement_period": null + }, + { + "id": "urn:ogf:network:sdx:link:amlight:A1-B1", + "name": "amlight:A1-B1", + "short_name": "redclara-miami", + "nni": null, + "ports": [ + { + "id": "urn:sdx:port:amlight.net:A1:1", + "label_range": [ + "100-200", + "1000" + ], + "name": "Novi100:1", + "node": "urn:sdx:node:amlight.net:A1", + "short_name": "A1:1", + "status": "up" + }, + { + "id": "urn:sdx:port:amlight.net:B1:3", + "name": "Novi01:3", + "node": "urn:sdx:node:amlight.net:B1", + "short_name": "B1:3", + "label_range": [ + "100-200", + "10001" + ], + "status": "up" + } + ], + "bandwidth": 100000, + "residual_bandwidth": 100000, + "latency": 5, + "packet_loss": 59.621339166831824, + "availability": 56.37376656633328, + "status": null, + "state": null, + "private_attributes": null, + "time_stamp": null, + "measurement_period": null + }, + { + "id": "urn:ogf:network:sdx:link:amlight:A1-B2", + "name": "amlight:A1-B2", + "short_name": "redclara-BocaRaton", + "nni": null, + "ports": [ + { + "id": "urn:sdx:port:amlight.net:A1:2", + "label_range": [ + "100-200", + "1000" + ], + "name": "Novi100:2", + "node": "urn:sdx:node:amlight.net:A1", + "short_name": "A1:2", + "status": "up" + }, + { + "id": "urn:sdx:port:amlight.net:B2:3", + "label_range": [ + "100-200", + "10001" + ], + "name": "Novi02:3", + "node": "urn:sdx:node:amlight.net:B2", + "short_name": "B2:3", + "status": "up" + } + ], + "bandwidth": 100000, + "residual_bandwidth": 100000, + "latency": 5, + "packet_loss": 59.621339166831824, + "availability": 56.37376656633328, + "status": null, + "state": null, + "private_attributes": null, + "time_stamp": null, + "measurement_period": null + }, + { + "id": "urn:ogf:network:sdx:link:sax:B1-B2", + "name": "sax:B1-B2", + "short_name": "SaoPaulo-Fortaleza", + "nni": null, + "ports": [ + { + "id": "urn:ogf:network:sdx:port:sax:B1:2", + "name": "Novi01:2", + "node": "urn:ogf:network:sdx:node:sax:B1", + "short_name": "B1:2", + "label_range": [ + "100-200", + "10001" + ], + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:sax:B2:2", + "label_range": [ + "100-200", + "10001" + ], + "name": "Novi02:2", + "node": "urn:ogf:network:sdx:node:sax:B2", + "short_name": "B2:2", + "status": "up" + } + ], + "bandwidth": 100000, + "residual_bandwidth": 100000, + "latency": 5, + "packet_loss": 59.621339166831824, + "availability": 56.37376656633328, + "status": null, + "state": null, + "private_attributes": null, + "time_stamp": null, + "measurement_period": null + }, + { + "id": "urn:ogf:network:sdx:link:sax:Panama-Fortaleza", + "name": "sax:Panama-Fortaleza", + "short_name": "Panama-Fortaleza", + "nni": null, + "ports": [ + { + "id": "urn:ogf:network:sdx:port:sax:B3:2", + "label_range": [ + "100-200", + "10001" + ], + "name": "Novi02:3", + "node": "urn:ogf:network:sdx:node:sax:B3", + "short_name": "B3:2", + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:sax:B2:4", + "label_range": [ + "100-200", + "10001" + ], + "name": "Novi02:4", + "node": "urn:ogf:network:sdx:node:sax:B2", + "short_name": "B2:3", + "status": "up" + } + ], + "bandwidth": 100000, + "residual_bandwidth": 100000, + "latency": 5, + "packet_loss": 59.621339166831824, + "availability": 56.37376656633328, + "status": null, + "state": null, + "private_attributes": null, + "time_stamp": null, + "measurement_period": null + }, + { + "id": "urn:ogf:network:sdx:link:sax:SanPaolo-Fortaleza", + "name": "nni:SanPaolo-Fortaleza", + "short_name": "BocaRaton-Fortaleza", + "nni": null, + "ports": [ + { + "id": "urn:ogf:network:sdx:port:sax:B3:3", + "label_range": [ + "100-200", + "10001" + ], + "name": "Novi03:3", + "node": "urn:ogf:network:sdx:node:sax:B3", + "short_name": "B3:3", + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:sax:B1:4", + "name": "Novi01:3", + "node": "urn:ogf:network:sdx:node:sax:B1", + "short_name": "B1:4", + "label_range": [ + "100-200", + "10001" + ], + "status": "up" + } + ], + "bandwidth": 100000, + "residual_bandwidth": 100000, + "latency": 5, + "packet_loss": 59.621339166831824, + "availability": 56.37376656633328, + "status": null, + "state": null, + "private_attributes": null, + "time_stamp": null, + "measurement_period": null + }, + { + "id": "urn:ogf:network:sdx:link:sax:A1-B1", + "name": "sax:A1-B1", + "short_name": "redclara-SaoPaulo", + "nni": null, + "ports": [ + { + "id": "urn:ogf:network:sdx:port:sax:A1:1", + "label_range": [ + "100-200", + "1000" + ], + "name": "Novi100:1", + "node": "urn:ogf:network:sdx:node:sax:A1", + "short_name": "A1:1", + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:sax:B1:3", + "name": "Novi01:3", + "node": "urn:ogf:network:sdx:node:sax:B1", + "short_name": "B1:3", + "label_range": [ + "100-200", + "10001" + ], + "status": "up" + } + ], + "bandwidth": 100000, + "residual_bandwidth": 100000, + "latency": 5, + "packet_loss": 59.621339166831824, + "availability": 56.37376656633328, + "status": null, + "state": null, + "private_attributes": null, + "time_stamp": null, + "measurement_period": null + }, + { + "id": "urn:ogf:network:sdx:link:sax:A1-B2", + "name": "sax:A1-B2", + "short_name": "redclara-Fortaleza", + "nni": null, + "ports": [ + { + "id": "urn:ogf:network:sdx:port:sax:A1:2", + "label_range": [ + "100-200", + "1000" + ], + "name": "Novi100:2", + "node": "urn:ogf:network:sdx:node:sax:A1", + "short_name": "A1:2", + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:sax:B2:3", + "label_range": [ + "100-200", + "10001" + ], + "name": "Novi02:3", + "node": "urn:ogf:network:sdx:node:sax:B2", + "short_name": "B2:3", + "status": "up" + } + ], + "bandwidth": 100000, + "residual_bandwidth": 100000, + "latency": 5, + "packet_loss": 59.621339166831824, + "availability": 56.37376656633328, + "status": null, + "state": null, + "private_attributes": null, + "time_stamp": null, + "measurement_period": null + }, + { + "id": "urn:ogf:network:sdx:link:nni:Miami-Sanpaolo", + "name": "nni:Miami-Sanpaolo", + "short_name": "Miami-Sanpaolo", + "nni": true, + "ports": [ + { + "id": "urn:sdx:port:amlight:B1:1", + "name": "Novi01:1", + "node": "urn:sdx:node:amlight.net:B1", + "short_name": "B1:1", + "label_range": [ + "100-200", + "10001" + ], + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:sax:B1:1", + "name": "Novi01:1", + "node": "urn:ogf:network:sdx:node:sax:B1", + "short_name": "B1:1", + "label_range": [ + "100-200", + "10001" + ], + "status": "up" + } + ], + "bandwidth": 100000, + "residual_bandwidth": 100000, + "latency": 10, + "packet_loss": 59.621339166831824, + "availability": 56.37376656633328, + "status": null, + "state": null, + "private_attributes": null, + "time_stamp": null, + "measurement_period": null + }, + { + "id": "urn:ogf:network:sdx:link:nni:BocaRaton-Fortaleza", + "name": "nni:BocaRaton-Fortaleza", + "short_name": "BocaRaton-Fortaleza", + "nni": true, + "ports": [ + { + "id": "urn:sdx:port:amlight.net:B2:1", + "label_range": [ + "100-200", + "1000" + ], + "name": "Novi02:1", + "node": "urn:sdx:node:amlight.net:B2", + "short_name": "B2:1", + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:sax:B2:1", + "label_range": [ + "100-200", + "1000" + ], + "name": "Novi02:1", + "node": "urn:ogf:network:sdx:node:sax:B2", + "short_name": "B2:1", + "status": "up" + } + ], + "bandwidth": 100000, + "residual_bandwidth": 100000, + "latency": 10, + "packet_loss": 59.621339166831824, + "availability": 56.37376656633328, + "status": null, + "state": null, + "private_attributes": null, + "time_stamp": null, + "measurement_period": null + }, + { + "id": "urn:ogf:network:sdx:link:zaoxi:B1-B2", + "name": "zaoxi:B1-B2", + "short_name": "Sangano-Capetown", + "nni": null, + "ports": [ + { + "id": "urn:ogf:network:sdx:port:zaoxi:B1:2", + "name": "Novi01:2", + "node": "urn:ogf:network:sdx:node:zaoxi:B1", + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:zaoxi:B2:2", + "label_range": [ + "100-200", + "10001" + ], + "name": "Novi02:2", + "node": "urn:ogf:network:sdx:node:zaoxi:B2", + "short_name": "B2:2", + "status": "up" + } + ], + "bandwidth": 100000, + "residual_bandwidth": 100000, + "latency": 5, + "packet_loss": 59.621339166831824, + "availability": 56.37376656633328, + "status": null, + "state": null, + "private_attributes": null, + "time_stamp": null, + "measurement_period": null + }, + { + "id": "urn:ogf:network:sdx:link:zaoxi:A1-B1", + "name": "zaoxi:A1-B1", + "short_name": "Karoo-Sangano", + "nni": null, + "ports": [ + { + "id": "urn:ogf:network:sdx:port:zaoxi:A1:1", + "label_range": [ + "100-200", + "1000" + ], + "name": "Novi100:1", + "node": "urn:ogf:network:sdx:node:zaoxi:A1", + "short_name": "A1:1", + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:zaoxi:B1:3", + "name": "Novi01:3", + "node": "urn:ogf:network:sdx:node:zaoxi:B1", + "short_name": "B1:3", + "label_range": [ + "100-200", + "10001" + ], + "status": "up" + } + ], + "bandwidth": 100000, + "residual_bandwidth": 100000, + "latency": 5, + "packet_loss": 59.621339166831824, + "availability": 56.37376656633328, + "status": null, + "state": null, + "private_attributes": null, + "time_stamp": null, + "measurement_period": null + }, + { + "id": "urn:ogf:network:sdx:link:zaoxi:A1-B2", + "name": "zaoxi:A1-B2", + "short_name": "Karoo-Capetown", + "nni": null, + "ports": [ + { + "id": "urn:ogf:network:sdx:port:zaoxi:A1:2", + "label_range": [ + "100-200", + "1000" + ], + "name": "Novi100:2", + "node": "urn:ogf:network:sdx:node:zaoxi:A1", + "short_name": "A1:2", + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:zaoxi:B2:3", + "label_range": [ + "100-200", + "10001" + ], + "name": "Novi02:3", + "node": "urn:ogf:network:sdx:node:zaoxi:B2", + "short_name": "B2:3", + "status": "up" + } + ], + "bandwidth": 100000, + "residual_bandwidth": 100000, + "latency": 5, + "packet_loss": 59.621339166831824, + "availability": 56.37376656633328, + "status": null, + "state": null, + "private_attributes": null, + "time_stamp": null, + "measurement_period": null + }, + { + "id": "urn:ogf:network:sdx:link:nni:Fortaleza-Sangano", + "name": "nni:Fortaleza-Sangano", + "short_name": "Fortaleza-Sangano", + "nni": true, + "ports": [ + { + "id": "urn:ogf:network:sdx:port:sax:B3:1", + "label_range": [ + "100-200", + "1000" + ], + "name": "Novi02:3", + "node": "urn:ogf:network:sdx:node:sax:B3", + "short_name": "B3:1", + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:zaoxi:B1:1", + "name": "Novi01:1", + "node": "urn:ogf:network:sdx:node:zaoxi:B1", + "short_name": "B1:1", + "label_range": [ + "100-200", + "10001" + ], + "status": "up" + } + ], + "bandwidth": 100000, + "residual_bandwidth": 100000, + "latency": 10, + "packet_loss": 59.621339166831824, + "availability": 56.37376656633328, + "status": null, + "state": null, + "private_attributes": null, + "time_stamp": null, + "measurement_period": null + } + ], + "private_attributes": null +} diff --git a/tests/data/service.json b/tests/data/service.json new file mode 100644 index 0000000..d796683 --- /dev/null +++ b/tests/data/service.json @@ -0,0 +1,3 @@ +{ + "owner":"FIU" +} \ No newline at end of file diff --git a/tests/data/test_request.json b/tests/data/test_request.json new file mode 100644 index 0000000..f5d4074 --- /dev/null +++ b/tests/data/test_request.json @@ -0,0 +1,18 @@ +{ + "id": "id", + "name": "AMLight", + "start_time": "2000-01-23T04:56:07.000Z", + "end_time": "2000-01-23T04:56:07.000Z", + "bandwidth_required": 100, + "latency_required": 20, + "egress_port": + { + "id": "urn:sdx:port:amlight.net:A1:1", + "name": "Novi100:1" + }, + "ingress_port": + { + "id": "urn:ogf:network:sdx:port:zaoxi:A1:2", + "name": "Novi100:2" + } +} \ No newline at end of file diff --git a/tests/data/zaoxi.json b/tests/data/zaoxi.json new file mode 100644 index 0000000..2f274c2 --- /dev/null +++ b/tests/data/zaoxi.json @@ -0,0 +1,267 @@ +{ + "id": "urn:ogf:network:sdx:topology:zaoxi.net", + "name": "ZAOXI-OXP", + "time_stamp": "2000-01-23T04:56:07+00:00", + "version": 1.0, + "model_version":"1.0.0", + "links": [ + { + "availability": 56.37376656633328, + "residual_bandwidth": 100000, + "id": "urn:ogf:network:sdx:link:zaoxi:B1-B2", + "latency": 5, + "name": "zaoxi:B1-B2", + "packet_loss": 59.621339166831824, + "ports": [ + { + "id": "urn:ogf:network:sdx:port:zaoxi:B1:2", + "name": "Novi01:2", + "node": "urn:ogf:network:sdx:node:zaoxi:B1", + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:zaoxi:B2:2", + "label_range": [ + "100-200", + "10001" + ], + "name": "Novi02:2", + "node": "urn:ogf:network:sdx:node:zaoxi:B2", + "short_name": "B2:2", + "status": "up" + } + ], + "short_name": "Sangano-Capetown", + "bandwidth": 100000 + }, + { + "availability": 56.37376656633328, + "residual_bandwidth": 100000, + "id": "urn:ogf:network:sdx:link:zaoxi:A1-B1", + "latency": 5, + "name": "zaoxi:A1-B1", + "packet_loss": 59.621339166831824, + "ports": [ + { + "id": "urn:ogf:network:sdx:port:zaoxi:A1:1", + "label_range": [ + "100-200", + "1000" + ], + "name": "Novi100:1", + "node": "urn:ogf:network:sdx:node:zaoxi:A1", + "short_name": "A1:1", + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:zaoxi:B1:3", + "name": "Novi01:3", + "node": "urn:ogf:network:sdx:node:zaoxi:B1", + "short_name": "B1:3", + "label_range": [ + "100-200", + "10001" + ], + "status": "up" + } + ], + "short_name": "Karoo-Sangano", + "bandwidth": 100000 + }, + { + "availability": 56.37376656633328, + "residual_bandwidth": 100000, + "id": "urn:ogf:network:sdx:link:zaoxi:A1-B2", + "latency": 5, + "name": "zaoxi:A1-B2", + "packet_loss": 59.621339166831824, + "ports": [ + { + "id": "urn:ogf:network:sdx:port:zaoxi:A1:2", + "label_range": [ + "100-200", + "1000" + ], + "name": "Novi100:2", + "node": "urn:ogf:network:sdx:node:zaoxi:A1", + "short_name": "A1:2", + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:zaoxi:B2:3", + "label_range": [ + "100-200", + "10001" + ], + "name": "Novi02:3", + "node": "urn:ogf:network:sdx:node:zaoxi:B2", + "short_name": "B2:3", + "status": "up" + } + ], + "short_name": "Karoo-Capetown", + "bandwidth": 100000 + }, + { + "availability": 56.37376656633328, + "residual_bandwidth": 100000, + "id": "urn:ogf:network:sdx:link:nni:Fortaleza-Sangano", + "latency": 10, + "name": "nni:Fortaleza-Sangano", + "packet_loss": 59.621339166831824, + "nni": "True", + "ports": [ + { + "id": "urn:ogf:network:sdx:port:sax:B3:1", + "label_range": [ + "100-200", + "1000" + ], + "name": "Novi02:3", + "node": "urn:ogf:network:sdx:node:sax:B3", + "short_name": "B3:1", + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:zaoxi:B1:1", + "name": "Novi01:1", + "node": "urn:ogf:network:sdx:node:zaoxi:B1", + "short_name": "B1:1", + "label_range": [ + "100-200", + "10001" + ], + "status": "up" + } + ], + "short_name": "Fortaleza-Sangano", + "bandwidth": 100000 + } + ], + "nodes": [ + { + "id": "urn:ogf:network:sdx:node:zaoxi:B1", + "location": { + "address": "Sangano", + "latitude": -9.533459658700743, + "longitude": 13.216709879405311 + }, + "name": "zaoxi:Novi01", + "ports": [ + { + "id": "urn:ogf:network:sdx:port:zaoxi:B1:1", + "name": "Novi01:1", + "node": "urn:ogf:network:sdx:node:zaoxi:B1", + "short_name": "B1:1", + "label_range": [ + "100-200", + "10001" + ], + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:zaoxi:B1:2", + "name": "Novi01:2", + "node": "urn:ogf:network:sdx:node:zaoxi:B1", + "short_name": "B1:2", + "label_range": [ + "100-200", + "10001" + ], + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:zaoxi:B1:3", + "name": "Novi01:3", + "node": "urn:ogf:network:sdx:node:zaoxi:B1", + "short_name": "B1:3", + "label_range": [ + "100-200", + "10001" + ], + "status": "up" + } + ], + "short_name": "B1" + }, + { + "id": "urn:ogf:network:sdx:node:zaoxi:B2", + "location": { + "address": "CapeTown", + "latitude": -3.73163824920348, + "longitude": -38.52443289673026 + }, + "name": "zaoxi:Novi02", + "ports": [ + { + "id": "urn:ogf:network:sdx:port:zaoxi:B2:1", + "label_range": [ + "100-200", + "1000" + ], + "name": "Novi02:1", + "node": "urn:ogf:network:sdx:node:zaoxi:B2", + "short_name": "B2:1", + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:zaoxi:B2:2", + "label_range": [ + "100-200", + "10001" + ], + "name": "Novi02:2", + "node": "urn:ogf:network:sdx:node:zaoxi:B2", + "short_name": "B2:2", + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:zaoxi:B2:3", + "label_range": [ + "100-200", + "10001" + ], + "name": "Novi02:3", + "node": "urn:ogf:network:sdx:node:zaoxi:B2", + "short_name": "B2:3", + "status": "up" + } + ], + "short_name": "B2" + }, + { + "id": "urn:ogf:network:sdx:node:zaoxi:A1", + "location": { + "address": "Karoo", + "latitude": -32.3632301851245, + "longitude": 22.541224555821298 + }, + "name": "zaoxi:Novi100", + "ports": [ + { + "id": "urn:ogf:network:sdx:port:zaoxi:A1:1", + "label_range": [ + "100-200", + "1000" + ], + "name": "Novi100:1", + "node": "urn:ogf:network:sdx:node:zaoxi:A1", + "short_name": "A1:1", + "status": "up" + }, + { + "id": "urn:ogf:network:sdx:port:zaoxi:A1:2", + "label_range": [ + "100-200", + "1000" + ], + "name": "Novi100:2", + "node": "urn:ogf:network:sdx:node:zaoxi:A1", + "short_name": "A1:2", + "status": "up" + } + ], + "short_name": "A1" + } + ] +} \ No newline at end of file diff --git a/tests/test_connection_handler.py b/tests/test_connection_handler.py new file mode 100644 index 0000000..8bde228 --- /dev/null +++ b/tests/test_connection_handler.py @@ -0,0 +1,29 @@ +import unittest + +#import parsing + +from sdxdatamodel.parsing.connectionhandler import ConnectionHandler +from sdxdatamodel.parsing.exceptions import DataModelException + +CONNECTION_P2P = './tests/data/p2p.json' +#CONNECTION_P2P = './tests/data/test_connection.json' + +class TestConnectionHandler(unittest.TestCase): + + def setUp(self): + self.handler = ConnectionHandler() # noqa: E501 + def tearDown(self): + pass + + def testImportConnection(self): + try: + print("Test Connection") + self.handler.import_connection(CONNECTION_P2P) + print(self.handler.connection) + except DataModelException as e: + print(e) + return False + return True + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/tests/test_connection_validator.py b/tests/test_connection_validator.py new file mode 100644 index 0000000..498641a --- /dev/null +++ b/tests/test_connection_validator.py @@ -0,0 +1,36 @@ +import unittest + +#import parsing + +from sdxdatamodel.validation.connectionvalidator import ConnectionValidator +from sdxdatamodel.parsing.connectionhandler import ConnectionHandler +from sdxdatamodel.parsing.exceptions import DataModelException +from sdxdatamodel.models.connection import Connection + +CONNECTION_P2P = './tests/data/p2p.json' +#CONNECTION_P2P = './tests/data/test_connection.json' + +class TestConnectionValidator(unittest.TestCase): + + def setUp(self): + self.handler = ConnectionHandler() + print("Import Connection:") + self.handler.import_connection(CONNECTION_P2P) + conn = self.handler.get_connection() + self.validator = ConnectionValidator() + self.validator.set_connection(conn) + + def tearDown(self): + pass + + def testConnection(self): + try: + self.validator.is_valid() + print(self.validator.get_connection()) + except DataModelException as e: + print(e) + return False + return True + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/tests/test_link_handler.py b/tests/test_link_handler.py new file mode 100644 index 0000000..2311645 --- /dev/null +++ b/tests/test_link_handler.py @@ -0,0 +1,26 @@ +import unittest + +from sdxdatamodel.parsing.linkhandler import LinkHandler +from sdxdatamodel.parsing.exceptions import DataModelException + +link = './tests/data/link.json' + +class TestLinkHandler(unittest.TestCase): + + def setUp(self): + self.handler = LinkHandler() # noqa: E501 + def tearDown(self): + pass + + def testImportLink(self): + try: + print("Test Link") + self.handler.import_link(link) + print(self.handler.link) + except DataModelException as e: + print(e) + return False + return True + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/tests/test_location_handler.py b/tests/test_location_handler.py new file mode 100644 index 0000000..7b7f5d4 --- /dev/null +++ b/tests/test_location_handler.py @@ -0,0 +1,26 @@ +import unittest + +from sdxdatamodel.parsing.locationhandler import LocationHandler +from sdxdatamodel.parsing.exceptions import DataModelException + +location = './tests/data/location.json' + +class TestPortHandler(unittest.TestCase): + + def setUp(self): + self.handler = LocationHandler() # noqa: E501 + def tearDown(self): + pass + + def testImportLocation(self): + try: + print("Test Location") + self.handler.import_location(location) + print(self.handler.location) + except DataModelException as e: + print(e) + return False + return True + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/tests/test_node_handler.py b/tests/test_node_handler.py new file mode 100644 index 0000000..7ee25cb --- /dev/null +++ b/tests/test_node_handler.py @@ -0,0 +1,26 @@ +import unittest + +from sdxdatamodel.parsing.nodehandler import NodeHandler +from sdxdatamodel.parsing.exceptions import DataModelException + +node = './tests/data/node.json' + +class TestNodeHandler(unittest.TestCase): + + def setUp(self): + self.handler = NodeHandler() # noqa: E501 + def tearDown(self): + pass + + def testImportNode(self): + try: + print("Test node") + self.handler.import_node(node) + print(self.handler.node) + except DataModelException as e: + print(e) + return False + return True + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/tests/test_port_handler.py b/tests/test_port_handler.py new file mode 100644 index 0000000..3a9dd44 --- /dev/null +++ b/tests/test_port_handler.py @@ -0,0 +1,26 @@ +import unittest + +from sdxdatamodel.parsing.porthandler import PortHandler +from sdxdatamodel.parsing.exceptions import DataModelException + +port = './tests/data/port.json' + +class TestPortHandler(unittest.TestCase): + + def setUp(self): + self.handler = PortHandler() # noqa: E501 + def tearDown(self): + pass + + def testImportPort(self): + try: + print("Test Port") + self.handler.import_port(port) + print(self.handler.port) + except DataModelException as e: + print(e) + return False + return True + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/tests/test_service_handler.py b/tests/test_service_handler.py new file mode 100644 index 0000000..63452e3 --- /dev/null +++ b/tests/test_service_handler.py @@ -0,0 +1,26 @@ +import unittest + +from sdxdatamodel.parsing.servicehandler import ServiceHandler +from sdxdatamodel.parsing.exceptions import DataModelException + +service = './tests/data/service.json' + +class TestServiceHandler(unittest.TestCase): + + def setUp(self): + self.handler = ServiceHandler() # noqa: E501 + def tearDown(self): + pass + + def testImportService(self): + try: + print("Test Service") + self.handler.import_service(service) + print(self.handler.service) + except DataModelException as e: + print(e) + return False + return True + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/tests/test_te_manager.py b/tests/test_te_manager.py new file mode 100644 index 0000000..c2fc9d8 --- /dev/null +++ b/tests/test_te_manager.py @@ -0,0 +1,42 @@ +import unittest +import json +from networkx import MultiGraph, Graph +import networkx as nx + +from sdxdatamodel import parsing +from sdxdatamodel import topologymanager + +from sdxdatamodel import validation +from sdxdatamodel.validation.topologyvalidator import TopologyValidator +from sdxdatamodel.parsing.topologyhandler import TopologyHandler +from sdxdatamodel.topologymanager.manager import TopologyManager +from sdxdatamodel.topologymanager.temanager import TEManager +from sdxdatamodel.parsing.exceptions import DataModelException + +TOPOLOGY = "./tests/data/sdx.json" +CONNECTION = "./tests/data/test_request.json" + +class TestTopologyManager(unittest.TestCase): + + def setUp(self): + with open(TOPOLOGY, 'r', encoding='utf-8') as t: + topology_data = json.load(t) + with open(CONNECTION, 'r', encoding='utf-8') as c: + connection_data = json.load(c) + + self.temanager = TEManager(topology_data, connection_data) + + def tearDown(self): + pass + + def testGenerateSolverInput(self): + print("generate networkx graph of the topology") + graph=self.temanager.graph + + print(graph.nodes[0]) + print(graph.edges) + + print("Test Convert Connection To Topology") + connection=self.temanager.generate_connection_te() + print(connection) + diff --git a/tests/test_topology_graph.py b/tests/test_topology_graph.py new file mode 100644 index 0000000..6cc57b6 --- /dev/null +++ b/tests/test_topology_graph.py @@ -0,0 +1,40 @@ +import unittest + +from networkx import MultiGraph, Graph +import matplotlib.pyplot as plt +import networkx as nx + +from sdxdatamodel.validation.topologyvalidator import TopologyValidator +from sdxdatamodel.parsing.topologyhandler import TopologyHandler +from sdxdatamodel.topologymanager.manager import TopologyManager +from sdxdatamodel.topologymanager.grenmlconverter import GrenmlConverter +from sdxdatamodel.parsing.exceptions import DataModelException + + +TOPOLOGY_AMLIGHT = './tests/data/amlight.json' +TOPOLOGY_SAX = './tests/data/sax.json' +TOPOLOGY_ZAOXI = './tests/data/zaoxi.json' + +class TestTopologyGrpah(unittest.TestCase): + + def setUp(self): + self.manager = TopologyManager() # noqa: E501 + self.handler = self.manager.handler + self.handler.topology_file_name(TOPOLOGY_AMLIGHT) + self.handler.import_topology() + self.manager.set_topology(self.handler.get_topology()) + + def tearDown(self): + pass + + def testGenerateGraph(self): + try: + print("Test Topology Graph") + graph = self.manager.generate_graph() + #pos = nx.spring_layout(graph, seed=225) # Seed for reproducible layout + nx.draw(graph) + plt.savefig("./tests/data/amlight.png") + except DataModelException as e: + print(e) + return False + return True \ No newline at end of file diff --git a/tests/test_topology_grenmlconverter.py b/tests/test_topology_grenmlconverter.py new file mode 100644 index 0000000..0c98f8a --- /dev/null +++ b/tests/test_topology_grenmlconverter.py @@ -0,0 +1,36 @@ +import unittest + +import sdxdatamodel.parsing +import sdxdatamodel.topologymanager + +from sdxdatamodel.validation.topologyvalidator import TopologyValidator +from sdxdatamodel.parsing.topologyhandler import TopologyHandler +from sdxdatamodel.topologymanager.manager import TopologyManager +from sdxdatamodel.topologymanager.grenmlconverter import GrenmlConverter +from sdxdatamodel.parsing.exceptions import DataModelException + + +TOPOLOGY_AMLIGHT = './tests/data/amlight.json' + +class TestTopologyGRENMLConverter(unittest.TestCase): + + def setUp(self): + self.manager = TopologyManager() # noqa: E501 + self.handler = self.manager.handler + self.handler.topology_file_name(TOPOLOGY_AMLIGHT) + self.handler.import_topology() + + def tearDown(self): + pass + + def testGrenmlConverter(self): + try: + print("Test Topology Converter") + print(self.handler.topology) + converter = GrenmlConverter(self.handler.topology) + converter.read_topology() + print(converter.get_xml_str()) + except DataModelException as e: + print(e) + return False + return True \ No newline at end of file diff --git a/tests/test_topology_handler.py b/tests/test_topology_handler.py new file mode 100644 index 0000000..32bb90f --- /dev/null +++ b/tests/test_topology_handler.py @@ -0,0 +1,44 @@ +import unittest + +from sdxdatamodel.parsing.topologyhandler import TopologyHandler +from sdxdatamodel.parsing.exceptions import DataModelException + +TOPOLOGY_AMLIGHT = './tests/data/amlight.json' + +class TestTopologyHandler(unittest.TestCase): + + def setUp(self): + self.handler = TopologyHandler(TOPOLOGY_AMLIGHT) + self.handler.import_topology() + def tearDown(self): + pass + + def testImportTopology(self): + try: + print("Test Topology") + print(self.handler.topology) + except DataModelException as e: + print(e) + return False + return True + + def testImportTopologyNodes(self): + print("Test Nodes: at least one:") + nodes=self.handler.topology.nodes + if nodes==None or len(nodes)==0: + print("Nodes are empty") + return False + print(nodes[0]) + return True + + def testImportTopologyLinks(self): + print("Test Links: at least one") + links=self.handler.topology.links + if links==None or len(links)==0: + print("Links are empty") + return False + print(links[0]) + return True + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/tests/test_topology_manager.py b/tests/test_topology_manager.py new file mode 100644 index 0000000..16042d0 --- /dev/null +++ b/tests/test_topology_manager.py @@ -0,0 +1,131 @@ +import unittest +import json +from networkx import MultiGraph, Graph +import matplotlib.pyplot as plt +import networkx as nx + +from sdxdatamodel import parsing +from sdxdatamodel import topologymanager + +from sdxdatamodel import validation +from sdxdatamodel.validation.topologyvalidator import TopologyValidator +from sdxdatamodel.parsing.topologyhandler import TopologyHandler +from sdxdatamodel.topologymanager.manager import TopologyManager +from sdxdatamodel.topologymanager.grenmlconverter import GrenmlConverter +from sdxdatamodel.parsing.exceptions import DataModelException + + +TOPOLOGY_AMLIGHT = './tests/data/amlight.json' +TOPOLOGY_SAX = './tests/data/sax.json' +TOPOLOGY_ZAOXI = './tests/data/zaoxi.json' + +TOPOLOGY_png = "./tests/data/sdx.png" +TOPOLOGY_IN = "./tests/data/sdx.json" +TOPOLOGY_OUT = "./tests/data/sdx-out.json" + +topology_file_list_3 = [TOPOLOGY_AMLIGHT,TOPOLOGY_ZAOXI,TOPOLOGY_SAX] +topology_file_list_update = [TOPOLOGY_ZAOXI] + +link_id = "urn:ogf:network:sdx:link:amlight:A1-B2" +inter_link_id = "urn:ogf:network:sdx:link:nni:Miami-Sanpaolo" + +class TestTopologyManager(unittest.TestCase): + + def setUp(self): + self.manager = TopologyManager() # noqa: E501 + + def tearDown(self): + pass + + def testMergeTopology(self): + print("Test Topology Merge!") + try: + for topology_file in topology_file_list_3: + with open(topology_file, 'r', encoding='utf-8') as data_file: + data = json.load(data_file) + print("Adding Topology:" + topology_file) + self.manager.add_topology(data) + with open(TOPOLOGY_OUT, 'w') as t_file: + json.dump(self.manager.topology.to_dict(), t_file, indent=4) + + except DataModelException as e: + print(e) + return False + return True + + def testUpdateTopology(self): + print("Test Topology Update!") + try: + self.testMergeTopology() + for topology_file in topology_file_list_update: + with open(topology_file, 'r', encoding='utf-8') as data_file: + data = json.load(data_file) + print("Updating Topology:" + topology_file) + self.manager.update_topology(data) + + with open(TOPOLOGY_OUT, 'w') as t_file: + json.dump(self.manager.topology.to_dict(), t_file, indent=4) + graph = self.manager.generate_graph() + #pos = nx.spring_layout(graph, seed=225) # Seed for reproducible layout + nx.draw(graph,with_labels = True) + plt.savefig(TOPOLOGY_png) + except DataModelException as e: + print(e) + return False + return True + + def testGrenmlConverter(self): + try: + print("Test Topology GRENML Converter") + self.testMergeTopology() + converter = GrenmlConverter(self.manager.get_topology()) + converter.read_topology() + print(converter.get_xml_str()) + except DataModelException as e: + print(e) + return False + return True + + def testGenerateGraph(self): + try: + print("Test Topology Graph") + self.testMergeTopology() + graph = self.manager.generate_graph() + #pos = nx.spring_layout(graph, seed=225) # Seed for reproducible layout + nx.draw(graph,with_labels = True) + plt.savefig(TOPOLOGY_png) + except DataModelException as e: + print(e) + return False + return True + + def testLinkPropertyUpdate(self): + print("Test Topology Link Property Update!") + try: + self.testMergeTopology() + self.manager.update_link_property(link_id,'latency',8) + self.manager.update_link_property(inter_link_id,'latency',8) + with open(TOPOLOGY_OUT, 'w') as t_file: + json.dump(self.manager.topology.to_dict(), t_file, indent=4) + except DataModelException as e: + print(e) + return False + return True + + def testLinkPropertyUpdateJson(self): + print("Test Topology JSON Link Property Update!") + try: + with open(TOPOLOGY_IN, 'r', encoding='utf-8') as data_file: + data = json.load(data_file) + self.manager.update_element_property_json(data,'links',link_id,'latency',20) + with open(TOPOLOGY_OUT, 'w') as t_file: + json.dump(data, t_file, indent=4) + except DataModelException as e: + print(e) + return False + return True + + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_topology_validator.py b/tests/test_topology_validator.py new file mode 100644 index 0000000..bbc6f16 --- /dev/null +++ b/tests/test_topology_validator.py @@ -0,0 +1,36 @@ +import unittest + +import sdxdatamodel.parsing + +from sdxdatamodel.validation.topologyvalidator import TopologyValidator +from sdxdatamodel.parsing.topologyhandler import TopologyHandler +from sdxdatamodel.parsing.exceptions import DataModelException + +TOPOLOGY_AMLIGHT = './tests/data/amlight.json' +TOPOLOGY_AMPATH = './tests/data/ampath.json' +TOPOLOGY_SAX = './tests/data/sax.json' +TOPOLOGY_ZAOXI = './tests/data/zaoxi.json' + +class TestTopologyValidator(unittest.TestCase): + + def setUp(self): + self.handler = TopologyHandler(TOPOLOGY_ZAOXI) + self.validator = TopologyValidator() + + def tearDown(self): + pass + + def testTopology(self): + try: + print("Import Topology:") + self.handler.import_topology() + self.validator.set_topology(self.handler.topology) + self.validator.is_valid() + print(self.handler.topology) + except DataModelException as e: + print(e) + return False + return True + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/validation/__init__.py b/validation/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/validation/validator.py b/validation/validator.py deleted file mode 100644 index 66785ca..0000000 --- a/validation/validator.py +++ /dev/null @@ -1,185 +0,0 @@ -""""" --------------------------------------------------------------------- -Synopsis: A validation class to evaluate that the supplied Topology object contains expected data format -""" -from collections.abc import Iterable -from grenml.models import Topology, Institution, Node, Link, GLOBAL_INSTITUTION_ID -from grenml.models.meta import GRENMLObject, Location, Lifetime -from re import match -ISO_FORMAT = r'\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[-+]\d{2}:\d{2}' -class TopologyValidator: - """ - The validation class made to validate a Topology - """ - def __init__(self): - self._topology = None - @property - def topology(self): - return self._topology - @topology.setter - def topology(self, topology): - if not isinstance(topology, Topology): - raise ValueError('The Validator must be passed a Topology object') - self._topology = topology - @property - def is_valid(self): - errors = self.validate(self.topology, raise_error=False) - for error in errors: - print(error) - return not bool(errors) - def validate(self, topology=None, raise_error=True): - if not topology and self.topology: - topology = self.topology - errors = self._validate_topology(topology) - if errors and raise_error: - raise ValueError('\n'.join(errors)) - return errors - def _validate_topology(self, topology: Topology, parent=None): - """ - Validate that the topology provided meets the XSD standards. - A topology must have the following: - - It must meet object default standards. - - It must have a Primary owner assigned - - It must have its primary owner in its institution list - - It must have the global institution in the institution list - :param topology: The topology being evaluated - :param parent: The Parent Topology. If this topology is the top level, then the parent should be None. - :return: A list of any issues in the data. - """ - errors = [] - errors += self._validate_object_defaults(topology, parent) - if not topology.primary_owner: - errors.append('Topology {} ID: {} must have a primary owner'.format(topology.name, topology.id)) - elif topology.primary_owner not in topology.institutions: - errors.append( - 'Topology {} ID: {} primary owner ID {} must be in the Topologies Institutions'.format( - topology.name, topology.id, topology.primary_owner, - ) - ) - if not topology._parent == parent: - errors.append( - 'Topology {} parent topology does not match. {} != {}'.format( - topology.id, topology._parent, parent - ) - ) - if GLOBAL_INSTITUTION_ID not in topology.institutions: - errors.append('Global Institution must be in Topology {}'.format(topology.id)) - for inst in topology.institutions: - errors += self._validate_institution(inst, topology) - for node in topology.nodes: - errors += self._validate_node(node, topology) - for link in topology.links: - errors += self._validate_link(link, topology) - for sub_topology in topology.topologies: - errors += self._validate_topology(sub_topology, topology) - return errors - def _validate_institution(self, institution: Institution, topology: Topology): - """ - Validate that the institution provided meets the XSD standards. - A institution must have the following: - - It must meet object default standards. - - It's Location values are valid - - The Institution Type must be in the list of valid Institution types - :param institution: The Institution being evaluated. - :param topology: The Parent Topology. - :return: A list of any issues in the data. - """ - errors = [] - errors += self._validate_object_defaults(institution, topology) - errors += self._validate_location(institution, False) - return errors - def _validate_node(self, node: Node, topology: Topology): - """ - Validate that the node provided meets the XSD standards. - A node must have the following: - - It must meet object default standards. - - It's Location values are valid - - It's Lifetime values are valid - - All owners in the node must be strings - - All owners in the node must be IDs for an institution in the parent topologies institution list - - The node must have its parent Topology's primary owner in its list of owners - :param node: The Node being evaluated. - :param topology: The Parent Topology. - :return: A list of any issues in the data. - """ - errors = [] - errors += self._validate_object_defaults(node, topology) - errors += self._validate_location(node) - errors += self._validate_lifetime(node) - for owner in node._owners: - if not isinstance(owner, str): - errors.append( - 'Node {} owner {} should be a string. Not {}'.format( - node.id, owner, owner.__class__.__name__ - ) - ) - if topology and owner not in topology.institutions: - errors.append( - 'Node {} listed owner id {} does not exist in parent Topology {}'.format( - node.id, owner, topology.id - ) - ) - if topology and topology.primary_owner not in node.owners: - errors.append( - 'Node {} does not have the Topology primary owner {} in its list of owners'.format( - node.id, topology.primary_owner - ) - ) - return errors - def _validate_link(self, link: Link, topology: Topology): - """ - Validate that the link provided meets the XSD standards. - A link must have the following: - - It must meet object default standards. - - It's Lifetime values are valid - - All owners in the link must be strings - - All owners in the link must be IDs for an institution in the parent topologies institution list - - The link must have its parent Topology's primary owner in its list of owners - - A link can only connect to 2 nodes - - The nodes that a link is connected to must be in the parent Topology's nodes list - :param link: The Link being evaluated. - :param topology: The Parent Topology. - :return: A list of any issues in the data. - """ - errors = [] - errors += self._validate_object_defaults(link, topology) - errors += self._validate_lifetime(link) - for owner in link._owners: - if not isinstance(owner, str): - errors.append( - 'Link {} owner {} should be a string. Not {}'.format( - link.id, owner, owner.__class__.__name__ - ) - ) - if topology and owner not in topology.institutions: - errors.append( - 'Link {} listed owner id {} does not exist in parent Topology {}'.format( - link.id, owner, topology.id - ) - ) - if topology and topology.primary_owner not in link.owners: - errors.append( - 'Link {} does not have the Topology primary owner {} in its list of owners'.format( - link.id, topology.primary_owner - ) - ) - if len(link._nodes) != 2: - errors.append( - 'Link {} must connect between 2 Nodes. Currently {}'.format( - link.id, str(link._nodes) - ) - ) - for node in link._nodes: - if not isinstance(node, str): - errors.append( - 'Link {} Node {} should be a string. Not {}'.format( - link.id, node, node.__class__.__name__ - ) - ) - if topology and node not in topology.nodes: - errors.append( - 'Link {} listed node id {} does not exist in parent Topology {}'.format( - link.id, node, topology.id - ) - ) - return errors \ No newline at end of file