mirror of
https://github.com/Telecominfraproject/oopt-gnpy-api.git
synced 2025-10-28 16:52:19 +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
|
||||
"""
|
||||
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'
|
||||
|
||||
|
||||
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
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user