mirror of
				https://github.com/Telecominfraproject/oopt-gnpy-api.git
				synced 2025-10-30 17:37:47 +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
	 Renato Ambrosone
					Renato Ambrosone