8 Commits
v0.1.1 ... main

Author SHA1 Message Date
Renato Ambrosone
b6041db35d FIX: endpoints are now reachable only if version is included in the path
Change-Id: Ica5e0c52b985e234e960d712e20a6df874e66b35
2025-05-20 13:48:37 +02:00
Renato Ambrosone
256341f937 DOC: added first usage guide in README-md
Change-Id: Ieb899fc581b531d5331bd5398ac403149186ac1e
2025-05-20 13:36:50 +02:00
AndreaDAmico
51b266bd2a Fixing PyPi publishing in main.yml
Change-Id: Iaef853a09755f41d875a3a426fe4d303baa2be73
2025-04-18 11:26:42 -04:00
AndreaDAmico
13a81e8f94 Defining PyPi publishing
Change-Id: I6412a016e592f461bdef9e8d34ccacf0b8f078f5
2025-04-18 10:41:05 -04:00
Renato Ambrosone
c30308eb92 FIX: api version is now consistent with release
Change-Id: I25582e7937ec912666e2ba48d5687ec3ae694883
2025-04-18 15:09:02 +02:00
Renato Ambrosone
7695db8674 Implemented unit tests for PathRequestService: invalid equipment, topology, and success case
Change-Id: If863b6d2458642a682f2e687501433045c2311c9
2025-04-11 13:14:02 +02:00
Renato Ambrosone
192bb265bd Added test for /path-request route with expected input and response
Change-Id: I1af159af39136416ea7a379e02296c8ef3b606bf
2025-04-10 20:10:37 +02:00
Renato Ambrosone
b9acee661c Implemented HTTPS by default and http flag option as argument
Change-Id: I1717da2aa4644cd73c224111e57f7c0ede9036df
2025-04-02 02:09:17 +02:00
14 changed files with 6423 additions and 35 deletions

View File

@@ -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 }}

View File

@@ -1,17 +1,71 @@
# GNPy API
[![Python versions](https://img.shields.io/pypi/pyversions/gnpy)](https://pypi.org/project/gnpy/)
[![Install via pip](https://img.shields.io/pypi/v/gnpy-api)](https://pypi.org/project/gnpy-api/)
[![Python versions](https://img.shields.io/pypi/pyversions/gnpy-api)](https://pypi.org/project/gnpy-api/)
[![Gerrit](https://img.shields.io/badge/patches-via%20Gerrit-blue)](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.

View File

@@ -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

View File

@@ -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']

View File

@@ -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

View File

@@ -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)

View File

@@ -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 =

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

View 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()

View File

@@ -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"}