mirror of
https://github.com/Telecominfraproject/oopt-gnpy.git
synced 2025-11-01 02:28:05 +00:00
convert_services_sheet. py reads all columns and creat adhoc attributes. Route constraint is saved in the "optimisation" field of the json format. it is used as strict constraint for path computation. automatic decision on node type is made based on the name. Spacing, nb of channel and input power have been set in the requests json, however this may not be inline with the ietf way: they should instead be link attributes. However the objective of the program currently diverges from this control oriented description , since requests explore whatif scenarios for each request with its specific transponder type. Introduction of loose case for the route constraint , input power, spacing, nb of channel, per service entry If no path exist to a loose node it is skipped. else a critical error is raised Minor corrections on convert and path request - convert: remove source and destination from intermediate list of nodes if they are listed - correction on the header of the sheet (column names) - path-request: removing debug printings for a nice demo :) Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
270 lines
12 KiB
Python
270 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
|
|
from gnpy.core.utils import load_json
|
|
from gnpy.core import network_from_json, build_network
|
|
from gnpy.core.equipment import read_eqpt_library
|
|
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)
|
|
tsp = jsondata['path-constraints']['te-bandwidth']['trx_type']
|
|
tsp_mode = jsondata['path-constraints']['te-bandwidth']['trx_mode']
|
|
# for debug
|
|
# print(tsp)
|
|
# refactoring into a simple expression
|
|
# tsp_found = False
|
|
# mode_found = False
|
|
# for t in tspjsondata:
|
|
# if t['type_variety']== tsp :
|
|
# # print('coucou')
|
|
# tsp_found = True
|
|
# for m in t['mode']:
|
|
# if m['format']==tsp_mode:
|
|
# mode_found = True
|
|
# b = m['baudrate']
|
|
# # for debug
|
|
# print(b)
|
|
# self.baudrate = b
|
|
try:
|
|
baudrate = [m['baudrate']
|
|
for t in tspjsondata if t['type_variety']== tsp
|
|
for m in t['mode'] if m['format']==tsp_mode][0]
|
|
# for debug
|
|
# print(f'coucou {baudrate}')
|
|
except IndexError:
|
|
msg = f'could not find tsp : {tsp} with mode: {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 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'])
|
|
|
|
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):
|
|
# si = SpectralInformation() # !! SI units W, Hz
|
|
# si = si.update(carriers=tuple(Channel(f, (sidata['f_min']+sidata['spacing']*f),
|
|
# baudrate*1e9, sidata['roll_off'], Power(sidata['power'], 0, 0)) for f in range(1,sidata['Nch'])))
|
|
# return si
|
|
|
|
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 load_network(filename,eqpt_filename):
|
|
# to be replaced with the good load_network
|
|
# important note: network should be created only once for a given
|
|
# simulation. Note that it only generates infrastructure information.
|
|
# Only one transceiver element is attached per roadm: it represents the
|
|
# logical starting point / stopping point for the propagation of
|
|
# the spectral information to be prpagated along a path.
|
|
# at that point it is not meant to represent the capacity of add drop ports
|
|
# As a result transponder type is not part of the network info. it is related to
|
|
# the list of services requests
|
|
|
|
input_filename = str(filename)
|
|
suffix_filename = str(filename.suffixes[0])
|
|
split_filename = [input_filename[0:len(input_filename)-len(suffix_filename)] , suffix_filename[1:]]
|
|
json_filename = split_filename[0]+'.json'
|
|
try:
|
|
assert split_filename[1] in ('json','xls','csv','xlsm')
|
|
except AssertionError as e:
|
|
print(f'invalid file extension .{split_filename[1]}')
|
|
raise e
|
|
if split_filename[1] != 'json':
|
|
print(f'parse excel input to {json_filename}')
|
|
convert_file(filename)
|
|
|
|
json_data = load_json(json_filename)
|
|
read_eqpt_library(eqpt_filename)
|
|
#print(json_data)
|
|
|
|
network = network_from_json(json_data)
|
|
build_network(network)
|
|
return network
|
|
|
|
def compute_path(network, pathreqlist):
|
|
# temporary : repeats calls from transmission_main_example
|
|
# to be merged when ready
|
|
# final function should only be compute_path(network,pathreqlist)
|
|
|
|
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')
|
|
#pathreq.nodes_list.insert(0,pathreq.source)
|
|
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)
|
|
# print(source.uid)
|
|
# destination = next(el for el in trx if el.uid == pathreq.destination)
|
|
# 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)
|
|
|
|
# si = create_input_spectral_information(sidata,pathreq.baudrate)
|
|
# 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[-1]))
|
|
return path_res_list
|
|
|
|
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')
|
|
print( args.eqpt_filename)
|
|
data = load_requests(args.service_filename,args.eqpt_filename)
|
|
network = load_network(args.network_filename,args.eqpt_filename)
|
|
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.snr),2)} , {round(mean(p.snr+10*log10(pths[i].baudrate/12.5)),2)}')
|
|
i = i+1
|
|
else:
|
|
with open(args.output, 'w') as f:
|
|
f.write(test) |