Add equipments and topolgies endpoints

- add POST, PUT, DELETE on equipments
- add POST, PUT, GET, DELETE on topogies
- path-computation request body can now have equipment id and/or
topology id instead of full data
- activate embedded https of Flask while waiting for real trusted
certificate
- update readme
- add request payload samples in yang directory
- equipment data are encrypted with Fernet

Signed-off-by: manuedelf <59697943+edelfour@users.noreply.github.com>
This commit is contained in:
manuedelf
2020-12-23 15:06:02 +01:00
parent 63545c86ed
commit 2c4f2fbb12
28 changed files with 1794 additions and 24 deletions

View File

@@ -2,13 +2,15 @@ FROM python:3.7-slim
WORKDIR /opt/application/oopt-gnpy
RUN mkdir -p /shared/example-data \
&& groupadd gnpy \
&& useradd -g gnpy -m gnpy \
&& useradd -u 1000 -g gnpy -m gnpy \
&& apt-get update \
&& apt-get install git -y \
&& rm -rf /var/lib/apt/lists/*
COPY . /opt/application/oopt-gnpy
WORKDIR /opt/application/oopt-gnpy
RUN pip install . \
RUN mkdir topology \
&& mkdir equipment \
&& pip install . \
&& chown -Rc gnpy:gnpy /opt/application/oopt-gnpy /shared/example-data
USER gnpy
ENTRYPOINT ["/opt/application/oopt-gnpy/.docker-entry.sh"]

View File

@@ -139,14 +139,25 @@ You can run it through command line or Docker.
.. code-block:: shell-session
$ docker run -p 8080:8080 -dit xxxx gnpy-rest
$ docker run -p 8080:8080 -it emmanuelledelfour/gnpy-experimental:candi-1.0 gnpy-rest
After starting the api server, you can lauch a request
When starting the api server will aks for an encryption/decryption key. This key i used to encrypt equipment file when using /api/v1/equipments endpoint.
This key is a Fernet key and can be generated this way:
.. code-block:: python
from cryptography.fernet import Fernet
Fernet.generate_key()
After typing the key, you can detach the container by typing ^P^Q.
After starting the api server, you can launch a request
.. code-block:: shell-session
$ curl -v -X POST -H "Content-Type: application/json" -d @<PATH_TO_JSON_REQUEST_FILE> http://localhost:8080/api/v1/path-computation
$ curl -v -X POST -H "Content-Type: application/json" -d @<PATH_TO_JSON_REQUEST_FILE> https://localhost:8080/api/v1/path-computation -k
TODO: api documentation, unit tests, real WSGI server with trusted certificates
Contributing
------------

View File

@@ -1,5 +1,9 @@
# coding: utf-8
from flask import Flask
app = Flask(__name__)
import gnpy.api.route.path_request_route
import gnpy.api.route.status_route
import gnpy.api.route.topology_route
import gnpy.api.route.equipments_route

View File

@@ -0,0 +1 @@
# coding: utf-8

View File

@@ -0,0 +1,14 @@
# coding: utf-8
class ConfigError(Exception):
""" Exception raise for configuration file error
Attributes:
message -- explanation of the error
"""
def __init__(self, message):
self.message = message
def __str__(self):
return self.message

View File

@@ -0,0 +1,14 @@
# coding: utf-8
class EquipmentError(Exception):
""" Exception raise for equipment error
Attributes:
message -- explanation of the error
"""
def __init__(self, message):
self.message = message
def __str__(self):
return self.message

View File

@@ -19,8 +19,10 @@ def common_error_handler(exception):
if not isinstance(exception, werkzeug.exceptions.HTTPException):
exception = werkzeug.exceptions.InternalServerError()
exception.description = "Something went wrong on our side."
else:
status_code = exception.code
response = Error(message=exception.name, description=exception.description,
code=exception.code)
code=status_code)
return werkzeug.Response(response=json.dumps(response.__dict__), status=status_code, mimetype='application/json')

View File

@@ -0,0 +1,14 @@
# coding: utf-8
class TopologyError(Exception):
""" Exception raise for topology error
Attributes:
message -- explanation of the error
"""
def __init__(self, message):
self.message = message
def __str__(self):
return self.message

View File

@@ -0,0 +1 @@
# coding: utf-8

View File

@@ -12,6 +12,6 @@ class Error:
:param description: The description of this Error.
:type description: str
"""
self._code = code
self._message = message
self._description = description
self.code = code
self.message = message
self.description = description

8
gnpy/api/model/result.py Normal file
View File

@@ -0,0 +1,8 @@
# coding: utf-8
class Result:
def __init__(self, message: str = None, description: str = None):
self.message = message
self.description = description

View File

@@ -12,21 +12,20 @@ import logging
from logging.handlers import RotatingFileHandler
import werkzeug
from flask import Flask
from flask_injector import FlaskInjector
from injector import singleton
from werkzeug.exceptions import InternalServerError
import gnpy.core.exceptions as exceptions
from gnpy.api import app
from gnpy.api.exception.exception_handler import bad_request_handler, common_error_handler
from gnpy.api.exception.topology_error import TopologyError
from gnpy.api.service import config_service
from gnpy.api.service.encryption_service import EncryptionService
from gnpy.api.service.equipment_service import EquipmentService
_logger = logging.getLogger(__name__)
from gnpy.api import app
@app.route('/api/v1/status', methods=['GET'])
def api_status():
return {"version": "v1", "status": "ok"}, 200
def _init_logger():
handler = RotatingFileHandler('api.log', maxBytes=1024 * 1024, backupCount=5, encoding='utf-8')
@@ -36,7 +35,7 @@ def _init_logger():
"message)s")
def _init_app():
def _init_app(key):
app.register_error_handler(KeyError, bad_request_handler)
app.register_error_handler(TypeError, bad_request_handler)
app.register_error_handler(ValueError, bad_request_handler)
@@ -49,14 +48,29 @@ def _init_app():
app.register_error_handler(exceptions.ParametersError, bad_request_handler)
app.register_error_handler(AssertionError, bad_request_handler)
app.register_error_handler(InternalServerError, common_error_handler)
app.register_error_handler(TopologyError, bad_request_handler)
for error_code in werkzeug.exceptions.default_exceptions:
app.register_error_handler(error_code, common_error_handler)
config = config_service.init_config()
config.add_section('SECRET')
config.set('SECRET', 'equipment', key)
app.config['properties'] = config
def _configure(binder):
binder.bind(EquipmentService,
to=EquipmentService(EncryptionService(app.config['properties'].get('SECRET', 'equipment'))),
scope=singleton)
app.config['properties'].pop('SECRET', None)
def main():
key = input('Enter encryption/decryption key: ')
_init_logger()
_init_app()
app.run(host='0.0.0.0', port=8080)
_init_app(key)
FlaskInjector(app=app, modules=[_configure])
app.run(host='0.0.0.0', port=8080, ssl_context='adhoc')
if __name__ == '__main__':

View File

@@ -0,0 +1,2 @@
# coding: utf-8

View File

@@ -0,0 +1,38 @@
# coding: utf-8
import http
import json
from flask import request
from gnpy.api import app
from gnpy.api.exception.equipment_error import EquipmentError
from gnpy.api.model.result import Result
from gnpy.api.service.equipment_service import EquipmentService
EQUIPMENT_BASE_PATH = '/api/v1/equipments'
EQUIPMENT_ID_PATH = EQUIPMENT_BASE_PATH + '/<equipment_id>'
@app.route(EQUIPMENT_BASE_PATH, methods=['POST'])
def create_equipment(equipment_service: EquipmentService):
if not request.is_json:
raise EquipmentError('Request body is not json')
equipment_identifier = equipment_service.save_equipment(request.json)
response = Result(message='Equipment creation ok', description=equipment_identifier)
return json.dumps(response.__dict__), 201, {'location': EQUIPMENT_BASE_PATH + '/' + equipment_identifier}
@app.route(EQUIPMENT_ID_PATH, methods=['PUT'])
def update_equipment(equipment_id, equipment_service: EquipmentService):
if not request.is_json:
raise EquipmentError('Request body is not json')
equipment_identifier = equipment_service.update_equipment(request.json, equipment_id)
response = Result(message='Equipment update ok', description=equipment_identifier)
return json.dumps(response.__dict__), http.HTTPStatus.OK, {
'location': EQUIPMENT_BASE_PATH + '/' + equipment_identifier}
@app.route(EQUIPMENT_ID_PATH, methods=['DELETE'])
def delete_equipment(equipment_id, equipment_service: EquipmentService):
equipment_service.delete_equipment(equipment_id)
return '', http.HTTPStatus.NO_CONTENT

View File

@@ -1,9 +1,14 @@
# coding: utf-8
import os
from pathlib import Path
from flask import request
from gnpy.api import app
from gnpy.api.exception.equipment_error import EquipmentError
from gnpy.api.exception.topology_error import TopologyError
from gnpy.api.service import topology_service
from gnpy.api.service.equipment_service import EquipmentService
from gnpy.api.service.path_request_service import path_requests_run
from gnpy.tools.json_io import _equipment_from_json, network_from_json
from gnpy.topology.request import ResultElement
@@ -12,11 +17,22 @@ _examples_dir = Path(__file__).parent.parent.parent / 'example-data'
@app.route('/api/v1/path-computation', methods=['POST'])
def compute_path():
def compute_path(equipment_service: EquipmentService):
data = request.json
service = data['gnpy-api:service']
topology = data['gnpy-api:topology']
equipment = _equipment_from_json(data['gnpy-api:equipment'],
if 'gnpy-api:topology' in data:
topology = data['gnpy-api:topology']
elif 'gnpy-api:topology_id' in data:
topology = topology_service.get_topology(data['gnpy-api:topology_id'])
else:
raise TopologyError('No topology found in request')
if 'gnpy-api:equipment' in data:
equipment = data['gnpy-api:equipment']
elif 'gnpy-api:topology_id' in data:
equipment = equipment_service.get_equipment(data['gnpy-api:equipment_id'])
else:
raise EquipmentError('No equipment found in request')
equipment = _equipment_from_json(equipment,
os.path.join(_examples_dir, 'std_medium_gain_advanced_config.json'))
network = network_from_json(topology, equipment)

View File

@@ -0,0 +1,7 @@
# coding: utf-8
from gnpy.api import app
@app.route('/api/v1/status', methods=['GET'])
def api_status():
return {"version": "v1", "status": "ok"}, 200

View File

@@ -0,0 +1,43 @@
# coding: utf-8
import http
import json
from flask import request
from gnpy.api import app
from gnpy.api.exception.topology_error import TopologyError
from gnpy.api.model.result import Result
from gnpy.api.service import topology_service
TOPOLOGY_BASE_PATH = '/api/v1/topologies'
TOPOLOGY_ID_PATH = TOPOLOGY_BASE_PATH + '/<topology_id>'
@app.route(TOPOLOGY_BASE_PATH, methods=['POST'])
def create_topology():
if not request.is_json:
raise TopologyError('Request body is not json')
topology_identifier = topology_service.save_topology(request.json)
response = Result(message='Topology creation ok', description=topology_identifier)
return json.dumps(response.__dict__), 201, {'location': TOPOLOGY_BASE_PATH + '/' + topology_identifier}
@app.route(TOPOLOGY_ID_PATH, methods=['PUT'])
def update_topology(topology_id):
if not request.is_json:
raise TopologyError('Request body is not json')
topology_identifier = topology_service.update_topology(request.json, topology_id)
response = Result(message='Topology update ok', description=topology_identifier)
return json.dumps(response.__dict__), http.HTTPStatus.OK, {
'location': TOPOLOGY_BASE_PATH + '/' + topology_identifier}
@app.route(TOPOLOGY_ID_PATH, methods=['GET'])
def get_topology(topology_id):
return topology_service.get_topology(topology_id), http.HTTPStatus.OK
@app.route(TOPOLOGY_ID_PATH, methods=['DELETE'])
def delete_topology(topology_id):
topology_service.delete_topology(topology_id)
return '', http.HTTPStatus.NO_CONTENT

View File

@@ -0,0 +1 @@
# coding: utf-8

View File

@@ -0,0 +1,37 @@
# coding: utf-8
import configparser
import os
from flask import current_app
from gnpy.api.exception.config_error import ConfigError
def init_config(properties_file_path: str = os.path.join(os.path.dirname(__file__),
'properties.ini')) -> configparser.ConfigParser:
"""
Read config from properties_file_path
@param properties_file_path: the properties file to read
@return: config parser
"""
if not os.path.exists(properties_file_path):
raise ConfigError('Properties file does not exist ' + properties_file_path)
config = configparser.ConfigParser()
config.read(properties_file_path)
return config
def get_topology_dir() -> str:
"""
Get the base dir where topologies are saved
@return: the directory of topologies
"""
return current_app.config['properties'].get('DIRECTORY', 'topology')
def get_equipment_dir() -> str:
"""
Get the base dir where equipments are saved
@return: the directory of equipments
"""
return current_app.config['properties'].get('DIRECTORY', 'equipment')

View File

@@ -0,0 +1,13 @@
# coding: utf-8
from cryptography.fernet import Fernet
class EncryptionService:
def __init__(self, key):
self._fernet = Fernet(key)
def encrypt(self, data):
return self._fernet.encrypt(data)
def decrypt(self, data):
return self._fernet.decrypt(data)

View File

@@ -0,0 +1,66 @@
# coding: utf-
import json
import os
import uuid
from injector import Inject
from gnpy.api.exception.equipment_error import EquipmentError
from gnpy.api.service import config_service
from gnpy.api.service.encryption_service import EncryptionService
class EquipmentService:
def __init__(self, encryption_service: EncryptionService):
self.encryption = encryption_service
def save_equipment(self, equipment):
"""
Save equipment to file.
@param equipment: json content
@return: a UUID identifier to identify the equipment
"""
equipment_identifier = str(uuid.uuid4())
# TODO: validate json content
self._write_equipment(equipment, equipment_identifier)
return equipment_identifier
def update_equipment(self, equipment, equipment_identifier):
"""
Update equipment with identifier equipment_identifier.
@param equipment_identifier: the identifier of the equipment to be updated
@param equipment: json content
@return: a UUID identifier to identify the equipment
"""
# TODO: validate json content
self._write_equipment(equipment, equipment_identifier)
return equipment_identifier
def _write_equipment(self, equipment, equipment_identifier):
equipment_dir = config_service.get_equipment_dir()
with(open(os.path.join(equipment_dir, '.'.join([equipment_identifier, 'json'])), 'wb')) as file:
file.write(self.encryption.encrypt(json.dumps(equipment).encode()))
def get_equipment(self, equipment_id: str) -> dict:
"""
Get the equipment with id equipment_id
@param equipment_id:
@return: the equipment in json format
"""
equipment_dir = config_service.get_equipment_dir()
equipment_file = os.path.join(equipment_dir, '.'.join([equipment_id, 'json']))
if not os.path.exists(equipment_file):
raise EquipmentError('Equipment with id {} does not exist '.format(equipment_id))
with(open(equipment_file, 'rb')) as file:
return json.loads(self.encryption.decrypt(file.read()))
def delete_equipment(self, equipment_id: str):
"""
Delete equipment with id equipment_id
@param equipment_id:
"""
equipment_dir = config_service.get_equipment_dir()
equipment_file = os.path.join(equipment_dir, '.'.join([equipment_id, 'json']))
if os.path.exists(equipment_file):
os.remove(equipment_file)

View File

@@ -12,6 +12,7 @@ from gnpy.topology.spectrum_assignment import build_oms_list, pth_assign_spectru
_logger = logging.getLogger(__name__)
def path_requests_run(service, network, equipment):
# Build the network once using the default power defined in SI in eqpt config
# TODO power density: db2linp(ower_dbm": 0)/power_dbm": 0 * nb channels as defined by

View File

@@ -0,0 +1,3 @@
[DIRECTORY]
topology: /opt/application/oopt-gnpy/topology
equipment: /opt/application/oopt-gnpy/equipment

View File

@@ -0,0 +1,62 @@
# coding: utf-
import json
import os
import uuid
from gnpy.api.exception.topology_error import TopologyError
from gnpy.api.service import config_service
def save_topology(topology):
"""
Save topology to file.
@param topology: json content
@return: a UUID identifier to identify the topology
"""
topology_identifier = str(uuid.uuid4())
# TODO: validate json content
_write_topology(topology, topology_identifier)
return topology_identifier
def update_topology(topology, topology_identifier):
"""
Update topology with identifier topology_identifier.
@param topology_identifier: the identifier of the topology to be updated
@param topology: json content
@return: a UUID identifier to identify the topology
"""
# TODO: validate json content
_write_topology(topology, topology_identifier)
return topology_identifier
def _write_topology(topology, topology_identifier):
topology_dir = config_service.get_topology_dir()
with(open(os.path.join(topology_dir, '.'.join([topology_identifier, 'json'])), 'w')) as file:
json.dump(topology, file)
def get_topology(topology_id: str) -> dict:
"""
Get the topology with id topology_id
@param topology_id:
@return: the topology in json format
"""
topology_dir = config_service.get_topology_dir()
topology_file = os.path.join(topology_dir, '.'.join([topology_id, 'json']))
if not os.path.exists(topology_file):
raise TopologyError('Topology with id {} does not exist '.format(topology_id))
with(open(topology_file, 'r')) as file:
return json.load(file)
def delete_topology(topology_id: str):
"""
Delete topology with id topology_id
@param topology_id:
"""
topology_dir = config_service.get_topology_dir()
topology_file = os.path.join(topology_dir, '.'.join([topology_id, 'json']))
if os.path.exists(topology_file):
os.remove(topology_file)

View File

@@ -0,0 +1,310 @@
{
"Edfa":[{
"type_variety": "high_detail_model_example",
"type_def": "advanced_model",
"gain_flatmax": 25.0,
"gain_min": 15.0,
"p_max": 21.0,
"advanced_config_from_json": "std_medium_gain_advanced_config.json",
"out_voa_auto": false,
"allowed_for_design": false
}, {
"type_variety": "Juniper_BoosterHG",
"type_def": "advanced_model",
"gain_flatmax": 25.0,
"gain_min": 10.0,
"p_max": 21.0,
"advanced_config_from_json": "Juniper-BoosterHG.json",
"out_voa_auto": false,
"allowed_for_design": false
},
{
"type_variety": "operator_model_example",
"type_def": "variable_gain",
"gain_flatmax": 26.0,
"gain_min": 15.0,
"p_max": 23.0,
"nf_min": 6.0,
"nf_max": 10.0,
"out_voa_auto": false,
"allowed_for_design": false
},
{
"type_variety": "low_noise",
"type_def": "openroadm",
"gain_flatmax": 27.0,
"gain_min": 12.0,
"p_max": 22.0,
"nf_coef": [-8.104e-4,-6.221e-2,-5.889e-1,37.62],
"allowed_for_design": false
},
{
"type_variety": "standard",
"type_def": "openroadm",
"gain_flatmax": 27.0,
"gain_min": 12.0,
"p_max": 22.0,
"nf_coef": [-5.952e-4,-6.250e-2,-1.071,28.99],
"allowed_for_design": false
},
{
"type_variety": "std_high_gain",
"type_def": "variable_gain",
"gain_flatmax": 35.0,
"gain_min": 25.0,
"p_max": 21.0,
"nf_min": 5.5,
"nf_max": 7.0,
"out_voa_auto": false,
"allowed_for_design": true
},
{
"type_variety": "std_medium_gain",
"type_def": "variable_gain",
"gain_flatmax": 26.0,
"gain_min": 15.0,
"p_max": 23.0,
"nf_min": 6.0,
"nf_max": 10.0,
"out_voa_auto": false,
"allowed_for_design": true
},
{
"type_variety": "std_low_gain",
"type_def": "variable_gain",
"gain_flatmax": 16.0,
"gain_min": 8.0,
"p_max": 23.0,
"nf_min": 6.5,
"nf_max": 11.0,
"out_voa_auto": false,
"allowed_for_design": true
},
{
"type_variety": "high_power",
"type_def": "variable_gain",
"gain_flatmax": 16.0,
"gain_min": 8.0,
"p_max": 25.0,
"nf_min": 9.0,
"nf_max": 15.0,
"out_voa_auto": false,
"allowed_for_design": false
},
{
"type_variety": "std_fixed_gain",
"type_def": "fixed_gain",
"gain_flatmax": 21.0,
"gain_min": 20.0,
"p_max": 21.0,
"nf0": 5.5,
"allowed_for_design": false
},
{
"type_variety": "4pumps_raman",
"type_def": "fixed_gain",
"gain_flatmax": 12.0,
"gain_min": 12.0,
"p_max": 21.0,
"nf0": -1.0,
"allowed_for_design": false
},
{
"type_variety": "hybrid_4pumps_lowgain",
"type_def": "dual_stage",
"raman": true,
"gain_min": 25.0,
"preamp_variety": "4pumps_raman",
"booster_variety": "std_low_gain",
"allowed_for_design": true
},
{
"type_variety": "hybrid_4pumps_mediumgain",
"type_def": "dual_stage",
"raman": true,
"gain_min": 25.0,
"preamp_variety": "4pumps_raman",
"booster_variety": "std_medium_gain",
"allowed_for_design": true
},
{
"type_variety": "medium+low_gain",
"type_def": "dual_stage",
"gain_min": 25.0,
"preamp_variety": "std_medium_gain",
"booster_variety": "std_low_gain",
"allowed_for_design": true
},
{
"type_variety": "medium+high_power",
"type_def": "dual_stage",
"gain_min": 25.0,
"preamp_variety": "std_medium_gain",
"booster_variety": "high_power",
"allowed_for_design": false
}
],
"Fiber":[{
"type_variety": "SSMF",
"dispersion": 1.67e-05,
"gamma": 0.00127,
"pmd_coef": 1.265e-15
},
{
"type_variety": "NZDF",
"dispersion": 0.5e-05,
"gamma": 0.00146,
"pmd_coef": 1.265e-15
},
{
"type_variety": "LOF",
"dispersion": 2.2e-05,
"gamma": 0.000843,
"pmd_coef": 1.265e-15
}
],
"RamanFiber":[{
"type_variety": "SSMF",
"dispersion": 1.67e-05,
"gamma": 0.00127,
"pmd_coef": 1.265e-15,
"raman_efficiency": {
"cr":[
0, 9.4E-06, 2.92E-05, 4.88E-05, 6.82E-05, 8.31E-05, 9.4E-05, 0.0001014, 0.0001069, 0.0001119,
0.0001217, 0.0001268, 0.0001365, 0.000149, 0.000165, 0.000181, 0.0001977, 0.0002192, 0.0002469,
0.0002749, 0.0002999, 0.0003206, 0.0003405, 0.0003592, 0.000374, 0.0003826, 0.0003841, 0.0003826,
0.0003802, 0.0003756, 0.0003549, 0.0003795, 0.000344, 0.0002933, 0.0002024, 0.0001158, 8.46E-05,
7.14E-05, 6.86E-05, 8.5E-05, 8.93E-05, 9.01E-05, 8.15E-05, 6.67E-05, 4.37E-05, 3.28E-05, 2.96E-05,
2.65E-05, 2.57E-05, 2.81E-05, 3.08E-05, 3.67E-05, 5.85E-05, 6.63E-05, 6.36E-05, 5.5E-05, 4.06E-05,
2.77E-05, 2.42E-05, 1.87E-05, 1.6E-05, 1.4E-05, 1.13E-05, 1.05E-05, 9.8E-06, 9.8E-06, 1.13E-05,
1.64E-05, 1.95E-05, 2.38E-05, 2.26E-05, 2.03E-05, 1.48E-05, 1.09E-05, 9.8E-06, 1.05E-05, 1.17E-05,
1.25E-05, 1.21E-05, 1.09E-05, 9.8E-06, 8.2E-06, 6.6E-06, 4.7E-06, 2.7E-06, 1.9E-06, 1.2E-06, 4E-07,
2E-07, 1E-07
],
"frequency_offset":[
0, 0.5e12, 1e12, 1.5e12, 2e12, 2.5e12, 3e12, 3.5e12, 4e12, 4.5e12, 5e12, 5.5e12, 6e12, 6.5e12, 7e12,
7.5e12, 8e12, 8.5e12, 9e12, 9.5e12, 10e12, 10.5e12, 11e12, 11.5e12, 12e12, 12.5e12, 12.75e12,
13e12, 13.25e12, 13.5e12, 14e12, 14.5e12, 14.75e12, 15e12, 15.5e12, 16e12, 16.5e12, 17e12,
17.5e12, 18e12, 18.25e12, 18.5e12, 18.75e12, 19e12, 19.5e12, 20e12, 20.5e12, 21e12, 21.5e12,
22e12, 22.5e12, 23e12, 23.5e12, 24e12, 24.5e12, 25e12, 25.5e12, 26e12, 26.5e12, 27e12, 27.5e12, 28e12,
28.5e12, 29e12, 29.5e12, 30e12, 30.5e12, 31e12, 31.5e12, 32e12, 32.5e12, 33e12, 33.5e12, 34e12, 34.5e12,
35e12, 35.5e12, 36e12, 36.5e12, 37e12, 37.5e12, 38e12, 38.5e12, 39e12, 39.5e12, 40e12, 40.5e12, 41e12,
41.5e12, 42e12
]
}
}
],
"Span":[{
"power_mode":true,
"delta_power_range_db": [-2.0, 3.0, 0.5],
"max_fiber_lineic_loss_for_raman": 0.25,
"target_extended_gain": 2.5,
"max_length": 150.0,
"length_units": "km",
"max_loss": 28.0,
"padding": 10.0,
"EOL": 0.0,
"con_in": 0.0,
"con_out": 0.0
}
],
"Roadm":[{
"target_pch_out_db": -20.0,
"add_drop_osnr": 38.0,
"pmd": 0.0,
"restrictions": {
"preamp_variety_list":[],
"booster_variety_list":[]
}
}],
"SI":[{
"f_min": 191.3e12,
"baud_rate": 32e9,
"f_max":195.1e12,
"spacing": 50e9,
"power_dbm": 0.0,
"power_range_db": [0.0,0.0,1.0],
"roll_off": 0.15,
"tx_osnr": 40.0,
"sys_margins": 2.0
}],
"Transceiver":[
{
"type_variety": "vendorA_trx-type1",
"frequency":{
"min": 191.35e12,
"max": 196.1e12
},
"mode":[{
"format": "mode 1",
"baud_rate": 32e9,
"OSNR": 11.0,
"bit_rate": 100e9,
"roll_off": 0.15,
"tx_osnr": 40.0,
"min_spacing": 37.5e9,
"cost":1.0
},
{
"format": "mode 2",
"baud_rate": 66e9,
"OSNR": 15.0,
"bit_rate": 200e9,
"roll_off": 0.15,
"tx_osnr": 40.0,
"min_spacing": 75e9,
"cost":1.0
}
]
},
{
"type_variety": "Voyager",
"frequency":{
"min": 191.35e12,
"max": 196.1e12
},
"mode":[
{
"format": "mode 1",
"baud_rate": 32e9,
"OSNR": 12.0,
"bit_rate": 100e9,
"roll_off": 0.15,
"tx_osnr": 40.0,
"min_spacing": 37.5e9,
"cost":1.0
},
{
"format": "mode 3",
"baud_rate": 44e9,
"OSNR": 18.0,
"bit_rate": 300e9,
"roll_off": 0.15,
"tx_osnr": 40.0,
"min_spacing": 62.5e9,
"cost":1.0
},
{
"format": "mode 2",
"baud_rate": 66e9,
"OSNR": 21.0,
"bit_rate": 400e9,
"roll_off": 0.15,
"tx_osnr": 40.0,
"min_spacing": 75e9,
"cost":1.0
},
{
"format": "mode 4",
"baud_rate": 66e9,
"OSNR": 16.0,
"bit_rate": 200e9,
"roll_off": 0.15,
"tx_osnr": 40.0,
"min_spacing": 75e9,
"cost":1.0
}
]
}
]
}

View File

@@ -0,0 +1,180 @@
{
"gnpy-api:service":{
"path-request": [
{
"request-id": "0",
"source": "trx Alice",
"destination": "trx Bob",
"src-tp-id": "trx Alice",
"dst-tp-id": "trx Bob",
"bidirectional": false,
"path-constraints": {
"te-bandwidth": {
"technology": "flexi-grid",
"trx_type": "Voyager",
"trx_mode": "mode 1",
"effective-freq-slot": [
{
"N": 0,
"M": 12
}
],
"spacing": 50000000000.0,
"path_bandwidth": 100000000000.0
}
}
},
{
"request-id": "1",
"source": "trx Alice",
"destination": "trx Bob",
"src-tp-id": "trx Alice",
"dst-tp-id": "trx Bob",
"bidirectional": false,
"path-constraints": {
"te-bandwidth": {
"technology": "flexi-grid",
"trx_type": "Voyager",
"trx_mode": "mode 1",
"spacing": 50000000000.0,
"path_bandwidth": 100000000000.0
}
}
},
{
"request-id": "2",
"source": "trx Alice",
"destination": "trx Bob",
"src-tp-id": "trx Alice",
"dst-tp-id": "trx Bob",
"bidirectional": false,
"path-constraints": {
"te-bandwidth": {
"technology": "flexi-grid",
"trx_type": "Voyager",
"trx_mode": "mode 2",
"spacing": 100000000000.0,
"path_bandwidth": 100000000000.0
}
}
},
{
"request-id": "3",
"source": "trx Alice",
"destination": "trx Bob",
"src-tp-id": "trx Alice",
"dst-tp-id": "trx Bob",
"bidirectional": true,
"path-constraints": {
"te-bandwidth": {
"technology": "flexi-grid",
"trx_type": "Voyager",
"spacing": 50000000000.0,
"path_bandwidth": 100000000000.0
}
},
"explicit-route-objects": {
"route-object-include-exclude": [
{
"explicit-route-usage": "route-include-ero",
"index": 0,
"num-unnum-hop": {
"node-id": "roadm Carol",
"link-tp-id": "link-tp-id is not used",
"hop-type": "LOOSE"
}
}
]
}
},
{
"request-id": "4",
"source": "trx Alice",
"destination": "trx Bob",
"src-tp-id": "trx Alice",
"dst-tp-id": "trx Bob",
"bidirectional": true,
"path-constraints": {
"te-bandwidth": {
"technology": "flexi-grid",
"trx_type": "Voyager",
"effective-freq-slot": [
{
"N": -284,
"M": 12
}
],
"spacing": 50000000000.0,
"path_bandwidth": 100000000000.0
}
}
},
{
"request-id": "5",
"source": "trx Bob1",
"destination": "trx Carol1",
"src-tp-id": "trx Bob1",
"dst-tp-id": "trx Carol1",
"bidirectional": true,
"path-constraints": {
"te-bandwidth": {
"technology": "flexi-grid",
"trx_type": "vendorA_trx-type1",
"spacing": 100000000000.0,
"path_bandwidth": 100000000000.0
}
}
},
{
"request-id": "6",
"source": "trx Bob1",
"destination": "trx Carol1",
"src-tp-id": "trx Bob1",
"dst-tp-id": "trx Carol1",
"bidirectional": true,
"path-constraints": {
"te-bandwidth": {
"technology": "flexi-grid",
"trx_type": "Voyager",
"trx_mode": "mode 1",
"spacing": 50000000000.0,
"path_bandwidth": 100000000000.0
}
}
},
{
"request-id": "7",
"source": "trx Bob1",
"destination": "trx Carol",
"src-tp-id": "trx Bob1",
"dst-tp-id": "trx Carol",
"bidirectional": true,
"path-constraints": {
"te-bandwidth": {
"technology": "flexi-grid",
"trx_type": "Voyager",
"trx_mode": "mode 1",
"spacing": 50000000000.0,
"path_bandwidth": 100000000000.0
}
}
}
],
"synchronization": [
{
"synchronization-id": "1",
"svec": {
"relaxable": false,
"disjointness": "node link",
"request-id-number": [
"1",
"0"
]
}
}
]
},
"gnpy-api:topology_id": "5cf39d4b-be10-4ee9-b38b-7f4db7403db7",
"gnpy-api:equipment_id": "9ed86e34-9d41-41b2-b8e4-984ca0901d47"
}

901
gnpy/yang/api-topology.json Normal file
View File

@@ -0,0 +1,901 @@
{
"elements": [
{
"uid": "trx Alice",
"type": "Transceiver",
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Alice",
"region": ""
}
}
},
{
"uid": "trx Bob",
"type": "Transceiver",
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Bob",
"region": ""
}
}
},
{
"uid": "trx Carol",
"type": "Transceiver",
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Carol",
"region": ""
}
}
},
{
"uid": "trx Bob1",
"type": "Transceiver",
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Bob",
"region": ""
}
}
},
{
"uid": "trx Carol1",
"type": "Transceiver",
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Carol",
"region": ""
}
}
},
{
"uid": "roadm Alice",
"type": "Roadm",
"params": {
"target_pch_out_db": -20.0,
"restrictions": {
"preamp_variety_list": [],
"booster_variety_list": []
}
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Alice",
"region": ""
}
}
},
{
"uid": "roadm Bob",
"type": "Roadm",
"params": {
"target_pch_out_db": -20.0,
"restrictions": {
"preamp_variety_list": [],
"booster_variety_list": []
}
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Bob",
"region": ""
}
}
},
{
"uid": "roadm Carol",
"type": "Roadm",
"params": {
"target_pch_out_db": -20.0,
"restrictions": {
"preamp_variety_list": [],
"booster_variety_list": []
}
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Carol",
"region": ""
}
}
},
{
"uid": "roadm Bob1",
"type": "Roadm",
"params": {
"target_pch_out_db": -20.0,
"restrictions": {
"preamp_variety_list": [],
"booster_variety_list": []
}
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Bob",
"region": ""
}
}
},
{
"uid": "roadm Carol1",
"type": "Roadm",
"params": {
"target_pch_out_db": -20.0,
"restrictions": {
"preamp_variety_list": [],
"booster_variety_list": []
}
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Carol",
"region": ""
}
}
},
{
"uid": "fiber (Alice → Bob)-",
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": 75.0,
"loss_coef": 0.2,
"length_units": "km",
"att_in": 0.0,
"con_in": 0.0,
"con_out": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "null",
"region": "null"
}
}
},
{
"uid": "fiber (Bob → Carol)-",
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": 80.0,
"loss_coef": 0.2,
"length_units": "km",
"att_in": 0.0,
"con_in": 0.0,
"con_out": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "null",
"region": "null"
}
}
},
{
"uid": "fiber (Bob1 → Carol1)-",
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": 80.0,
"loss_coef": 0.5,
"length_units": "km",
"att_in": 0.0,
"con_in": 0.0,
"con_out": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "null",
"region": "null"
}
}
},
{
"uid": "fiber (Carol → Dan)-",
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": 83.0,
"loss_coef": 0.2,
"length_units": "km",
"att_in": 0.0,
"con_in": 0.0,
"con_out": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "null",
"region": "null"
}
}
},
{
"uid": "fiber (Dan → Alice)-",
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": 60.0,
"loss_coef": 0.2,
"length_units": "km",
"att_in": 0.0,
"con_in": 0.0,
"con_out": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "null",
"region": "null"
}
}
},
{
"uid": "fiber (Bob → Alice)-",
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": 75.0,
"loss_coef": 0.2,
"length_units": "km",
"att_in": 0.0,
"con_in": 0.0,
"con_out": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "null",
"region": "null"
}
}
},
{
"uid": "fiber (Carol → Bob)-",
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": 80.0,
"loss_coef": 0.2,
"length_units": "km",
"att_in": 0.0,
"con_in": 0.0,
"con_out": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "null",
"region": "null"
}
}
},
{
"uid": "fiber (Carol1 → Bob1)-",
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": 80.0,
"loss_coef": 0.5,
"length_units": "km",
"att_in": 0.0,
"con_in": 0.0,
"con_out": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "null",
"region": "null"
}
}
},
{
"uid": "fiber (Dan → Carol)-",
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": 83.0,
"loss_coef": 0.2,
"length_units": "km",
"att_in": 0.0,
"con_in": 0.0,
"con_out": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "null",
"region": "null"
}
}
},
{
"uid": "fiber (Alice → Dan)-",
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": 60.0,
"loss_coef": 0.2,
"length_units": "km",
"att_in": 0.0,
"con_in": 0.0,
"con_out": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "null",
"region": "null"
}
}
},
{
"uid": "east edfa in Alice to Bob",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 18.5,
"delta_p": -1.5,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Alice",
"region": ""
}
}
},
{
"uid": "east edfa in Bob to Carol",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 19.0,
"delta_p": -1.0,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Bob",
"region": ""
}
}
},
{
"uid": "east edfa in Bob1 to Carol1",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 19.0,
"delta_p": -1.0,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Bob",
"region": ""
}
}
},
{
"uid": "east edfa in Carol to Dan",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 19.0,
"delta_p": -1.0,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Carol",
"region": ""
}
}
},
{
"uid": "east edfa in Dan to Alice",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 15.600000000000001,
"delta_p": -2.0,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Dan",
"region": ""
}
}
},
{
"uid": "east edfa in Bob to Alice",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 18.5,
"delta_p": -1.5,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Bob",
"region": ""
}
}
},
{
"uid": "east edfa in Alice to Dan",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 18.0,
"delta_p": -2.0,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Alice",
"region": ""
}
}
},
{
"uid": "east edfa in Carol to Bob",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 19.0,
"delta_p": -1.0,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Carol",
"region": ""
}
}
},
{
"uid": "east edfa in Carol1 to Bob1",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 19.0,
"delta_p": -1.0,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Carol",
"region": ""
}
}
},
{
"uid": "west edfa in Alice to Bob",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 16.5,
"delta_p": 0.0,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Alice",
"region": ""
}
}
},
{
"uid": "west edfa in Bob to Carol",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 17.0,
"delta_p": 0.0,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Bob",
"region": ""
}
}
},
{
"uid": "west edfa in Bob1 to Carol1",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 17.0,
"delta_p": 0.0,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Bob",
"region": ""
}
}
},
{
"uid": "west edfa in Carol to Dan",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 17.6,
"delta_p": 0.0,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Carol",
"region": ""
}
}
},
{
"uid": "west edfa in Dan to Alice",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 13.0,
"delta_p": -1.0,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Dan",
"region": ""
}
}
},
{
"uid": "west edfa in Bob to Alice",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 16.5,
"delta_p": 0.0,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Bob",
"region": ""
}
}
},
{
"uid": "west edfa in Alice to Dan",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 14.0,
"delta_p": 0.0,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Alice",
"region": ""
}
}
},
{
"uid": "west edfa in Carol to Bob",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 17.0,
"delta_p": 0.0,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Carol",
"region": ""
}
}
},
{
"uid": "west edfa in Carol1 to Bob1",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 17.0,
"delta_p": 0.0,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Carol",
"region": ""
}
}
}
],
"connections": [
{
"from_node": "trx Alice",
"to_node": "roadm Alice"
},
{
"from_node": "trx Bob",
"to_node": "roadm Bob"
},
{
"from_node": "trx Bob1",
"to_node": "roadm Bob1"
},
{
"from_node": "trx Carol",
"to_node": "roadm Carol"
},
{
"from_node": "trx Carol1",
"to_node": "roadm Carol1"
},
{
"from_node": "roadm Alice",
"to_node": "east edfa in Alice to Bob"
},
{
"from_node": "roadm Alice",
"to_node": "east edfa in Alice to Dan"
},
{
"from_node": "roadm Alice",
"to_node": "trx Alice"
},
{
"from_node": "roadm Bob",
"to_node": "east edfa in Bob to Alice"
},
{
"from_node": "roadm Bob1",
"to_node": "east edfa in Bob1 to Carol1"
},
{
"from_node": "roadm Bob",
"to_node": "east edfa in Bob to Carol"
},
{
"from_node": "roadm Bob",
"to_node": "trx Bob"
},
{
"from_node": "roadm Bob1",
"to_node": "trx Bob1"
},
{
"from_node": "roadm Carol",
"to_node": "east edfa in Carol to Bob"
},
{
"from_node": "roadm Carol1",
"to_node": "east edfa in Carol1 to Bob1"
},
{
"from_node": "roadm Carol",
"to_node": "east edfa in Carol to Dan"
},
{
"from_node": "roadm Carol",
"to_node": "trx Carol"
},
{
"from_node": "roadm Carol1",
"to_node": "trx Carol1"
},
{
"from_node": "fiber (Alice → Bob)-",
"to_node": "west edfa in Bob to Alice"
},
{
"from_node": "fiber (Bob → Carol)-",
"to_node": "west edfa in Carol to Bob"
},
{
"from_node": "fiber (Bob1 → Carol1)-",
"to_node": "west edfa in Carol1 to Bob1"
},
{
"from_node": "fiber (Carol → Dan)-",
"to_node": "east edfa in Dan to Alice"
},
{
"from_node": "fiber (Dan → Alice)-",
"to_node": "west edfa in Alice to Dan"
},
{
"from_node": "fiber (Bob → Alice)-",
"to_node": "west edfa in Alice to Bob"
},
{
"from_node": "fiber (Carol → Bob)-",
"to_node": "west edfa in Bob to Carol"
},
{
"from_node": "fiber (Carol1 → Bob1)-",
"to_node": "west edfa in Bob1 to Carol1"
},
{
"from_node": "fiber (Dan → Carol)-",
"to_node": "west edfa in Carol to Dan"
},
{
"from_node": "fiber (Alice → Dan)-",
"to_node": "west edfa in Dan to Alice"
},
{
"from_node": "east edfa in Alice to Bob",
"to_node": "fiber (Alice → Bob)-"
},
{
"from_node": "east edfa in Bob to Carol",
"to_node": "fiber (Bob → Carol)-"
},
{
"from_node": "east edfa in Bob1 to Carol1",
"to_node": "fiber (Bob1 → Carol1)-"
},
{
"from_node": "east edfa in Carol to Dan",
"to_node": "fiber (Carol → Dan)-"
},
{
"from_node": "east edfa in Dan to Alice",
"to_node": "fiber (Dan → Alice)-"
},
{
"from_node": "east edfa in Bob to Alice",
"to_node": "fiber (Bob → Alice)-"
},
{
"from_node": "east edfa in Alice to Dan",
"to_node": "fiber (Alice → Dan)-"
},
{
"from_node": "east edfa in Carol to Bob",
"to_node": "fiber (Carol → Bob)-"
},
{
"from_node": "east edfa in Carol1 to Bob1",
"to_node": "fiber (Carol1 → Bob1)-"
},
{
"from_node": "west edfa in Alice to Bob",
"to_node": "roadm Alice"
},
{
"from_node": "west edfa in Bob to Carol",
"to_node": "roadm Bob"
},
{
"from_node": "west edfa in Bob1 to Carol1",
"to_node": "roadm Bob1"
},
{
"from_node": "west edfa in Bob1 to Carol1",
"to_node": "roadm Bob1"
},
{
"from_node": "west edfa in Carol to Dan",
"to_node": "roadm Carol"
},
{
"from_node": "west edfa in Dan to Alice",
"to_node": "fiber (Dan → Carol)-"
},
{
"from_node": "west edfa in Bob to Alice",
"to_node": "roadm Bob"
},
{
"from_node": "west edfa in Alice to Dan",
"to_node": "roadm Alice"
},
{
"from_node": "west edfa in Carol to Bob",
"to_node": "roadm Carol"
},
{
"from_node": "west edfa in Carol1 to Bob1",
"to_node": "roadm Carol1"
}
]
}

View File

@@ -10,4 +10,9 @@ scipy>=1.3.0,<2
Sphinx>=2.4.4,<3
sphinxcontrib-bibtex>=0.4.2,<1
xlrd>=1.2.0,<2
flask>=1.1.2
flask>=1.1.2
gnpy~=2.2.0
cryptography~=3.3.1
Werkzeug~=1.0.1
setuptools~=50.3.1
Flask-Injector