From bfdbe913f9b0cca4eeca20a9630b90b9bcc1a312 Mon Sep 17 00:00:00 2001 From: ismaeel <mohammad.ismaael@cnit.it> Date: Wed, 3 Jul 2024 08:39:38 +0000 Subject: [PATCH] Fix bugs on deconfigurations Roadms --- proto/context.proto | 20 +- src/context/client/ContextClient.py | 16 + .../service/ContextServiceServicerImpl.py | 17 +- src/context/service/database/OpticalConfig.py | 462 +++++++++++++++++- .../OpticalConfig/OpticalConfigModel.py | 74 +++ .../models/OpticalConfig/RoadmModel.py | 81 +++ .../TransponderModel.py} | 44 +- .../database/models/OpticalConfig/__init__.py | 13 + .../service/database/uuids/OpticalConfig.py | 18 +- src/device/service/OpenConfigServicer.py | 57 ++- src/device/service/Tools.py | 2 +- .../service/drivers/oc_driver/OCDriver.py | 80 ++- .../drivers/oc_driver/templates/VPN/roadms.py | 5 +- .../templates/descovery_tool/roadms.py | 109 ++++- .../service_handlers/oc/OCServiceHandler.py | 1 + .../service/task_scheduler/TaskExecutor.py | 33 +- src/webui/service/base_optical/route.py | 12 +- src/webui/service/opticalconfig/routes.py | 92 ++-- .../templates/opticalconfig/details.html | 98 ++-- .../service/templates/opticalconfig/home.html | 16 +- 20 files changed, 1054 insertions(+), 196 deletions(-) create mode 100644 src/context/service/database/models/OpticalConfig/OpticalConfigModel.py create mode 100644 src/context/service/database/models/OpticalConfig/RoadmModel.py rename src/context/service/database/models/{OpticalConfigModel.py => OpticalConfig/TransponderModel.py} (64%) create mode 100644 src/context/service/database/models/OpticalConfig/__init__.py diff --git a/proto/context.proto b/proto/context.proto index f881bca5f..99208f3a9 100644 --- a/proto/context.proto +++ b/proto/context.proto @@ -77,15 +77,17 @@ service ContextService { // ------------------------------ Experimental ----------------------------- - rpc GetOpticalConfig (Empty ) returns (OpticalConfigList ) {} - rpc SetOpticalConfig (OpticalConfig ) returns (OpticalConfigId ) {} - rpc SelectOpticalConfig(OpticalConfigId) returns (OpticalConfig ) {} - rpc DeleteOpticalConfig(OpticalConfigId) returns (Empty ) {} - - rpc SetOpticalLink (OpticalLink ) returns (Empty ) {} - rpc GetOpticalLink (LinkId ) returns (OpticalLink ) {} - rpc DeleteOpticalLink (LinkId ) returns (Empty ) {} - rpc GetOpticalLinkList (Empty) returns (OpticalLinkList) {} + rpc GetOpticalConfig (Empty ) returns ( OpticalConfigList ) {} + rpc SetOpticalConfig (OpticalConfig ) returns ( OpticalConfigId ) {} + rpc UpdateOpticalConfig (OpticalConfig ) returns ( OpticalConfigId ) {} + rpc SelectOpticalConfig(OpticalConfigId ) returns ( OpticalConfig ) {} + rpc DeleteOpticalConfig(OpticalConfigId ) returns ( Empty ) {} + rpc DeleteOpticalChannel(OpticalConfig ) returns ( Empty ) {} + + rpc SetOpticalLink (OpticalLink ) returns ( Empty ) {} + rpc GetOpticalLink (LinkId ) returns ( OpticalLink ) {} + rpc DeleteOpticalLink (LinkId ) returns ( Empty ) {} + rpc GetOpticalLinkList (Empty ) returns ( OpticalLinkList ) {} } // ----- Generic ------------------------------------------------------------------------------------------------------- diff --git a/src/context/client/ContextClient.py b/src/context/client/ContextClient.py index b325bece3..e30885529 100644 --- a/src/context/client/ContextClient.py +++ b/src/context/client/ContextClient.py @@ -447,6 +447,14 @@ class ContextClient: response = self.stub.SetOpticalConfig(request) LOGGER.debug('SetOpticalConfig result: {:s}'.format(grpc_message_to_json_string(response))) return response + + @RETRY_DECORATOR + def UpdateOpticalConfig(self, request : OpticalConfig) -> OpticalConfigId: + LOGGER.debug('SetOpticalConfig request: {:s}'.format(grpc_message_to_json_string(request))) + response_future = self.stub.UpdateOpticalConfig.future(request) + response = response_future.result() + LOGGER.debug('SetOpticalConfig result: {:s}'.format(grpc_message_to_json_string(response))) + return response @RETRY_DECORATOR def GetOpticalConfig(self, request : Empty) -> OpticalConfigList: @@ -461,6 +469,7 @@ class ContextClient: response = self.stub.SelectOpticalConfig(request) LOGGER.debug('SelectOpticalConfig result: {:s}'.format(grpc_message_to_json_string(response))) return response + @RETRY_DECORATOR def DeleteOpticalConfig(self,request : OpticalConfigId) -> Empty: LOGGER.debug('DeleteOpticalConfig request: {:s}'.format(grpc_message_to_json_string(request))) @@ -468,6 +477,13 @@ class ContextClient: LOGGER.debug('DeleteOpticalConfig result: {:s}'.format(grpc_message_to_json_string(response))) return response + @RETRY_DECORATOR + def DeleteOpticalChannel(self,request : OpticalConfig) -> Empty: + LOGGER.debug('DeleteOpticalChannel request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.DeleteOpticalChannel(request) + LOGGER.debug('DeleteOpticalChannel result: {:s}'.format(grpc_message_to_json_string(response))) + return response + #--------------------------- Optical Link ------------------------ def GetOpticalLinkList(self, request: Empty) -> OpticalLinkList: LOGGER.debug('ListOpticalLinks request: {:s}'.format(grpc_message_to_json_string(request))) diff --git a/src/context/service/ContextServiceServicerImpl.py b/src/context/service/ContextServiceServicerImpl.py index 454a65fe2..5ac319c29 100644 --- a/src/context/service/ContextServiceServicerImpl.py +++ b/src/context/service/ContextServiceServicerImpl.py @@ -45,8 +45,9 @@ from .database.Slice import ( slice_delete, slice_get, slice_list_ids, slice_list_objs, slice_select, slice_set, slice_unset) from .database.Topology import ( topology_delete, topology_get, topology_get_details, topology_list_ids, topology_list_objs, topology_set) -from .database.OpticalConfig import set_opticalconfig, select_opticalconfig, get_opticalconfig ,delete_opticalconfig - +from .database.OpticalConfig import (set_opticalconfig, select_opticalconfig, get_opticalconfig + ,delete_opticalconfig ,update_opticalconfig ,delete_opticalchannel +) from .database.OpticalLink import optical_link_delete,optical_link_get,optical_link_list_objs,optical_link_set LOGGER = logging.getLogger(__name__) @@ -312,6 +313,11 @@ class ContextServiceServicerImpl(ContextServiceServicer, ContextPolicyServiceSer def SetOpticalConfig(self, request : OpticalConfig, context : grpc.ServicerContext) -> OpticalConfigId: result = set_opticalconfig(self.db_engine, request) return OpticalConfigId(**result) + + @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) + def UpdateOpticalConfig(self, request : OpticalConfig, context : grpc.ServicerContext) -> OpticalConfigId: + result = update_opticalconfig(self.db_engine, request) + return OpticalConfigId(**result) @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def SelectOpticalConfig(self, request : OpticalConfigId, context : grpc.ServicerContext) -> OpticalConfig: @@ -327,7 +333,12 @@ class ContextServiceServicerImpl(ContextServiceServicer, ContextPolicyServiceSer delete_opticalconfig(self.db_engine,self.messagebroker, request) return Empty() - + @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) + def DeleteOpticalChannel (self, request : OpticalConfig, context : grpc.ServicerContext) -> Empty: + delete_opticalchannel(self.db_engine,self.messagebroker, request) + + return Empty() + #--------------------- Experimental Optical Link ------------------- diff --git a/src/context/service/database/OpticalConfig.py b/src/context/service/database/OpticalConfig.py index a3618e2ba..033a25a56 100644 --- a/src/context/service/database/OpticalConfig.py +++ b/src/context/service/database/OpticalConfig.py @@ -19,8 +19,12 @@ from sqlalchemy.engine import Engine from sqlalchemy.orm import Session, sessionmaker from sqlalchemy_cockroachdb import run_transaction from common.proto.context_pb2 import OpticalConfig, OpticalConfigId , Empty , EventTypeEnum -from .models.OpticalConfigModel import OpticalConfigModel , TransponderTypeModel ,OpticalChannelModel -from context.service.database.uuids.OpticalConfig import channel_get_uuid , opticalconfig_get_uuid ,transponder_get_uuid +from .models.OpticalConfig.OpticalConfigModel import OpticalConfigModel +from .models.OpticalConfig.TransponderModel import TransponderTypeModel ,OpticalChannelModel +from .models.OpticalConfig.RoadmModel import RoadmTypeModel, ChannelModel +from context.service.database.uuids.OpticalConfig import ( + channel_get_uuid , opticalconfig_get_uuid ,transponder_get_uuid,roadm_get_uuid + ) from .Events import notify_event_opticalconfig LOGGER = logging.getLogger(__name__) @@ -50,8 +54,10 @@ def set_opticalconfig(db_engine : Engine, request : OpticalConfig): device_id = request.device_id device_uuid = request.device_id.device_uuid.uuid channels = [] + transponder=[] - + roadms=[] + channel_namespace= None OpticalConfig_data = [] config_type=None is_transpondre=False @@ -59,22 +65,25 @@ def set_opticalconfig(db_engine : Engine, request : OpticalConfig): LOGGER.info(f"cofigy_type {request.config}") if request.config: config = json.loads(request.config) + if 'channel_namespace' in config: + channel_namespace=config['channel_namespace'] if "type" in config: config_type= config["type"] if config_type == "optical-transponder": is_transpondre=True transceivers = [] - + if channel_namespace is None and 'channel_namespace' in config: + channel_namespace=config['channel_namespace'] - if 'transceivers' in config['transponder'] and len(config['transponder']['transceivers']['transceiver']) > 0: - transceivers = [transceiver for transceiver in config['transponder'] ['transceivers']['transceiver']] + if 'transceivers' in config and len(config['transceivers']['transceiver']) > 0: + transceivers = [transceiver for transceiver in config ['transceivers']['transceiver']] - if 'channels' in config['transponder'] and len(config['transponder']['channels']) > 0: + if 'channels' in config and len(config['channels']) > 0: #channels = [channel['name']['index'] for channel in config['channels']] - for channel_params in config['transponder']['channels']: + for channel_params in config['channels']: channels.append( { # "opticalconfig_uuid":opticalconfig_uuid, @@ -98,13 +107,71 @@ def set_opticalconfig(db_engine : Engine, request : OpticalConfig): }) + if config_type == "optical-roadm": + + + if channel_namespace is None and 'channel_namespace' in config: + channel_namespace=config['channel_namespace'] + + + + + if 'media_channels' in config and len(config['media_channels']) > 0: + #channels = [channel['name']['index'] for channel in config['channels']] + channel_num=0 + for channel_params in config['media_channels']: + channel_num+=1 + channels.append( + { + # "opticalconfig_uuid":opticalconfig_uuid, + "roadm_uuid" : roadm_get_uuid(device_id), + "channel_uuid" : channel_get_uuid(f'media_channel_{channel_num}',device_uuid), + "band_name" : channel_params['band_name'], + "lower_frequency" : int(channel_params["lower_frequency"]) if "lower_frequency" in channel_params else 0, + "upper_frequency" : int(channel_params["upper_frequency"]) if "upper_frequency" in channel_params else 0, + "dest_port" : channel_params["dest_port"] if "dest_port" in channel_params else '', + "src_port" : channel_params["src_port"] if "src_port" in channel_params else '', + "status" : channel_params["status"] if "status" in channel_params else "", + "type" : 'media_channel', + "optical_band_parent":str(channel_params['optical_band_parent']) if 'optical_band_parent' in channel_params else None, + "channel_index" : channel_params['channel_index'] if 'channel_index' in channel_params else None, + } + ) + if 'optical_bands' in config and len(config['optical_bands']) > 0: + #channels = [channel['name']['index'] for channel in config['channels']] + channel_num=0 + for channel_params in config['optical_bands']: + channel_num+=1 + channels.append( + { + # "opticalconfig_uuid":opticalconfig_uuid, + "roadm_uuid" : roadm_get_uuid(device_id), + "channel_uuid" : channel_get_uuid(f'optical_bands_{channel_num}',device_uuid), + "band_name" : channel_params['band_name'], + "lower_frequency" : int(channel_params["lower_frequency"]) if "lower_frequency" in channel_params else 0, + "upper_frequency" : int(channel_params["upper_frequency"]) if "upper_frequency" in channel_params else 0, + "dest_port" : channel_params["dest_port"] if "dest_port" in channel_params else '', + "src_port" : channel_params["src_port"] if "src_port" in channel_params else '', + "status" : channel_params["status"] if "status" in channel_params else "", + "type" : 'optical_band', + "channel_index" : channel_params['channel_index'] if 'channel_index' in channel_params else None, + "optical_band_parent":None + + } + ) + + roadms.append({ + "roadm_uuid":roadm_get_uuid(device_id), + "opticalconfig_uuid":opticalconfig_uuid, + }) + OpticalConfig_data.append( { "opticalconfig_uuid":opticalconfig_uuid, # "transcievers" : transceivers, # "interfaces" :"", - "channel_namespace" : config['transponder'].get("channel_namespace",None) if is_transpondre else None , + "channel_namespace" : channel_namespace , "endpoints" : [json.dumps(endpoint) for endpoint in config.get("endpoints",[])], "device_uuid": device_uuid, "type":config_type @@ -112,6 +179,7 @@ def set_opticalconfig(db_engine : Engine, request : OpticalConfig): ) LOGGER.info(f"added OpticalConfig_data {OpticalConfig_data}") + LOGGER.info(f"added channels {channels}") def callback(session:Session)->bool: stmt = insert(OpticalConfigModel).values(OpticalConfig_data) @@ -155,11 +223,296 @@ def set_opticalconfig(db_engine : Engine, request : OpticalConfig): ) stmt = stmt.returning(OpticalChannelModel.channel_uuid) opticalChannel_id = session.execute(stmt).fetchone() + + + if config_type == 'optical-roadm': + if (len(roadms)>0): + stmt = insert(RoadmTypeModel).values(roadms) + + stmt = stmt.on_conflict_do_update( + index_elements=[RoadmTypeModel.roadm_uuid], + set_=dict( + circuits=stmt.excluded.circuits + ) + ) + stmt = stmt.returning(RoadmTypeModel.roadm_uuid) + roadm_id = session.execute(stmt).fetchone() + + if (channels is not None and len(channels)>0) : + + stmt = insert(ChannelModel).values(channels) + + stmt = stmt.on_conflict_do_update( + index_elements=[ChannelModel.channel_uuid ], + set_=dict( + band_name= stmt.excluded.band_name , + lower_frequency = stmt.excluded.lower_frequency, + upper_frequency = stmt.excluded.upper_frequency, + type=stmt.excluded.type, + status=stmt.excluded.status, + dest_port=stmt.excluded.dest_port, + src_port=stmt.excluded.src_port, + channel_index=stmt.excluded.channel_index, + optical_band_parent = stmt.excluded.optical_band_parent + + + ) + + ) + stmt = stmt.returning(ChannelModel.channel_uuid) + opticalChannel_id = session.execute(stmt).fetchone() + opticalconfig_id = run_transaction(sessionmaker(bind=db_engine), callback) return {'opticalconfig_uuid': opticalconfig_id} + +def update_opticalconfig(db_engine : Engine, request : OpticalConfig): + + opticalconfig_id = OpticalConfigId() + device_id = request.device_id + device_uuid = request.device_id.device_uuid.uuid + channels = [] + transponder=[] + roadms=[] + channel_namespace= None + OpticalConfig_data = [] + config_type=None + is_transpondre=False + opticalconfig_uuid =opticalconfig_get_uuid(device_id) + LOGGER.info(f"udpate_cofigy_type {request.config}") + if request.config : + config = json.loads(request.config) + + if 'new_config' in config: + if 'type' in config: + config_type=config['type'] + if "type" in config['new_config'] and config_type is None: + + config_type= config['new_config']["type"] + if 'channel_namespace' in config['new_config']: + channel_namespace=config['new_config'] ['channel_namespace'] + + + if config_type == "optical-transponder": + is_transpondre=True + transceivers = [] + if channel_namespace is None and 'channel_namespace' in config['new_config']: + channel_namespace=config['new_config']['channel_namespace'] + + if 'transceivers' in config['new_config'] and len(config['new_config']['transceivers']['transceiver']) > 0: + transceivers = [transceiver for transceiver in config['new_config'] ['transceivers']['transceiver']] + + if 'channels' in config['new_config'] and len(config['new_config']['channels']) > 0: + #channels = [channel['name']['index'] for channel in config['channels']] + + for channel_params in config['new_config']['channels']: + channels.append( + { + # "opticalconfig_uuid":opticalconfig_uuid, + "transponder_uuid" : transponder_get_uuid(device_id), + "channel_uuid" : channel_get_uuid(channel_params['name']['index'],device_uuid), + "channel_name" : channel_params['name']['index'], + "frequency" : int(channel_params["frequency"]) if "frequency" in channel_params else 0, + "operational_mode" : int(channel_params["operational-mode"]) if "operational-mode" in channel_params else 0, + "target_output_power" : channel_params["target-output-power"] if "target-output-power" in channel_params else '', + "status" : channel_params["status"] if "status" in channel_params else "" + } + ) + elif 'flow_handled' in config and 'new_config' in config : + + target_config = config['new_config'] + dest_pair=None + src=None + dst=None + src_pair=config['flow_handled'][0] + + src,dst=src_pair + if src_pair is None and len(config['flow_handled'])>1 : + dest_pair=config['flow_handled'][1] + src,dst=dest_pair + channel_index=src if src is not None and src !='0' else dst + channel_name=f"channel-{channel_index}" + channels.append( + { + # "opticalconfig_uuid":opticalconfig_uuid, + "transponder_uuid" : transponder_get_uuid(device_id), + "channel_uuid" : channel_get_uuid(channel_name,device_uuid), + "channel_name" :channel_name, + "frequency" : int(target_config["frequency"]) if "frequency" in target_config else 0, + "operational_mode" : int(target_config["operational-mode"]) if "operational-mode" in target_config else 0, + "target_output_power" : target_config["target-output-power"] if "target-output-power" in target_config else '', + "status" : target_config["status"] if "status" in target_config else "" + } + ) + + transponder.append({ + "transponder_uuid":transponder_get_uuid(device_id), + "transcievers":transceivers, + "interfaces":None, + "opticalconfig_uuid":opticalconfig_uuid, + }) + + + if config_type == "optical-roadm": + + if 'flow_handled' in config and len(config['flow_handled'])==0 : + config['flow_handled'] =[(None,None)] + if channel_namespace is None and 'channel_namespace' in config['new_config']: + channel_namespace=config['new_config']['channel_namespace'] + + if 'is_opticalband' in config and not config['is_opticalband']: + #channels = [channel['name']['index'] for channel in config['channels']] + channel_num=0 + for flow in config['flow_handled']: + src_port,dest_port=flow + channel_num+=1 + channels.append( + { + # "opticalconfig_uuid":opticalconfig_uuid, + "roadm_uuid" : roadm_get_uuid(device_id), + "channel_uuid" : channel_get_uuid(f'media_channel_{channel_num}',device_uuid), + "band_name" : config['new_config']['band_type'], + "lower_frequency" : int(int(config['new_config']['frequency']) - (int(config['new_config']['band'])/2)) if "frequency" in config['new_config'] else 0, + "upper_frequency" :int(int(config['new_config']['frequency']) + (int(config['new_config']['band'])/2)) if "frequency" in config['new_config'] else 0, + "dest_port" : dest_port, + "src_port" : src_port, + "status" : config['new_config']["status"] if "status" in config['new_config'] else "", + "type" : 'media_channel', + "optical_band_parent":str( config['new_config']['ob_id']) if 'ob_id' in config['new_config'] else None, + "channel_index":str(config["new_config"]["flow_id"]) if "flow_id" in config["new_config"] else None + } + ) + if 'is_opticalband' in config and config['is_opticalband']: + #channels = [channel['name']['index'] for channel in config['channels']] + + channel_num=0 + for flow in config['flow_handled']: + src_port,dest_port=flow + LOGGER.info(f"udpate_optical_bands src: {src_port} and dest :{dest_port} for flow {flow}") + channel_num+=1 + channels.append( + + { + # "opticalconfig_uuid":opticalconfig_uuid, + "roadm_uuid" : roadm_get_uuid(device_id), + "channel_uuid" : channel_get_uuid(f'optical_bands_{channel_num}',device_uuid), + "band_name" : config['new_config']['band_type'], + "lower_frequency" : int(config['new_config']["low-freq"]) if "low-freq" in config['new_config'] else 0, + "upper_frequency" : int(config['new_config']["up-freq"]) if "up-freq" in config['new_config'] else 0, + "dest_port" :dest_port, + "src_port" : src_port, + "status" : config['new_config']["status"] if "status" in config['new_config'] else "", + "type" : 'optical_band', + "channel_index" :str( config['new_config']['ob_id']) if 'ob_id' in config['new_config'] else None + } + ) + + roadms.append({ + "roadm_uuid":roadm_get_uuid(device_id), + "opticalconfig_uuid":opticalconfig_uuid, + }) + + + OpticalConfig_data.append( + { + "opticalconfig_uuid":opticalconfig_uuid, + # "transcievers" : transceivers, + # "interfaces" :"", + "channel_namespace" : channel_namespace , + "endpoints" : [json.dumps(endpoint) for endpoint in config['new_config'].get("endpoints",[])], + "device_uuid": device_uuid, + "type":config_type + } + ) + + LOGGER.info(f"udpate_ OpticalConfig_data {OpticalConfig_data}") + LOGGER.info(f"udpate_ channels {channels}") + + def callback(session:Session)->bool: + stmt = insert(OpticalConfigModel).values(OpticalConfig_data) + + stmt = stmt.on_conflict_do_update( + index_elements=[OpticalConfigModel.opticalconfig_uuid], + set_=dict( + channel_namespace=stmt.excluded.channel_namespace + ) + ) + stmt = stmt.returning(OpticalConfigModel.opticalconfig_uuid) + opticalconfig_id = session.execute(stmt).fetchone() + if config_type == 'optical-transponder': + if (len(transponder)>0): + stmt = insert(TransponderTypeModel).values(transponder) + + stmt = stmt.on_conflict_do_update( + index_elements=[TransponderTypeModel.transponder_uuid], + set_=dict( + transcievers= stmt.excluded.transcievers , + ) + + ) + stmt = stmt.returning(TransponderTypeModel.transponder_uuid) + transponder_id = session.execute(stmt).fetchone() + + if (len(channels)>0) : + + stmt = insert(OpticalChannelModel).values(channels) + + stmt = stmt.on_conflict_do_update( + index_elements=[OpticalChannelModel.channel_uuid ], + set_=dict( + channel_name= stmt.excluded.channel_name , + frequency = stmt.excluded.frequency, + operational_mode=stmt.excluded.operational_mode, + target_output_power=stmt.excluded.target_output_power, + status = stmt.excluded.status, + + ) + + ) + stmt = stmt.returning(OpticalChannelModel.channel_uuid) + opticalChannel_id = session.execute(stmt).fetchone() + if config_type == 'optical-roadm': + if (len(roadms)>0): + stmt = insert(RoadmTypeModel).values(roadms) + + stmt = stmt.on_conflict_do_update( + index_elements=[RoadmTypeModel.roadm_uuid], + set_=dict( + circuits=stmt.excluded.circuits + ) + ) + stmt = stmt.returning(RoadmTypeModel.roadm_uuid) + roadm_id = session.execute(stmt).fetchone() + + if (channels is not None and len(channels)>0) : + + stmt = insert(ChannelModel).values(channels) + + stmt = stmt.on_conflict_do_update( + index_elements=[ChannelModel.channel_uuid ], + set_=dict( + band_name= stmt.excluded.band_name , + lower_frequency = stmt.excluded.lower_frequency, + upper_frequency = stmt.excluded.upper_frequency, + type=stmt.excluded.type, + status=stmt.excluded.status, + dest_port=stmt.excluded.dest_port, + src_port=stmt.excluded.src_port, + + ) + + ) + stmt = stmt.returning(ChannelModel.channel_uuid) + opticalChannel_id = session.execute(stmt).fetchone() + + + + opticalconfig_id = run_transaction(sessionmaker(bind=db_engine), callback) + return {'opticalconfig_uuid': opticalconfig_id} + + def select_opticalconfig(db_engine:Engine,request:OpticalConfigId): def callback(session : Session) -> OpticalConfig: result = OpticalConfig() @@ -188,8 +541,97 @@ def delete_opticalconfig(db_engine : Engine ,messagebroker : MessageBroker, req if deleted: notify_event_opticalconfig(messagebroker, EventTypeEnum.EVENTTYPE_REMOVE, opticalconfig_uuid) - + return Empty() + +def delete_opticalchannel(db_engine : Engine ,messagebroker : MessageBroker, request : OpticalConfig): + config = json.loads(request.config) + device_id = request.device_id + device_uuid = request.device_id.device_uuid.uuid + opticalconfig_uuid = request.opticalconfig_id.opticalconfig_uuid + channels=[] + config_type=None + if "type" in config : + config_type= config["type"] + if 'new_config' in config: + + if config_type == 'optical-transponder': + for flow in config['flow']: + + src,dest = flow + channel_index= src if src is not None and src!='0' else dest + channel_name= f"channel-{channel_index}" + channels.append( + { + # "opticalconfig_uuid":opticalconfig_uuid, + "transponder_uuid" : transponder_get_uuid(device_id), + "channel_uuid" : channel_get_uuid(channel_name ,device_uuid), + "channel_name" :channel_name , + "frequency" : 0, + "operational_mode" : None, + "target_output_power" : None, + "status" :"DISABLED" + } + ) + elif config_type == 'optical-roadm': + channel_num=0 + if 'flow' in config['new_config'] : + if config['new_config']['flow'] ==0: + channel_num=1 + channel_name=f'optical_bands_{channel_num}' if config['new_config']['is_opticalband'] else f'media_channel_{channel_num}' + channels.append( channel_get_uuid(channel_name,device_uuid),device_uuid) + else : + for flow in config['new_config']['flow']: + channel_num +=1 + channel_name=f'optical_bands_{channel_num}' if config['new_config']['is_opticalband'] else f'media_channel_{channel_num}' + channels.append( channel_get_uuid(channel_name,device_uuid),device_uuid) + else : + return + + LOGGER.info(f"delete channel {channels}") + + + def callback(session : Session): + all_suceed=[] + if config_type =='optical-roadm': + query = session.query(ChannelModel) + for channel_uuid in channels: + num_deleted = session.query(ChannelModel).filter_by(channel_uuid=channel_uuid).delete() + all_suceed.append(num_deleted > 0) + return all_suceed + elif config_type == 'optical-transponder' : + if (len(channels)>0) : + + stmt = insert(OpticalChannelModel).values(channels) + + stmt = stmt.on_conflict_do_update( + index_elements=[OpticalChannelModel.channel_uuid ], + set_=dict( + channel_name= stmt.excluded.channel_name , + frequency = stmt.excluded.frequency, + operational_mode=stmt.excluded.operational_mode, + target_output_power=stmt.excluded.target_output_power, + status=stmt.excluded.status + + ) + + ) + stmt = stmt.returning(OpticalChannelModel.channel_uuid) + opticalChannel_id = session.execute(stmt).fetchone() + all_suceed.append(True) + return all_suceed + else: + return all_suceed + + + all_deleted = run_transaction(sessionmaker(bind=db_engine), callback) + + for stat in all_deleted: + if not stat: + return + notify_event_opticalconfig(messagebroker, EventTypeEnum.EVENTTYPE_REMOVE, opticalconfig_uuid) return Empty() + + \ No newline at end of file diff --git a/src/context/service/database/models/OpticalConfig/OpticalConfigModel.py b/src/context/service/database/models/OpticalConfig/OpticalConfigModel.py new file mode 100644 index 000000000..515a21a85 --- /dev/null +++ b/src/context/service/database/models/OpticalConfig/OpticalConfigModel.py @@ -0,0 +1,74 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json , logging +from sqlalchemy import Column, String, Integer , ForeignKey, Boolean +from sqlalchemy.dialects.postgresql import ARRAY +from sqlalchemy.orm import relationship +from context.service.database.models._Base import _Base +from .RoadmModel import RoadmTypeModel + +class OpticalConfigModel(_Base): + __tablename__ = 'optical_config' + opticalconfig_uuid = Column(String, primary_key=True) + channel_namespace = Column(String, nullable=True) + endpoints = Column(ARRAY(String), nullable=True) + type = Column(String,nullable=False) + + # transcievers = Column(ARRAY(String), nullable=True) + # interfaces = Column(String, nullable=True) + + + #channels = relationship("OpticalChannelModel") + transponders = relationship("TransponderTypeModel") + roadms = relationship("RoadmTypeModel") + + + device_uuid = Column(ForeignKey("device.device_uuid",ondelete="CASCADE"),index=True ,nullable=False) + device= relationship("DeviceModel", back_populates='optical_config') + + + + def dump_id (self ): + return { + "opticalconfig_uuid":self.opticalconfig_uuid, + "device_uuid" :self.device_uuid + } + + def dump(self): + obj={ + # "channels" : [channel.dump() for channel in self.channels], + # "transceivers" : {"transceiver": [transciever for transciever in self.transcievers]}, + # "interfaces" : {"interface":json.loads(self.interfaces) if self.interfaces else ''}, + "channel_namespace" : self.channel_namespace, + "endpoints" : [json.loads(endpoint) for endpoint in self.endpoints if endpoint], + "device_name" : self.device.device_name, + "type" : self.type + } + if self.type =="optical-transponder" : + channels= [transponer.dump() for transponer in self.transponders ][0] + obj['channels']=channels['channels'] if 'channels' in channels else None + obj['transceivers']=channels['transceivers'] if 'transceivers' in channels else None + obj['interfaces']=channels['interfaces'] if 'interfaces' in channels else None + obj['trasponder_uuid']=channels['trasponder_uuid'] if 'trasponder_uuid' in channels else None + + if self.type =="optical-roadm" : + channels=[roadms.dump() for roadms in self.roadms ][0] + obj['channels']=channels['channels'] if 'channels' in channels else None + obj['roadm_uuid']=channels['roadm_uuid'] if 'roadm_uuid' in channels else None + + + logging.info(f"optical_config_model {obj}") + return obj + diff --git a/src/context/service/database/models/OpticalConfig/RoadmModel.py b/src/context/service/database/models/OpticalConfig/RoadmModel.py new file mode 100644 index 000000000..8e37a3cf6 --- /dev/null +++ b/src/context/service/database/models/OpticalConfig/RoadmModel.py @@ -0,0 +1,81 @@ + +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json , logging +from sqlalchemy import Column, String, Integer , ForeignKey, Boolean +from sqlalchemy.dialects.postgresql import ARRAY +from sqlalchemy.orm import relationship +from context.service.database.models._Base import _Base + + + + +class RoadmTypeModel (_Base): + + __tablename__ = 'roadm_type' + roadm_uuid = Column(String, primary_key=True) + + channels = relationship("ChannelModel") + circuits = Column (String,nullable=True) + + opticalconfig_uuid = Column(ForeignKey('optical_config.opticalconfig_uuid', ondelete='CASCADE' ),index=True ,nullable=False) + opticalconfig = relationship('OpticalConfigModel', back_populates='roadms') + + def dump_id (self): + return { + "roadm_uuid":self.roadm_uuid + } + + def dump (self): + return { + "channels" : [channel.dump() for channel in self.channels], + "roadm_uuid" : self.dump_id() + } + +class ChannelModel(_Base): + __tablename__ = 'channel' + channel_uuid = Column(String, primary_key=True) + band_name = Column (String,nullable=True) + lower_frequency = Column(Integer, nullable=True) + upper_frequency = Column(Integer, nullable=True) + channel_index = Column(String , nullable=True) + status = Column(String , nullable=True) + src_port = Column(String, nullable=True) + dest_port = Column(String, nullable=True) + type = Column(String, nullable=False) + optical_band_parent = Column(String, nullable=True) + + roadm_uuid = Column(ForeignKey('roadm_type.roadm_uuid', ondelete='CASCADE' ),nullable=False) + roadm = relationship('RoadmTypeModel',back_populates='channels') + # opticalconfig_uuid = Column(ForeignKey('optical_config.opticalconfig_uuid', ondelete='CASCADE' ), primary_key=True) + # opticalconfig = relationship('OpticalConfigModel', back_populates='channels') + def dump_id (self ): + return { + "channel_uuid":self.channel_uuid + } + + def dump(self): + return { + "band_name" :self.band_name, + "lower_frequency" : self.lower_frequency, + "upper_frequency" : self.upper_frequency, + "type" : self.type, + "src_port" : self.src_port, + "dest_port" : self.dest_port, + "status":self.status, + "optical_band_parent":self.optical_band_parent, + "channel_index":self.channel_index + } + diff --git a/src/context/service/database/models/OpticalConfigModel.py b/src/context/service/database/models/OpticalConfig/TransponderModel.py similarity index 64% rename from src/context/service/database/models/OpticalConfigModel.py rename to src/context/service/database/models/OpticalConfig/TransponderModel.py index 46aaf2f9a..9a07536b1 100644 --- a/src/context/service/database/models/OpticalConfigModel.py +++ b/src/context/service/database/models/OpticalConfig/TransponderModel.py @@ -1,3 +1,4 @@ + # Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,50 +17,11 @@ import json , logging from sqlalchemy import Column, String, Integer , ForeignKey, Boolean from sqlalchemy.dialects.postgresql import ARRAY from sqlalchemy.orm import relationship -from ._Base import _Base +from context.service.database.models._Base import _Base + -class OpticalConfigModel(_Base): - __tablename__ = 'optical_config' - opticalconfig_uuid = Column(String, primary_key=True) - channel_namespace = Column(String, nullable=True) - endpoints = Column(ARRAY(String), nullable=True) - type = Column(String,nullable=False) - - # transcievers = Column(ARRAY(String), nullable=True) - # interfaces = Column(String, nullable=True) - - - #channels = relationship("OpticalChannelModel") - transponders=relationship("TransponderTypeModel") - - - device_uuid = Column(ForeignKey("device.device_uuid",ondelete="CASCADE"),index=True ,nullable=False) - device= relationship("DeviceModel", back_populates='optical_config') - - - def dump_id (self ): - return { - "opticalconfig_uuid":self.opticalconfig_uuid, - "device_uuid" :self.device_uuid - } - def dump(self): - obj={ - # "channels" : [channel.dump() for channel in self.channels], - # "transceivers" : {"transceiver": [transciever for transciever in self.transcievers]}, - # "interfaces" : {"interface":json.loads(self.interfaces) if self.interfaces else ''}, - "channel_namespace" : self.channel_namespace, - "endpoints" : [json.loads(endpoint) for endpoint in self.endpoints if endpoint], - "device_name" : self.device.device_name, - "type" : self.type - } - if self.type =="optical-transponder" : - channels= [transponer.dump() for transponer in self.transponders ][0] - obj['transponder']=channels - logging.info(f"optical_config_model {obj}") - return obj - class TransponderTypeModel (_Base): diff --git a/src/context/service/database/models/OpticalConfig/__init__.py b/src/context/service/database/models/OpticalConfig/__init__.py new file mode 100644 index 000000000..38d04994f --- /dev/null +++ b/src/context/service/database/models/OpticalConfig/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/src/context/service/database/uuids/OpticalConfig.py b/src/context/service/database/uuids/OpticalConfig.py index 2af60cd01..7bf75a9a2 100644 --- a/src/context/service/database/uuids/OpticalConfig.py +++ b/src/context/service/database/uuids/OpticalConfig.py @@ -30,9 +30,25 @@ def transponder_get_uuid( raise InvalidArgumentsException([ ('transponder uuid', opticalconfig_id), - ], extra_details=['Channel name is required to produce a channel UUID']) + ], extra_details=['opticalconfig id is required to produce a transponder UUID']) + + +def roadm_get_uuid( + opticalconfig_id :str , allow_random : bool = False +) -> str: + + + if opticalconfig_id is not None: + return get_uuid_from_string(f"{opticalconfig_id}-roadm") + + + raise InvalidArgumentsException([ + ('roadm uuid', opticalconfig_id), + + ], extra_details=['opticalconfig id is required to produce a roadm UUID']) + def opticalconfig_get_uuid ( device_id: DeviceId, allow_random : bool = False) -> str : device_uuid = device_id.device_uuid.uuid if (len(device_uuid)>0): diff --git a/src/device/service/OpenConfigServicer.py b/src/device/service/OpenConfigServicer.py index affdf304a..358f98fc2 100644 --- a/src/device/service/OpenConfigServicer.py +++ b/src/device/service/OpenConfigServicer.py @@ -16,7 +16,7 @@ import grpc, logging, json from common.method_wrappers.Decorator import MetricsPool, safe_and_metered_rpc_method from common.method_wrappers.ServiceExceptions import NotFoundException from common.proto.context_pb2 import ( - Device, DeviceId, DeviceOperationalStatusEnum, Empty, OpticalConfig, OpticalConfig,OpticalConfigList + Device, DeviceId, DeviceOperationalStatusEnum, Empty, OpticalConfig, OpticalConfig,OpticalConfigList,EndPoint ) from common.proto.device_pb2_grpc import DeviceServiceServicer from common.tools.context_queries.Device import get_device @@ -25,7 +25,7 @@ from context.client.ContextClient import ContextClient from .driver_api._Driver import _Driver from .driver_api.DriverInstanceCache import DriverInstanceCache, get_driver from .monitoring.MonitoringLoops import MonitoringLoops -from .Tools import extract_resources +from .Tools import extract_resources , get_endpoint_matching from .Tools import check_no_endpoints LOGGER = logging.getLogger(__name__) @@ -97,9 +97,26 @@ class OpenConfigServicer(DeviceServiceServicer): is_all_good=False LOGGER.info(f"resluts {results} and is_all_good {is_all_good}") if is_all_good: - driver.GetConfig(resource_keys=[]) - #TODO: add a control with the NETCONF get - #driver.GetConfig(resource_keys=filter_fields) + #driver.GetConfig(resource_keys=[]) + config = json.loads(request.config) + flow_handled=[ ] + if "new_config" in config and "flow" in config: + for flow in config['flow']: + src,dest= flow + LOGGER.info(f"src {src} and dest {dest}") + src_raw=get_endpoint_matching(device,src)if src != '0' else '0' + dest_raw=get_endpoint_matching(device,dest) if dest != '0' else '0' + src_name = src_raw.name if isinstance(src_raw,EndPoint) else '0' + dest_name= dest_raw.name if isinstance(dest_raw,EndPoint) else '0' + flow_handled.append((src_name,dest_name)) + LOGGER.info(f"flow_handled {flow_handled}") + if len(flow_handled)>0: + config['flow_handled']=flow_handled + + request.config=json.dumps(config) + + context_client.UpdateOpticalConfig(request) + context_client.close() except Exception as e: LOGGER.info("error in configuring %s",e) return Empty() @@ -107,14 +124,15 @@ class OpenConfigServicer(DeviceServiceServicer): @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def GetDeviceConfiguration (self, request : OpticalConfigList, context : grpc.ServicerContext) -> Empty: - + context_client = ContextClient() + for configs in request.opticalconfigs: device_uuid = configs.device_id.device_uuid.uuid LOGGER.info(f" Get DeviceConfigure {device_uuid}") try: - context_client = ContextClient() + device = get_device( context_client, device_uuid, rw_copy=True, include_endpoints=True, include_components=False, include_config_rules=False) @@ -135,8 +153,9 @@ class OpenConfigServicer(DeviceServiceServicer): #TODO: add a control with the NETCONF get #driver.GetConfig(resource_keys=filter_fields) except Exception as e: - LOGGER.info("error in configuring %s",e) - return Empty() + LOGGER.info("error in configuring %s",e) + context_client.close() + return Empty() @@ -144,7 +163,7 @@ class OpenConfigServicer(DeviceServiceServicer): @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def DisableOpticalDevice (self, request : OpticalConfig, context : grpc.ServicerContext) -> Empty: - LOGGER.info(f"Disable request from openconfigservicer {request}") + roadm_configuration=None device_uuid = request.device_id.device_uuid.uuid resources:list[dict]=[] is_all_good=True @@ -161,15 +180,25 @@ class OpenConfigServicer(DeviceServiceServicer): resources,conditions=extract_resources(config=config,device=device) LOGGER.info(f" Disable resources from openconfigservicer {resources} and conditions {conditions}") driver : _Driver = get_driver(self.driver_instance_cache, device) - results = driver.DeleteConfig(resources=resources,conditions=conditions) + if 'edit_type' in conditions and conditions['edit_type'] == 'optical-band': + roadm_configuration = driver.GetConfig() + for resource_data in roadm_configuration: + resource_key, resource_value = resource_data + if resource_key.startswith('/opticalconfigs/opticalconfig/'): + roadm_configuration=resource_value["opticalconfig"] + results = driver.DeleteConfig(resources=resources,conditions=conditions,optical_device_configuration=roadm_configuration) for result in results: if not result : is_all_good=False LOGGER.info(f"resluts {results} and is_all_good {is_all_good}") if is_all_good: - driver.GetConfig(resource_keys=[]) - #TODO: add a control with the NETCONF get - #driver.GetConfig(resource_keys=filter_fields) + config = json.loads(request.config) + flow_handled=[ ] + if "new_config" in config : + + + context_client.DeleteOpticalChannel(request) + context_client.close() except Exception as e: LOGGER.info("error in Disable configuring %s",e) return Empty() diff --git a/src/device/service/Tools.py b/src/device/service/Tools.py index 542057805..0a700481f 100644 --- a/src/device/service/Tools.py +++ b/src/device/service/Tools.py @@ -516,7 +516,7 @@ def extract_resources(config : dict, device : Device) -> list[list[dict],dict]: resources.append(is_key_existed('status', keys_dic=config['new_config'] , key_name_to_use="admin-state")) resources.append(is_key_existed('band_type', keys_dic=config['new_config'], key_name_to_use='name')) resources.append(is_key_existed('ob_id', keys_dic=config['new_config'], key_name_to_use='optical-band-parent')) - resources.append(is_key_existed('name', keys_dic=config['new_config'], key_name_to_use='channel_name')) + #resources.append(is_key_existed('name', keys_dic=config['new_config'], key_name_to_use='channel_name')) if not is_opticalband: if 'frequency' in config['new_config'] and 'band' in config['new_config'] and conditions['edit_type'] == 'media-channel': diff --git a/src/device/service/drivers/oc_driver/OCDriver.py b/src/device/service/drivers/oc_driver/OCDriver.py index 4e398f227..ffb57da05 100644 --- a/src/device/service/drivers/oc_driver/OCDriver.py +++ b/src/device/service/drivers/oc_driver/OCDriver.py @@ -38,7 +38,7 @@ from context.client.ContextClient import ContextClient from common.proto.context_pb2 import ( OpticalConfig) from .templates.descovery_tool.transponders import transponder_values_extractor -from .templates.descovery_tool.roadms import roadm_values_extractor ,openroadm_values_extractor +from .templates.descovery_tool.roadms import roadm_values_extractor ,openroadm_values_extractor,extract_media_channels DEBUG_MODE = False logging.getLogger('ncclient.manager').setLevel(logging.DEBUG if DEBUG_MODE else logging.WARNING) logging.getLogger('ncclient.transport.ssh').setLevel(logging.DEBUG if DEBUG_MODE else logging.WARNING) @@ -228,7 +228,7 @@ class OCDriver(_Driver): self.__netconf_handler = NetconfSessionHandler(self.address, self.port, **(self.settings)) self.__type = self.settings.get("type","optical-transponder") self.__device_uuid=device_uuid - + self.__pending_tasks=[] self.Connect() logging.info(f"settings {settings}") @@ -277,14 +277,14 @@ class OCDriver(_Driver): xml_data = self.__netconf_handler.get().data_xml logging.info(f"type {self.__type}") if (self.__type == "optical-transponder"): - oc_values["transponder"]={} + extracted_values=transponder_values_extractor(data_xml=xml_data,resource_keys=transponder_filter_fields,dic=config) transceivers,optical_channels_params,channel_namespace,endpoints,ports_result=extracted_values - oc_values["transponder"]["channels"]=optical_channels_params - oc_values["transponder"]["transceivers"]=transceivers - oc_values["transponder"]["channel_namespace"]=channel_namespace - oc_values["transponder"]["endpoints"]=endpoints - oc_values["transponder"]["ports"]=ports_result + oc_values["channels"]=optical_channels_params + oc_values["transceivers"]=transceivers + oc_values["channel_namespace"]=channel_namespace + oc_values["endpoints"]=endpoints + oc_values["ports"]=ports_result elif (self.__type =='openroadm') : extracted_values=openroadm_values_extractor(data_xml=xml_data,resource_keys=[],dic=oc_values) ports_result = extracted_values[1] @@ -294,6 +294,8 @@ class OCDriver(_Driver): else : extracted_values=roadm_values_extractor(data_xml=xml_data,resource_keys=[],dic=config) ports_result = extracted_values[0] + oc_values['optical_bands']=extracted_values[1] + oc_values['media_channels']=extracted_values[2] #results.append((resource_key, e)) # if validation fails, store the exception @@ -337,9 +339,69 @@ class OCDriver(_Driver): return results @metered_subclass_method(METRICS_POOL) - def DeleteConfig(self, resources : List[Tuple[str, Any]],conditions:dict) -> List[Union[bool, Exception]]: + def DeleteConfig(self, resources : List[Tuple[str, Any]],conditions:dict,optical_device_configuration=None) -> List[Union[bool, Exception]]: chk_type('resources', resources, list) if len(resources) == 0: return [] + + logging.info(f"device_config {optical_device_configuration}") + if 'edit_type' in conditions and conditions['edit_type'] == 'optical-band': + roadm_config={} + if isinstance(optical_device_configuration,OpticalConfig): + roadm_config = json.loads(optical_device_configuration.config) + + + if ("media_channels" in roadm_config): + media_channels= roadm_config["media_channels"] + ob_id = next((i['value'] for i in resources if i['resource_key']=='index'),None) + logging.info(f"media_channels {media_channels}") + logging.info(f"ob_id {ob_id}") + if len(media_channels)>0: + do_pending=False + for channel in media_channels: + if "optical_band_parent" in channel: + parent_ob_id=channel["optical_band_parent"] + if str(ob_id) == str(parent_ob_id): + do_pending=True + ob_existed=next((ob for ob in self.__pending_tasks if ob['ob_id']==ob_id),None) + if ob_existed is not None: + ob_existed["dependencies"] +=1 + ob_existed["dependent_media_channel"].append(channel['channel_index']) + else: + ob_channel={"resources":resources + ,'conditions':conditions + ,'dependent_media_channel':[channel['channel_index']] + ,"ob_id":ob_id + ,"dependencies":1 + } + self.__pending_tasks.append(ob_channel) + logging.info(f"pending_taks_ob {self.__pending_tasks}") + if(do_pending): return [] + + if 'edit_type' in conditions and conditions['edit_type'] == 'media-channel' : + channel_index = next((i['value'] for i in resources if i['resource_key']=='index'),None) + start_tasks=[] + for pending_ob in self.__pending_tasks: + is_dependent=next((m_c for m_c in pending_ob["dependent_media_channel"] if str(m_c) ==str(channel_index)),None) + if is_dependent is not None: + start_tasks.append({"resources":resources ,'conditions':conditions}) + pending_ob["dependencies"] -=1 + if (pending_ob["dependencies"] ==0): + start_tasks.append(pending_ob) + logging.info(f"pending_taks_mc {self.__pending_tasks}") + logging.info(f"start_tasks {start_tasks}") + if len(start_tasks) >0 : + with self.__lock: + for task in start_tasks: + if self.__netconf_handler.use_candidate: + with self.__netconf_handler.locked(target='candidate'): + results = edit_config( + self.__netconf_handler, self.__logger, task["resources"], target='candidate', delete=True, + commit_per_rule=self.__netconf_handler.commit_per_rule,conditions=task["conditions"]) + else: + results = edit_config(self.__netconf_handler, self.__logger, task["resources"], delete=True,conditions=task["conditions"]) + return results + + with self.__lock: if self.__netconf_handler.use_candidate: with self.__netconf_handler.locked(target='candidate'): diff --git a/src/device/service/drivers/oc_driver/templates/VPN/roadms.py b/src/device/service/drivers/oc_driver/templates/VPN/roadms.py index 6760f3751..4e3c24291 100644 --- a/src/device/service/drivers/oc_driver/templates/VPN/roadms.py +++ b/src/device/service/drivers/oc_driver/templates/VPN/roadms.py @@ -22,8 +22,9 @@ from .common import seperate_port_config def create_media_channel (resources): + optical_band_namespaces="http://flex-scale-project.eu/yang/flex-scale-mg-on" results=[] - unwanted_keys=['destination_port','source_port','channel_namespace','frequency','operational-mode', 'optical-band-parent'] + unwanted_keys=['destination_port','source_port','channel_namespace','frequency','operational-mode'] config,ports,index= seperate_port_config(resources,unwanted_keys=unwanted_keys) doc, tag, text = Doc().tagtext() @@ -46,6 +47,8 @@ def create_media_channel (resources): if resource['resource_key'] == "index": with tag('index'):text(str(int(index)+i)) + elif resource['resource_key']== 'optical-band-parent' : + with tag('optical-band-parent',xmlns=optical_band_namespaces):text(resource['value']) else: with tag(resource['resource_key']):text(resource['value']) if ('destination_port' in ports) and (ports['destination_port'][i] is not None): diff --git a/src/device/service/drivers/oc_driver/templates/descovery_tool/roadms.py b/src/device/service/drivers/oc_driver/templates/descovery_tool/roadms.py index 499a85e99..00276b01f 100644 --- a/src/device/service/drivers/oc_driver/templates/descovery_tool/roadms.py +++ b/src/device/service/drivers/oc_driver/templates/descovery_tool/roadms.py @@ -22,17 +22,108 @@ from typing import Collection, Dict, Any +def extract_channel_xmlns (data_xml:str,is_opticalband:bool): + xml_bytes = data_xml.encode("utf-8") + root = ET.fromstring(xml_bytes) + + namespace=None + channels=None + + if (not is_opticalband) : + + optical_channel_namespaces = { + 'ns': 'urn:ietf:params:xml:ns:netconf:base:1.0', + 'oc': 'http://openconfig.net/yang/platform', + } + + channels= root.find('.//{*}optical-channel',optical_channel_namespaces) + if channels is not None : + optical_channel_namespace = channels.tag.replace("optical-channel", "") + namespace=optical_channel_namespace.replace("{", "").replace("}", "") + else : + optical_band_namespaces= { + 'oc':'http://openconfig.net/yang/wavelength-router' + } + + channels= root.find('.//{*}optical-bands',optical_band_namespaces) + if channels is not None: + optical_channel_namespace = channels.tag.replace("optical-bands", "") + namespace=optical_channel_namespace.replace("{", "").replace("}", "") + + + return namespace +def extract_optical_bands (data_xml:str,namespace:str): + namespaces={"oc":namespace} + xml_bytes = data_xml.encode("utf-8") + root = ET.fromstring(xml_bytes) + op_bands=[] + optical_bands= root.find('.//oc:optical-bands',namespaces) + logging.info(f'optical_bands {optical_bands}') + if optical_bands is not None : + optical_bands_ele= optical_bands.findall('.//oc:optical-band',namespaces) + + + for optical_band in optical_bands_ele: + + band_ele=optical_band.find('.//oc:name',namespaces) + lower_freq_ele=optical_band.find('.//oc:lower-frequency',namespaces) + upper_freq_ele=optical_band.find('.//oc:upper-frequency',namespaces) + admin_status_ele=optical_band.find('.//oc:admin-status',namespaces) + source_ele=optical_band.find('.//oc:source/oc:config/oc:port-name',namespaces) + dest_ele=optical_band.find('.//oc:dest/oc:config/oc:port-name',namespaces) + channel_index= optical_band.find('.//oc:index',namespaces) + op_band_obj={ + 'band_name':band_ele.text if band_ele is not None else None, + 'lower_frequency':lower_freq_ele.text if lower_freq_ele is not None else None, + 'upper_frequency':upper_freq_ele.text if upper_freq_ele is not None else None, + 'status':admin_status_ele.text if admin_status_ele is not None else None, + 'src_port':source_ele.text if source_ele is not None else None, + 'dest_port':dest_ele.text if dest_ele is not None else None, + "channel_index":channel_index.text if channel_index is not None else None + } + op_bands.append(op_band_obj) + + return op_bands + +def extract_media_channels (data_xml:str): + optical_band_namespaces="http://flex-scale-project.eu/yang/flex-scale-mg-on" + namespaces={"oc":"http://openconfig.net/yang/wavelength-router",'ob_parent':optical_band_namespaces} + xml_bytes = data_xml.encode("utf-8") + root = ET.fromstring(xml_bytes) + media_channels= root.find(f'.//oc:media-channels',namespaces) + op_bands=[] + if media_channels is not None : + media_channels_ele= media_channels.findall('.//oc:channel',namespaces) - - -######################################################################### - -#################################### ROADMAs ############################ + for optical_band in media_channels_ele: + + band_ele=optical_band.find('.//oc:name',namespaces) + lower_freq_ele=optical_band.find('.//oc:lower-frequency',namespaces) + upper_freq_ele=optical_band.find('.//oc:upper-frequency',namespaces) + admin_status_ele=optical_band.find('.//oc:admin-status',namespaces) + source_ele=optical_band.find('.//oc:source/oc:config/oc:port-name',namespaces) + dest_ele=optical_band.find('.//oc:dest/oc:config/oc:port-name',namespaces) + ob_parent=optical_band.find('.//ob_parent:optical-band-parent',namespaces) + channel_index= optical_band.find('.//oc:index',namespaces) + op_band_obj={ + 'band_name':band_ele.text if band_ele is not None else None, + 'lower_frequency':lower_freq_ele.text if lower_freq_ele is not None else None, + 'upper_frequency':upper_freq_ele.text if upper_freq_ele is not None else None, + 'status':admin_status_ele.text if admin_status_ele is not None else None, + 'src_port':source_ele.text if source_ele is not None else None, + 'dest_port':dest_ele.text if dest_ele is not None else None, + 'optical_band_parent':ob_parent.text if ob_parent is not None else None, + 'channel_index':channel_index.text if channel_index is not None else None + } + op_bands.append(op_band_obj) + + return op_bands + + -########################################################################## def extract_roadm_ports (xml_data:str): @@ -69,6 +160,10 @@ def extract_roadm_ports (xml_data:str): def roadm_values_extractor (data_xml:str,resource_keys:list,dic:dict): ports_result=[] ports = extract_roadm_ports(data_xml) + namespcae= extract_channel_xmlns(data_xml,True) + optical_bands=extract_optical_bands(data_xml=data_xml,namespace=namespcae) + media_cahannels=extract_media_channels(data_xml) + if len(ports)>0 : for port in ports : @@ -77,7 +172,7 @@ def roadm_values_extractor (data_xml:str,resource_keys:list,dic:dict): resource_value = {'uuid': port, 'type':'MG_ON_OPTICAL_PORT_WAVEBAND'} ports_result.append((resource_key, resource_value)) - return [ports_result] + return [ports_result,optical_bands,media_cahannels] diff --git a/src/service/service/service_handlers/oc/OCServiceHandler.py b/src/service/service/service_handlers/oc/OCServiceHandler.py index 30e2f48d6..ea63cd408 100644 --- a/src/service/service/service_handlers/oc/OCServiceHandler.py +++ b/src/service/service/service_handlers/oc/OCServiceHandler.py @@ -66,6 +66,7 @@ class OCServiceHandler(_ServiceHandler): results = [] #new cycle for setting optical devices + LOGGER.info(f"settings_oc_service: {settings}") for device_uuid in flows.keys(): try: dev_flows = flows[device_uuid] diff --git a/src/service/service/task_scheduler/TaskExecutor.py b/src/service/service/task_scheduler/TaskExecutor.py index feed35536..1b8aeaf99 100644 --- a/src/service/service/task_scheduler/TaskExecutor.py +++ b/src/service/service/task_scheduler/TaskExecutor.py @@ -127,20 +127,29 @@ class TaskExecutor: optical_config = OpticalConfig() setting = settings.value if settings else "" + config_type=None new_config = {} try: + result = self._context_client.SelectOpticalConfig(optical_config_id) LOGGER.info("resul from select optical config %s",result) new_config = json.loads(result.config) + if 'type' in new_config: + config_type=new_config['type'] + if config_type == 'optical-transponder': + setting['status']='ENABLED' if result is not None : + new_config["new_config"] = setting + new_config["is_opticalband"] = is_opticalband new_config["flow"] = flows result.config = json.dumps(new_config) optical_config.CopyFrom(result) self._device_client.ConfigureOpticalDevice(optical_config) self._store_grpc_object(CacheableObjectType.DEVICE, device_key, device) + except Exception as e: LOGGER.info("error in configure_optical_device %s",e) @@ -168,18 +177,24 @@ class TaskExecutor: try: + + result = self._context_client.SelectOpticalConfig(optical_config_id) # for extractor in device service to extract the index , dummy data for freq and band required indexes["frequency"]=None indexes["band"]=None - - new_optical_config = OpticalConfig() - new_config["new_config"]=indexes - new_config["flow"] = flows - new_config["is_opticalband"] = is_opticalband - new_optical_config.config= json.dumps(new_config) - new_optical_config.opticalconfig_id.CopyFrom (optical_config_id) - new_optical_config.device_id.CopyFrom(device.device_id) - self._device_client.DisableOpticalDevice(new_optical_config) + if result is not None : + new_config = json.loads(result.config) + + new_optical_config = OpticalConfig() + new_config["new_config"]=indexes + new_config["flow"] = flows + new_config["is_opticalband"] = is_opticalband + result.config = json.dumps(new_config) + # new_optical_config.config= json.dumps(new_config) + # new_optical_config.opticalconfig_id.CopyFrom (optical_config_id) + # new_optical_config.device_id.CopyFrom(device.device_id) + self._device_client.DisableOpticalDevice(result) + except Exception as e: LOGGER.info("error in deconfigure_optical_device %s",e) diff --git a/src/webui/service/base_optical/route.py b/src/webui/service/base_optical/route.py index a07126819..8f3634a89 100644 --- a/src/webui/service/base_optical/route.py +++ b/src/webui/service/base_optical/route.py @@ -26,10 +26,10 @@ device_client = DeviceClient() context_client = ContextClient() @base_optical.get('/') def home(): - context_client.connect() - opticalConfig_list:OpticalConfigList = context_client.GetOpticalConfig(Empty()) - context_client.close() - device_client.connect() - device_client.GetDeviceConfiguration(opticalConfig_list) - device_client.close() + # context_client.connect() + # opticalConfig_list:OpticalConfigList = context_client.GetOpticalConfig(Empty()) + # context_client.close() + # device_client.connect() + # device_client.GetDeviceConfiguration(opticalConfig_list) + # device_client.close() return render_template("base_optical/home.html") \ No newline at end of file diff --git a/src/webui/service/opticalconfig/routes.py b/src/webui/service/opticalconfig/routes.py index 5882ef74c..872ed97c3 100644 --- a/src/webui/service/opticalconfig/routes.py +++ b/src/webui/service/opticalconfig/routes.py @@ -24,9 +24,9 @@ DESCRIPTOR_LOADER_NUM_WORKERS = 10 @opticalconfig.get("/") def home() : - config=[] + list_config=[] deviceId= DeviceId() - + channels_num = 0 if 'context_uuid' not in session or 'topology_uuid' not in session: flash("Please select a context!", "warning") return redirect(url_for("main.home")) @@ -41,23 +41,24 @@ def home() : value=json.loads(configs.config) if type(configs.config)==str else configs.config config_type = value["type"] - if (config_type == 'optical-transponder'): - channels_num = value['transponder'] - value["channels_number"]=len(channels_num['channels']) if 'channels' in channels_num else 0 - + if ('channels' in value): + + + channels_num=len(value['channels']) + value["channels_number"]=channels_num # value['operationalMode']=value['operational-mode'] # value['targetOutputPower']=value['target-output-power'] value['opticalconfig_id']=configs.opticalconfig_id # value['line_port']=value["line-port"] - config.append(value) + list_config.append(value) - logging.info("opticalConfig %s",config) + logging.info("opticalConfig %s",list_config) context_client.close() return render_template( - 'opticalconfig/home.html', config=config) + 'opticalconfig/home.html', config=list_config) @@ -86,9 +87,9 @@ def show_details(config_uuid): LOGGER.info("config details from show detail %s",config) - if 'channels' in config['transponder']: + if 'channels' in config: - for channel in config['transponder']['channels'] : + for channel in config['channels'] : new_config={} new_config["name"]=channel['name'] new_config['operationalMode']=channel['operational-mode'] if 'operational-mode' in channel else '' @@ -98,9 +99,30 @@ def show_details(config_uuid): new_config["status"] = channel['status'] if 'status' in channel else "" device_details.append(new_config) + + if config_type == 'optical-roadm': + + LOGGER.info("config details from show detail %s",config) + + + if 'channels' in config: + + for channel in config['channels'] : + new_config={} + new_config["band_name"]=channel['band_name'] if 'band_name' in channel else None + new_config['type']=channel['type'] if 'type' in channel else '' + new_config['src_port']=channel['src_port'] if 'src_port' in channel else '' + new_config['dest_port']=channel['dest_port'] if 'dest_port' in channel else '' + new_config["lower_frequency"]=channel['lower_frequency'] if 'lower_frequency' in channel else '' + new_config["upper_frequency"]=channel['upper_frequency'] if 'upper_frequency' in channel else '' + new_config["status"] = channel['status'] if 'status' in channel else "" + new_config['optical_band_parent']= channel['optical_band_parent'] if 'optical_band_parent' in channel else '' + new_config['channel_index']= channel['channel_index'] if 'channel_index' in channel else '' + + device_details.append(new_config) LOGGER.info("device details %s",device_details) - return render_template('opticalconfig/details.html', device=device_details,config_id=config_uuid,device_name=device_name) + return render_template('opticalconfig/details.html', device=device_details,config_id=config_uuid,device_name=device_name,type=config_type) @@ -152,7 +174,7 @@ def update_externally () : new_opticalconfig = OpticalConfig() new_opticalconfig.CopyFrom(opticalconfig) config =json.loads(opticalconfig.config) - channels= config['transponder']['channels'] + channels= config['channels'] target_channel =next((item for item in channels if item["name"]['index'] == channel_name) , None) LOGGER.info(f"target channel {target_channel}") target_power=device.get( "target-output-power") @@ -162,14 +184,14 @@ def update_externally () : if target_channel: if target_power : - target_channel["target-output-power"] =target_power + target_channel["target-output-power"] =str(target_power) if freq : target_channel["frequency"] =freq if mode : target_channel["operational-mode"] =mode - del target_channel['name'] + #del target_channel['name'] config["new_config"]=target_channel config["new_config"]["channel_name"]=channel_name config["flow"]=[(port,'0')] @@ -260,36 +282,16 @@ def update(config_uuid,channel_name): flash(f'Problem updating the device. {e}', 'danger') return render_template('myabout/update.html', device=response, form=form, submit_text='Update Device',channel_name=channel_name) -# @opticalconfig.route('<path:config_uuid>/<path:interface_name>/update_interface', methods=['GET', 'POST']) -# def update_interface (config_uuid,interface_name): -# form = UpdateInterfaceForm() -# opticalconfigId=OpticalConfigId() -# opticalconfigId.opticalconfig_uuid=config_uuid -# context_client.connect() -# response = context_client.SelectOpticalConfig(myid) -# context_client.close() -# LOGGER.info("response %s",response) -# opticalconfig = OpticalConfig() -# opticalconfig.CopyFrom(response) -# config =json.loads(opticalconfig.config) -# new_config={} -# if form.validate_on_submit(): -# new_config["ip"]=form.ip.data if form.ip.data != "" else config["interfaces"]["interface"]["ip"] -# new_config["prefix-length"]=form.prefix_length.data if form.prefix_length.data != "" else config["interfaces"]["interface"]["prefix-length"] -# new_config["name"]=config["interfaces"]["interface"]["name"] -# new_config["enabled"]=config["interfaces"]["interface"]["enabled"] - -# opticalconfig.config=json.dumps({"update_interface":new_config}) -# try: -# device_client.connect() -# device_client.ConfigureOpticalDevice(opticalconfig) -# device_client.close() -# flash(f' device was updated.', 'success') -# return redirect(url_for('opticalconfig.show_details',config_uuid=config_uuid)) -# except Exception as e: # pylint: disable=broad-except -# flash(f'Problem updating the device. {e}', 'danger') -# return render_template('opticalconfig/update_interface.html', -# device=response, form=form, submit_text='Update interface',interface_name=interface_name) + +@opticalconfig.route('refresh_all',methods=['POST','GET']) +def refresh_all (): + context_client.connect() + opticalConfig_list:OpticalConfigList = context_client.GetOpticalConfig(Empty()) + context_client.close() + device_client.connect() + device_client.GetDeviceConfiguration(opticalConfig_list) + device_client.close() + return home() @opticalconfig.route('<path:config_uuid>/add_transceiver', methods=['GET','POST']) def add_transceiver (config_uuid): diff --git a/src/webui/service/templates/opticalconfig/details.html b/src/webui/service/templates/opticalconfig/details.html index e2048d7db..e1d009efd 100644 --- a/src/webui/service/templates/opticalconfig/details.html +++ b/src/webui/service/templates/opticalconfig/details.html @@ -56,42 +56,70 @@ </div> </div> - {% for channel in device %} - <div class="col-sm-12"> - <span style="font-weight: 700;">channel name:{{channel.name.index}}</span> - </div> - - <div class="col-sm-4"> - <span>Frequency:</span> - <p class="font-weight-bold" style="font-weight: 700;">{{channel.frequency}}</p> - </div> - <div class="col-sm-4"> - <span>Target Output Power:</span> - <span class="font-weight-bold" style="font-weight: 700;">{{channel.targetOutputPower}}</span> - </div> - <div class="col-sm-4"> - <span>Operational Mode:</span> - <span class="font-weight-bold" style="font-weight: 700;">{{channel.operationalMode}}</span> - </div> - <div class="col-sm-4"> - <span>Line Port:</span> - <span class="font-weight-bold" style="font-weight: 700;">{{channel.line_port}}</span> - </div> - {% if channel.status%} - <div class="col-sm-4"> - <span>Channel Status :</span> - <span class="font-weight-bold" style="font-weight:700 ;">{{channel.status}}</span> - <span> - <a id="update" class="btn btn-secondary" href="{{ url_for('opticalconfig.update_status',config_uuid=config_id,channel_name=channel.name.index)}}"> - <i class="bi bi-pencil-square"></i> - </a> - </span> - </div> - {%endif %} - - <div class="col-sm-12 hr bg-primary" style="height:1px; margin:5px 0;"></div> - {% endfor %} + {% if type == 'optical-transponder' %} + <table class="table table-striped table-hover"> + <thead> + <tr> + <th scope="col">channel name</th> + <th scope="col">Frequency</th> + <th scope="col">Target Output Power</th> + <th scope="col">Operational Mode</th> + <th scope="col">Line Port</th> + <th scope="col">Channel Status</th> + </tr> + </thead> + <tbody> + + {% for channel in device %} + <tr style="background-color:{%if channel.status == 'DISABLED' %} gray {% endif %};"> + <td>{{channel.name.index}}</td> + <td>{{channel.frequency}}</td> + <td> {{channel.targetOutputPower}}</td> + <td>{{channel.operationalMode}}</td> + <td>{{channel.line_port}}</td> + <td> {{channel.status}}</td> + </tr> + {% endfor %} + </tbody> + </table> + {%else%} + <table class="table table-striped table-hover"> + <thead> + <tr> + <th scope="col">Channel Index</th> + <th scope="col">Optical Band Parent</th> + <th scope="col">Band Name</th> + <th scope="col">Lower Frequency</th> + <th scope="col">Upper Frequency</th> + <th scope="col">type</th> + <th scope="col"> Source Port</th> + <th scope="col">Destination Port</th> + <th scope="col">Channel Status </th> + + </tr> + </thead> + <tbody> + + {% for channel in device %} + <tr> + <td>{{channel.channel_index}}</td> + <td>{{channel.optical_band_parent}}</td> + <td> {{channel.band_name}}</td> + <td>{{channel.lower_frequency}}</td> + <td>{{channel.upper_frequency}}</td> + <td> {{channel.type}}</td> + <td>{{channel.src_port}}</td> + <td>{{channel.dest_port}}</td> + <td> {{channel.status}}</td> + + </tr> + {% endfor %} + + </tbody> + </table> + + {% endif%} </div> diff --git a/src/webui/service/templates/opticalconfig/home.html b/src/webui/service/templates/opticalconfig/home.html index 1a49c0e00..5eda33ede 100644 --- a/src/webui/service/templates/opticalconfig/home.html +++ b/src/webui/service/templates/opticalconfig/home.html @@ -13,12 +13,12 @@ See the License for the specific language governing permissions and limitations under the License. --> - <a class="nav-link active" aria-current="page" href="{{ url_for('base_optical.home') }}">Optical Config</a> + {% extends 'base.html' %} {% block content %} <h1>My Configurations</h1> - + {% if config %} <div class="row"> <div class="col-sm-12"> <div class="row mb-3 "> @@ -35,10 +35,15 @@ <i class="bi bi-x-square"></i> Delete All </button> --> + <button type="button" class="btn btn-info" + onclick="window.location.href='{{ url_for('opticalconfig.refresh_all') }}'"> + <i class="bi bi-arrow-clockwise"></i> + Refresh + </button> </div> </div> </div> - {% if config %} + <table class="table table-striped table-hover"> <thead> @@ -57,12 +62,12 @@ <td>{{device.opticalconfig_id.opticalconfig_uuid}}</td> <td>{{device.device_name}}</td> <td> {{device.type}}</td> - {% if device.type == 'optical-transponder'%} + {% if device.channels_number %} <td>{{ device.channels_number }}</td> {%else%} <td>__ </td> {%endif%} - + {% if device.opticalconfig_id %} <td> <a href="{{ url_for('opticalconfig.show_details', config_uuid=device.opticalconfig_id.opticalconfig_uuid) }}"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-eye" viewBox="0 0 16 16"> @@ -71,6 +76,7 @@ </svg> </a> </td> + {%endif%} </tr> {% endfor %} -- GitLab