mirror of
				https://github.com/Telecominfraproject/oopt-gnpy-api.git
				synced 2025-10-31 01:47:49 +00:00 
			
		
		
		
	Compare commits
	
		
			8 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | b6041db35d | ||
|   | 256341f937 | ||
|   | 51b266bd2a | ||
|   | 13a81e8f94 | ||
|   | c30308eb92 | ||
|   | 7695db8674 | ||
|   | 192bb265bd | ||
|   | b9acee661c | 
							
								
								
									
										39
									
								
								.github/workflows/main.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										39
									
								
								.github/workflows/main.yml
									
									
									
									
										vendored
									
									
								
							| @@ -53,6 +53,45 @@ jobs: | ||||
|           - tox_env: docs | ||||
|             dnf_install: graphviz | ||||
|  | ||||
|   release-build: | ||||
|     runs-on: ubuntu-latest | ||||
|     needs: build | ||||
|     if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') && github.repository_owner == 'Telecominfraproject' }} | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|  | ||||
|       - uses: actions/setup-python@v5 | ||||
|         with: | ||||
|           python-version: "3.12" | ||||
|  | ||||
|       - name: build release distributions | ||||
|         run: | | ||||
|           python -m pip install build | ||||
|           python -m build | ||||
|  | ||||
|       - name: upload windows dists | ||||
|         uses: actions/upload-artifact@v4 | ||||
|         with: | ||||
|           name: release-dists | ||||
|           path: dist/ | ||||
|  | ||||
|   pypi-publish: | ||||
|       runs-on: ubuntu-latest | ||||
|       needs: | ||||
|         - release-build | ||||
|       permissions: | ||||
|         id-token: write | ||||
|  | ||||
|       steps: | ||||
|         - name: Retrieve release distributions | ||||
|           uses: actions/download-artifact@v4 | ||||
|           with: | ||||
|             name: release-dists | ||||
|             path: dist/ | ||||
|  | ||||
|         - name: Publish release distributions to PyPI | ||||
|           uses: pypa/gh-action-pypi-publish@release/v1 | ||||
|  | ||||
|   other-platforms: | ||||
|     name: Tests on other platforms | ||||
|     runs-on: ${{ matrix.os }} | ||||
|   | ||||
							
								
								
									
										72
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										72
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,17 +1,71 @@ | ||||
| # GNPy API | ||||
| [](https://pypi.org/project/gnpy/) | ||||
| [](https://pypi.org/project/gnpy-api/) | ||||
| [](https://pypi.org/project/gnpy-api/) | ||||
| [](https://review.gerrithub.io/q/project:Telecominfraproject/oopt-gnpy-api) | ||||
|  | ||||
| REST API (experimental) | ||||
| ----------------------- | ||||
| ``gnpyapi`` provides an experimental api for requesting several paths at once. It is based on Flask server. | ||||
| You can run it through command line or Docker. | ||||
| This repository extends GNPy with additional interfaces, allowing for more flexible control and interaction with its simulation engine. These interfaces can be used to integrate GNPy into software-defined networking (SDN) architectures or other external applications. | ||||
|  | ||||
| [GNPy](https://github.com/Telecominfraproject/oopt-gnpy) is an open-source Python-based library that models and evaluates the performance of optical networks. It is widely used for path computation, QoT (Quality of Transmission) estimation, and network planning. | ||||
|  | ||||
| ## 🚀 Installation | ||||
| ### Build from Source - Option 1 | ||||
| Clone the repository and install the package: | ||||
| ```bash | ||||
| python3 setup.py install | ||||
| ``` | ||||
| or if you want to install it in a docker container: | ||||
| ```bash | ||||
| docker docker build ./ -t gnpy-api | ||||
| ``` | ||||
|  | ||||
| ### PiPy - Option 2 | ||||
| ```bash | ||||
| pip install gnpy-api | ||||
| ``` | ||||
|  | ||||
|  | ||||
|  | ||||
|     $ curl --location 'http://localhost:8080/api/v1/path-request' --header 'Content-Type: application/json' --data @gnpyapi/exampledata/planning_demand_example.json  | ||||
|  | ||||
| TODO: api documentation, unit tests, real WSGI server with trusted certificates | ||||
|  | ||||
| ### DockerHub - Option 3 | ||||
| Coming....  | ||||
| ## Quick Start | ||||
|  | ||||
| tbd | ||||
| ## 🧪 Usage - CLI | ||||
| Start the REST API server: | ||||
| ```bash | ||||
| python ./samples/rest_example.py | ||||
| ``` | ||||
| See the help for the REST API: | ||||
| ```bash | ||||
| python ./samples/rest_example.py -h | ||||
| ``` | ||||
| Send example data to the REST API: | ||||
| ```bash | ||||
| curl --location 'https://localhost:8080/api/v0.1/path-request' --header 'Content-Type: application/json' --data @gnpyapi/exampledata/planning_demand_example.json -k | ||||
| ``` | ||||
| ## 🧪 Usage - Docker | ||||
|  | ||||
|  | ||||
|  | ||||
| ## 🔄 Compatibility | ||||
|  | ||||
| Different versions of this interface extension are compatible with specific versions of [GNPy](https://github.com/Telecominfraproject/oopt-gnpy). Please refer to the table below to ensure that you are using a supported version combination. | ||||
|  | ||||
|  | ||||
|  | ||||
| | Interface Version | Compatible GNPy Version | Notes                          | | ||||
| |-------------------|-----------------|--------------------------------| | ||||
| | `v0.1.x`          | `>=2.12.1`      | Initial release                | | ||||
|  | ||||
| ⚠️ If you use an incompatible combination, some features may not work correctly or may produce unexpected errors. | ||||
|  | ||||
| To check your GNPy version, run: | ||||
|  | ||||
| ```bash | ||||
| pip show gnpy | ||||
| ``` | ||||
|  | ||||
| ## 📚 Documentation | ||||
| Refer to the official [GNPy documentation](https://github.com/Telecominfraproject/oopt-gnpy/tree/master/docs) for information about network modeling and simulation capabilities. | ||||
|  | ||||
| API documentation is available in the docs folder. | ||||
| @@ -4,6 +4,9 @@ | ||||
| """ | ||||
| from flask import Flask | ||||
|  | ||||
| API_VERSION = "/api/v0.1" | ||||
|  | ||||
| app = Flask(__name__) | ||||
|  | ||||
| import gnpyapi.core.route.path_request_route  # noqa: F401, E402 | ||||
| import gnpyapi.core.route.path_request_route  # noqa: E402 | ||||
| import gnpyapi.core.route.status_route  # noqa: F401, E402 | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| # coding: utf-8 | ||||
| from pathlib import Path | ||||
|  | ||||
| from flask import request | ||||
|  | ||||
| @@ -7,15 +6,12 @@ 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 | ||||
| from gnpyapi.core import API_VERSION | ||||
|  | ||||
| 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' | ||||
| PATH_REQUEST_BASE_PATH = '/path-request' | ||||
|  | ||||
|  | ||||
| @app.route(PATH_REQUEST_BASE_PATH, methods=['POST']) | ||||
| @app.route(API_VERSION + PATH_REQUEST_BASE_PATH, methods=['POST']) | ||||
| def path_request(path_request_service: PathRequestService): | ||||
|     data = request.json | ||||
|     service = data['gnpy-api:service'] | ||||
|   | ||||
| @@ -1,7 +1,8 @@ | ||||
| # coding: utf-8 | ||||
| from gnpyapi.core import app | ||||
| from gnpyapi.core import API_VERSION | ||||
|  | ||||
|  | ||||
| @app.route('/api/v1/status', methods=['GET']) | ||||
| @app.route(API_VERSION + '/status', methods=['GET']) | ||||
| def api_status(): | ||||
|     return {"version": "v1", "status": "ok"}, 200 | ||||
|     return {"version": "v0.1", "status": "ok"}, 200 | ||||
|   | ||||
| @@ -19,6 +19,7 @@ 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 | ||||
| import argparse | ||||
|  | ||||
| _logger = logging.getLogger(__name__) | ||||
|  | ||||
| @@ -52,13 +53,22 @@ def _init_app(): | ||||
|         app.register_error_handler(error_code, common_error_handler) | ||||
|  | ||||
|  | ||||
| def main(): | ||||
| def main(http: bool = False): | ||||
|     _init_logger() | ||||
|     _init_app() | ||||
|     FlaskInjector(app=app) | ||||
|  | ||||
|     app.run(host='0.0.0.0', port=8080) | ||||
|     if http: | ||||
|         app.run(host='0.0.0.0', port=8080) | ||||
|     else: | ||||
|         app.run(host='0.0.0.0', port=8080, ssl_context='adhoc') | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     main() | ||||
|     parser = argparse.ArgumentParser(description="Rest API example") | ||||
|  | ||||
|     parser.add_argument("--http", action="store_true", help="run server with http instead of https") | ||||
|  | ||||
|     args = parser.parse_args() | ||||
|  | ||||
|     main(http=args.http) | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| [metadata] | ||||
| name = gnpyapi | ||||
| name = gnpy-api | ||||
| description-file = README.md | ||||
| description-content-type = text/markdown; variant=GFM | ||||
| author = Telecom Infra Project | ||||
| author-email = tbd | ||||
| author-email = adamico@nec-labs.com | ||||
| license = BSD-3-Clause | ||||
| home-page = https://github.com/Telecominfraproject/oopt-gnpy-api | ||||
| project_urls = | ||||
| @@ -51,6 +51,7 @@ install_requires = | ||||
|     gnpy==2.12.1 | ||||
|     flask>=1.1.2 | ||||
|     Flask-Injector | ||||
|     pyopenssl==25.0.0 | ||||
|  | ||||
| [options.extras_require] | ||||
| tests = | ||||
|   | ||||
							
								
								
									
										1578
									
								
								tests/data/req/planning_demand_example.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1578
									
								
								tests/data/req/planning_demand_example.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										1578
									
								
								tests/data/req/planning_demand_wrong_eqpt.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1578
									
								
								tests/data/req/planning_demand_wrong_eqpt.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										1578
									
								
								tests/data/req/planning_demand_wrong_topology.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1578
									
								
								tests/data/req/planning_demand_wrong_topology.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										1469
									
								
								tests/data/res/planning_demand_res.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1469
									
								
								tests/data/res/planning_demand_res.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										0
									
								
								tests/service/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								tests/service/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										55
									
								
								tests/service/test_path_request_service.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								tests/service/test_path_request_service.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| #!/usr/bin/env python3 | ||||
| # -*- coding: utf-8 -*- | ||||
| # @Author: Esther Le Rouzic | ||||
| # @Date:   2025-02-03 | ||||
| import json | ||||
| from pathlib import Path | ||||
|  | ||||
| import pytest | ||||
| from gnpyapi.core.exception.equipment_error import EquipmentError | ||||
|  | ||||
| from gnpyapi.core.service.path_request_service import PathRequestService | ||||
| from gnpyapi.core.exception.topology_error import TopologyError | ||||
|  | ||||
| TEST_DATA_DIR = Path(__file__).parent.parent / 'data' | ||||
| TEST_REQ_DIR = TEST_DATA_DIR / 'req' | ||||
| TEST_RES_DIR = TEST_DATA_DIR / 'res' | ||||
|  | ||||
|  | ||||
| def read_json_file(path): | ||||
|     with open(path, "r") as file: | ||||
|         return json.load(file) | ||||
|  | ||||
|  | ||||
| def test_path_request_success(): | ||||
|     input_data = read_json_file(TEST_REQ_DIR / "planning_demand_example.json") | ||||
|     expected_response = read_json_file(TEST_RES_DIR / "planning_demand_res.json") | ||||
|     topology = input_data["gnpy-api:topology"] | ||||
|     equipment = input_data["gnpy-api:equipment"] | ||||
|     service = input_data["gnpy-api:service"] | ||||
|  | ||||
|     result = PathRequestService.path_request(topology, equipment, service) | ||||
|     assert result == expected_response | ||||
|  | ||||
|  | ||||
| def test_path_request_invalid_equipment(): | ||||
|     input_data = read_json_file(TEST_REQ_DIR / "planning_demand_wrong_eqpt.json") | ||||
|     topology = input_data["gnpy-api:topology"] | ||||
|     equipment = input_data["gnpy-api:equipment"] | ||||
|     service = input_data["gnpy-api:service"] | ||||
|  | ||||
|     with pytest.raises(EquipmentError) as exc: | ||||
|         PathRequestService.path_request(topology, equipment, service) | ||||
|     assert "invalid" in str(exc.value).lower() | ||||
|     assert "deltap" in str(exc.value).lower() | ||||
|  | ||||
|  | ||||
| def test_path_request_invalid_topology(): | ||||
|     input_data = read_json_file(TEST_REQ_DIR / "planning_demand_wrong_topology.json") | ||||
|     topology = input_data["gnpy-api:topology"] | ||||
|     equipment = input_data["gnpy-api:equipment"] | ||||
|     service = input_data["gnpy-api:service"] | ||||
|  | ||||
|     with pytest.raises(TopologyError) as exc: | ||||
|         PathRequestService.path_request(topology, equipment, service) | ||||
|     assert "can not find" in str(exc.value).lower() | ||||
| @@ -2,21 +2,47 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # @Author: Esther Le Rouzic | ||||
| # @Date:   2025-02-03 | ||||
|  | ||||
| import json | ||||
| from pathlib import Path | ||||
| import subprocess | ||||
| import pytest   # noqa: F401 | ||||
|  | ||||
| import pytest | ||||
| from flask_injector import FlaskInjector | ||||
|  | ||||
| from gnpyapi.core import app | ||||
|  | ||||
| YANG_DIR = Path(__file__).parent.parent / 'gnpyapi' / 'yang' | ||||
| SAMPLE_DIR = Path(__file__).parent.parent / 'samples' | ||||
|  | ||||
| TEST_DATA_DIR = Path(__file__).parent / 'data' | ||||
| TEST_REQ_DIR = TEST_DATA_DIR / 'req' | ||||
| TEST_RES_DIR = TEST_DATA_DIR / 'res' | ||||
|  | ||||
| def test_pyang(): | ||||
|     """Verify that yang models pss pyang | ||||
|     """ | ||||
|     res = subprocess.run(['pyang', '-f', 'tree', '--tree-line-length', '69', | ||||
|                           '-p', YANG_DIR, YANG_DIR / 'gnpy-api@2021-01-06.yang'], | ||||
|                          stdout=subprocess.PIPE, check=True) | ||||
|     if res.returncode != 0: | ||||
|         assert False, f'pyang failed: exit code {res.returncode}' | ||||
| API_VERSION = '/api/v0.1' | ||||
|  | ||||
|  | ||||
| def read_json_file(path): | ||||
|     with open(path, "r") as file: | ||||
|         return json.load(file) | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def client(): | ||||
|     app.testing = True | ||||
|     FlaskInjector(app=app) | ||||
|     with app.test_client() as client: | ||||
|         yield client | ||||
|  | ||||
|  | ||||
| def test_echo(client): | ||||
|     input_data = read_json_file(TEST_REQ_DIR / "planning_demand_example.json") | ||||
|     expected_response = read_json_file(TEST_RES_DIR / "planning_demand_res.json") | ||||
|  | ||||
|     response = client.post(f"{API_VERSION}/path-request", json=input_data) | ||||
|     assert response.status_code == 201 | ||||
|     assert response.get_json() == expected_response | ||||
|  | ||||
|  | ||||
| def test_status(client): | ||||
|     response = client.get(f"{API_VERSION}/status") | ||||
|     assert response.status_code == 200 | ||||
|     assert response.get_json() == {"version": "v0.1", "status": "ok"} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user