refactor: API now rely on gnpy function without re-implementation

Change-Id: Ib71f62f74eaa9fd87606a977f1f2c830b71668d9
This commit is contained in:
Renato Ambrosone
2025-03-25 16:04:49 +01:00
parent 6637ca8315
commit e48f524d1a
24 changed files with 280 additions and 20 deletions

7
Dockerfile Normal file
View File

@@ -0,0 +1,7 @@
FROM python:3.9-slim
COPY . /oopt-gnpy-api
WORKDIR /oopt-gnpy-api
RUN apt update; apt install -y git
RUN pip install .
RUN mkdir -p /opt/application/oopt-gnpy/autodesign
CMD [ "python", "./samples/rest_example.py" ]

0
gnpyapi/__init__.py Normal file
View File

View File

@@ -2,3 +2,6 @@
"""GNPy official API
"""
from flask import Flask
app = Flask(__name__)

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,34 @@
# coding: utf-8
import json
import re
import werkzeug
from gnpyapi.core.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):
exception_str = " ".join(str(exception).split())
response = Error(message='bad request', description=_reaesc.sub('', exception_str.replace("\n", " ")),
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,13 @@
# 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

@@ -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

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

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

View File

@@ -0,0 +1,31 @@
# coding: utf-8
from pathlib import Path
from flask import request
from gnpyapi.core import app
from gnpyapi.core.exception.equipment_error import EquipmentError
from gnpyapi.core.exception.topology_error import TopologyError
from gnpyapi.core.service.path_request_service import PathRequestService
PATH_COMPUTATION_BASE_PATH = '/api/v1/path-computation'
PATH_REQUEST_BASE_PATH = '/api/v1/path-request'
AUTODESIGN_PATH = PATH_COMPUTATION_BASE_PATH + '/<path_computation_id>/autodesign'
_examples_dir = Path(__file__).parent.parent.parent / 'example-data'
@app.route(PATH_REQUEST_BASE_PATH, methods=['POST'])
def path_request(path_request_service: PathRequestService):
data = request.json
service = data['gnpy-api:service']
if 'gnpy-api:topology' in data:
topology = data['gnpy-api:topology']
else:
raise TopologyError('No topology found in request')
if 'gnpy-api:equipment' in data:
equipment = data['gnpy-api:equipment']
else:
raise EquipmentError('No equipment found in request')
return path_request_service.path_request(topology, equipment, service), 201

View File

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

View File

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

View File

@@ -0,0 +1,4 @@
# coding: utf-8
class ConfigService:
def __init__(self):
pass

View File

@@ -0,0 +1,5 @@
# coding: utf-
class EquipmentService:
def __init__(self):
pass

View File

@@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
import logging
from gnpy.core.exceptions import EquipmentConfigError, NetworkTopologyError
from gnpy.tools.json_io import results_to_json, load_eqpt_topo_from_json
from gnpy.tools.worker_utils import designed_network, planning
from gnpyapi.core.exception.topology_error import TopologyError
from gnpyapi.core.exception.equipment_error import EquipmentError
_logger = logging.getLogger(__name__)
class PathRequestService:
def __init__(self):
pass
@staticmethod
def path_request(topology: dict, equipment: dict, service: dict = None) -> dict:
try:
(equipment, network) = load_eqpt_topo_from_json(equipment, topology)
network, _, _ = designed_network(equipment, network)
# todo parse request
_, _, _, _, _, result = planning(network, equipment, service)
return results_to_json(result)
except EquipmentConfigError as e:
_logger.error(f"An equipment error occurred: {e}")
raise EquipmentError(str(e))
except NetworkTopologyError as e:
_logger.error(f"An equipment error occurred: {e}")
raise TopologyError(str(e))
except Exception as e:
_logger.error(f"An error occurred during path request: {e}")
raise

View File

@@ -0,0 +1,5 @@
# coding: utf-
class TopologyService:
def __init__(self):
pass

View File

View File

@@ -1,11 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Examples of api calls
"""
# for the moment just launch gnpy to check everything is OK
from gnpy.tools.cli_examples import transmission_main_example
transmission_main_example()

64
samples/rest_example.py Normal file
View File

@@ -0,0 +1,64 @@
#!/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 gnpyapi.core import app
from gnpyapi.core.exception.equipment_error import EquipmentError
from gnpyapi.core.exception.exception_handler import bad_request_handler, common_error_handler
from gnpyapi.core.exception.path_computation_error import PathComputationError
from gnpyapi.core.exception.topology_error import TopologyError
_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():
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(EquipmentError, 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)
def main():
_init_logger()
_init_app()
FlaskInjector(app=app)
app.run(host='0.0.0.0', port=8080)
if __name__ == '__main__':
main()

View File

@@ -12,15 +12,6 @@ YANG_DIR = Path(__file__).parent.parent / 'gnpyapi' / 'yang'
SAMPLE_DIR = Path(__file__).parent.parent / 'samples'
def test_sample():
"""Just for the ci
"""
res = subprocess.run(['python', SAMPLE_DIR / 'fake_sample.py'],
stdout=subprocess.PIPE, check=True)
if res.returncode != 0:
assert False, f'gnpy call failed: exit code {res.returncode}'
def test_pyang():
"""Verify that yang models pss pyang
"""