6 Commits

Author SHA1 Message Date
EstherLerouzic
f6dab0477b Add the possibilty to input id instead of explicit topo or eqpt
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I81c9fd56773e6f998bc2bdf87fc2aef817e252a4
2021-03-02 15:25:26 +01:00
manuedelf
ce4a226615 add autodesign routes
Signed-off-by: manuedelf <59697943+edelfour@users.noreply.github.com>
2021-01-25 23:11:05 +01:00
manuedelf
78fc0c0680 fix error in if
Signed-off-by: manuedelf <59697943+edelfour@users.noreply.github.com>
2021-01-05 16:24:25 +01:00
manuedelf
2c4f2fbb12 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>
2020-12-23 15:06:02 +01:00
manuedelf
63545c86ed Put api in a dedicated python package
Signed-off-by: manuedelf <59697943+edelfour@users.noreply.github.com>
2020-12-22 13:49:46 +01:00
manuedelf
aa78d00158 remove obsolete example 2020-11-18 14:16:58 +01:00
35 changed files with 2150 additions and 2010 deletions

1
.dockerignore Normal file
View File

@@ -0,0 +1 @@
venv/

2
.gitignore vendored
View File

@@ -65,3 +65,5 @@ target/
# MacOS DS_store
.DS_Store
venv/

View File

@@ -2,13 +2,16 @@ 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 \
&& mkdir autodesign \
&& 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
------------

9
gnpy/api/__init__.py Normal file
View File

@@ -0,0 +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

@@ -0,0 +1,33 @@
# coding: utf-8
import json
import re
import werkzeug
from gnpy.api.model.error import Error
_reaesc = re.compile(r'\x1b[^m]*m')
def common_error_handler(exception):
"""
:type exception: Exception
"""
status_code = 500
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=status_code)
return werkzeug.Response(response=json.dumps(response.__dict__), status=status_code, mimetype='application/json')
def bad_request_handler(exception):
response = Error(message='bad request', description=_reaesc.sub('', str(exception)),
code=400)
return werkzeug.Response(response=json.dumps(response.__dict__), status=400, mimetype='application/json')

View File

