improving path_request_run robustness to wrong excel input

- adding a test to check that transponder types and modes are part of the eqpt library
- adding a formating on numeical value input for path request ids
- automatically printing the results into a csv file in addition to the json file
  and with the saime name+.csv
- printing results in a more pretty way for demo

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
This commit is contained in:
EstherLerouzic
2018-09-11 11:09:28 +01:00
parent 008a88192c
commit 3ade885e41
4 changed files with 236 additions and 127 deletions

View File

@@ -53,7 +53,16 @@ class Element:
class Request_element(Element): class Request_element(Element):
def __init__(self,Request,eqpt_filename): def __init__(self,Request,eqpt_filename):
self.request_id = int(Request.request_id) # request_id is str
# excel has automatic number formatting that adds .0 on integer values
# the next lines recover the pure int value, assuming this .0 is unwanted
if not isinstance(Request.request_id,str):
value = str(int(Request.request_id))
if value.endswith('.0'):
value = value[:-2]
self.request_id = value
else:
self.request_id = Request.request_id
self.source = Request.source self.source = Request.source
self.destination = Request.destination self.destination = Request.destination
self.srctpid = f'trx {Request.source}' self.srctpid = f'trx {Request.source}'
@@ -61,21 +70,27 @@ class Request_element(Element):
# test that trx_type belongs to eqpt_config.json # test that trx_type belongs to eqpt_config.json
# if not replace it with a default # if not replace it with a default
equipment = load_equipment(eqpt_filename) equipment = load_equipment(eqpt_filename)
if equipment['Transceiver'][Request.trx_type]: try :
self.trx_type = Request.trx_type if equipment['Transceiver'][Request.trx_type]:
self.mode = Request.mode self.trx_type = Request.trx_type
else: if [mode for mode in equipment['Transceiver'][Request.trx_type].mode]:
#TODO : this case must raise an error instead of using Voyager self.mode = Request.mode
self.trx_type = 'Voyager_16QAM' except KeyError:
print(f'Transceiver type {Request.trx_type} is not defined in {eqpt_filename}') msg = f'could not find tsp : {Request.trx_type} with mode: {Request.mode} in eqpt library \nComputation stopped.'
print('replaced by Voyager_16QAM') #print(msg)
logger.critical(msg)
exit()
# excel input are in GHz and dBm
self.spacing = Request.spacing * 1e9 self.spacing = Request.spacing * 1e9
self.power = db2lin(Request.power) * 1e-3 self.power = db2lin(Request.power) * 1e-3
self.nb_channel = int(Request.nb_channel) self.nb_channel = int(Request.nb_channel)
if isinstance(Request.disjoint_from,str): if not isinstance(Request.disjoint_from,str):
self.disjoint_from = [int(n) for n in Request.disjoint_from.split()] value = str(int(Request.disjoint_from))
if value.endswith('.0'):
value = value[:-2]
else: else:
self.disjoint_from = [int(Request.disjoint_from)] value = Request.disjoint_from
self.disjoint_from = [n for n in value.split()]
self.nodes_list = [] self.nodes_list = []
if Request.nodes_list : if Request.nodes_list :
self.nodes_list = Request.nodes_list.split(' | ') self.nodes_list = Request.nodes_list.split(' | ')

View File

