mirror of
https://github.com/Telecominfraproject/oopt-gnpy.git
synced 2025-11-02 02:57:52 +00:00
Excel_userguide has been updated with a description of how to use the service sheet documentation on convert_service_sheet has been added bugfix on convert_service_sheet to avoid multiple file writing bugfix on create_eqpt_sheet.py to avoid the creation of a line when site is FUSED type Completing README and adding templates for json files Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
286 lines
12 KiB
Python
286 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
# TelecomInfraProject/gnpy/examples
|
|
# Module name : path_requests_run.py
|
|
# Version :
|
|
# License : BSD 3-Clause Licence
|
|
# Copyright (c) 2018, Telecom Infra Project
|
|
|
|
"""
|
|
@author: esther.lerouzic
|
|
@author: jeanluc-auge
|
|
read json request file in accordance with:
|
|
Yang model for requesting Path Computation
|
|
draft-ietf-teas-yang-path-computation-01.txt.
|
|
and returns path results in terms of path and feasibility
|
|
|
|
"""
|
|
|
|
from sys import exit
|
|
from argparse import ArgumentParser
|
|
from pathlib import Path
|
|
from collections import namedtuple
|
|
from logging import getLogger, basicConfig, CRITICAL, DEBUG, INFO
|
|
from json import dumps, loads
|
|
from networkx import (draw_networkx_nodes, draw_networkx_edges,
|
|
draw_networkx_labels, dijkstra_path, NetworkXNoPath)
|
|
from numpy import mean
|
|
from examples.convert_service_sheet import convert_service_sheet, Request_element, Element
|
|
from gnpy.core.utils import load_json
|
|
from gnpy.core import network_from_json, build_network
|
|
from examples.transmission_main_example import load_equipment, load_network
|
|
from examples.convert import convert_file
|
|
from gnpy.core.elements import Transceiver, Roadm, Edfa, Fused
|
|
from gnpy.core.utils import db2lin, lin2db
|
|
from gnpy.core.info import SpectralInformation, Channel, Power
|
|
from copy import copy, deepcopy
|
|
from numpy import log10
|
|
|
|
#EQPT_LIBRARY_FILENAME = Path(__file__).parent / 'eqpt_config.json'
|
|
|
|
logger = getLogger(__name__)
|
|
|
|
parser = ArgumentParser(description = 'A function that computes performances for a list of services provided in a json file or an excel sheet.')
|
|
parser.add_argument('network_filename', nargs='?', type = Path, default= Path(__file__).parent / 'meshTopologyExampleV2.xls')
|
|
parser.add_argument('service_filename', nargs='?', type = Path, default= Path(__file__).parent / 'meshTopologyExampleV2.xls')
|
|
parser.add_argument('eqpt_filename', nargs='?', type = Path, default=Path(__file__).parent / 'eqpt_config.json')
|
|
parser.add_argument('-v', '--verbose', action='count')
|
|
parser.add_argument('-o', '--output', default=None)
|
|
|
|
|
|
class Path_request():
|
|
def __init__(self,jsondata,tspjsondata):
|
|
self.request_id = jsondata['request-id']
|
|
self.source = jsondata['src-tp-id']
|
|
self.destination = jsondata['dst-tp-id']
|
|
# retrieving baudrate out of transponder type and mode (format)
|
|
self.tsp = jsondata['path-constraints']['te-bandwidth']['trx_type']
|
|
self.tsp_mode = jsondata['path-constraints']['te-bandwidth']['trx_mode']
|
|
# for debug
|
|
# print(tsp)
|
|
try:
|
|
baudrate = [m['baudrate']
|
|
for t in tspjsondata if t['type_variety']== self.tsp
|
|
for m in t['mode'] if m['format']==self.tsp_mode][0]
|
|
# for debug
|
|
# print(f'coucou {baudrate}')
|
|
except IndexError:
|
|
msg = f'could not find tsp : {self.tsp} with mode: {self.tsp_mode} in eqpt library'
|
|
logger.critical(msg)
|
|
raise ValueError(msg)
|
|
self.baudrate = baudrate
|
|
|
|
nodes_list = jsondata['optimizations']['explicit-route-include-objects']
|
|
self.nodes_list = [n['unnumbered-hop']['node-id'] for n in nodes_list]
|
|
# create a list for individual loose capability for each node ...
|
|
# even if convert_service_sheet fills it with the same value
|
|
self.loose_list = [n['unnumbered-hop']['hop-type'] for n in nodes_list]
|
|
|
|
self.spacing = jsondata['path-constraints']['te-bandwidth']['spacing']
|
|
self.power = jsondata['path-constraints']['te-bandwidth']['output-power']
|
|
self.nb_channel = jsondata['path-constraints']['te-bandwidth']['max-nb-of-channel']
|
|
|
|
def __str__(self):
|
|
return '\t'.join([f'{self.source}',
|
|
f'{self.destination}'])
|
|
def __repr__(self):
|
|
return '\t'.join([f'{self.source}',
|
|
f'{self.destination}',
|
|
'\n'])
|
|
|
|
class Result_element(Element):
|
|
def __init__(self,path_request,computed_path):
|
|
self.path_id = int(path_request.request_id)
|
|
self.path_request = path_request
|
|
self.computed_path = computed_path
|
|
hop_type = []
|
|
for e in computed_path :
|
|
if isinstance(e, Transceiver) :
|
|
hop_type.append(' - '.join([path_request.tsp,path_request.tsp_mode]))
|
|
else:
|
|
hop_type.append('not recorded')
|
|
self.hop_type = hop_type
|
|
uid = property(lambda self: repr(self))
|
|
@property
|
|
def pathresult(self):
|
|
return {
|
|
'path-id': self.path_id,
|
|
'path-properties':{
|
|
'path-metric': [
|
|
{
|
|
'metric-type': 'SNR@bandwidth',
|
|
'accumulative-value': round(mean(self.computed_path[-1].snr),2)
|
|
},
|
|
{
|
|
'metric-type': 'SNR@0.1nm',
|
|
'accumulative-value': round(mean(self.computed_path[-1].snr+10*log10(self.path_request.baudrate/12.5)),2)
|
|
}
|
|
],
|
|
'path-srlgs': {
|
|
'usage': 'not used yet',
|
|
'values': 'not used yet'
|
|
},
|
|
'path-route-objects': [
|
|
{
|
|
'path-route-object': {
|
|
'index': self.computed_path.index(n),
|
|
'unnumbered-hop': {
|
|
'node-id': n.uid,
|
|
'link-tp-id': n.uid,
|
|
'hop-type': self.hop_type[self.computed_path.index(n)],
|
|
'direction': 'not used'
|
|
},
|
|
'label-hop': {
|
|
'te-label': {
|
|
'generic': 'not used yet',
|
|
'direction': 'not used yet'
|
|
}
|
|
}
|
|
}
|
|
} for n in self.computed_path
|
|
]
|
|
}
|
|
}
|
|
|
|
@property
|
|
def json(self):
|
|
return self.pathresult
|
|
|
|
def load_SI(filename):
|
|
with open(filename) as f:
|
|
json_data = loads(f.read())
|
|
return json_data['SI'][0]
|
|
|
|
def load_Transceiver(filename):
|
|
with open(filename) as f:
|
|
json_data = loads(f.read())
|
|
return json_data['Transceiver']
|
|
|
|
def requests_from_json(json_data,eqpt_filename):
|
|
requests_list = []
|
|
tspjsondata = load_Transceiver(eqpt_filename)
|
|
for req in json_data['path-request']:
|
|
#print(f'{req}')
|
|
requests_list.append(Path_request(req,tspjsondata))
|
|
|
|
return requests_list
|
|
|
|
def create_input_spectral_information(sidata,baudrate,power,spacing,nb_channel):
|
|
si = SpectralInformation() # !! SI units W, Hz
|
|
si = si.update(carriers=tuple(Channel(f, (sidata['f_min']+spacing*f),
|
|
baudrate*1e9, sidata['roll_off'], Power(power, 0, 0)) for f in range(1,nb_channel)))
|
|
return si
|
|
|
|
def load_requests(filename,eqpt_filename):
|
|
if filename.suffix.lower() == '.xls':
|
|
logger.info('Automatically converting requests from XLS to JSON')
|
|
json_data = convert_service_sheet(filename,eqpt_filename)
|
|
else:
|
|
with open(filename) as f:
|
|
json_data = loads(f.read())
|
|
return json_data
|
|
|
|
def compute_path(network, pathreqlist):
|
|
# temporary : repeats calls from transmission_main_example
|
|
# to be merged when ready
|
|
|
|
path_res_list = []
|
|
trx = [n for n in network.nodes() if isinstance(n, Transceiver)]
|
|
roadm = [n for n in network.nodes() if isinstance(n, Roadm)]
|
|
edfa = [n for n in network.nodes() if isinstance(n, Edfa)]
|
|
# TODO include also fused in the element check : too difficult because of direction
|
|
# fused = [n for n in network.nodes() if isinstance(n, Fused)]
|
|
sidata = load_SI(args.eqpt_filename)
|
|
for pathreq in pathreqlist:
|
|
pathreq.nodes_list.append(pathreq.destination)
|
|
#we assume that the destination is a strict constraint
|
|
pathreq.loose_list.append('strict')
|
|
print(f'Computing path from {pathreq.source} to {pathreq.destination}')
|
|
print(f'with explicit path: {pathreq.nodes_list}')
|
|
|
|
source = next(el for el in trx if el.uid == pathreq.source)
|
|
# start the path with its source
|
|
total_path = [source]
|
|
for n in pathreq.nodes_list:
|
|
# print(n)
|
|
try :
|
|
node = next(el for el in trx if el.uid == n)
|
|
except StopIteration:
|
|
try:
|
|
node = next(el for el in roadm if el.uid == f'roadm {n}')
|
|
except StopIteration:
|
|
try:
|
|
node = next(el for el in edfa
|
|
if el.uid.startswith(f'egress edfa in {n}'))
|
|
except StopIteration:
|
|
msg = f'could not find node : {n} in network topology: \
|
|
not a trx, roadm, edfa or fused element'
|
|
logger.critical(msg)
|
|
raise ValueError(msg)
|
|
# extend path list without repeating source -> skip first element in the list
|
|
try:
|
|
total_path.extend(dijkstra_path(network, source, node)[1:])
|
|
source = node
|
|
except NetworkXNoPath:
|
|
# for debug
|
|
# print(pathreq.loose_list)
|
|
# print(pathreq.nodes_list.index(n))
|
|
if pathreq.loose_list[pathreq.nodes_list.index(n)] == 'loose':
|
|
print(f'could not find a path from {source.uid} to loose node : {n} in network topology')
|
|
print(f'node {n} is skipped')
|
|
else:
|
|
msg = f'could not find a path from {source.uid} to node : {n} in network topology'
|
|
logger.critical(msg)
|
|
raise ValueError(msg)
|
|
# for debug
|
|
# print(f'{pathreq.baudrate} {pathreq.power} {pathreq.spacing} {pathreq.nb_channel}')
|
|
si = create_input_spectral_information(sidata,pathreq.baudrate,pathreq.power,pathreq.spacing,pathreq.nb_channel)
|
|
for el in total_path:
|
|
si = el(si)
|
|
# print(el)
|
|
# we record the last tranceiver object in order to have th whole
|
|
# information about spectrum. Important Note: since transceivers
|
|
# attached to roadms are actually logical elements to simulate
|
|
# performance, several demands having the same destination may use
|
|
# the same transponder for the performance simaulation. This is why
|
|
# we use deepcopy: to ensure each propagation is recorded and not
|
|
# overwritten
|
|
|
|
# path_res_list.append(deepcopy(destination))
|
|
path_res_list.append(deepcopy(total_path))
|
|
return path_res_list
|
|
|
|
def path_result_json(pathresult):
|
|
data = {
|
|
'path': [n.json for n in pathresult]
|
|
}
|
|
return data
|
|
|
|
|
|
if __name__ == '__main__':
|
|
args = parser.parse_args()
|
|
basicConfig(level={2: DEBUG, 1: INFO, 0: CRITICAL}.get(args.verbose, CRITICAL))
|
|
logger.info(f'Computing path requests {args.service_filename} into JSON format')
|
|
# for debug
|
|
# print( args.eqpt_filename)
|
|
data = load_requests(args.service_filename,args.eqpt_filename)
|
|
equipment = load_equipment(args.eqpt_filename)
|
|
network = load_network(args.network_filename,equipment)
|
|
build_network(network, equipment=equipment)
|
|
pths = requests_from_json(data, args.eqpt_filename)
|
|
test = compute_path(network,pths)
|
|
|
|
if args.output is None:
|
|
print("todo write results")
|
|
print("demand\t\t\t\tsnr@bandwidth\tsnr@0.1nm")
|
|
i = 0
|
|
|
|
for p in test:
|
|
print(f'{pths[i].source} to {pths[i].destination} : {round(mean(p[-1].snr),2)} ,\
|
|
{round(mean(p[-1].snr+10*log10(pths[i].baudrate/12.5)),2)}')
|
|
i = i+1
|
|
else:
|
|
result = []
|
|
for p in test:
|
|
result.append(Result_element(pths[test.index(p)],p))
|
|
with open(args.output, 'w') as f:
|
|
f.write(dumps(path_result_json(result), indent=2)) |