@@ -0,0 +1,14 @@
# coding: utf-8
class PathComputationError(Exception):
""" Exception raise for path computation error 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 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

17
gnpy/api/model/error.py Normal file
View File

@@ -0,0 +1,17 @@
# coding: utf-8
class Error:
def __init__(self, code: int = None, message: str = None, description: str = None):
"""Error
:param code: The code of this Error.
:type code: int
:param message: The message of this Error.
:type message: str
:param description: The description of this Error.
:type description: str
"""
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

83
gnpy/api/rest_example.py Normal file
View File

@@ -0,0 +1,83 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
gnpy.tools.rest_example
=======================
GNPy as a rest API example
'''
import logging
from logging.handlers import RotatingFileHandler
import werkzeug
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.path_computation_error import PathComputationError
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
from gnpy.api.service.path_request_service import PathRequestService
_logger = logging.getLogger(__name__)
def _init_logger():
handler = RotatingFileHandler('api.log', maxBytes=1024 * 1024, backupCount=5, encoding='utf-8')
ch = logging.StreamHandler()
logging.basicConfig(level=logging.INFO, handlers=[handler, ch],
format="%(asctime)s %(levelname)s %(name)s(%(lineno)s) [%(threadName)s - %(thread)d] - %("
"message)s")
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)
app.register_error_handler(exceptions.ConfigurationError, bad_request_handler)
app.register_error_handler(exceptions.DisjunctionError, bad_request_handler)
app.register_error_handler(exceptions.EquipmentConfigError, bad_request_handler)
app.register_error_handler(exceptions.NetworkTopologyError, bad_request_handler)
app.register_error_handler(exceptions.ServiceError, bad_request_handler)
app.register_error_handler(exceptions.SpectrumError, bad_request_handler)
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)
app.register_error_handler(PathComputationError, 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)
binder.bind(PathRequestService,
to=PathRequestService(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(key)
FlaskInjector(app=app, modules=[_configure])
app.run(host='0.0.0.0', port=8080, ssl_context='adhoc')
if __name__ == '__main__':
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

@@ -0,0 +1,63 @@
# coding: utf-8
import http
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 PathRequestService
from gnpy.tools.json_io import _equipment_from_json, network_from_json
from gnpy.topology.request import ResultElement
PATH_COMPUTATION_BASE_PATH = '/api/v1/path-computation'
AUTODESIGN_PATH = PATH_COMPUTATION_BASE_PATH + '/<path_computation_id>/autodesign'
_examples_dir = Path(__file__).parent.parent.parent / 'example-data'
@app.route(PATH_COMPUTATION_BASE_PATH, methods=['POST'])
def compute_path(equipment_service: EquipmentService, path_request_service: PathRequestService):
data = request.json
service = data['gnpy-api:service']
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:equipment_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)
propagatedpths, reversed_propagatedpths, rqs, path_computation_id = path_request_service.path_requests_run(service,
network,
equipment)
# Generate the output
result = []
# assumes that list of rqs and list of propgatedpths have same order
for i, pth in enumerate(propagatedpths):
result.append(ResultElement(rqs[i], pth, reversed_propagatedpths[i]))
return {"result": {"response": [n.json for n in result]}}, 201, {
'location': AUTODESIGN_PATH.replace('<path_computation_id>', path_computation_id)}
@app.route(AUTODESIGN_PATH, methods=['GET'])
def get_autodesign(path_computation_id, path_request_service: PathRequestService):
return path_request_service.get_autodesign(path_computation_id), http.HTTPStatus.OK
@app.route(AUTODESIGN_PATH, methods=['DELETE'])
def delete_autodesign(path_computation_id, path_request_service: PathRequestService):
path_request_service.delete_autodesign(path_computation_id)
return '', http.HTTPStatus.NO_CONTENT

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,45 @@
# 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')
def get_autodesign_dir() -> str:
"""
Get the base dir where autodesign are saved
@return: the directory of equipments
"""
return current_app.config['properties'].get('DIRECTORY', 'autodesign')

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

@@ -0,0 +1,100 @@
# -*- coding: utf-8 -*-
import json
import logging
import os
import uuid
import gnpy.core.ansi_escapes as ansi_escapes
from gnpy.api.exception.path_computation_error import PathComputationError
from gnpy.api.service import config_service
from gnpy.api.service.encryption_service import EncryptionService
from gnpy.core.network import build_network
from gnpy.core.utils import lin2db, automatic_nch
from gnpy.tools.json_io import requests_from_json, disjunctions_from_json, network_to_json
from gnpy.topology.request import (compute_path_dsjctn, requests_aggregation,
correct_json_route_list,
deduplicate_disjunctions, compute_path_with_disjunction)
from gnpy.topology.spectrum_assignment import build_oms_list, pth_assign_spectrum
_logger = logging.getLogger(__name__)
class PathRequestService:
def __init__(self, encryption_service: EncryptionService):
self.encryption = encryption_service
def path_requests_run(self, 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
# spacing, f_min and f_max
p_db = equipment['SI']['default'].power_dbm
p_total_db = p_db + lin2db(automatic_nch(equipment['SI']['default'].f_min,
equipment['SI']['default'].f_max, equipment['SI']['default'].spacing))
build_network(network, equipment, p_db, p_total_db)
path_computation_identifier = str(uuid.uuid4())
autodesign_dir = config_service.get_autodesign_dir()
with(open(os.path.join(autodesign_dir, '.'.join([path_computation_identifier, 'json'])), 'wb')) as file:
file.write(self.encryption.encrypt(json.dumps(network_to_json(network)).encode()))
oms_list = build_oms_list(network, equipment)
rqs = requests_from_json(service, equipment)
# check that request ids are unique. Non unique ids, may
# mess the computation: better to stop the computation
all_ids = [r.request_id for r in rqs]
if len(all_ids) != len(set(all_ids)):
for item in list(set(all_ids)):
all_ids.remove(item)
msg = f'Requests id {all_ids} are not unique'
_logger.critical(msg)
raise ValueError('Requests id ' + all_ids + ' are not unique')
rqs = correct_json_route_list(network, rqs)
# pths = compute_path(network, equipment, rqs)
dsjn = disjunctions_from_json(service)
# need to warn or correct in case of wrong disjunction form
# disjunction must not be repeated with same or different ids
dsjn = deduplicate_disjunctions(dsjn)
rqs, dsjn = requests_aggregation(rqs, dsjn)
# TODO export novel set of aggregated demands in a json file
_logger.info(f'{ansi_escapes.blue}The following services have been requested:{ansi_escapes.reset}' + str(rqs))
_logger.info(f'{ansi_escapes.blue}Computing all paths with constraints{ansi_escapes.reset}')
pths = compute_path_dsjctn(network, equipment, rqs, dsjn)
_logger.info(f'{ansi_escapes.blue}Propagating on selected path{ansi_escapes.reset}')
propagatedpths, reversed_pths, reversed_propagatedpths = compute_path_with_disjunction(network, equipment, rqs,
pths)
# Note that deepcopy used in compute_path_with_disjunction returns
# a list of nodes which are not belonging to network (they are copies of the node objects).
# so there can not be propagation on these nodes.
pth_assign_spectrum(pths, rqs, oms_list, reversed_pths)
return propagatedpths, reversed_propagatedpths, rqs, path_computation_identifier
def get_autodesign(self, path_computation_id):
"""
Get the autodesign with id topology_id
@param path_computation_id:
@return: the autodesign in json format
"""
autodesign_dir = config_service.get_autodesign_dir()
autodesign_file = os.path.join(autodesign_dir, '.'.join([path_computation_id, 'json']))
if not os.path.exists(autodesign_file):
raise PathComputationError('Autodesign with id {} does not exist '.format(path_computation_id))
with(open(autodesign_file, 'rb')) as file:
return json.loads(self.encryption.decrypt(file.read()))
def delete_autodesign(self, path_computation_id: str):
"""
Delete autodesign with id equipment_id
@param path_computation_id:
"""
autodesign_dir = config_service.get_autodesign_dir()
autodesign_file = os.path.join(autodesign_dir, '.'.join([path_computation_id, 'json']))
if os.path.exists(autodesign_file):
os.remove(autodesign_file)

View File

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

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)

File diff suppressed because it is too large Load Diff

View File

@@ -1,172 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
gnpy.tools.rest_example
=======================
GNPy as a rest API example
'''
import json
import logging
import os
import re
from logging.handlers import RotatingFileHandler
from pathlib import Path
import werkzeug
from flask import Flask, request
from numpy import mean
from werkzeug.exceptions import InternalServerError
import gnpy.core.ansi_escapes as ansi_escapes
import gnpy.core.exceptions as exceptions
from gnpy.core.network import build_network
from gnpy.core.utils import lin2db, automatic_nch
from gnpy.tools.json_io import requests_from_json, disjunctions_from_json, _equipment_from_json, network_from_json
from gnpy.topology.request import (ResultElement, compute_path_dsjctn, requests_aggregation,
correct_json_route_list,
deduplicate_disjunctions, compute_path_with_disjunction)
from gnpy.topology.spectrum_assignment import build_oms_list, pth_assign_spectrum
_logger = logging.getLogger(__name__)
_examples_dir = Path(__file__).parent.parent / 'example-data'
_reaesc = re.compile(r'\x1b[^m]*m')
app = Flask(__name__)
@app.route('/api/v1/path-computation', methods=['POST'])
def compute_path():
data = request.json
service = data['gnpy-api:service']
topology = data['gnpy-api:topology']
equipment = _equipment_from_json(data['gnpy-api:equipment'],
os.path.join(_examples_dir, 'std_medium_gain_advanced_config.json'))
network = network_from_json(topology, equipment)
propagatedpths, reversed_propagatedpths, rqs = path_requests_run(service, network, equipment)
# Generate the output
result = []
# assumes that list of rqs and list of propgatedpths have same order
for i, pth in enumerate(propagatedpths):
result.append(ResultElement(rqs[i], pth, reversed_propagatedpths[i]))
return {"result": {"response": [n.json for n in result]}}, 201
@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')
ch = logging.StreamHandler()
logging.basicConfig(level=logging.INFO, handlers=[handler, ch],
format="%(asctime)s %(levelname)s %(name)s(%(lineno)s) [%(threadName)s - %(thread)d] - %("
"message)s")
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
# spacing, f_min and f_max
p_db = equipment['SI']['default'].power_dbm
p_total_db = p_db + lin2db(automatic_nch(equipment['SI']['default'].f_min,
equipment['SI']['default'].f_max, equipment['SI']['default'].spacing))
build_network(network, equipment, p_db, p_total_db)
oms_list = build_oms_list(network, equipment)
rqs = requests_from_json(service, equipment)
# check that request ids are unique. Non unique ids, may
# mess the computation: better to stop the computation
all_ids = [r.request_id for r in rqs]
if len(all_ids) != len(set(all_ids)):
for item in list(set(all_ids)):
all_ids.remove(item)
msg = f'Requests id {all_ids} are not unique'
_logger.critical(msg)
raise ValueError('Requests id ' + all_ids + ' are not unique')
rqs = correct_json_route_list(network, rqs)
# pths = compute_path(network, equipment, rqs)
dsjn = disjunctions_from_json(service)
# need to warn or correct in case of wrong disjunction form
# disjunction must not be repeated with same or different ids
dsjn = deduplicate_disjunctions(dsjn)
rqs, dsjn = requests_aggregation(rqs, dsjn)
# TODO export novel set of aggregated demands in a json file
_logger.info(f'{ansi_escapes.blue}The following services have been requested:{ansi_escapes.reset}' + str(rqs))
_logger.info(f'{ansi_escapes.blue}Computing all paths with constraints{ansi_escapes.reset}')
pths = compute_path_dsjctn(network, equipment, rqs, dsjn)
_logger.info(f'{ansi_escapes.blue}Propagating on selected path{ansi_escapes.reset}')
propagatedpths, reversed_pths, reversed_propagatedpths = compute_path_with_disjunction(network, equipment, rqs,
pths)
# Note that deepcopy used in compute_path_with_disjunction returns
# a list of nodes which are not belonging to network (they are copies of the node objects).
# so there can not be propagation on these nodes.
pth_assign_spectrum(pths, rqs, oms_list, reversed_pths)
return propagatedpths, reversed_propagatedpths, rqs
def common_error_handler(exception):
"""
:type exception: Exception
"""
status_code = 500
if not isinstance(exception, werkzeug.exceptions.HTTPException):
exception = werkzeug.exceptions.InternalServerError()
exception.description = "Something went wrong on our side."
response = {
'message': exception.name,
'description': exception.description,
'code': exception.code
}
return werkzeug.Response(response=json.dumps(response), status=status_code, mimetype='application/json')
def bad_request_handler(exception):
response = {
'message': 'bad request',
'description': _reaesc.sub('', str(exception)),
'code': 400
}
return werkzeug.Response(response=json.dumps(response), status=400, mimetype='application/json')
def _init_app():
app.register_error_handler(KeyError, bad_request_handler)
app.register_error_handler(TypeError, bad_request_handler)
app.register_error_handler(ValueError, bad_request_handler)
app.register_error_handler(exceptions.ConfigurationError, bad_request_handler)
app.register_error_handler(exceptions.DisjunctionError, bad_request_handler)
app.register_error_handler(exceptions.EquipmentConfigError, bad_request_handler)
app.register_error_handler(exceptions.NetworkTopologyError, bad_request_handler)
app.register_error_handler(exceptions.ServiceError, bad_request_handler)
app.register_error_handler(exceptions.SpectrumError, bad_request_handler)
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)
for error_code in werkzeug.exceptions.default_exceptions:
app.register_error_handler(error_code, common_error_handler)
def main():
_init_logger()
_init_app()
app.run(host='0.0.0.0', port=8080)
if __name__ == '__main__':
main()

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

@@ -0,0 +1,78 @@
module gnpy-api {
yang-version 1.1;
namespace "gnpy:gnpy-api";
prefix gnpyapi;
import gnpy-network-topology {
prefix gnpynt;
}
import gnpy-path-computation-simplified {
prefix gnpypc;
}
import gnpy-eqpt-config {
prefix gnpyeqpt;
}
import ietf-yang-types {
prefix ietftypes;
}
organization
"Telecom Infra Project OOPT PSE Working Group";
contact
"WG Web: <https://github.com/Telecominfraproject/oopt-gnpy>
contact: <mailto:ahmed.triki@orange.com>
contact: <mailto:esther.lerouzic@orange.com>
";
description
"YANG model for gnpy api input for path computation - TransportPCE preversion";
revision 2021-01-06 {
description
"draft for experimental/2020-candi.
Add the possibility to use a topology_id or an equipment_id
";
reference
"YANG model for api input for path computation with gnpy";
}
container service {
description
"Describe the service file to connect to gnpy";
uses gnpypc:service;
}
container result {
uses gnpypc:result;
description
"Describe the response object to gnpy";
}
choice topo {
case explicit {
container topology {
description
"Describe the topology file to connect to gnpy";
uses gnpynt:topo;
}
}
case id {
leaf topology_id {
type ietftypes:uuid;
mandatory true;
}
}
}
choice eqpt {
case explicit {
container equipment {
description
"Describe the equipment library to connect to gnpy";
uses gnpyeqpt:eqpt;
}
}
case id {
leaf equipment_id {
type ietftypes:uuid;
mandatory true;
}
}
}
}

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

View File

@@ -48,4 +48,4 @@ console_scripts =
gnpy-transmission-example = gnpy.tools.cli_examples:transmission_main_example
gnpy-path-request = gnpy.tools.cli_examples:path_requests_run
gnpy-convert-xls = gnpy.tools.convert:_do_convert
gnpy-rest = gnpy.tools.rest_example:main
gnpy-rest = gnpy.api.rest_example:main