mirror of
https://github.com/Telecominfraproject/oopt-gnpy-api.git
synced 2025-10-29 01:02:18 +00:00
refactor: API now rely on gnpy function without re-implementation
Change-Id: Ib71f62f74eaa9fd87606a977f1f2c830b71668d9
This commit is contained in:
7
Dockerfile
Normal file
7
Dockerfile
Normal 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
0
gnpyapi/__init__.py
Normal file
@@ -2,3 +2,6 @@
|
|||||||
|
|
||||||
"""GNPy official API
|
"""GNPy official API
|
||||||
"""
|
"""
|
||||||
|
from flask import Flask
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
1
gnpyapi/core/exception/__init__.py
Normal file
1
gnpyapi/core/exception/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# coding: utf-8
|
||||||
14
gnpyapi/core/exception/config_error.py
Normal file
14
gnpyapi/core/exception/config_error.py
Normal 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
|
||||||
14
gnpyapi/core/exception/equipment_error.py
Normal file
14
gnpyapi/core/exception/equipment_error.py
Normal 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
|
||||||
34
gnpyapi/core/exception/exception_handler.py
Normal file
34
gnpyapi/core/exception/exception_handler.py
Normal 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')
|
||||||
14
gnpyapi/core/exception/path_computation_error.py
Normal file
14
gnpyapi/core/exception/path_computation_error.py
Normal 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
|
||||||
13
gnpyapi/core/exception/topology_error.py
Normal file
13
gnpyapi/core/exception/topology_error.py
Normal 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
|
||||||
1
gnpyapi/core/model/__init__.py
Normal file
1
gnpyapi/core/model/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# coding: utf-8
|
||||||
17
gnpyapi/core/model/error.py
Normal file
17
gnpyapi/core/model/error.py
Normal 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
gnpyapi/core/model/result.py
Normal file
8
gnpyapi/core/model/result.py
Normal 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
|
||||||
1
gnpyapi/core/route/__init__.py
Normal file
1
gnpyapi/core/route/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# coding: utf-8
|
||||||
31
gnpyapi/core/route/path_request_route.py
Normal file
31
gnpyapi/core/route/path_request_route.py
Normal 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
|
||||||
7
gnpyapi/core/route/status_route.py
Normal file
7
gnpyapi/core/route/status_route.py
Normal 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
|
||||||
1
gnpyapi/core/service/__init__.py
Normal file
1
gnpyapi/core/service/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# coding: utf-8
|
||||||
4
gnpyapi/core/service/config_service.py
Normal file
4
gnpyapi/core/service/config_service.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
class ConfigService:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
5
gnpyapi/core/service/equipment_service.py
Normal file
5
gnpyapi/core/service/equipment_service.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# coding: utf-
|
||||||
|
class EquipmentService:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
36
gnpyapi/core/service/path_request_service.py
Normal file
36
gnpyapi/core/service/path_request_service.py
Normal 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
|
||||||
5
gnpyapi/core/service/topology_service.py
Normal file
5
gnpyapi/core/service/topology_service.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# coding: utf-
|
||||||
|
|
||||||
|
class TopologyService:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
0
gnpyapi/tools/__init__.py
Normal file
0
gnpyapi/tools/__init__.py
Normal 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
64
samples/rest_example.py
Normal 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()
|
||||||
@@ -12,15 +12,6 @@ YANG_DIR = Path(__file__).parent.parent / 'gnpyapi' / 'yang'
|
|||||||
SAMPLE_DIR = Path(__file__).parent.parent / 'samples'
|
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():
|
def test_pyang():
|
||||||
"""Verify that yang models pss pyang
|
"""Verify that yang models pss pyang
|
||||||
"""
|
"""
|
||||||
|
|||||||
Reference in New Issue
Block a user