@@ -30,7 +30,7 @@ from gnpy.core.network import load_network, build_network, set_roadm_loss
from gnpy.core.equipment import load_equipment, trx_mode_params from gnpy.core.equipment import load_equipment, trx_mode_params
from gnpy.core.elements import Transceiver, Roadm, Edfa, Fused from gnpy.core.elements import Transceiver, Roadm, Edfa, Fused
from gnpy.core.utils import db2lin, lin2db from gnpy.core.utils import db2lin, lin2db
from gnpy.core.request import Path_request, Result_element, compute_constrained_path, propagate from gnpy.core.request import Path_request, Result_element, compute_constrained_path, propagate, jsontocsv
from copy import copy, deepcopy from copy import copy, deepcopy
#EQPT_LIBRARY_FILENAME = Path(__file__).parent / 'eqpt_config.json' #EQPT_LIBRARY_FILENAME = Path(__file__).parent / 'eqpt_config.json'
@@ -138,17 +138,31 @@ if __name__ == '__main__':
test = compute_path(network, equipment, pths) test = compute_path(network, equipment, pths)
#TODO write results #TODO write results
print("demand\tsnr@bandwidth\tsnr@0.1nm")
header = ['demand','snr@bandwidth','snr@0.1nm','Receiver minOSNR']
data = []
data.append(header)
for i, p in enumerate(test): for i, p in enumerate(test):
if p: if p:
print(f'{pths[i].source} to {pths[i].destination} : \t{round(mean(p[-1].snr),2)} ,\ line = [f'{pths[i].source} to {pths[i].destination} : ', f'{round(mean(p[-1].snr),2)}',\
\t{round(mean(p[-1].snr+lin2db(pths[i].baud_rate/(12.5e9))),2)}') f'{round(mean(p[-1].snr+lin2db(pths[i].baud_rate/(12.5e9))),2)}',\
f'{pths[i].OSNR}']
else: else:
print(f'no path from {pths[i].source} to {pths[i].destination} ') line = [f'no path from {pths[i].source} to {pths[i].destination} ']
data.append(line)
col_width = max(len(word) for row in data for word in row) # padding
for row in data:
print(''.join(word.ljust(col_width) for word in row))
if args.output : if args.output :
result = [] result = []
for p in test: for p in test:
result.append(Result_element(pths[test.index(p)],p)) result.append(Result_element(pths[test.index(p)],p))
with open(args.output, 'w') as f: with open(args.output, 'w') as f:
f.write(dumps(path_result_json(result), indent=2)) f.write(dumps(path_result_json(result), indent=2))
fnamecsv = next(s for s in args.output.split('.')) + '.csv'
with open(fnamecsv,"w") as fcsv :
jsontocsv(path_result_json(result),equipment,fcsv)

View File

@@ -14,15 +14,11 @@ and write results in an CSV file
""" """
from sys import exit
from csv import writer
from argparse import ArgumentParser from argparse import ArgumentParser
from pathlib import Path from pathlib import Path
from json import dumps, loads from json import loads
from gnpy.core.equipment import load_equipment from gnpy.core.equipment import load_equipment
from gnpy.core.utils import lin2db from gnpy.core.request import jsontocsv
START_LINE = 5
parser = ArgumentParser(description = 'A function that writes json path results in an excel sheet.') parser = ArgumentParser(description = 'A function that writes json path results in an excel sheet.')
@@ -30,65 +26,14 @@ parser.add_argument('filename', nargs='?', type = Path)
parser.add_argument('output_filename', nargs='?', type = Path) parser.add_argument('output_filename', nargs='?', type = Path)
parser.add_argument('eqpt_filename', nargs='?', type = Path, default=Path(__file__).parent / 'eqpt_config.json') parser.add_argument('eqpt_filename', nargs='?', type = Path, default=Path(__file__).parent / 'eqpt_config.json')
if __name__ == '__main__': if __name__ == '__main__':
args = parser.parse_args() args = parser.parse_args()
print(f'coucou {args.output_filename}')
with open(args.output_filename,"w") as file : with open(args.output_filename,"w") as file :
mywriter = writer(file)
mywriter.writerow(('path-id','source','destination','transponder-type',\
'transponder-mode','baud rate (Gbaud)', 'input power (dBm)','path','OSNR@bandwidth','OSNR@0.1nm','SNR@bandwidth','SNR@0.1nm','Pass?'))
with open(args.filename) as f: with open(args.filename) as f:
print(f'Reading {args.filename}')
json_data = loads(f.read()) json_data = loads(f.read())
equipment = load_equipment(args.eqpt_filename) equipment = load_equipment(args.eqpt_filename)
tspjsondata = equipment['Transceiver'] print(f'Writing in {args.output_filename}')
#print(tspjsondata) jsontocsv(json_data,equipment,file)
for p in json_data['path']:
path_id = int(p['path-id'])
source = p['path-properties']['path-route-objects'][0]\
['path-route-object']['unnumbered-hop']['node-id']
destination = p['path-properties']['path-route-objects'][-1]\
['path-route-object']['unnumbered-hop']['node-id']
pth = ' | '.join([ e['path-route-object']['unnumbered-hop']['node-id']
for e in p['path-properties']['path-route-objects']])
[tsp,mode] = p['path-properties']['path-route-objects'][0]\
['path-route-object']['unnumbered-hop']['hop-type'].split(' - ')
# find the min acceptable OSNR, baud rate from the eqpt library based on tsp (tupe) and mode (format)
try:
[minosnr, baud_rate] = next([m['OSNR'] , m['baud_rate']]
for m in equipment['Transceiver'][tsp].mode if m['format']==mode)
# for debug
# print(f'coucou {baud_rate}')
except IndexError:
msg = f'could not find tsp : {self.tsp} with mode: {self.tsp_mode} in eqpt library'
raise ValueError(msg)
output_snr = next(e['accumulative-value']
for e in p['path-properties']['path-metric'] if e['metric-type'] == 'SNR@0.1nm')
output_snrbandwidth = next(e['accumulative-value']
for e in p['path-properties']['path-metric'] if e['metric-type'] == 'SNR@bandwidth')
output_osnr = next(e['accumulative-value']
for e in p['path-properties']['path-metric'] if e['metric-type'] == 'OSNR@0.1nm')
output_osnrbandwidth = next(e['accumulative-value']
for e in p['path-properties']['path-metric'] if e['metric-type'] == 'OSNR@bandwidth')
power = next(e['accumulative-value']
for e in p['path-properties']['path-metric'] if e['metric-type'] == 'reference_power')
mywriter.writerow((path_id,
source,
destination,
tsp,
mode,
baud_rate*1e-9,
round(lin2db(power)+30,2),
pth,
output_osnrbandwidth,
output_osnr,
output_snrbandwidth,
output_snr,
output_snr >= minosnr
))

