mirror of
				https://github.com/Telecominfraproject/oopt-gnpy-api.git
				synced 2025-11-03 19:38:02 +00:00 
			
		
		
		
	Compare commits
	
		
			9 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					ce320e73d6 | ||
| 
						 | 
					b6041db35d | ||
| 
						 | 
					256341f937 | ||
| 
						 | 
					51b266bd2a | ||
| 
						 | 
					13a81e8f94 | ||
| 
						 | 
					c30308eb92 | ||
| 
						 | 
					7695db8674 | ||
| 
						 | 
					192bb265bd | ||
| 
						 | 
					b9acee661c | 
							
								
								
									
										48
									
								
								.github/workflows/main.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										48
									
								
								.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 }}
 | 
			
		||||
@@ -70,17 +109,20 @@ jobs:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
        include:
 | 
			
		||||
          - os: windows-2019
 | 
			
		||||
            python_version: "3.10"
 | 
			
		||||
          - os: windows-2022
 | 
			
		||||
            python_version: "3.11"
 | 
			
		||||
          - os: windows-2022
 | 
			
		||||
            python_version: "3.12"
 | 
			
		||||
          - os: windows-2025
 | 
			
		||||
            python_version: "3.11"
 | 
			
		||||
          - os: windows-2025
 | 
			
		||||
            python_version: "3.12"
 | 
			
		||||
          - os: macos-13
 | 
			
		||||
            python_version: "3.12"
 | 
			
		||||
          - os: macos-14
 | 
			
		||||
            python_version: "3.12"
 | 
			
		||||
 | 
			
		||||
          - os: macos-15
 | 
			
		||||
            python_version: "3.12"
 | 
			
		||||
  paywalled-platforms:
 | 
			
		||||
    name: Tests on paywalled platforms
 | 
			
		||||
    if: github.repository_owner == 'Telecominfraproject'
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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)
 | 
			
		||||
 | 
			
		||||
    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