mirror of
https://github.com/Telecominfraproject/oopt-gnpy.git
synced 2025-11-01 02:28:05 +00:00
update Excel_userguide.rst for the services uid: now accepts strings Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
337 lines
14 KiB
Python
337 lines
14 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 collections import namedtuple
|
|
from logging import getLogger, basicConfig, CRITICAL, DEBUG, INFO
|
|
from networkx import (dijkstra_path, NetworkXNoPath)
|
|
from numpy import mean
|
|
from examples.convert_service_sheet import convert_service_sheet, Request_element, Element
|
|
from gnpy.core.elements import Transceiver, Roadm, Edfa, Fused
|
|
from gnpy.core.network import set_roadm_loss
|
|
from gnpy.core.utils import db2lin, lin2db
|
|
from gnpy.core.info import create_input_spectral_information, SpectralInformation, Channel, Power
|
|
from copy import copy, deepcopy
|
|
from csv import writer
|
|
|
|
logger = getLogger(__name__)
|
|
|
|
|
|
RequestParams = namedtuple('RequestParams','request_id source destination trx_type'+
|
|
' trx_mode nodes_list loose_list spacing power nb_channel frequency format baud_rate OSNR bit_rate roll_off')
|
|
|
|
class Path_request:
|
|
def __init__(self, *args, **params):
|
|
params = RequestParams(**params)
|
|
self.request_id = params.request_id
|
|
self.source = params.source
|
|
self.destination = params.destination
|
|
self.tsp = params.trx_type
|
|
self.tsp_mode = params.trx_mode
|
|
self.baud_rate = params.baud_rate
|
|
self.nodes_list = params.nodes_list
|
|
self.loose_list = params.loose_list
|
|
self.spacing = params.spacing
|
|
self.power = params.power
|
|
self.nb_channel = params.nb_channel
|
|
self.frequency = params.frequency
|
|
self.format = params.format
|
|
self.OSNR = params.OSNR
|
|
self.bit_rate = params.bit_rate
|
|
self.roll_off = params.roll_off
|
|
|
|
def __str__(self):
|
|
return '\n\t'.join([ f'{type(self).__name__} {self.request_id}',
|
|
f'source: {self.source}',
|
|
f'destination: {self.destination}'])
|
|
def __repr__(self):
|
|
return '\n\t'.join([ f'{type(self).__name__} {self.request_id}',
|
|
f'source: \t{self.source}',
|
|
f'destination:\t{self.destination}',
|
|
f'trx type:\t{self.tsp}',
|
|
f'trx mode:\t{self.tsp_mode}',
|
|
f'baud_rate:\t{self.baud_rate * 1e-9} Gbaud',
|
|
f'bit_rate:\t{self.bit_rate * 1e-9} Gb/s',
|
|
f'spacing:\t{self.spacing * 1e-9} GHz',
|
|
f'power: \t{round(lin2db(self.power)+30,2)} dBm'
|
|
'\n'])
|
|
|
|
class Result_element(Element):
|
|
def __init__(self,path_request,computed_path):
|
|
self.path_id = 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):
|
|
if not self.computed_path:
|
|
return {
|
|
'path-id': self.path_id,
|
|
'path-properties':{
|
|
'path-metric': [
|
|
{
|
|
'metric-type': 'SNR@bandwidth',
|
|
'accumulative-value': 'None'
|
|
},
|
|
{
|
|
'metric-type': 'SNR@0.1nm',
|
|
'accumulative-value': 'None'
|
|
},
|
|
{
|
|
'metric-type': 'OSNR@bandwidth',
|
|
'accumulative-value': 'None'
|
|
},
|
|
{
|
|
'metric-type': 'OSNR@0.1nm',
|
|
'accumulative-value': 'None'
|
|
},
|
|
{
|
|
'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': 0,
|
|
'unnumbered-hop': {
|
|
'node-id': self.path_request.source,
|
|
'link-tp-id': self.path_request.source,
|
|
'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'
|
|
}
|
|
}
|
|
}
|
|
},
|
|
{
|
|
'path-route-object': {
|
|
'index': 1,
|
|
'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'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
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
|
|
def json(self):
|
|
return self.pathresult
|
|
|
|
def compute_constrained_path(network, req):
|
|
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)]
|
|
source = next(el for el in trx if el.uid == req.source)
|
|
# start the path with its source
|
|
# TODO : avoid loops due to constraints , guess name base on string,
|
|
# avoid crashing if on req is not correct
|
|
total_path = [source]
|
|
for n in req.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(req.loose_list)
|
|
# print(req.nodes_list.index(n))
|
|
if req.loose_list[req.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)
|
|
print(msg)
|
|
total_path = []
|
|
|
|
# preparing disjonction feature
|
|
# for p in all_simple_paths(network,\
|
|
# source=next(el for el in trx if el.uid == req.source),\
|
|
# target=next(el for el in trx if el.uid == req.destination)):
|
|
# print([e.uid for e in p if isinstance(e,Roadm)])
|
|
|
|
return total_path
|
|
|
|
def propagate(path, req, equipment, show=False):
|
|
#update roadm loss in case of power sweep (power mode only)
|
|
set_roadm_loss(path, equipment, lin2db(req.power*1e3))
|
|
si = create_input_spectral_information(
|
|
req.frequency['min'], req.roll_off,
|
|
req.baud_rate, req.power, req.spacing, req.nb_channel)
|
|
for el in path:
|
|
si = el(si)
|
|
if show :
|
|
print(el)
|
|
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
|
|
)) |