View File

@@ -25,6 +25,7 @@ from gnpy.core.network import set_roadm_loss
from gnpy.core.utils import db2lin, lin2db from gnpy.core.utils import db2lin, lin2db
from gnpy.core.info import create_input_spectral_information, SpectralInformation, Channel, Power from gnpy.core.info import create_input_spectral_information, SpectralInformation, Channel, Power
from copy import copy, deepcopy from copy import copy, deepcopy
from csv import writer
logger = getLogger(__name__) logger = getLogger(__name__)
@@ -70,7 +71,7 @@ class Path_request:
class Result_element(Element): class Result_element(Element):
def __init__(self,path_request,computed_path): def __init__(self,path_request,computed_path):
self.path_id = int(path_request.request_id) self.path_id = path_request.request_id
self.path_request = path_request self.path_request = path_request
self.computed_path = computed_path self.computed_path = computed_path
hop_type = [] hop_type = []
@@ -83,56 +84,125 @@ class Result_element(Element):
uid = property(lambda self: repr(self)) uid = property(lambda self: repr(self))
@property @property
def pathresult(self): def pathresult(self):
return { if not self.computed_path:
'path-id': self.path_id, return {
'path-properties':{ 'path-id': self.path_id,
'path-metric': [ 'path-properties':{
{ 'path-metric': [
'metric-type': 'SNR@bandwidth', {
'accumulative-value': round(mean(self.computed_path[-1].snr),2) 'metric-type': 'SNR@bandwidth',
}, 'accumulative-value': 'None'
{ },
'metric-type': 'SNR@0.1nm', {
'accumulative-value': round(mean(self.computed_path[-1].snr+lin2db(self.path_request.baud_rate/12.5e9)),2) 'metric-type': 'SNR@0.1nm',
}, 'accumulative-value': 'None'
{ },
'metric-type': 'OSNR@bandwidth', {
'accumulative-value': round(mean(self.computed_path[-1].osnr_ase),2) 'metric-type': 'OSNR@bandwidth',
}, 'accumulative-value': 'None'
{ },
'metric-type': 'OSNR@0.1nm', {
'accumulative-value': round(mean(self.computed_path[-1].osnr_ase_01nm),2) 'metric-type': 'OSNR@0.1nm',
}, 'accumulative-value': 'None'
{ },
'metric-type': 'reference_power', {
'accumulative-value': self.path_request.power 'metric-type': 'reference_power',
} 'accumulative-value': self.path_request.power
], }
'path-srlgs': { ],
'usage': 'not used yet', 'path-srlgs': {
'values': 'not used yet' 'usage': 'not used yet',
}, 'values': 'not used yet'
'path-route-objects': [ },
{ 'path-route-objects': [
'path-route-object': { {
'index': self.computed_path.index(n), 'path-route-object': {
'unnumbered-hop': { 'index': 0,
'node-id': n.uid, 'unnumbered-hop': {
'link-tp-id': n.uid, 'node-id': self.path_request.source,
'hop-type': self.hop_type[self.computed_path.index(n)], 'link-tp-id': self.path_request.source,
'direction': 'not used' 'hop-type': ' - '.join([self.path_request.tsp, self.path_request.tsp_mode]),
'direction': 'not used'
},
'label-hop': {
'te-label': {
'generic': 'not used yet',
'direction': 'not used yet'
}
}
}
}, },
'label-hop': { {
'te-label': { 'path-route-object': {
'generic': 'not used yet', 'index': 1,
'direction': 'not used yet' 'unnumbered-hop': {
'node-id': self.path_request.destination,
'link-tp-id': self.path_request.destination,
'hop-type': ' - '.join([self.path_request.tsp, self.path_request.tsp_mode]),
'direction': 'not used'
},
'label-hop': {
'te-label': {
'generic': 'not used yet',
'direction': 'not used yet'
}
} }
} }
} }
} for n in self.computed_path ]
] }
}
else:
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+lin2db(self.path_request.baud_rate/12.5e9)),2)
},
{
'metric-type': 'OSNR@bandwidth',
'accumulative-value': round(mean(self.computed_path[-1].osnr_ase),2)
},
{
'metric-type': 'OSNR@0.1nm',
'accumulative-value': round(mean(self.computed_path[-1].osnr_ase_01nm),2)
},
{
'metric-type': 'reference_power',
'accumulative-value': self.path_request.power
}
],
'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 @property
def json(self): def json(self):
@@ -192,4 +262,69 @@ def propagate(path, req, equipment, show=False):
si = el(si) si = el(si)
if show : if show :
print(el) print(el)
return path return path
def jsontocsv(json_data,equipment,fileout):
# read json path result file in accordance with:
# Yang model for requesting Path Computation
# draft-ietf-teas-yang-path-computation-01.txt.
# and write results in an CSV file
mywriter = writer(fileout)
mywriter.writerow(('path-id','source','destination','transponder-type',\
'transponder-mode','baud rate (Gbaud)', 'input power (dBm)','path',\
'OSNR@bandwidth','OSNR@0.1nm','SNR@bandwidth','SNR@0.1nm','Pass?'))
tspjsondata = equipment['Transceiver']
#print(tspjsondata)
for p in json_data['path']:
path_id = p['path-id']
source = p['path-properties']['path-route-objects'][0]\
['path-route-object']['unnumbered-hop']['node-id']
destination = p['path-properties']['path-route-objects'][-1]\
['path-route-object']['unnumbered-hop']['node-id']
pth = ' | '.join([ e['path-route-object']['unnumbered-hop']['node-id']
for e in p['path-properties']['path-route-objects']])
[tsp,mode] = p['path-properties']['path-route-objects'][0]\
['path-route-object']['unnumbered-hop']['hop-type'].split(' - ')
# find the min acceptable OSNR, baud rate from the eqpt library based on tsp (tupe) and mode (format)
try:
[minosnr, baud_rate] = next([m['OSNR'] , m['baud_rate']]
for m in equipment['Transceiver'][tsp].mode if m['format']==mode)
# for debug
# print(f'coucou {baud_rate}')
except IndexError:
msg = f'could not find tsp : {self.tsp} with mode: {self.tsp_mode} in eqpt library'
raise ValueError(msg)
output_snr = next(e['accumulative-value']
for e in p['path-properties']['path-metric'] if e['metric-type'] == 'SNR@0.1nm')
output_snrbandwidth = next(e['accumulative-value']
for e in p['path-properties']['path-metric'] if e['metric-type'] == 'SNR@bandwidth')
output_osnr = next(e['accumulative-value']
for e in p['path-properties']['path-metric'] if e['metric-type'] == 'OSNR@0.1nm')
output_osnrbandwidth = next(e['accumulative-value']
for e in p['path-properties']['path-metric'] if e['metric-type'] == 'OSNR@bandwidth')
power = next(e['accumulative-value']
for e in p['path-properties']['path-metric'] if e['metric-type'] == 'reference_power')
if isinstance(output_snr, str):
isok = ''
else:
isok = output_snr >= minosnr
mywriter.writerow((path_id,
source,
destination,
tsp,
mode,
baud_rate*1e-9,
round(lin2db(power)+30,2),
pth,
output_osnrbandwidth,
output_osnr,
output_snrbandwidth,
output_snr,
isok
))