diff --git a/.gitignore b/.gitignore index 7635bb0d2d784ada9a05abec91d2a6ec729a7ecd..5b7ac121a7512842cb158fbb3fc514c504beebfb 100644 --- a/.gitignore +++ b/.gitignore @@ -192,4 +192,3 @@ libyang/ # Other logs **/logs/*.log.* - diff --git a/proto/context.proto b/proto/context.proto index ed194a87d5cf2d2cdc786811bb0e7482333ce121..9fde6a3c132843eeab8e921ad49aedb3f2cd18fb 100644 --- a/proto/context.proto +++ b/proto/context.proto @@ -251,6 +251,7 @@ enum DeviceDriverEnum { DEVICEDRIVER_MORPHEUS = 17; DEVICEDRIVER_RYU = 18; DEVICEDRIVER_GNMI_NOKIA_SRLINUX = 19; + DEVICEDRIVER_OPENROADM = 20; } enum DeviceOperationalStatusEnum { diff --git a/src/common/DeviceTypes.py b/src/common/DeviceTypes.py index 008cb448d1cc849dddf67bf314e0dccec0cd81ae..1be1085536f68c527d52dd074bc422150397933d 100644 --- a/src/common/DeviceTypes.py +++ b/src/common/DeviceTypes.py @@ -30,6 +30,7 @@ class DeviceTypeEnum(Enum): EMULATED_OPEN_LINE_SYSTEM = 'emu-open-line-system' EMULATED_OPTICAL_ROADM = 'emu-optical-roadm' EMULATED_OPTICAL_TRANSPONDER = 'emu-optical-transponder' + EMULATED_OPEN_ROADM = 'emu-optical-openroadm' EMULATED_OPTICAL_SPLITTER = 'emu-optical-splitter' # passive component required for XR Constellation EMULATED_P4_SWITCH = 'emu-p4-switch' EMULATED_PACKET_RADIO_ROUTER = 'emu-packet-radio-router' diff --git a/src/common/tools/context_queries/OpticalConfig.py b/src/common/tools/context_queries/OpticalConfig.py index 23d1244cd60a505a329af522509c69f3f5559738..3e8a5380e7f1d46639a0890e43e89fe27f9ecd2d 100644 --- a/src/common/tools/context_queries/OpticalConfig.py +++ b/src/common/tools/context_queries/OpticalConfig.py @@ -67,6 +67,18 @@ def opticalconfig_get_uuid( ('name', device_name), ], extra_details=['At least one is required to produce a OpticalConfig UUID']) +def opticalconfig_uuid_get_duuid( + device_uuid , allow_random : bool = False +) -> str: + + if (len(device_uuid)>0): + return get_uuid_from_string(f"{device_uuid}_opticalconfig") + if allow_random: return get_uuid_random() + + raise InvalidArgumentsException([ + ('DeviceId ', device_id), + ], extra_details=['device_id is required to produce a OpticalConfig UUID']) + def ob_get_uuid( ob_name:str , allow_random : bool = False diff --git a/src/context/service/database/OpticalConfig.py b/src/context/service/database/OpticalConfig.py index 24a669f04fba3356e5d789fc5a9ccfd9b5f40034..7f6942d046e2ec477422d3450625bd370a3baf08 100644 --- a/src/context/service/database/OpticalConfig.py +++ b/src/context/service/database/OpticalConfig.py @@ -77,8 +77,7 @@ def set_opticalconfig(db_engine : Engine, request : OpticalConfig): if channel_namespace is None and 'channel_namespace' in config: channel_namespace=config['channel_namespace'] - # if 'transceivers' in config and len(config['transceivers']['transceiver']) > 0: - # transceivers = [transceiver for transceiver in config ['transceivers']['transceiver']] + if 'interfaces' in config and len(config['interfaces']) > 0: for interface in config['interfaces']: interface_name=interface["name"] if "name" in interface else None @@ -86,24 +85,24 @@ def set_opticalconfig(db_engine : Engine, request : OpticalConfig): "transponder_uuid" : transponder_get_uuid(device_id), 'interface_uuid' : interface_get_uuid(interface_name,device_uuid), "name" : interface_name, - "status" : interface["status"] if "status" in interface else None, - "operation_status" : interface["operation_status"] if "operation_status" in interface else None, - "ifindex" : interface["ifindex"] if "ifindex" in interface else None, - "in_octets" : interface["in_octets"] if "in_octets" in interface else None, - "in_pkts" : interface["in_pkts"] if "in_pkts" in interface else None, - "in_unicast_pkts" : interface["in_unicast_pkts"] if "in_unicast_pkts" in interface else None, - "in_broadcast_pkts" : interface["in_broadcast_pkts"] if "in_broadcast_pkts" in interface else None, - "in_multicast_pkts" : interface["in_multicast_pkts"] if "in_multicast_pkts" in interface else None, - "out_discards" : interface["out_discards"] if "out_discards" in interface else None, - "out_errors" : interface["out_errors"] if "out_errors" in interface else None, - "in_discards" : interface["in_discards"] if "in_discards" in interface else None, - "in_errors" : interface["in_errors"] if "in_errors" in interface else None, - "out_octets" : interface["out_octets"] if "out_octets" in interface else None, - "out_pkts" : interface["out_pkts"] if "out_pkts" in interface else None, - "out_unicast_pkts" : interface["out_unicast_pkts"] if "out_unicast_pkts" in interface else None, + "status" : interface["status" ] if "status" in interface else None, + "operation_status" : interface["operation_status" ] if "operation_status" in interface else None, + "ifindex" : interface["ifindex" ] if "ifindex" in interface else None, + "in_octets" : interface["in_octets" ] if "in_octets" in interface else None, + "in_pkts" : interface["in_pkts" ] if "in_pkts" in interface else None, + "in_unicast_pkts" : interface["in_unicast_pkts" ] if "in_unicast_pkts" in interface else None, + "in_broadcast_pkts" : interface["in_broadcast_pkts" ] if "in_broadcast_pkts" in interface else None, + "in_multicast_pkts" : interface["in_multicast_pkts" ] if "in_multicast_pkts" in interface else None, + "out_discards" : interface["out_discards" ] if "out_discards" in interface else None, + "out_errors" : interface["out_errors" ] if "out_errors" in interface else None, + "in_discards" : interface["in_discards" ] if "in_discards" in interface else None, + "in_errors" : interface["in_errors" ] if "in_errors" in interface else None, + "out_octets" : interface["out_octets" ] if "out_octets" in interface else None, + "out_pkts" : interface["out_pkts" ] if "out_pkts" in interface else None, + "out_unicast_pkts" : interface["out_unicast_pkts" ] if "out_unicast_pkts" in interface else None, "out_broadcast_pkts" : interface["out_broadcast_pkts"] if "out_broadcast_pkts" in interface else None, "out_multicast_pkts" : interface["out_multicast_pkts"] if "out_multicast_pkts" in interface else None, - "last_clear" : interface["last_clear"] if "last_clear" in interface else None + "last_clear" : interface["last_clear" ] if "last_clear" in interface else None }) if 'channels' in config and len(config['channels']) > 0: @@ -178,7 +177,7 @@ def set_opticalconfig(db_engine : Engine, request : OpticalConfig): if 'interfaces' in config: for interface in config['interfaces']: interfaces.append({ - "interface_uuid" : interface_get_uuid(interface['name']), + "interface_uuid" : interface_get_uuid(interface['name'],device_uuid), 'name' : interface["name"], "type" : interface["type"], "administrative_state": interface["administrative_state"], @@ -186,7 +185,27 @@ def set_opticalconfig(db_engine : Engine, request : OpticalConfig): "port" : interface["port"], "interface_list" : interface["interface_list"], "frequency" : interface["frequency"], - "width" : interface["width"], + "width" : int(float(interface["width"])) if 'width' in interface else 0, + "roadm_uuid" : roadm_get_uuid(device_id), + }) + if 'circuits' in config: + for interface in config['circuits']: + ports_g='' + if 'port' in interface : + for p in interface['port']: + if ports_g != '': ports_g +='/' + ports_g= ports_g+ p['port_name'] + + interfaces.append({ + "interface_uuid" : interface_get_uuid(interface["interface_uuid"],device_uuid), + 'name' : interface["interface_list"] if 'interface_list' in interface else interface["interface_uuid"], + "type" : "Null", + "administrative_state": interface["administrative_state"] if 'administrative_state' in interface else "not-in-service", + "circuit_pack_name" : interface["circuit_pack_name"], + "port" : ports_g, + "interface_list" : interface["interface_list"] if 'interface_list' in interface else "Null", + "frequency" : interface["frequency"] if 'frequency' in interface else 0.0, + "width" : int(float(interface["width"])) if 'width' in interface else 0, "roadm_uuid" : roadm_get_uuid(device_id), }) roadms.append({ @@ -243,17 +262,17 @@ def set_opticalconfig(db_engine : Engine, request : OpticalConfig): ) stmt = stmt.returning(OpticalChannelModel.channel_uuid) channel_id = session.execute(stmt).fetchone() - if (len(interfaces)>0) : - model_columns = inspect(TransponderInterfaceModel).c.keys() - stmt = insert(TransponderInterfaceModel).values(interfaces) - - stmt = stmt.on_conflict_do_update( - index_elements=[TransponderInterfaceModel.interface_uuid ], - set_={field: stmt.excluded[field] for field in model_columns if field != 'interface_uuid' } + if (len(interfaces)>0) : + model_columns = inspect(TransponderInterfaceModel).c.keys() + stmt = insert(TransponderInterfaceModel).values(interfaces) - ) - stmt = stmt.returning(TransponderInterfaceModel.interface_uuid) - interface_id = session.execute(stmt).fetchone() + stmt = stmt.on_conflict_do_update( + index_elements=[TransponderInterfaceModel.interface_uuid ], + set_={field: stmt.excluded[field] for field in model_columns if field != 'interface_uuid' } + + ) + stmt = stmt.returning(TransponderInterfaceModel.interface_uuid) + interface_id = session.execute(stmt).fetchone() if config_type == DeviceTypeEnum.OPTICAL_ROADM._value_: @@ -299,24 +318,27 @@ def set_opticalconfig(db_engine : Engine, request : OpticalConfig): stmt = stmt.returning(RoadmTypeModel.roadm_uuid) roadm_id = session.execute(stmt).fetchone() - if len(interfaces) > 0: - stmt = insert(ORInterfaceModel).values(interfaces) - stmt = stmt.on_conflict_do_update( - index_elements=[ORInterfaceModel.interface_uuid], - set_=dict( - name = stmt.excluded.name, - frequency = stmt.excluded.frequency, - administrative_state = stmt.excluded.administrative_state, - type = stmt.excluded.type, - circuit_pack_name = stmt.excluded.circuit_pack_name, - port = stmt.excluded.port, - interface_list = stmt.excluded.interface_list, - width = stmt.excluded.width, - ) - ) - stmt = stmt.returning(ORInterfaceModel.interface_uuid) - opticalChannel_id = session.execute(stmt).fetchone() - + if len(interfaces) > 0: + ifc_uuids=[] + for ifc in interfaces : + stmt = insert(ORInterfaceModel).values(**ifc) + update_stmt = stmt.on_conflict_do_update( + index_elements=[ORInterfaceModel.interface_uuid], + set_=dict( + name = stmt.excluded.name, + frequency = stmt.excluded.frequency, + administrative_state = stmt.excluded.administrative_state, + type = stmt.excluded.type, + circuit_pack_name = stmt.excluded.circuit_pack_name, + port = stmt.excluded.port, + interface_list = stmt.excluded.interface_list, + width = stmt.excluded.width, + ) + ).returning(ORInterfaceModel.interface_uuid) + result = session.execute(update_stmt).fetchone() + ifc_uuids.append(result) + else : + session.query(ORInterfaceModel).delete() opticalconfig_id = run_transaction(sessionmaker(bind=db_engine), callback) return {'opticalconfig_uuid': opticalconfig_id} @@ -335,7 +357,6 @@ def update_opticalconfig(db_engine : Engine, request : OpticalConfig): #is_transpondre = False opticalconfig_uuid = opticalconfig_get_uuid(device_id) is_optical_band=None - LOGGER.info(f"update_opticalconfig {request}") if request.config : config = json.loads(request.config) @@ -360,27 +381,27 @@ def update_opticalconfig(db_engine : Engine, request : OpticalConfig): for interface in config['new_config']['interfaces']: interface_name=interface["name"] if "name" in interface else None interfaces.append({ - "transponder_uuid" : transponder_get_uuid(device_id), - 'interface_uuid' : interface_get_uuid(interface_name,device_uuid), - "name" : interface_name, - "status" : interface["status"] if "status" in interface else None, - "operation_status" : interface["operation_status"] if "operation_status" in interface else None, - "ifindex" : interface["ifindex"] if "ifindex" in interface else None, - "in_octets" : interface["in_octets"] if "in_octets" in interface else None, - "in_pkts" : interface["in_pkts"] if "in_pkts" in interface else None, - "in_unicast_pkts" : interface["in_unicast_pkts"] if "in_unicast_pkts" in interface else None, - "in_broadcast_pkts" : interface["in_broadcast_pkts"] if "in_broadcast_pkts" in interface else None, - "in_multicast_pkts" : interface["in_multicast_pkts"] if "in_multicast_pkts" in interface else None, - "out_discards" : interface["out_discards"] if "out_discards" in interface else None, - "out_errors" : interface["out_errors"] if "out_errors" in interface else None, - "in_discards" : interface["in_discards"] if "in_discards" in interface else None, - "in_errors" : interface["in_errors"] if "in_errors" in interface else None, - "out_octets" : interface["out_octets"] if "out_octets" in interface else None, - "out_pkts" : interface["out_pkts"] if "out_pkts" in interface else None, - "out_unicast_pkts" : interface["out_unicast_pkts"] if "out_unicast_pkts" in interface else None, - "out_broadcast_pkts" : interface["out_broadcast_pkts"] if "out_broadcast_pkts" in interface else None, - "out_multicast_pkts" : interface["out_multicast_pkts"] if "out_multicast_pkts" in interface else None, - "last_clear" : interface["last_clear"] if "last_clear" in interface else None + "transponder_uuid" : transponder_get_uuid(device_id), + 'interface_uuid' : interface_get_uuid(interface_name,device_uuid), + "name" : interface_name, + "status" : interface["status" ] if "status" in interface else None, + "operation_status" : interface["operation_status" ] if "operation_status" in interface else None, + "ifindex" : interface["ifindex" ] if "ifindex" in interface else None, + "in_octets" : interface["in_octets" ] if "in_octets" in interface else None, + "in_pkts" : interface["in_pkts" ] if "in_pkts" in interface else None, + "in_unicast_pkts" : interface["in_unicast_pkts" ] if "in_unicast_pkts" in interface else None, + "in_broadcast_pkts" : interface["in_broadcast_pkts" ] if "in_broadcast_pkts" in interface else None, + "in_multicast_pkts" : interface["in_multicast_pkts" ] if "in_multicast_pkts" in interface else None, + "out_discards" : interface["out_discards" ] if "out_discards" in interface else None, + "out_errors" : interface["out_errors" ] if "out_errors" in interface else None, + "in_discards" : interface["in_discards" ] if "in_discards" in interface else None, + "in_errors" : interface["in_errors" ] if "in_errors" in interface else None, + "out_octets" : interface["out_octets" ] if "out_octets" in interface else None, + "out_pkts" : interface["out_pkts" ] if "out_pkts" in interface else None, + "out_unicast_pkts" : interface["out_unicast_pkts" ] if "out_unicast_pkts" in interface else None, + "out_broadcast_pkts" : interface["out_broadcast_pkts"] if "out_broadcast_pkts" in interface else None, + "out_multicast_pkts" : interface["out_multicast_pkts"] if "out_multicast_pkts" in interface else None, + "last_clear" : interface["last_clear" ] if "last_clear" in interface else None }) if 'channels' in config['new_config'] and len(config['new_config']['channels']) > 0: @@ -618,7 +639,7 @@ def delete_opticalchannel(db_engine : Engine, messagebroker : MessageBroker, req opticalconfig_uuid = request.opticalconfig_id.opticalconfig_uuid channels = [] config_type = None - + logging.info(f"DeleteOpticalchannel {request.config}") if "type" in config : config_type= config["type"] if 'new_config' in config: @@ -640,6 +661,23 @@ def delete_opticalchannel(db_engine : Engine, messagebroker : MessageBroker, req "target_output_power": None, "status" : "DISABLED" }) + + elif config_type == DeviceTypeEnum.OPEN_ROADM._value_: + if 'new_config' in config and 'interfaces' in config['new_config']: + for i in config['new_config']['interfaces']: + channels.append({ + "interface_uuid" : interface_get_uuid(i['interface_name'],device_uuid), + 'name' : i["interface_name"], + "type" :'Null', + "administrative_state": "Null", + "circuit_pack_name" : 'Null', + "port" : "Null", + "interface_list" :'Null', + "frequency" : "Null", + "width" : 0, + "roadm_uuid" : roadm_get_uuid(device_id), + }) + elif config_type == DeviceTypeEnum.OPTICAL_ROADM._value_: channel_num = flow_id @@ -668,7 +706,7 @@ def delete_opticalchannel(db_engine : Engine, messagebroker : MessageBroker, req channels.append(channel_get_uuid(channel_name, device_uuid)) channel_num += 1 - LOGGER.info(f"channels to delete {channels}") + def callback(session : Session): all_suceed = [] @@ -692,6 +730,12 @@ def delete_opticalchannel(db_engine : Engine, messagebroker : MessageBroker, req stmt = stmt.returning(OpticalChannelModel.channel_uuid) opticalChannel_id = session.execute(stmt).fetchone() all_suceed.append(True) + elif config_type == DeviceTypeEnum.OPEN_ROADM._value_: + if len(channels) > 0: + for i in channels : + num_deleted = session.query(ORInterfaceModel).filter_by(interface_uuid=i['interface_uuid']).delete() + all_suceed.append(num_deleted > 0) + return all_suceed all_deleted = run_transaction(sessionmaker(bind=db_engine), callback) diff --git a/src/context/service/database/models/OpticalConfig/RoadmModel.py b/src/context/service/database/models/OpticalConfig/RoadmModel.py index fb48b3e4ea0a231007b0fb64621c9854d66b9fea..744a11e3450f49c9883b4f0e293669270f6ce3dd 100644 --- a/src/context/service/database/models/OpticalConfig/RoadmModel.py +++ b/src/context/service/database/models/OpticalConfig/RoadmModel.py @@ -79,7 +79,7 @@ class ORInterfaceModel (_Base): __tablename__ = 'open_roadm_interface' interface_uuid = Column(String, primary_key = True) - name = Column(String, nullable = False, unique = True) + name = Column(String, nullable = False) type = Column(String, nullable = True) administrative_state = Column(String, nullable = True) circuit_pack_name = Column(String, nullable = True) diff --git a/src/context/service/database/models/enums/DeviceDriver.py b/src/context/service/database/models/enums/DeviceDriver.py index f48b96fccfab0015aa2f403af228716e99241ff6..faa8ace3af721368e843fd95a604f029d5534371 100644 --- a/src/context/service/database/models/enums/DeviceDriver.py +++ b/src/context/service/database/models/enums/DeviceDriver.py @@ -42,6 +42,7 @@ class ORM_DeviceDriverEnum(enum.Enum): MORPHEUS = DeviceDriverEnum.DEVICEDRIVER_MORPHEUS RYU = DeviceDriverEnum.DEVICEDRIVER_RYU GNMI_NOKIA_SRLINUX = DeviceDriverEnum.DEVICEDRIVER_GNMI_NOKIA_SRLINUX + OPENROADM = DeviceDriverEnum.DEVICEDRIVER_OPENROADM grpc_to_enum__device_driver = functools.partial( grpc_to_enum, DeviceDriverEnum, ORM_DeviceDriverEnum) diff --git a/src/device/service/DeviceServiceServicerImpl.py b/src/device/service/DeviceServiceServicerImpl.py index f6523a107e898087ccf0c274d81f6547667fd6bb..f52347a57851118f3c655cb084bebb9e62763216 100644 --- a/src/device/service/DeviceServiceServicerImpl.py +++ b/src/device/service/DeviceServiceServicerImpl.py @@ -69,6 +69,7 @@ class DeviceServiceServicerImpl(DeviceServiceServicer): context_client = ContextClient() device = get_device(context_client, device_uuid, rw_copy=True) + if device is None: # not in context, create blank one to get UUID, and populate it below device = Device() @@ -126,7 +127,11 @@ class DeviceServiceServicerImpl(DeviceServiceServicer): else: t_pop_endpoints = None - is_optical_device = request.device_drivers[0] == DeviceDriverEnum.DEVICEDRIVER_OC + OPTICAL_DRIVERS = { + DeviceDriverEnum.DEVICEDRIVER_OC, + DeviceDriverEnum.DEVICEDRIVER_OPENROADM, + } + is_optical_device = request.device_drivers[0] in OPTICAL_DRIVERS if len(device.device_config.config_rules) == len(connection_config_rules) and not is_optical_device: # created from request, populate config rules using driver t7 = time.time() diff --git a/src/device/service/OpenConfigServicer.py b/src/device/service/OpenConfigServicer.py index 437f31924d8753f9b57c40e7e72e3034c0ac6136..e43739255fc1e200044e6371abd5d349a4d2c97f 100644 --- a/src/device/service/OpenConfigServicer.py +++ b/src/device/service/OpenConfigServicer.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import grpc, logging, json +import grpc, logging, json , traceback 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 ( @@ -22,6 +22,7 @@ from common.proto.context_pb2 import ( from common.proto.device_pb2_grpc import DeviceServiceServicer from common.tools.context_queries.Device import get_device from common.tools.mutex_queues.MutexQueues import MutexQueues +from common.DeviceTypes import DeviceTypeEnum from context.client.ContextClient import ContextClient from .driver_api._Driver import _Driver from .driver_api.DriverInstanceCache import DriverInstanceCache, get_driver @@ -96,24 +97,36 @@ class OpenConfigServicer(DeviceServiceServicer): LOGGER.info(f"resources {resources}") LOGGER.info(f"conditions {conditions}") driver : _Driver = get_driver(self.driver_instance_cache, device) - results = driver.SetConfig(resources=resources,conditions=conditions) + results,new_config = driver.SetConfig(resources=resources,conditions=conditions) - for result in results: - if result is not None: - is_all_good = False - raise Exception(result) + errors = [r for r in results if r is not None] + if errors: + raise Exception(f"Driver errors: {errors}") if is_all_good: #driver.GetConfig(resource_keys=[]) config = json.loads(request.config) + handled_flow = next((i for i in resources if i['resource_key'] == 'handled_flow'), None) if handled_flow is not None and len(handled_flow) > 0: config['flow_handled'] = handled_flow['value'] + + + if 'interfaces' in new_config and device.device_type == DeviceTypeEnum.OPEN_ROADM._value_: + LOGGER.info(f"with_new_config {new_config}") + config["interfaces"].extend(new_config['interfaces']) + LOGGER.info(f"updating_the_oc {request}") request.config=json.dumps(config) - context_client.UpdateOpticalConfig(request) - context_client.close() + context_client.SetOpticalConfig(request) + else : + request.config=json.dumps(config) + context_client.UpdateOpticalConfig(request) + except Exception as e: - LOGGER.info("error in configuring %s",e) + logging.error("Exception occurred:\n%s", traceback.format_exc()) + finally : + context_client.close() + return Empty() @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) @@ -150,7 +163,7 @@ class OpenConfigServicer(DeviceServiceServicer): resources : list[dict] = [] is_all_good = True config = json.loads(request.config) - LOGGER.info(f"from disable optical device {config}") + #LOGGER.info(f"from disable optical device {config}") try: context_client = ContextClient() device = get_device( @@ -163,7 +176,15 @@ class OpenConfigServicer(DeviceServiceServicer): resources, conditions = extract_resources(config=config, device=device) driver : _Driver = get_driver(self.driver_instance_cache, device) - results = driver.DeleteConfig(resources=resources,conditions=conditions) + results,config_delete = driver.DeleteConfig(resources=resources,conditions=conditions) + if config_delete and 'interfaces' in config_delete: + if 'new_config'in config : + config["new_config"]=config_delete + else : + config['new_config'] ={} + config["new_config"]=config_delete + + request.config=json.dumps(config) for result in results: if result is not None: is_all_good = False diff --git a/src/device/service/Tools.py b/src/device/service/Tools.py index 153a2c105170302faada354a825caafc23cc9cc9..6b8eb4caefe916b38c37f9fed57a5eb789e1c53a 100644 --- a/src/device/service/Tools.py +++ b/src/device/service/Tools.py @@ -555,10 +555,10 @@ def update_endpoints(src_device : Device, dst_device : Device) -> None: if len(src_topology_uuid) > 0: dst_topology_id.topology_uuid.uuid = src_topology_uuid if len(src_context_uuid) > 0: dst_topology_id.context_id.context_uuid.uuid = src_context_uuid -def get_edit_target(device : Device, is_opticalband : bool) -> str: - if is_opticalband: return 'optical-band' +def get_edit_target(device : Device, is_opticalband : bool,has_flow_id:bool) -> str: + if is_opticalband:return 'optical-band' if device.device_type == DeviceTypeEnum.OPTICAL_ROADM._value_: return 'media-channel' - if device.device_type == DeviceTypeEnum.OPEN_ROADM._value_: return 'network-media-channel' + if device.device_type == DeviceTypeEnum.OPEN_ROADM._value_: return 'or' return 'optical-channel' def is_key_existed(key : str, keys_dic = dict, key_name_to_use = None) -> dict: @@ -576,7 +576,8 @@ def extract_resources(config : dict, device : Device) -> list[list[dict], dict]: conditions = {} resources : list[dict] = [] is_opticalband = config.get('is_opticalband', False) - conditions['edit_type'] = get_edit_target(device, is_opticalband) + flow_id=is_key_existed('flow_id', keys_dic=config['new_config'], key_name_to_use='index') + conditions['edit_type'] = get_edit_target(device, is_opticalband,'value' in flow_id) if device.device_type == DeviceTypeEnum.OPEN_ROADM._value_ : ports_dic = is_key_existed('ports',keys_dic=config['new_config']) @@ -636,19 +637,50 @@ def extract_resources(config : dict, device : Device) -> list[list[dict], dict]: resources.append(is_key_existed('bidir', keys_dic=config['new_config'])) #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': - if config['new_config']['frequency'] is not None and config['new_config']['band'] is not None: - lower_frequency = int(int(config['new_config']['frequency']) - (int(config['new_config']['band'])/2)+1) - upper_frequency = int(int(config['new_config']['frequency']) + (int(config['new_config']['band'])/2)) - resources.append(is_key_existed('flow_id', keys_dic=config['new_config'], key_name_to_use='index')) - #resources.append({'resource_key':'index','value':config['new_config']['flow_id'] if 'flow_id' in config['new_config'] else None}) - else: - lower_frequency = config['new_config']['low-freq'] if 'low-freq' in config['new_config'] else None - upper_frequency = config['new_config']['up-freq' ] if 'up-freq' in config['new_config'] else None - resources.append(is_key_existed('ob_id', keys_dic=config['new_config'], key_name_to_use='index')) - #resources.append({'resource_key':'index','value':config['new_config']['ob_id'] if 'ob_id' in config['new_config'] else None}) - resources.append({'resource_key': 'lower-frequency', 'value': lower_frequency}) - resources.append({'resource_key': 'upper-frequency', 'value': upper_frequency}) + if device.device_type == DeviceTypeEnum.OPEN_ROADM._value_ : + resources.append(is_key_existed('band', keys_dic=config['new_config'], key_name_to_use='band')) + + for t_port in handled_flow: + for i in list(t_port): + if i : + t_circuit ={} + for c in config['interfaces'] : + + ports_list = c["port"].split('/') + + if i in ports_list : + # in case it has no type the targeted port has no configuration yet + if c['type'] =='Null' : + resources.append({'resource_key': 'interface_list'+'-'+i, 'value': c["interface_list"] }) + resources.append({"resource_key":"supporting-circuit-pack-name"+'-'+i,"value":c["circuit_pack_name"]}) + resources.append({"resource_key":"administrative_state"+'-'+i,"value":c["administrative_state"]}) + resources.append({"resource_key":"frequency","value":c["frequency"]}) + resources.append({'resource_key': 'interface_type'+'-'+i, 'value': "Null" }) + # in case it has nmc type , extract only the interface name which may be used later in cross connection + elif c['type'] == 'nmc': + resources.append({'resource_key': 'interface_name'+'-'+i+'-'+'nmc', 'value': c["name"] }) + resources.append({'resource_key': 'interface_list'+'-'+i, 'value': c["interface_list"] }) + resources.append({'resource_key': 'interface_type'+'-'+i+'-'+'nmc', 'value': "nmc" }) + elif c['type'] == 'srg': + resources.append({'resource_key': 'interface_name'+'-'+i+'-'+'srg', 'value': c["name"] }) + resources.append({'resource_key': 'interface_list'+'-'+i, 'value': c["interface_list"] }) + resources.append({'resource_key': 'interface_type'+'-'+i+'-'+'srg', 'value': "srg" }) + + + if not is_opticalband: + + if 'frequency' in config['new_config'] and 'band' in config['new_config'] and conditions['edit_type'] == 'media-channel': + if config['new_config']['frequency'] is not None and config['new_config']['band'] is not None: + lower_frequency = int(int(config['new_config']['frequency']) - (int(config['new_config']['band'])/2)+1) + upper_frequency = int(int(config['new_config']['frequency']) + (int(config['new_config']['band'])/2)) + resources.append(flow_id) + #resources.append({'resource_key':'index','value':config['new_config']['flow_id'] if 'flow_id' in config['new_config'] else None}) + else: + lower_frequency = config['new_config']['low-freq'] if 'low-freq' in config['new_config'] else None + upper_frequency = config['new_config']['up-freq' ] if 'up-freq' in config['new_config'] else None + resources.append(is_key_existed('ob_id', keys_dic=config['new_config'], key_name_to_use='index')) + #resources.append({'resource_key':'index','value':config['new_config']['ob_id'] if 'ob_id' in config['new_config'] else None}) + resources.append({'resource_key': 'lower-frequency', 'value': lower_frequency}) + resources.append({'resource_key': 'upper-frequency', 'value': upper_frequency}) return [resources, conditions] diff --git a/src/device/service/driver_api/DriverInstanceCache.py b/src/device/service/driver_api/DriverInstanceCache.py index 624e61fadc7580b67b914a901bdea33ffa02e023..d2528280932569ce5b08c25488077faa01ea5f5e 100644 --- a/src/device/service/driver_api/DriverInstanceCache.py +++ b/src/device/service/driver_api/DriverInstanceCache.py @@ -53,7 +53,7 @@ class DriverInstanceCache: MSG = 'Driver({:s}) selected for device({:s}) with filter_fields({:s})...' LOGGER.info(MSG.format(str(driver_class.__name__), str(device_uuid), str(filter_fields))) - if driver_class.__name__ == "OCDriver": + if driver_class.__name__ == "OCDriver" or driver_class.__name__ == "OpenROADMDriver": driver_instance : _Driver = driver_class(address, port, device_uuid=device_uuid, **settings) else: driver_instance : _Driver = driver_class(address, port, **settings) diff --git a/src/device/service/drivers/__init__.py b/src/device/service/drivers/__init__.py index e7eaef83328347dd5f66bc675d38e89bd230efd4..5768e23101bdf4a2e490aec7b6dab451553259ce 100644 --- a/src/device/service/drivers/__init__.py +++ b/src/device/service/drivers/__init__.py @@ -225,6 +225,20 @@ if LOAD_ALL_DEVICE_DRIVERS: FilterFieldEnum.DRIVER : DeviceDriverEnum.DEVICEDRIVER_OC, } ])) + +if LOAD_ALL_DEVICE_DRIVERS: + from .openroadm.OpenROADMDriver import OpenROADMDriver # pylint: disable=wrong-import-position + DRIVERS.append( + (OpenROADMDriver, [ + { + # Real Packet Router, specifying OpenConfig Driver => use OpenConfigDriver + FilterFieldEnum.DEVICE_TYPE: [ + DeviceTypeEnum.OPEN_ROADM, + + ], + FilterFieldEnum.DRIVER : DeviceDriverEnum.DEVICEDRIVER_OPENROADM, + } + ])) if LOAD_ALL_DEVICE_DRIVERS: from .qkd.QKDDriver2 import QKDDriver # pylint: disable=wrong-import-position diff --git a/src/device/service/drivers/oc_driver/OCDriver.py b/src/device/service/drivers/oc_driver/OCDriver.py index c4020eb69173b7316514dd090005f1609ae3eeac..f7c093ba35e7039e4df120dfd178a58579bc10ba 100644 --- a/src/device/service/drivers/oc_driver/OCDriver.py +++ b/src/device/service/drivers/oc_driver/OCDriver.py @@ -36,8 +36,8 @@ from context.client.ContextClient import ContextClient from common.proto.context_pb2 import OpticalConfig from .templates.discovery_tool.transponders import transponder_values_extractor from .templates.discovery_tool.roadms import roadm_values_extractor -from .templates.discovery_tool.open_roadm import openroadm_values_extractor -from .templates.VPN.openroadm import network_media_channel_handler + + DEBUG_MODE = False logging.getLogger('ncclient.manager').setLevel(logging.DEBUG if DEBUG_MODE else logging.WARNING) @@ -154,8 +154,7 @@ def edit_config( ): str_method = 'DeleteConfig' if delete else 'SetConfig' results = [] - - + new_config = {} str_config_messages=[] if netconf_handler.vendor is None : if str_method == 'SetConfig': @@ -165,10 +164,6 @@ def edit_config( elif (conditions['edit_type']=='optical-band'): #roadm optical-band str_config_messages = create_optical_band(resources) - elif (conditions['edit_type']=='network-media-channel'): - commit_per_rule=True - #openroadm network media channel - str_config_messages = network_media_channel_handler(resources) else : #roadm media-channel str_config_messages=create_media_channel(resources) @@ -185,9 +180,7 @@ def edit_config( str_config_messages=delete_optical_band(resources) else : str_config_messages=disable_media_channel(resources) - - logging.info(f"edit_template : {str_config_messages}") - + for str_config_message in str_config_messages: # configuration of the received templates if str_config_message is None: raise UnsupportedResourceKeyException("CONFIG") @@ -196,9 +189,7 @@ def edit_config( test_option=test_option, error_option=error_option, format=format) if commit_per_rule: netconf_handler.commit() # configuration commit - - #results[i] = True - results.append(result) + results.append(result) else : if netconf_handler.vendor == "CISCO": @@ -240,19 +231,23 @@ def edit_config( chk_length(str_resource_name, resource, min_length=2, max_length=2) resource_key,resource_value = resource chk_string(str_resource_name + '.key', resource_key, allow_empty=False) - str_config_messages = compose_config( # get template for configuration - resource_key, resource_value, delete=delete, vendor=netconf_handler.vendor, message_renderer=netconf_handler.message_renderer) - for str_config_message in str_config_messages: # configuration of the received templates + str_config_messages = compose_config( + resource_key, + resource_value, + delete=delete, + vendor=netconf_handler.vendor, + message_renderer=netconf_handler.message_renderer) + for str_config_message in str_config_messages: if str_config_message is None: raise UnsupportedResourceKeyException(resource_key) logger.debug('[{:s}] str_config_message[{:d}] = {:s}'.format( str_method, len(str_config_message), str(str_config_message))) - netconf_handler.edit_config( # configure the device + netconf_handler.edit_config( config=str_config_message, target=target, default_operation=default_operation, test_option=test_option, error_option=error_option, format=format) if commit_per_rule: - netconf_handler.commit() # configuration commit + netconf_handler.commit() if 'table_connections' in resource_key: - time.sleep(5) # CPU usage might exceed critical level after route redistribution, BGP daemon needs time to reload + time.sleep(5) #results[i] = True results.append(True) @@ -272,7 +267,7 @@ def edit_config( logger.exception(msg.format(str_method, str_operation, str(resources))) results = [e for _ in resources] # if commit fails, set exception in each resource - return results + return [results,new_config] class OCDriver(_Driver): def __init__(self, address : str, port : int,device_uuid=None, **settings) -> None: @@ -330,6 +325,7 @@ class OCDriver(_Driver): oc_values["type"] = self.__type try: xml_data = self.__netconf_handler.get().data_xml + if self.__type == "optical-transponder": extracted_values = transponder_values_extractor( data_xml=xml_data, resource_keys=transponder_filter_fields, dic=config @@ -341,10 +337,7 @@ class OCDriver(_Driver): 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] - oc_values['interfaces'] = extracted_values[0]['interfaces'] + else: extracted_values = roadm_values_extractor(data_xml=xml_data, resource_keys=[], dic=config) ports_result = extracted_values[0] @@ -386,6 +379,7 @@ class OCDriver(_Driver): results = edit_config( self.__netconf_handler, self.__logger, resources, conditions=conditions ) + logging.info(f"results { results}") return results @metered_subclass_method(METRICS_POOL) diff --git a/src/device/service/drivers/oc_driver/templates/VPN/common.py b/src/device/service/drivers/oc_driver/templates/VPN/common.py index 07968a9715abadc657cacde4d7d5bdda3fb00cf5..55d404f8dc3d55dc84468ad809e22a2853413672 100644 --- a/src/device/service/drivers/oc_driver/templates/VPN/common.py +++ b/src/device/service/drivers/oc_driver/templates/VPN/common.py @@ -55,3 +55,9 @@ def filter_config(resources:list,unwanted_keys=[])->list[list,dict,str]: ports = extract_ports(resources=resources) logging.info(f"filter_config {ports}") return [config,ports,index] + + +def convert_Thz (freq_hz:int)->int : + freq_thz = freq_hz / 1_000_000 + freq_thz_rounded = round(freq_thz, 2) + return freq_thz_rounded diff --git a/src/device/service/drivers/oc_driver/templates/VPN/openroadm.py b/src/device/service/drivers/oc_driver/templates/VPN/openroadm.py deleted file mode 100644 index bbbdb36d9459e35706c212a037b524f950079e3d..0000000000000000000000000000000000000000 --- a/src/device/service/drivers/oc_driver/templates/VPN/openroadm.py +++ /dev/null @@ -1,187 +0,0 @@ -# Copyright 2022-2025 ETSI SDG TeraFlowSDN (TFS) (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. - - - -from yattag import Doc, indent -import logging -from .common import seperate_port_config ,filter_config -from decimal import Decimal - - - -create_mc_err= ''' - - - - MC-TTP-DEG2-RX-193.3 - Media-Channel-TTP-193.3THz-degree-2-in - openROADM-if:mediaChannelTrailTerminationPoint - inService - DEG2-AMPRX - DEG2-AMPRX-IN - OMS-DEG2-TTP-RX - - 193.25 - 193.35 - - - - MC-TTP-DEG2-TX-193.3 - Media-Channel-TTP-193.3THz-degree-2-out - openROADM-if:mediaChannelTrailTerminationPoint - inService - DEG2-AMPTX - DEG2-AMPTX-OUT - OMS-DEG2-TTP-TX - - 193.25 - 193.35 - - - - - - - -''' - -def define_interface_name (type:str,interface_list:str,freq:int)->str: - interface_str = interface_list.split('-') - port_rank='' - port_type='' - if (len(interface_str)==4): - port_rank=interface_str[1] - port_type=interface_str[3] - elif (len(interface_str)==5): - port_rank=interface_str[2] - port_type=interface_str[3] - else : - port_rank=interface_list - port_type=interface_list+'type' - - - return f'{type.upper()}-{port_rank}-{port_type}-{freq}' - - - -def create_media_channel (resources): - - frequency_dict=next((r for r in resources if r['resource_key']== 'frequency'),None) - width_dict=next((r for r in resources if r['resource_key']== 'width'),None) - interfaces_lists =next((r for r in resources if r['resource_key']== 'interfaces'),None) - administrative_state= next((r for r in resources if r['resource_key']== 'administrative-state'),None) - min_freq= int(Decimal(frequency_dict["value"])*1000) - (int(width_dict["value"])/2) - max_freq = int(Decimal(frequency_dict["value"])*1000) + (int(width_dict["value"])/2) - #config,_,_ = filter_config(resources=resources,unwanted_keys=unwanted_keys) - - or_device_ns="http://org/openroadm/device" - or_interface_ns="http://org/openroadm/interfaces" - - results=[] - logging.info(f"from openroadm mc {resources}") - doc, tag, text = Doc().tagtext() - with tag('config',xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"): - with tag('org-openroadm-device', ('xmlns',or_device_ns)): - - for interface in interfaces_lists["value"]: - port = next((r for r in interface if r['resource_key']=='supporting-port'),None) - circuit_pack =next((r for r in interface if r['resource_key']=='supporting-circuit-pack-name'),None) - interface_list = next((r for r in interface if r['resource_key']=='supporting-interface-list'),None) - mc_name = define_interface_name('mc-ttp',interface_list['value'],frequency_dict['value']) - interface.append({'resource_key':'mc_name','value':mc_name}) - with tag('interface'): - - with tag('name'):text(mc_name) - with tag('description'):text(f'Media-channel-{frequency_dict["value"]}THz') - with tag('type'):text("openROADM-if:mediaChannelTrailTerminationPoint") - with tag('administrative-state'):text(administrative_state["value"]) - with tag('supporting-circuit-pack-name'):text(circuit_pack["value"]) - with tag('supporting-port'):text(port["value"]) - with tag('supporting-interface-list'):text(interface_list["value"]) - with tag('mc-ttp',xmlns="http://org/openroadm/media-channel-interfaces"): - with tag('max-freq'):text(max_freq) - with tag('min-freq'):text(min_freq) - - - - result = indent( - doc.getvalue(), - indentation = ' '*2, - newline = '' - ) - results.append(result) - logging.info(f"from openroadm mc results {results}") - return [results,resources] - - - - -def create_network_media_channel (resources): - - logging.info(f"nmc resources {resources}") - - unwanted_keys= ['max-freq','min-freq'] - #config,_,_ = filter_config(resources=resources,unwanted_keys=unwanted_keys) - - or_device_ns="http://org/openroadm/device" - frequency_dict=next((r for r in resources if r['resource_key']== 'frequency'),None) - width_dict=next((r for r in resources if r['resource_key']== 'width'),None) - interfaces_lists =next((r for r in resources if r['resource_key']== 'interfaces'),None) - administrative_state= next((r for r in resources if r['resource_key']== 'administrative-state'),None) - - - results=[] - doc, tag, text = Doc().tagtext() - with tag('config',xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"): - with tag('org-openroadm-device', ('xmlns',or_device_ns)): - for interface in interfaces_lists["value"]: - port = next((r for r in interface if r['resource_key']=='supporting-port'),None) - circuit_pack =next((r for r in interface if r['resource_key']=='supporting-circuit-pack-name'),None) - interface_list = next((r for r in interface if r['resource_key']=='mc_name'),None) - nmc_name = define_interface_name('nmc-ctp',interface_list['value'],frequency_dict['value']) - - with tag('interface'): - - with tag('name'):text(nmc_name) - with tag('description'):text(f'Media-channel-{frequency_dict["value"]}THz') - with tag('type'):text("openROADM-if:networkMediaChannelConnectionTerminationPoint") - with tag('administrative-state'):text(administrative_state["value"]) - with tag('supporting-circuit-pack-name'):text(circuit_pack["value"]) - with tag('supporting-port'):text(port["value"]) - with tag('supporting-interface-list'):text(interface_list["value"]) - with tag('nmc-ctp',xmlns="http://org/openroadm/network-media-channel-interfaces"): - with tag('frequency'):text(frequency_dict['value']) - with tag('width'):text(width_dict['value']) - - - - result = indent( - doc.getvalue(), - indentation = ' '*2, - newline = '' - ) - results.append(result) - logging.info(f"nmc message {results}") - return results - - -def network_media_channel_handler (resources): - unwanted_keys=["config_type"] - config,_,_ = filter_config(resources=resources,unwanted_keys=unwanted_keys) - mc_list,resources_updated= create_media_channel(resources=config) - nmc_list= create_network_media_channel(resources=resources_updated) - mc_list.extend(nmc_list) - - return mc_list diff --git a/src/device/service/drivers/openroadm/OpenROADMDriver.py b/src/device/service/drivers/openroadm/OpenROADMDriver.py new file mode 100644 index 0000000000000000000000000000000000000000..f1a388b84b079d28fb03cd2fe0cccd940fecff3e --- /dev/null +++ b/src/device/service/drivers/openroadm/OpenROADMDriver.py @@ -0,0 +1,304 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (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 time +import json +import logging, pytz, re, threading +from typing import Any, List, Tuple, Union +from apscheduler.executors.pool import ThreadPoolExecutor +from apscheduler.jobstores.memory import MemoryJobStore +from apscheduler.schedulers.background import BackgroundScheduler +from ncclient.manager import Manager, connect_ssh +from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method +from common.tools.client.RetryDecorator import delay_exponential +from common.type_checkers.Checkers import chk_length, chk_string, chk_type +from device.service.driver_api.Exceptions import UnsupportedResourceKeyException +from device.service.driver_api._Driver import _Driver + + +from .RetryDecorator import retry +from context.client.ContextClient import ContextClient +from common.proto.context_pb2 import OpticalConfig + +from .templates.discovery_tool.open_roadm import openroadm_values_extractor +from .templates.Provisioning.openroadm import ( + or_handler + , srg_network_media_channel_handle + ,handle_or_deconfiguration + ) + +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) +logging.getLogger('apscheduler.executors.default').setLevel(logging.INFO if DEBUG_MODE else logging.ERROR) +logging.getLogger('apscheduler.scheduler').setLevel(logging.INFO if DEBUG_MODE else logging.ERROR) +logging.getLogger('monitoring-client').setLevel(logging.INFO if DEBUG_MODE else logging.ERROR) + + + +SAMPLE_EVICTION_SECONDS = 30.0 # seconds +SAMPLE_RESOURCE_KEY = 'interfaces/interface/state/counters' + +MAX_RETRIES = 15 +DELAY_FUNCTION = delay_exponential(initial=0.01, increment=2.0, maximum=5.0) +RETRY_DECORATOR = retry(max_retries=MAX_RETRIES, delay_function=DELAY_FUNCTION, prepare_method_name='connect') +context_client= ContextClient() + + +class NetconfSessionHandler: + def __init__(self, address : str, port : int, **settings) -> None: + self.__lock = threading.RLock() + self.__connected = threading.Event() + self.__address = address + self.__port = int(port) + self.__username = settings.get('username') + self.__password = settings.get('password') + self.__vendor = settings.get('vendor',None) + self.__version = settings.get('version', "1") + self.__key_filename = settings.get('key_filename') + self.__hostkey_verify = settings.get('hostkey_verify', True) + self.__look_for_keys = settings.get('look_for_keys', True) + self.__allow_agent = settings.get('allow_agent', True) + self.__force_running = settings.get('force_running', False) + self.__commit_per_rule = settings.get('commit_per_rule', False) + self.__device_params = settings.get('device_params', {}) + self.__manager_params = settings.get('manager_params', {}) + self.__nc_params = settings.get('nc_params', {}) + self.__message_renderer = settings.get('message_renderer','jinja') + + self.__manager : Manager = None + self.__candidate_supported = False + + def connect(self): + with self.__lock: + self.__manager = connect_ssh( + host=self.__address, port=self.__port, username=self.__username, password=self.__password, + device_params=self.__device_params, manager_params=self.__manager_params, nc_params=self.__nc_params, + key_filename=self.__key_filename, hostkey_verify=self.__hostkey_verify, allow_agent=self.__allow_agent, + look_for_keys=self.__look_for_keys) + self.__candidate_supported = ':candidate' in self.__manager.server_capabilities + self.__connected.set() + + def disconnect(self): + if not self.__connected.is_set(): return + with self.__lock: + self.__manager.close_session() + + @property + def use_candidate(self): return self.__candidate_supported and not self.__force_running + + @property + def commit_per_rule(self): return self.__commit_per_rule + + @property + def vendor(self): return self.__vendor + + @property + def version(self): return self.__version + + @property + def message_renderer(self): return self.__message_renderer + + @RETRY_DECORATOR + def get(self, filter=None, with_defaults=None): # pylint: disable=redefined-builtin + with self.__lock: + config=self.__manager.get(filter=filter, with_defaults=with_defaults) + return config + + @RETRY_DECORATOR + def edit_config( + self, config, target='running', default_operation=None, test_option=None, + error_option=None, format='xml' # pylint: disable=redefined-builtin + ): + response = None + with self.__lock: + response= self.__manager.edit_config( + config, target=target, default_operation=default_operation, test_option=test_option, + error_option=error_option, format=format) + + str_respones = str(response) + if re.search(r'', str_respones): + return None + return str_respones + + @RETRY_DECORATOR + def locked(self, target): + return self.__manager.locked(target=target) + + @RETRY_DECORATOR + def commit(self, confirmed=False, timeout=None, persist=None, persist_id=None): + return self.__manager.commit(confirmed=confirmed, timeout=timeout, persist=persist, persist_id=persist_id) + +DRIVER_NAME = 'oc' +METRICS_POOL = MetricsPool('Device', 'Driver', labels={'driver': DRIVER_NAME}) +def edit_config( # edit the configuration of openconfig devices + netconf_handler : NetconfSessionHandler, logger : logging.Logger, resources : List[Tuple[str, Any]] + ,conditions, delete=False, + commit_per_rule=False, target='running', test_option=None, error_option=None, + format='xml' +): + str_method = 'DeleteConfig' if delete else 'SetConfig' + results = [] + new_config = {} + str_config_messages=[] + if netconf_handler.vendor is None : + if str_method == 'SetConfig': + + if (conditions['edit_type']== 'or'): + str_config_messages,interfaces= or_handler(resources) + new_config['interfaces']= interfaces + + #Disabling of the Configuration + else: + if (conditions['edit_type']=='or'): + str_config_messages,interfaces=handle_or_deconfiguration(resources) + new_config["interfaces"]=interfaces + + + for index,str_config_message in enumerate(str_config_messages): + # configuration of the received templates + if str_config_message is None: raise UnsupportedResourceKeyException("CONFIG") + result= netconf_handler.edit_config( # configure the device + config=str_config_message, target='running', + test_option=test_option, error_option=error_option, format=format) + if commit_per_rule: + if index == len(str_config_messages) - 1 : + netconf_handler.commit() # configuration commit + + #results[i] = True + results.append(result) + + + + return [results,new_config] + +class OpenROADMDriver(_Driver): + def __init__(self, address : str, port : int,device_uuid=None, **settings) -> None: + super().__init__(DRIVER_NAME, address, port, **settings) + self.__logger = logging.getLogger('{:s}:[{:s}:{:s}]'.format(str(__name__), str(self.address), str(self.port))) + self.__lock = threading.Lock() + + self.__started = threading.Event() + self.__terminate = threading.Event() + self.__scheduler = BackgroundScheduler(daemon=True) + self.__scheduler.configure( + jobstores = {'default': MemoryJobStore()}, + executors = {'default': ThreadPoolExecutor(max_workers=1)}, + job_defaults = {'coalesce': False, 'max_instances': 3}, + timezone=pytz.utc) + self._temp_address=f"{address}{port}" + + self.__netconf_handler = NetconfSessionHandler(self.address, self.port, **(self.settings)) + self.__type = self.settings.get("type","openroadm") + self.__device_uuid = device_uuid + self.Connect() + + def Connect(self) -> bool: + with self.__lock: + if self.__started.is_set(): return True + self.__netconf_handler.connect() + self.__scheduler.start() + self.__started.set() + return True + + def Disconnect(self) -> bool: + with self.__lock: + self.__terminate.set() + if not self.__started.is_set(): return True + self.__scheduler.shutdown() + self.__netconf_handler.disconnect() + return True + + @metered_subclass_method(METRICS_POOL) + def GetInitialConfig(self) -> List[Tuple[str, Any]]: + with self.__lock: + return [] + + @metered_subclass_method(METRICS_POOL) + def GetConfig(self, resource_keys : List[str] = []) -> List[Tuple[str, Union[Any, None, Exception]]]: + chk_type('resources', resource_keys, list) + results = [] + opticalConfig= OpticalConfig() + + with self.__lock: + config = {} + transceivers = {} + oc_values = {} + ports_result = [] + oc_values["type"] = self.__type + try: + xml_data = self.__netconf_handler.get().data_xml + + + if (self.__type =='openroadm'): + extracted_values=openroadm_values_extractor(data_xml=xml_data, resource_keys=[], dic=oc_values) + ports_result = extracted_values[1] + oc_values['interfaces'] = extracted_values[0]['interfaces'] + oc_values ['circuits'] = extracted_values[0]['circuits'] + + + + #///////////////////////// store optical configurtaion //////////////////////////////////////////////////////// + + opticalConfig.config=json.dumps(oc_values) + if self.__device_uuid is not None: + opticalConfig.device_id.device_uuid.uuid=self.__device_uuid + results.append((f"/opticalconfigs/opticalconfig/{self.__device_uuid}",{"opticalconfig":opticalConfig})) + + except Exception as e: # pylint: disable=broad-except + MSG = 'Exception retrieving {:s}' + self.__logger.info("error from getConfig %s",e) + self.__logger.exception(MSG.format(e)) + + if len(ports_result) > 0: results.extend(ports_result) + return results + + @metered_subclass_method(METRICS_POOL) + def SetConfig(self, resources : List[Tuple[str, Any]],conditions:dict) -> List[Union[bool, Exception]]: + if len(resources) == 0: return [] + results = [] + with self.__lock: + if self.__netconf_handler.use_candidate: + with self.__netconf_handler.locked(target='candidate'): + results = edit_config( + self.__netconf_handler, self.__logger, resources, conditions, target='candidate', + commit_per_rule=self.__netconf_handler.commit_per_rule + ) + else: + results = edit_config( + self.__netconf_handler, self.__logger, resources, conditions=conditions + ) + logging.info(f"results { results}") + return results + + @metered_subclass_method(METRICS_POOL) + def DeleteConfig( + self, resources : List[Tuple[str, Any]], conditions : dict, + + ) -> List[Union[bool, Exception]]: + chk_type('resources', resources, list) + if len(resources) == 0: return [] + + with self.__lock: + if self.__netconf_handler.use_candidate: + with self.__netconf_handler.locked(target='candidate'): + results = edit_config( + self.__netconf_handler, self.__logger, resources, target='candidate', delete=True, + commit_per_rule=self.__netconf_handler.commit_per_rule, conditions=conditions + ) + else: + results = edit_config( + self.__netconf_handler, self.__logger, resources, delete=True, conditions=conditions + ) + return results diff --git a/src/device/service/drivers/openroadm/RetryDecorator.py b/src/device/service/drivers/openroadm/RetryDecorator.py new file mode 100644 index 0000000000000000000000000000000000000000..df37414c15fa2be63ba74e6c6f9955749b15302a --- /dev/null +++ b/src/device/service/drivers/openroadm/RetryDecorator.py @@ -0,0 +1,46 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (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 logging, time +from common.tools.client.RetryDecorator import delay_linear + +LOGGER = logging.getLogger(__name__) + +def retry(max_retries=0, delay_function=delay_linear(initial=0, increment=0), + prepare_method_name=None, prepare_method_args=[], prepare_method_kwargs={}): + def _reconnect(func): + def wrapper(self, *args, **kwargs): + if prepare_method_name is not None: + prepare_method = getattr(self, prepare_method_name, None) + if prepare_method is None: raise Exception('Prepare Method ({}) not found'.format(prepare_method_name)) + num_try, given_up = 0, False + while not given_up: + try: + return func(self, *args, **kwargs) + except OSError as e: + if str(e) != 'Socket is closed': raise + + num_try += 1 + given_up = num_try > max_retries + if given_up: raise Exception('Giving up... {:d} tries failed'.format(max_retries)) from e + if delay_function is not None: + delay = delay_function(num_try) + time.sleep(delay) + LOGGER.info('Retry {:d}/{:d} after {:f} seconds...'.format(num_try, max_retries, delay)) + else: + LOGGER.info('Retry {:d}/{:d} immediate...'.format(num_try, max_retries)) + + if prepare_method_name is not None: prepare_method(*prepare_method_args, **prepare_method_kwargs) + return wrapper + return _reconnect diff --git a/src/device/service/drivers/openroadm/Tools.py b/src/device/service/drivers/openroadm/Tools.py new file mode 100644 index 0000000000000000000000000000000000000000..45407d38d28892f9bbe049520cc3d615ec1bc965 --- /dev/null +++ b/src/device/service/drivers/openroadm/Tools.py @@ -0,0 +1,38 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (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 hashlib +import uuid +import xml.dom.minidom, xmltodict + +def xml_pretty_print(data : str): + return xml.dom.minidom.parseString(data).toprettyxml() + +def xml_to_file(data : str, file_path : str) -> None: + with open(file_path, mode='w', encoding='UTF-8') as f: + f.write(xml_pretty_print(data)) + +def xml_to_dict(data : str): + return xmltodict.parse(data) + +def generate_uuid_from_numbers(code:str) ->str: + # Concatenate the numbers into a single string + + + # Generate a hash value using MD5 algorithm + hash_value = hashlib.md5(code.encode()).hexdigest() + + # Convert the hash value into a UUID + generated_uuid = uuid.UUID(hash_value) + + return str(generated_uuid) diff --git a/src/device/service/drivers/openroadm/__init__.py b/src/device/service/drivers/openroadm/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..3ccc21c7db78aac26daa1f8c5ff8e1ffd3f35460 --- /dev/null +++ b/src/device/service/drivers/openroadm/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2022-2025 ETSI SDG TeraFlowSDN (TFS) (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/device/service/drivers/openroadm/templates/Provisioning/__init__.py b/src/device/service/drivers/openroadm/templates/Provisioning/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..3ccc21c7db78aac26daa1f8c5ff8e1ffd3f35460 --- /dev/null +++ b/src/device/service/drivers/openroadm/templates/Provisioning/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2022-2025 ETSI SDG TeraFlowSDN (TFS) (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/device/service/drivers/openroadm/templates/Provisioning/common.py b/src/device/service/drivers/openroadm/templates/Provisioning/common.py new file mode 100644 index 0000000000000000000000000000000000000000..6edaefc2fd9339dc368cefd2ac8a49334ba0c5c5 --- /dev/null +++ b/src/device/service/drivers/openroadm/templates/Provisioning/common.py @@ -0,0 +1,63 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (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 logging + + +def seperate_port_config(resources:list,unwanted_keys=[])->list[list,dict,str]: + config=[] + ports={} + index=None + for item in resources : + if len(unwanted_keys)>0: + if (item['value'] is not None and (item['resource_key'] not in unwanted_keys)): + config.append({'resource_key':item['resource_key'], 'value':item['value']} ) + #if (item['resource_key'] == 'destination_port' or item['resource_key'] == 'source_port') and item['value'] is not None: + # ports[item['resource_key']]=item['value'] + if (item['resource_key'] == 'destination_port' or item['resource_key'] == 'source_port'): + ports[item['resource_key']]=item['value'] + if (item['resource_key']=='index' and item['value'] is not None) : + index=item['value'] + logging.info(f"seperate_port_config {ports}") + return [config,ports,index] + +def extract_ports (resources:list): + if len(resources) ==0 :return + ports=[] + flow=next((i for i in resources if i['resource_key']=='handled_flow'),None) + if flow is not None: + ports = flow['value'] + return ports + +def filter_config(resources:list,unwanted_keys=[])->list[list,dict,str]: + config=[] + ports=() + index=None + for item in resources : + if len(unwanted_keys)>0: + if (item['value'] is not None and (item['resource_key'] not in unwanted_keys)): + config.append({'resource_key':item['resource_key'], 'value':item['value']} ) + if (item['resource_key']=='index' and item['value'] is not None) : + index=item['value'] + #if (item['resource_key'] == 'destination_port' or item['resource_key'] == 'source_port') and item['value'] is not None: + # ports[item['resource_key']]=item['value'] + ports = extract_ports(resources=resources) + logging.info(f"filter_config {ports}") + return [config,ports,index] + + +def convert_Thz (freq_hz:int)->int : + freq_thz = freq_hz / 1_000_000 + freq_thz_rounded = round(freq_thz, 2) + return freq_thz_rounded diff --git a/src/device/service/drivers/openroadm/templates/Provisioning/openroadm.py b/src/device/service/drivers/openroadm/templates/Provisioning/openroadm.py new file mode 100644 index 0000000000000000000000000000000000000000..32c03b026e98c75d529b747013219fc204eda285 --- /dev/null +++ b/src/device/service/drivers/openroadm/templates/Provisioning/openroadm.py @@ -0,0 +1,358 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (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. + + + +from yattag import Doc, indent +import logging +from .common import seperate_port_config ,filter_config ,convert_Thz +from decimal import Decimal + + + +or_device_ns_1='urn:ietf:params:xml:ns:netconf:base:1.0' +or_device_ns="http://org/openroadm/device" + + +def define_interface_name_old (type:str,interface_list:str,freq:int)->str: + + if interface_list : + interface_str = interface_list.split('-') + port_rank='' + port_type='' + if (len(interface_str)==4): + port_rank=interface_str[1] + port_type=interface_str[3] + elif (len(interface_str)==5): + port_rank=interface_str[2] + port_type=interface_str[3] + elif (len(interface_str)==2): + port_rank=interface_str[0] + port_type=interface_str[1] + + else : + port_rank=interface_list + port_type=interface_list+'type' + + + return f'{type.upper()}-{port_rank}-{port_type}-{freq}' + return '' + +def define_interface_name (type:str,port:str,freq:int)->str: + + return f'{type.upper()}-{port.upper()}-{freq}' + + + +def create_network_media_channel (resources,port,cross_conn_resources,interfaces): + + band=next((r for r in resources if r['resource_key']== 'band'),None) + frequency_dict=next((r for r in resources if r['resource_key']== 'frequency'),None) + freq_thz=None + if frequency_dict and frequency_dict['value']: + freq_thz=frequency_dict['value'] + + doc, tag, text = Doc().tagtext() + + if port: + with tag('config',xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"): + with tag('org-openroadm-device', ('xmlns',or_device_ns)): + #port,i_list,circuit=infer_opposite_port(i,interface_list["value"]) + interface_list =next((r for r in resources if r['resource_key']== 'interface_list'+'-'+port),None) + circuit_pack =next((r for r in resources if r['resource_key']=='supporting-circuit-pack-name'+'-'+port),None) + nmc_name = define_interface_name(f'nmc',port,freq_thz) + cross_conn_resources[f'nmc_name_{port}']=nmc_name + cross_conn_resources[f'nmc_port_{port}']=port + cross_conn_resources['count']+=1 + interfaces.append({ + "name" : nmc_name, + "type" : 'nmc', + "administrative_state": 'inService', + "circuit_pack_name" : circuit_pack["value"], + "port" : port, + "interface_list" : interface_list["value"], + "frequency" : freq_thz, + "width" : band['value'], + "roadm_uuid" : "" + }) + + with tag('interface'): + + with tag('name'):text(nmc_name) + with tag('description'):text(f'Media-channel-{freq_thz}THz') + with tag('type'):text("openROADM-if:networkMediaChannelConnectionTerminationPoint") + with tag('administrative-state'):text("inService") + with tag('supporting-circuit-pack-name'):text(circuit_pack['value']) + with tag('supporting-port'):text(port) + with tag('supporting-interface-list'):text(interface_list["value"]) + with tag('nmc-ctp',xmlns="http://org/openroadm/network-media-channel-interfaces"): + with tag('frequency'):text(freq_thz) + with tag('width'):text(band['value']) + + result = indent( + doc.getvalue(), + indentation = ' '*2, + newline = '' + ) + + return result + + +def or_create_media_channel (resources,port,cross_conn_resources,interfaces): + frequency_dict=next((r for r in resources if r['resource_key']== 'frequency'),None) + freq_thz=None + if frequency_dict and frequency_dict['value']: + freq_thz=frequency_dict['value'] + band=next((r for r in resources if r['resource_key']== 'band'),None) + min_freq= int(Decimal(frequency_dict["value"])) - (int(band["value"])/2) + max_freq =int(Decimal(frequency_dict["value"])) + (int(band["value"])/2) + results=[] + doc, tag, text = Doc().tagtext() + + if port: + with tag('config',xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"): + with tag('org-openroadm-device', ('xmlns',or_device_ns)): + + interface_list =next((r for r in resources if r['resource_key']== 'interface_list'+'-'+port),None) + circuit_pack =next((r for r in resources if r['resource_key']=='supporting-circuit-pack-name'+'-'+port),None) + mc_name = define_interface_name(f'mc-ttp',port,freq_thz) + interfaces.append({ + "name" : mc_name, + "type" : 'mc', + "administrative_state" : 'inService', + "circuit_pack_name" : circuit_pack["value"], + "port" : port, + "interface_list" : interface_list["value"], + "frequency" : freq_thz, + "width" : band["value"], + "roadm_uuid" : "" + }) + + with tag('interface'): + + with tag('name'):text(mc_name) + with tag('description'):text(f'Media-channel-{freq_thz}THz') + with tag('type'):text("openROADM-if:mediaChannelTrailTerminationPoint") + with tag('administrative-state'):text("inService") + with tag('supporting-circuit-pack-name'):text(circuit_pack["value"]) + with tag('supporting-port'):text(port) + with tag('supporting-interface-list'):text(interface_list["value"]) + with tag('mc-ttp',xmlns="http://org/openroadm/media-channel-interfaces"): + with tag('max-freq'):text(max_freq) + with tag('min-freq'):text(min_freq) + result = indent( + doc.getvalue(), + indentation = ' '*2, + newline = '' + ) + + results.append(result) + nmc_result = create_network_media_channel(resources,port,cross_conn_resources,interfaces) + results.append(nmc_result) + return results + + + + +def create_cross_connection (resources): + + src,dst = resources['ports'] + connection_name=resources[f'nmc_name_{src}']+' to '+resources[f'nmc_name_{dst}'] + doc, tag, text = Doc().tagtext() + with tag('config',xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"): + with tag('org-openroadm-device', ('xmlns',or_device_ns)): + with tag ('roadm-connections'): + with tag('connection-name'):text(connection_name) + with tag('opticalControlMode'):text('off') + with tag('target-output-power'):text('0') + with tag('source'): + with tag('src-if'):text(resources[f'nmc_name_{src}']) + with tag('destination') : + with tag('dst-if'):text(resources[f'nmc_name_{dst}']) + + result = indent( + doc.getvalue(), + indentation = ' '*2, + newline = '' + ) + + # logging.info(f"nmc message {results}") + return result + + + + +def srg_network_media_channel_handle (resources,port,cross_conn_resources,interfaces): + + + band=next((r for r in resources if r['resource_key']== 'band'),None) + frequency_dict=next((r for r in resources if r['resource_key']== 'frequency'),None) + freq_thz= frequency_dict["value"] + doc, tag, text = Doc().tagtext() + if port : + with tag('config',xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"): + with tag('org-openroadm-device', ('xmlns',or_device_ns)): + + interface_list =next((r for r in resources if r['resource_key']== 'interface_list'+'-'+port),None) + circuit_pack =next((r for r in resources if r['resource_key']=='supporting-circuit-pack-name'+'-'+port),None) + srg_name = define_interface_name(f'nmc-srg',port,freq_thz) + cross_conn_resources[f'nmc_name_{port}']=srg_name + cross_conn_resources[f'nmc_port_{port}']=port + cross_conn_resources['count']+=1 + + interfaces.append({ + "name" : srg_name, + "type" : 'srg', + "administrative_state" : 'inService', + "circuit_pack_name" : circuit_pack["value"], + "port" : port, + "interface_list" : interface_list["value"], + "frequency" : freq_thz, + "roadm_uuid" : "" + }) + + with tag('interface'): + + with tag('name'):text(srg_name) + with tag('description'):text(f'Network-Media-Channel-CTP-{freq_thz}THz') + with tag('type'):text("openROADM-if:networkMediaChannelConnectionTerminationPoint") + with tag('administrative-state'):text("inService") + with tag('supporting-circuit-pack-name'):text(circuit_pack["value"]) + with tag('supporting-port'):text(port) + with tag('nmc-ctp',xmlns="http://org/openroadm/network-media-channel-interfaces"): + with tag('frequency'):text(freq_thz) + with tag('width'):text(band['value']) + result = indent( + doc.getvalue(), + indentation = ' '*2, + newline = '' + ) + + return result + +def delete_interface (interface_name) : + doc, tag, text = Doc().tagtext() + with tag('config',xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"): + with tag('org-openroadm-device', xmlns=or_device_ns, **{'xmlns:nc': or_device_ns_1}): + with tag('interface', **{'nc:operation': 'delete'}): + with tag('name'):text(interface_name) + + result = indent( + doc.getvalue(), + indentation = ' '*2, + newline = '' + ) + return result + +def delete_coss_connection (resources) : + + src,dst = resources['ports'] + connection_name=resources[f'nmc_name_{src}']+' to '+resources[f'nmc_name_{dst}'] + doc, tag, text = Doc().tagtext() + with tag('config',xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"): + with tag('org-openroadm-device', xmlns=or_device_ns, **{'xmlns:nc': or_device_ns_1}): + with tag ('roadm-connections', **{'nc:operation': 'delete'}): + with tag('connection-name'):text(connection_name) + + result = indent( + doc.getvalue(), + indentation = ' '*2, + newline = '' + ) + + return result + +def or_handler (resources): + + _,ports,_= filter_config(resources,unwanted_keys=[]) + edit_templates= [] + interfaces= [] + cross_conn_resources={"count":0} + doc, tag, text = Doc().tagtext() + logging.info(f'or_handler {resources} ') + with tag('config',xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"): + with tag('org-openroadm-device', ('xmlns',or_device_ns)): + for lst_i in [list(j) for j in ports ] : + for i in lst_i: + + if 'SRG' in i : + edit_templates.append ( + srg_network_media_channel_handle(resources,i,cross_conn_resources,interfaces) + ) + + else : + + edit_templates.extend(or_create_media_channel(resources ,i,cross_conn_resources,interfaces)) + + if cross_conn_resources['count']==2 : + cross_conn_resources['ports']=lst_i + edit_templates.append(create_cross_connection(cross_conn_resources)) + cross_conn_resources["count"]=0 + return (edit_templates,interfaces) + + +def delete_mc (mc_interfaces): + + results = [] + for i in mc_interfaces: + results.append( delete_interface(i)) + return results + +def handle_or_deconfiguration (resources): + + _,ports,_= filter_config(resources,unwanted_keys=[]) + edit_templates= [] + interfaces= [] + mcs_to_delete=[] + cross_conn_resources={"count":0} + + for lst_i in [list(j) for j in ports ] : + for p in lst_i : + if p : + if 'SRG' in p : + interface_list = interface_list =next((r for r in resources if r['resource_key']== 'interface_name'+'-'+p+'-srg'),None) + if interface_list : + cross_conn_resources[f'nmc_name_{p}']=interface_list['value'] + cross_conn_resources[f'nmc_port_{p}']=p + cross_conn_resources['count']+=1 + interfaces.append({ + 'interface_name':interface_list['value'] + }) + + edit_templates.append(delete_interface(interface_list['value'])) + else : + nmc_interface = interface_list =next((r for r in resources if r['resource_key']== 'interface_name'+'-'+p+'-nmc'),None) + if nmc_interface : + cross_conn_resources[f'nmc_name_{p}']=nmc_interface['value'] + cross_conn_resources[f'nmc_port_{p}']=p + cross_conn_resources['count']+=1 + mc_interface = nmc_interface['value'].replace('NMC',"MC-TTP") + interfaces.extend([{ + 'interface_name':nmc_interface['value'] + }, + { + 'interface_name':mc_interface + }] + + ) + mcs_to_delete.append(mc_interface) + #delete_interface(mc_interface,doc,tag,text) + edit_templates.append(delete_interface(nmc_interface['value'])) + if cross_conn_resources['count']==2 : + logging.info("should cross connection be initiated") + cross_conn_resources['ports']=lst_i + edit_templates.insert(0,delete_coss_connection(cross_conn_resources)) + cross_conn_resources["count"]=0 + edit_templates.extend(delete_mc(mcs_to_delete)) + return (edit_templates,interfaces) diff --git a/src/device/service/drivers/openroadm/templates/__init__.py b/src/device/service/drivers/openroadm/templates/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..7363515f07a52d996229bcbd72932ce1423258d7 --- /dev/null +++ b/src/device/service/drivers/openroadm/templates/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2022-2025 ETSI SDG TeraFlowSDN (TFS) (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/device/service/drivers/oc_driver/templates/discovery_tool/open_roadm.py b/src/device/service/drivers/openroadm/templates/discovery_tool/open_roadm.py similarity index 72% rename from src/device/service/drivers/oc_driver/templates/discovery_tool/open_roadm.py rename to src/device/service/drivers/openroadm/templates/discovery_tool/open_roadm.py index 9a75f020d1ecf3442d384e4b5ccdd88935e1a75a..39e089aeeee1eac0d3cb039edabeeb6dc6f7a831 100644 --- a/src/device/service/drivers/oc_driver/templates/discovery_tool/open_roadm.py +++ b/src/device/service/drivers/openroadm/templates/discovery_tool/open_roadm.py @@ -53,7 +53,7 @@ def extract_roadm_circuits_pack (xml_data:str): port_info={} port_name=port.find('./oc:port-name',namespace) port_qual= port.find("./oc:port-qual",namespace) - + #if port_qual is not None and port_qual.text != 'roadm-external':continue if port_name is not None : port_info["port_name"]=port_name.text if port_qual is not None : @@ -62,11 +62,12 @@ def extract_roadm_circuits_pack (xml_data:str): # if port_info["port_qual"] == 'roadm-external': # circuit_ports.append(port_info) if (circuit_name is not None): - circuit_info["circuit_name"]=circuit_name.text + circuit_info["circuit_pack_name"]=circuit_name.text + circuit_info["interface_uuid"]=circuit_name.text if (circuit_type is not None): circuit_info["circuit_type"]=circuit_type.text if (circuit_adminstrative_status is not None): - circuit_info["circuit_adminstrative_status"]=circuit_adminstrative_status.text + circuit_info["administrative_state"]=circuit_adminstrative_status.text if (circuit_equipment_state is not None): circuit_info["circuit_equipment_state"]=circuit_equipment_state.text if (circuit_mode is not None): @@ -74,14 +75,11 @@ def extract_roadm_circuits_pack (xml_data:str): if (slot is not None): circuit_info["slot"]=slot.text if (shelf is not None): - circuit_info["shelf"]=shelf.text - logging.info(f"circuit_ports {circuit_ports}") - circuit_info["ports"]=circuit_ports - + circuit_info["shelf"]=shelf.text + circuit_info["port"]=circuit_ports + if len(circuit_info["port"])==0 : continue circuits_list.append(circuit_info) - - - + return circuits_list @@ -137,14 +135,13 @@ def extract_openroadm_interface (xml_data:str): interface_list =interface.find('.//oc:supporting-interface-list',namespace) or_interfaces.append({ - 'interface_list':name.text if name is not None else None, - 'administrative_state':administrative_state.text if administrative_state is not None else None, - 'circuit_pack_name':circuit_pack_name.text if circuit_pack_name is not None else None, - 'port':port.text if port is not None else None , - 'type':type.text if type is not None else None + 'interface_list' : name.text if name is not None else None, + 'administrative_state' : administrative_state.text if administrative_state is not None else None, + 'circuit_pack_name' : circuit_pack_name.text if circuit_pack_name is not None else None, + 'port' : port.text if port is not None else None , + 'type' : type.text if type is not None else None }) if mc is not None : - print (mc) frequency=None width=None min_frequency = mc.find('.//mc:min-freq',namespace) @@ -155,15 +152,15 @@ def extract_openroadm_interface (xml_data:str): mc= { - 'name':name.text if name is not None else None, - 'description':description.text if description is not None else None , - 'type':"media_channel", - 'administrative_state':administrative_state.text if administrative_state is not None else None, - 'circuit_pack_name':circuit_pack_name.text if circuit_pack_name is not None else None, - 'port':port.text if port is not None else None , - 'interface_list': interface_list.text if interface_list is not None else None, - 'frequency': str(frequency), - 'width':width + 'name' : name.text if name is not None else None, + 'description' : description.text if description is not None else None , + 'type' : "mc", + 'administrative_state': administrative_state.text if administrative_state is not None else None, + 'circuit_pack_name' : circuit_pack_name.text if circuit_pack_name is not None else None, + 'port' : port.text if port is not None else None , + 'interface_list' : interface_list.text if interface_list is not None else None, + 'frequency' : str(frequency), + 'width' : width } or_config.append(mc) @@ -175,18 +172,17 @@ def extract_openroadm_interface (xml_data:str): frequency = nmc.find('.//nmc:frequency',namespace) width=nmc.find('.//nmc:width',namespace) nmc= { - 'name':name.text if name is not None else None, - 'description':description.text if description is not None else None , - 'type':"network_media_channel", - 'administrative_state':administrative_state.text if administrative_state is not None else None, - 'circuit_pack_name':circuit_pack_name.text if circuit_pack_name is not None else None, - 'port':port.text if port is not None else None , - 'interface_list': interface_list.text if interface_list is not None else None, - 'frequency': frequency.text if frequency is not None else None, - 'width':width.text if width is not None else None + 'name' : name.text if name is not None else None, + 'description' : description.text if description is not None else None , + 'type' : "nmc" if interface_list is not None else "srg", + 'administrative_state': administrative_state.text if administrative_state is not None else None, + 'circuit_pack_name' : circuit_pack_name.text if circuit_pack_name is not None else None, + 'port' : port.text if port is not None else None , + 'interface_list' : interface_list.text if interface_list is not None else None, + 'frequency' : frequency.text if frequency is not None else None, + 'width' : width.text if width is not None else None } or_config.append(nmc) - logging.info(f"or_config for or {or_config}") return [or_interfaces,or_config] @@ -200,22 +196,27 @@ def openroadm_values_extractor (data_xml:str,resource_keys:list,dic:dict): dic['interfaces']=config for circuit in circuits_list : - circuit_name=circuit['circuit_name'] + circuit_name=circuit['circuit_pack_name'] location = Location() location.circuit_pack=circuit_name - for port in circuit['ports']: + for port in circuit['port']: if port is not None and 'port_name' in port : resource_key = '/endpoints/endpoint[{:s}]'.format(port["port_name"]) - resource_value = {'uuid': port["port_name"] - , 'type':port["port_qual"] if "port_qual" in port else None, - 'location':{"circuit_pack":location.circuit_pack} + resource_value = { + 'uuid' : port["port_name"] + , 'type' : port["port_qual"] if "port_qual" in port else None, + 'location': {"circuit_pack":location.circuit_pack} } ports_result.append((resource_key, resource_value)) + for interface in interfaces: existed=False circuit_name=interface['circuit_pack_name'] interface_list=interface['interface_list'] - + if 'circuits' in dic and len (dic['circuits']): + target= next((item for item in dic['circuits'] if item.get('circuit_pack_name') == circuit_name), None) + if target : + target ['interface_list'] = interface_list location_interface=f'{interface_list}/{circuit_name}' port = interface["port"] type = interface['type'] @@ -237,9 +238,10 @@ def openroadm_values_extractor (data_xml:str,resource_keys:list,dic:dict): if not existed: resource_key = '/endpoints/endpoint[{:s}]'.format(port) - resource_value = {'uuid': f'{port}' - , 'type':type , - 'location':{"interface":location_interface} + resource_value = { + 'uuid' : f'{port}' + , 'type' : type , + 'location': {"interface":location_interface} } ports_result.append((resource_key, resource_value)) diff --git a/src/opticalcontroller/OpticalController.py b/src/opticalcontroller/OpticalController.py index 4c211f9789f802da00ccfc6a3bce3cac35000a98..3e7cc28c5099553d56afa840acc7caf43874cede 100644 --- a/src/opticalcontroller/OpticalController.py +++ b/src/opticalcontroller/OpticalController.py @@ -107,13 +107,13 @@ class AddFlexLightpath(Resource): else: return "Error", 404 # @optical.route('/DelFlexLightpath////') -@optical.route('/DelFlexLightpath////') -@optical.route('/DelFlexLightpath/////') +@optical.route('/DelFlexLightpath////') +@optical.route('/DelFlexLightpath/////') @optical.response(200, 'Success') @optical.response(404, 'Error, not found') class DelFLightpath(Resource): @staticmethod - def delete( src, dst, bitrate, o_band_id, flow_id=None): + def delete( src, dst, bitrate, o_band_id=None, flow_id=None): flow = None match1=False ob_id=None @@ -122,18 +122,20 @@ class DelFLightpath(Resource): if flow_id in rsa.db_flows.keys(): flow = rsa.db_flows[flow_id] match1 = flow["src"] == src and flow["dst"] == dst and flow["bitrate"] == bitrate - ob_id = flow["parent_opt_band"] + ob_id = flow["parent_opt_band"] if 'parent_opt_band' in flow else None flow['is_active']=False if flow is not None: bidir = flow["bidir"] if bidir: match2 = flow["src"] == dst and flow["dst"] == src and flow["bitrate"] == bitrate if match1 or match2: - ob_id = flow["parent_opt_band"] - rsa.del_flow(flow, flow_id, ob_id) + ob_id = flow["parent_opt_band"] if 'parent_opt_band' in flow else None rsa.db_flows[flow_id]["is_active"] = False - if flow_id in rsa.optical_bands[ob_id]["served_lightpaths"]: - rsa.optical_bands[ob_id]["served_lightpaths"].remove(flow_id) + if ob_id: + rsa.del_flow(flow, flow_id, ob_id) + if flow_id in rsa.optical_bands[ob_id]["served_lightpaths"]: + rsa.optical_bands[ob_id]["served_lightpaths"].remove(flow_id) + #if rsa.optical_bands[ob_id]["reverse_optical_band_id"] != 0: # rev_ob_id = rsa.optical_bands[ob_id]["reverse_optical_band_id"] # rsa.optical_bands[rev_ob_id]["served_lightpaths"].remove(flow_id) @@ -151,12 +153,15 @@ class DelFLightpath(Resource): # if len( rsa.optical_bands[ob_id]["served_lightpaths"]) != 0: # return "DELETE_NOT_ALLOWED" ,400 - ob_id = flow["parent_opt_band"] - rsa.del_flow(flow,flow_id,ob_id) + ob_id = flow["parent_opt_band"] if 'parent_opt_band' in flow else None + if ob_id: + rsa.del_flow(flow,flow_id,ob_id) if debug: - print(f"vor ob_id {ob_id} rsa.optical_bands {rsa.optical_bands[ob_id]}") - print(f"rsa.links_dict {rsa.links_dict}") + print(f"rsa.links_dict {rsa.links_dict}") + if ob_id: + print(f"vor ob_id {ob_id} rsa.optical_bands {rsa.optical_bands[ob_id]}") + return "flow {} deleted".format(flow_id), 200 else: return "flow {} not matching".format(flow_id), 404 @@ -263,7 +268,7 @@ class DelOpticalBandSimple(Resource): -@optical.route('/DelLightpath////') +@optical.route('/DelLightpath////') @optical.response(200, 'Success') @optical.response(404, 'Error, not found') class DelLightpath(Resource): @@ -274,7 +279,7 @@ class DelLightpath(Resource): match1 = flow["src"] == src and flow["dst"] == dst and flow["bitrate"] == bitrate match2 = flow["src"] == dst and flow["dst"] == src and flow["bitrate"] == bitrate if match1 or match2: - rsa.del_flow(flow) + rsa.del_flow(flow,flow_id) rsa.db_flows[flow_id]["is_active"] = False if debug: print(rsa.links_dict) @@ -355,6 +360,7 @@ class GetTopology(Resource): global rsa if (rsa is not None): + return "Opticalcontroller is synchronised" ,200 topog_id = TopologyId() topog_id.topology_uuid.uuid=topology_id @@ -368,6 +374,9 @@ class GetTopology(Resource): OPTICAL_ROADM_TYPES = { DeviceTypeEnum.OPTICAL_ROADM.value, DeviceTypeEnum.EMULATED_OPTICAL_ROADM.value } + OPTICAL_OPENROADM_TYPES = { + DeviceTypeEnum.OPEN_ROADM.value, DeviceTypeEnum.EMULATED_OPEN_ROADM.value + } OPTICAL_TRANSPONDER_TYPES = { DeviceTypeEnum.OPTICAL_TRANSPONDER.value, DeviceTypeEnum.EMULATED_OPTICAL_TRANSPONDER.value } @@ -377,6 +386,8 @@ class GetTopology(Resource): dev_type = "OC-ROADM" elif device.device_type in OPTICAL_TRANSPONDER_TYPES: dev_type = "OC-TP" + elif device.device_type in OPTICAL_OPENROADM_TYPES: + dev_type = "OC-ROADM" else: continue diff --git a/src/opticalcontroller/RSA.py b/src/opticalcontroller/RSA.py index b111ea93da0b253f1f056d4cbabff213562a7e58..8c445fe7202928e7878f054205fb5841face76ed 100644 --- a/src/opticalcontroller/RSA.py +++ b/src/opticalcontroller/RSA.py @@ -126,7 +126,7 @@ class RSA(): path = shortest_path(self.g, self.g.get_vertex(src), self.g.get_vertex(dst)) print("INFO: Path from {} to {} with distance: {}".format(src, dst, self.g.get_vertex(dst).get_distance())) if debug: - print(path) + print(f"compute_path shortest_path {path}") links = [] for i in range(0, len(path) - 1): s = path[i] @@ -300,14 +300,14 @@ class RSA(): fib['used'] = False #fib[band].sort() - def restore_link_2(self, fib, slots, band, link): - print("start restoring link") - for i in slots: - fib[band][str(i)] = 1 - if 'used' in fib: - fib['used'] = False - #fib[band].keys().sort() - #set_link_update(fib,link,test="restoration") + # def restore_link_2(self, fib, slots, band, link): + # print("start restoring link") + # for i in slots: + # fib[band][str(i)] = 1 + # if 'used' in fib: + # fib['used'] = False + # #fib[band].keys().sort() + # #set_link_update(fib,link,test="restoration") def restore_optical_band(self, optical_band_id, slots, band): @@ -353,9 +353,9 @@ class RSA(): link = self.get_link_by_name(l) fib = link["optical_details"] #self.restore_link(fib, slots, band) - self.restore_link_2(fib, slots, band, link=link) + self.restore_link(fib, slots, band) if debug: - print(fib[band]) + print(f"fib[band] {fib[band]}") if o_b_id is not None: if debug: print("restoring OB") @@ -370,13 +370,13 @@ class RSA(): for l in links: r_l = reverse_link(l) if debug: - print(r_l) + print(f"reverse_link {r_l}") rlink = self.get_link_by_name(r_l) fib = rlink["optical_details"] #fib = self.get_link_by_name(r_l)["optical_details"] if list_in_list(slots, str_list_to_int(fib[band].keys())): #self.restore_link(fib, slots, band, link=l) - self.restore_link_2(fib, slots, band, link=rlink) + self.restore_link(fib, slots, band) if debug: print(fib[band]) ''' diff --git a/src/service/service/ServiceServiceServicerImpl.py b/src/service/service/ServiceServiceServicerImpl.py index 0e8ae981f4af59df512f741a49513dc90eaf7d24..bfa8f27b75b1779f152b846bc2875578895c00e1 100644 --- a/src/service/service/ServiceServiceServicerImpl.py +++ b/src/service/service/ServiceServiceServicerImpl.py @@ -260,7 +260,7 @@ class ServiceServiceServicerImpl(ServiceServiceServicer): DEFAULT_TOPOLOGY_NAME, context_id_x) topology_details = context_client.GetTopologyDetails( TopologyId(**topology_id_x)) - + refresh_opticalcontroller(topology_id_x) # devices = get_devices_in_topology(context_client, TopologyId(**topology_id_x), ContextId(**context_id_x)) devices = topology_details.devices @@ -270,8 +270,8 @@ class ServiceServiceServicerImpl(ServiceServiceServicer): device_names : Dict[str, str] = dict() for device in devices: device_uuid = device.device_id.device_uuid.uuid - device_names[device_uuid] = device.name # ID => name - device_names[device.name] = device.name # name => name (that way, if not present, crash) + device_names[device_uuid] = device.name + device_names[device.name] = device.name devs = [] ports = [] @@ -283,6 +283,7 @@ class ServiceServiceServicerImpl(ServiceServiceServicer): endpoint_device_name = device_names[endpoint_device_uuid] devs.append(endpoint_device_name) ports.append(endpoint_id.endpoint_uuid.uuid) + src = devs[0] dst = devs[1] bidir = None @@ -449,6 +450,30 @@ class ServiceServiceServicerImpl(ServiceServiceServicer): params['bidir' ] = bidir + tasks_scheduler = TasksScheduler(self.service_handler_factory) + tasks_scheduler.compose_from_optical_service(service, params=params, is_delete=True) + tasks_scheduler.execute_all() + return Empty() + elif oc_type ==2 : + + if len(service.service_config.config_rules) > 0: + c_rules_dict = json.loads( + service.service_config.config_rules[0].custom.resource_value) + ob_id=None + flow_id=None + + if ("flow_id" in c_rules_dict): + flow_id = c_rules_dict["flow_id"] + #if ("ob_id" in c_rules_dict): + # ob_id = c_rules_dict["ob_id"] + params['bitrate']=bitrate + params['dst']=dst + params['src']=src + params['ob_id']=ob_id + params['flow_id']=flow_id + params['bidir'] = bidir + + tasks_scheduler = TasksScheduler(self.service_handler_factory) tasks_scheduler.compose_from_optical_service(service, params=params, is_delete=True) tasks_scheduler.execute_all() diff --git a/src/service/service/service_handler_api/FilterFields.py b/src/service/service/service_handler_api/FilterFields.py index 14f975316ce3c83d95467b1a99549d5862dd3d59..61b1cbe04fa3ae5acfe9010ce687729b15c2db0c 100644 --- a/src/service/service/service_handler_api/FilterFields.py +++ b/src/service/service/service_handler_api/FilterFields.py @@ -55,6 +55,7 @@ DEVICE_DRIVER_VALUES = { DeviceDriverEnum.DEVICEDRIVER_MORPHEUS, DeviceDriverEnum.DEVICEDRIVER_RYU, DeviceDriverEnum.DEVICEDRIVER_GNMI_NOKIA_SRLINUX, + DeviceDriverEnum.DEVICEDRIVER_OPENROADM, } # Map allowed filter fields to allowed values per Filter field. If no restriction (free text) None is specified diff --git a/src/service/service/service_handlers/__init__.py b/src/service/service/service_handlers/__init__.py index 10a7effc225858ae966e7f4543b1711789ccfb54..224ebabb303d185655a97fbddc189237e3861876 100644 --- a/src/service/service/service_handlers/__init__.py +++ b/src/service/service/service_handlers/__init__.py @@ -165,7 +165,10 @@ SERVICE_HANDLERS = [ (OCServiceHandler, [ { FilterFieldEnum.SERVICE_TYPE : ServiceTypeEnum.SERVICETYPE_OPTICAL_CONNECTIVITY, - FilterFieldEnum.DEVICE_DRIVER : DeviceDriverEnum.DEVICEDRIVER_OC, + FilterFieldEnum.DEVICE_DRIVER : [ + DeviceDriverEnum.DEVICEDRIVER_OC, + DeviceDriverEnum.DEVICEDRIVER_OPENROADM + ], } ]), (IP_LinkServiceHandler, [ diff --git a/src/service/service/service_handlers/oc/OCServiceHandler.py b/src/service/service/service_handlers/oc/OCServiceHandler.py index 01ebaebbe1e5ec31e9e5c8d4b6888dead103ff24..d2a9fd58a3ac2dc679dd2cca4486b1b8925a3812 100644 --- a/src/service/service/service_handlers/oc/OCServiceHandler.py +++ b/src/service/service/service_handlers/oc/OCServiceHandler.py @@ -25,8 +25,8 @@ from service.service.service_handler_api.SettingsHandler import SettingsHandler from service.service.task_scheduler.TaskExecutor import TaskExecutor from .ConfigRules import setup_config_rules, teardown_config_rules from .OCTools import ( - endpoints_to_flows,convert_endpoints_to_flows - #handle_flows_names, check_media_channel_existance + endpoints_to_flows,convert_endpoints_to_flows, + convert_or_endpoints_to_flows ) LOGGER = logging.getLogger(__name__) @@ -81,8 +81,6 @@ class OCServiceHandler(_ServiceHandler): self, endpoints : List[Tuple[str, str, Optional[str]]], connection_uuid : Optional[str] = None ) -> List[Union[bool, Exception]]: - LOGGER.info(f"endpoints {endpoints} ") - chk_type('endpoints', endpoints, list) if len(endpoints) == 0: return [] is_opticalband =False @@ -96,30 +94,18 @@ class OCServiceHandler(_ServiceHandler): settings = self.__settings_handler.get('/settings') bidir = settings.value.get("bidir") - LOGGER.debug(f"settings bvalue is: {settings}") - # settings = self.__settings_handler.get('/settings') - - # in case service expanded , the only part to reconfigure is optical band ob_expansion =settings.value.get('ob-expanded',None) if ob_expansion : if not is_opticalband: - LOGGER.debug(f"ob-expanded bvalue is: {ob_expansion} and is_opticalband {is_opticalband}") + LOGGER.info(f"ob-expanded bvalue is: {ob_expansion} and is_opticalband {is_opticalband}") return results - LOGGER.info(f"endpoints {endpoints} is_opticalband {is_opticalband} ") - #flow is the new variable that stores input-output relationship - #flows = convert_endpoints_to_flows(endpoints) flows = endpoints_to_flows(endpoints, bidir, is_opticalband) - LOGGER.info(f"endpoints {flows} is_opticalband {is_opticalband} ") - #handled_flows=handle_flows_names(flows=flows,task_executor=self.__task_executor) - - #new cycle for setting optical devices for device_uuid, dev_flows in flows.items(): try: device_obj = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid))) - LOGGER.info(f"device {device_obj.name} ") if settings is not None: self.__task_executor.configure_optical_device(device_obj, settings, dev_flows , is_opticalband ,connection_uuid) @@ -136,7 +122,7 @@ class OCServiceHandler(_ServiceHandler): self, endpoints : List[Tuple[str, str, Optional[str]]], connection_uuid : Optional[str] = None ) -> List[Union[bool, Exception]]: is_opticalband =False - + is_openroadm =False service_uuid = self.__service.service_id.service_uuid.uuid chk_type('endpoints', endpoints, list) if len(endpoints) == 0: return [] @@ -146,16 +132,18 @@ class OCServiceHandler(_ServiceHandler): settings = self.__settings_handler.get('/settings-ob_{}'.format(connection_uuid)) else: settings = self.__settings_handler.get('/settings') - + bidir = settings.value.get("bidir",None) - flows = convert_endpoints_to_flows(endpoints) - results = [] + results = [] for endpoint in endpoints: try: device_uuid, endpoint_uuid = get_device_endpoint_uuids(endpoint) - device_obj = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid))) + + # temporarily until updating convert to flow , cause it doesn't support openroadm endpoints flows + if device_obj.device_type == DeviceTypeEnum.OPEN_ROADM._value_ : + if not is_openroadm: is_openroadm=True device_settings = self.__settings_handler.get_device_settings(device_obj) endpoint_obj = get_endpoint_matching(device_obj, endpoint_uuid) endpoint_settings = self.__settings_handler.get_endpoint_settings(device_obj, endpoint_obj) @@ -175,6 +163,12 @@ class OCServiceHandler(_ServiceHandler): except Exception as e: # pylint: disable=broad-except LOGGER.exception('Unable to DeleteEndpoint({:s})'.format(str(endpoint))) results.append(e) + + if is_openroadm: + flows = convert_or_endpoints_to_flows(endpoints, bidir) + else: + flows = endpoints_to_flows(endpoints, bidir, is_opticalband) + for device_uuid, dev_flows in flows.items(): try: channel_indexes= [] @@ -192,24 +186,22 @@ class OCServiceHandler(_ServiceHandler): dst_endpoint_obj = get_endpoint_matching(device_obj, dst) dist_enpoint_name=dst_endpoint_obj.name channel_indexes.append((src_enpoint_name,dist_enpoint_name)) - else: + elif(device_obj.device_type == DeviceTypeEnum.OPTICAL_ROADM._value_): if not is_opticalband: if 'flow_id' in settings.value: channel_indexes.append(settings.value["flow_id"]) elif is_opticalband: if "ob_id" in settings.value: channel_indexes.append(settings.value["ob_id"]) - + elif (device_obj.device_type == DeviceTypeEnum.OPEN_ROADM._value_): + channel_indexes.append(settings.value["flow_id"]) if len(channel_indexes) > 0: errors = self.__task_executor.deconfigure_optical_device( device=device_obj, channel_indexes=channel_indexes, is_opticalband=is_opticalband, dev_flow=dev_flows, bidir=bidir ) - # if (len(errors)==0): - # service_id =self.__service.service_id - # if not is_opticalband : - # self.__task_executor.delete_setting(service_id,"/settings","value") + results.append(True) except Exception as e: # pylint: disable=broad-except LOGGER.exception('Unable to DeleteEndpoint({:s})'.format(str(endpoint))) @@ -256,8 +248,6 @@ class OCServiceHandler(_ServiceHandler): def DeleteConfig(self, resources : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: chk_type('resources', resources, list) if len(resources) == 0: return [] - service_id = self.__service.service_id - results = [] for resource in resources: try: diff --git a/src/service/service/service_handlers/oc/OCTools.py b/src/service/service/service_handlers/oc/OCTools.py index 24bc7b384dab6edd3c44b62a7448914857b86ee9..14cd7cbedd1c0aa4390067f7244baead946b84a9 100644 --- a/src/service/service/service_handlers/oc/OCTools.py +++ b/src/service/service/service_handlers/oc/OCTools.py @@ -20,12 +20,11 @@ from service.service.service_handler_api.Tools import get_endpoint_matching log = logging.getLogger(__name__) + + def convert_endpoints_to_flows(endpoints : List[Tuple[str, str, Optional[str]]])->Dict: - #entries = List[Tuple[str, str, str, Optional[str]]] - #entries = Dict[str: List[Tuple[str, str]]] + entries = {} - #tuple is in, out - #end = len(endpoints) if isinstance(endpoints,list) else 0 end = len(endpoints) i = 0 bidir = 0 @@ -33,7 +32,6 @@ def convert_endpoints_to_flows(endpoints : List[Tuple[str, str, Optional[str]]]) while(i < end): endpoint = endpoints[i] device_uuid, endpoint_uuid = endpoint[0:2] - log.info("current OCTools step {}, {}, {}".format(i, device_uuid, endpoint_uuid)) if device_uuid not in entries.keys(): entries[device_uuid] = [] if i == 0: @@ -41,7 +39,7 @@ def convert_endpoints_to_flows(endpoints : List[Tuple[str, str, Optional[str]]]) entries[device_uuid].append(entry_tuple) next_endpoint = endpoints[i+1] next_device_uuid, next_endpoint_uuid = next_endpoint[0:2] - if next_device_uuid == device_uuid: + if next_device_uuid == device_uuid : bidir = 1 log.info("connection is bidirectional") entry_tuple = next_endpoint_uuid, "0" @@ -63,7 +61,6 @@ def convert_endpoints_to_flows(endpoints : List[Tuple[str, str, Optional[str]]]) entry_tuple = endpoint_uuid, next_endpoint_uuid entries[device_uuid].append(entry_tuple) i = i + 1 - log.info("current OCTools step {}, {}, {}".format(i, next_device_uuid, device_uuid)) else: log.debug("ERROR in unidirectional connection 4") return {} @@ -103,6 +100,8 @@ def convert_endpoints_to_flows(endpoints : List[Tuple[str, str, Optional[str]]]) #i+3 next_3_endpoint = endpoints[i+3] next_3_device_uuid, next_3_endpoint_uuid = next_3_endpoint[0:2] + log.debug(f"de {device_uuid} and i {i}") + log.debug(f"de2 {next_2_device_uuid} and dev3 {next_3_device_uuid}") if next_2_device_uuid == next_3_device_uuid and next_3_device_uuid == device_uuid: entry_tuple = next_2_endpoint_uuid, next_3_endpoint_uuid entries[device_uuid].append(entry_tuple) @@ -115,101 +114,67 @@ def convert_endpoints_to_flows(endpoints : List[Tuple[str, str, Optional[str]]]) -def convert_endpoints_to_flows(endpoints : List[Tuple[str, str, Optional[str]]])->Dict: - #entries = List[Tuple[str, str, str, Optional[str]]] - #entries = Dict[str: List[Tuple[str, str]]] + +def convert_or_endpoints_to_flows(endpoints : List[Tuple[str, str, Optional[str]]],bidir:bool)->Dict: + entries = {} - #tuple is in, out - #end = len(endpoints) if isinstance(endpoints,list) else 0 end = len(endpoints) i = 0 - bidir = 0 - log.debug("end={}".format(end)) - while(i < end): - endpoint = endpoints[i] - device_uuid, endpoint_uuid = endpoint[0:2] - log.info("current OCTools step {}, {}, {}".format(i, device_uuid, endpoint_uuid)) - if device_uuid not in entries.keys(): - entries[device_uuid] = [] - if i == 0: - entry_tuple = "0", endpoint_uuid - entries[device_uuid].append(entry_tuple) + if bidir: + log.info(f"i starts with {i} ") + i = i + 1 + while(i < end-2): + #i + endpoint = endpoints[i] + device_uuid, endpoint_uuid = endpoint[0:2] + + if device_uuid not in entries.keys(): + entries[device_uuid] = [] + #i+1 next_endpoint = endpoints[i+1] next_device_uuid, next_endpoint_uuid = next_endpoint[0:2] if next_device_uuid == device_uuid: - bidir = 1 - log.info("connection is bidirectional") - entry_tuple = next_endpoint_uuid, "0" + entry_tuple = endpoint_uuid, next_endpoint_uuid entries[device_uuid].append(entry_tuple) - i = i + 1 else: - log.debug("connection is unidirectional") - else: - if not bidir: - if i == end-1: - #is the last node - entry_tuple = endpoint_uuid, "0" - entries[device_uuid].append(entry_tuple) - else: - #it is a transit node - next_endpoint = endpoints[i+1] - next_device_uuid, next_endpoint_uuid = next_endpoint[0:2] - if next_device_uuid == device_uuid: - entry_tuple = endpoint_uuid, next_endpoint_uuid - entries[device_uuid].append(entry_tuple) - i = i + 1 - log.info("current OCTools step {}, {}, {}".format(i, next_device_uuid, device_uuid)) - else: - log.debug("ERROR in unidirectional connection 4") - return {} + + return {} + #i+2 + + next_2_endpoint = endpoints[i+2] + next_2_device_uuid, next_2_endpoint_uuid = next_2_endpoint[0:2] + #i+3 + next_3_endpoint = endpoints[i+3] + next_3_device_uuid, next_3_endpoint_uuid = next_3_endpoint[0:2] + log.info(f"dev {device_uuid} ") + log.info(f"dev2 {next_2_device_uuid} dev3 {next_3_device_uuid} ") + if next_2_device_uuid == next_3_device_uuid and next_3_device_uuid == device_uuid: + entry_tuple = next_2_endpoint_uuid, next_3_endpoint_uuid + entries[device_uuid].append(entry_tuple) + i = i + 4 else: - log.debug("Ocheck i {}, {}, {}".format(i, i+1, end-1)) - if i + 1 == end-1: - log.debug("current OCTools step {}, {}, {}".format(i, device_uuid, endpoint_uuid)) - #is the last node - entry_tuple = endpoint_uuid, "0" - entries[device_uuid].append(entry_tuple) - next_endpoint = endpoints[i+1] - log.debug("OCTools i+1 step {}, {}, {}".format(i+1, next_device_uuid, device_uuid)) - next_device_uuid, next_endpoint_uuid = next_endpoint[0:2] - if next_device_uuid == device_uuid: - entry_tuple = "0", next_endpoint_uuid - entries[device_uuid].append(entry_tuple) - i = i + 1 - else: - log.debug("ERROR in bidirectional connection 2") - return entries - else: - log.debug("OCTools i+1+2+3 step {}, {}, {}".format(i+1, next_device_uuid, device_uuid)) - #i+1 - next_endpoint = endpoints[i+1] - next_device_uuid, next_endpoint_uuid = next_endpoint[0:2] - if next_device_uuid == device_uuid: - entry_tuple = endpoint_uuid, next_endpoint_uuid - entries[device_uuid].append(entry_tuple) - else: - log.debug("ERROR in bidirectional connection 3") - log.debug("{}, {}, {}".format(i, next_device_uuid, device_uuid)) - return entries - #i+2 - next_2_endpoint = endpoints[i+2] - next_2_device_uuid, next_2_endpoint_uuid = next_2_endpoint[0:2] - #i+3 - next_3_endpoint = endpoints[i+3] - next_3_device_uuid, next_3_endpoint_uuid = next_3_endpoint[0:2] - log.debug(f"de {device_uuid} and i {i}") - log.debug(f"de2 {next_2_device_uuid} and dev3 {next_3_device_uuid}") - if next_2_device_uuid == next_3_device_uuid and next_3_device_uuid == device_uuid: - entry_tuple = next_2_endpoint_uuid, next_3_endpoint_uuid - entries[device_uuid].append(entry_tuple) - i = i + 3 - else: - log.debug("ERROR in bidirection connection 4") - return {} - i = i + 1 - return entries + return {} + else: + i +=1 + while(i < end-1): + #i + endpoint = endpoints[i] + device_uuid, endpoint_uuid = endpoint[0:2] + if device_uuid not in entries.keys(): + entries[device_uuid] = [] + #i+1 + next_endpoint = endpoints[i+1] + next_device_uuid, next_endpoint_uuid = next_endpoint[0:2] + if next_device_uuid == device_uuid: + entry_tuple = endpoint_uuid, next_endpoint_uuid + entries[device_uuid].append(entry_tuple) + i = i + 2 + else: + return {} + + return entries @@ -387,7 +352,7 @@ def conn_flows(endpoints : List[Tuple[str, str, Optional[str]]], bidir : int): i = i + 2 else: return {} - #rx tp + #rx tp endpoint = endpoints[i] device_uuid, endpoint_uuid = endpoint[0:2] if device_uuid not in entries.keys(): diff --git a/src/service/service/task_scheduler/TaskScheduler.py b/src/service/service/task_scheduler/TaskScheduler.py index 5c777a811b42b7da63e03ac215e5266cff800bef..eb61948962aa77c0d4e946146a76d5efcbb11ae5 100644 --- a/src/service/service/task_scheduler/TaskScheduler.py +++ b/src/service/service/task_scheduler/TaskScheduler.py @@ -34,8 +34,8 @@ from .tasks.Task_ServiceDelete import Task_ServiceDelete from .tasks.Task_ServiceSetStatus import Task_ServiceSetStatus from .TaskExecutor import CacheableObjectType, TaskExecutor from .tasks.Task_OpticalServiceConfigDelete import Task_OpticalServiceConfigDelete -from service.service.tools.OpticalTools import delete_lightpath - +from service.service.tools.OpticalTools import delete_lightpath ,DelFlexLightpath +from common.Constants import OpticalServiceType if TYPE_CHECKING: from service.service.service_handler_api.ServiceHandlerFactory import ServiceHandlerFactory @@ -347,25 +347,6 @@ class TasksScheduler: ) self._add_connection_to_executor_cache(connection) self._dag.add(connection_key, service_updating_key) - - - # for connection in pathcomp_reply.connections: - - # connection_key = include_connection( - # connection.connection_id, connection.service_id, has_media_channel=has_media_channel, - # has_optical_band=has_optical_band - # ) - # self._add_connection_to_executor_cache(connection) - - # self._executor.get_service(connection.service_id) - # for sub_service_id in connection.sub_service_ids: - # _,service_key_done = include_service( - # sub_service_id, has_media_channel=has_media_channel, - # has_optical_band=has_optical_band - # ) - # self._executor.get_service(sub_service_id) - # self._dag.add(connection_key, service_key_done) - t1 = time.time() LOGGER.debug('[compose_from_service] elapsed_time: {:f} sec'.format(t1-t0)) @@ -396,20 +377,30 @@ class TasksScheduler: if str_item_key in explored_items: continue connections = self._context_client.ListConnections(item.service_id) has_media_channel,has_optical_band=self.check_service_for_media_channel(connections=connections,item=item.service_id) - + oc_type = 1 if len(service.service_config.config_rules) > 0: - - - reply,code = delete_lightpath( - params['src'] - ,params ['dst'] + for constraint in service.service_constraints: + if "type" in constraint.custom.constraint_type: + oc_type = OpticalServiceType(str(constraint.custom.constraint_value)) + if oc_type == 2 : + reply,code = delete_lightpath( + params['src'] + , params ['dst'] , params['bitrate'] - , params['ob_id'] - ,delete_band=not has_media_channel , flow_id= params['flow_id'] ) - + else : + reply,code = DelFlexLightpath( + params['src'] + , params ['dst'] + , params['bitrate'] + , params['ob_id'] + , delete_band=not has_media_channel + , flow_id= params['flow_id'] + ) + + if code == 400 and reply_not_allowed in reply : MSG = 'Deleteion for the service is not Allowed , Served Lightpaths is not empty' raise Exception(MSG) @@ -443,22 +434,19 @@ class TasksScheduler: self._add_connection_to_executor_cache(connection) pending_items_to_explore.put(connection) - - - - + explored_items.add(str_item_key) elif isinstance(item, Connection): - - if code == 400 and reply_not_allowed in reply:break - str_item_key = grpc_message_to_json_string(item.connection_id) if str_item_key in explored_items: continue - connection_key = include_connection(item.connection_id, item.service_id,has_media_channel=has_media_channel,has_optical_band=has_optical_band) + connection_key = include_connection( item.connection_id + , item.service_id + , has_media_channel=has_media_channel + , has_optical_band=has_optical_band ) self._add_connection_to_executor_cache(connection) if include_service_config is not None : @@ -469,9 +457,7 @@ class TasksScheduler: if has_optical_band and is_media_channel: include_service_config(item.connection_id - , item.service_id - - ) + , item.service_id ) self._executor.get_service(item.service_id) @@ -479,7 +465,9 @@ class TasksScheduler: for sub_service_id in item.sub_service_ids: - _,service_key_done = include_service(sub_service_id,has_media_channel=has_media_channel,has_optical_band=has_optical_band) + _,service_key_done = include_service(sub_service_id + ,has_media_channel=has_media_channel + ,has_optical_band=has_optical_band) self._executor.get_service(sub_service_id) self._dag.add(service_key_done, connection_key) pending_items_to_explore.put(sub_service_id) diff --git a/src/service/service/task_scheduler/tasks/Task_OpticalConnectionConfigure.py b/src/service/service/task_scheduler/tasks/Task_OpticalConnectionConfigure.py index 8a991326c453c625f174ff8e7125f21f338a4244..0cfe6cf9930ae14fa5986aa9fadb24bd1510750e 100644 --- a/src/service/service/task_scheduler/tasks/Task_OpticalConnectionConfigure.py +++ b/src/service/service/task_scheduler/tasks/Task_OpticalConnectionConfigure.py @@ -15,12 +15,15 @@ from typing import TYPE_CHECKING, Dict, Tuple from common.DeviceTypes import DeviceTypeEnum from common.method_wrappers.ServiceExceptions import OperationFailedException -from common.proto.context_pb2 import ConnectionId, Device +from common.proto.context_pb2 import ConnectionId, Device , ServiceTypeEnum + from common.tools.grpc.Tools import grpc_message_to_json_string from service.service.service_handler_api.Tools import check_errors_setendpoint from service.service.task_scheduler.TaskExecutor import TaskExecutor from service.service.tools.EndpointIdFormatters import endpointids_to_raw from service.service.tools.ObjectKeys import get_connection_key +from service.service.service_handlers.oc.OCServiceHandler import OCServiceHandler + from ._Task import _Task import logging @@ -28,7 +31,8 @@ import logging if TYPE_CHECKING: from service.service.service_handler_api._ServiceHandler import _ServiceHandler -KEY_TEMPLATE = 'optical_Connection ({connection_id:s}):configure' +KEY_TEMPLATE = 'optical_Connection ({connection_id:s}):configure' +supported_handlers = (OCServiceHandler) class Task_OpticalConnectionConfigure(_Task): def __init__(self, task_executor : TaskExecutor, connection_id : ConnectionId) -> None: @@ -56,14 +60,20 @@ class Task_OpticalConnectionConfigure(_Task): for _, (handler, connection_devices) in service_handlers.items(): if service_handler is None : service_handler=handler else : - if type(handler) != type(service_handler) : - raise Exception("Devices are not compatible ") - + logging.info(f"type_servicehandler {handler} and {service_handler}") + if type(handler) != type(service_handler) : + if service.service_type == ServiceTypeEnum.SERVICETYPE_OPTICAL_CONNECTIVITY: + if isinstance(handler, supported_handlers) and isinstance(service_handler, supported_handlers): + break + + raise Exception("Devices are not compatible ") + connection_uuid = connection.connection_id.connection_uuid.uuid endpointids_to_set = endpointids_to_raw(connection.path_hops_endpoint_ids) errors = list() - + connection_uuid = connection.connection_id.connection_uuid.uuid + results_setendpoint = service_handler.SetEndpoint(endpointids_to_set, connection_uuid=connection_uuid) errors.extend(check_errors_setendpoint(endpointids_to_set, results_setendpoint)) @@ -74,7 +84,7 @@ class Task_OpticalConnectionConfigure(_Task): raise OperationFailedException(MSG.format(str_connection, str_service), extra_details=errors) self._task_executor.set_connection(connection) - + results_setendOpticalConfigs = service_handler.SetOpticalConfig( endpointids_to_set, connection_uuid=connection_uuid ) @@ -85,4 +95,3 @@ class Task_OpticalConnectionConfigure(_Task): str_connection = grpc_message_to_json_string(connection) str_service = grpc_message_to_json_string(service) raise OperationFailedException(MSG.format(str_connection, str_service), extra_details=errors) - diff --git a/src/service/service/task_scheduler/tasks/Task_OpticalConnectionDeconfigure.py b/src/service/service/task_scheduler/tasks/Task_OpticalConnectionDeconfigure.py index 9a43e5e0f7ce0843d84d1de2644e2c8099a0690d..0bebd7bd09476567b0532c68d5de0116b3ca03c1 100644 --- a/src/service/service/task_scheduler/tasks/Task_OpticalConnectionDeconfigure.py +++ b/src/service/service/task_scheduler/tasks/Task_OpticalConnectionDeconfigure.py @@ -20,6 +20,7 @@ from service.service.task_scheduler.TaskExecutor import TaskExecutor from service.service.tools.EndpointIdFormatters import endpointids_to_raw from service.service.tools.ObjectKeys import get_connection_key from ._Task import _Task +from service.service.service_handlers.openroadm.OpenROADMServiceHandler import OpenROADMServiceHandler KEY_TEMPLATE = 'optical_connection({connection_id:s}):deconfigure' @@ -48,37 +49,31 @@ class Task_OpticalConnectionDeconfigure(_Task): service = self._task_executor.get_service(connection.service_id) errors = [] service_handler_settings = {} - service_handlers = self._task_executor.get_service_handlers( - connection, service, **service_handler_settings - ) - # TODO: improve to select different service handlers when needed - # By now, assume a single service handler is retrieved for all the - # device types in the path, i.e., all entries carry the same - # service handler, so we choose the first one retrieved. - if len(service_handlers) < 1: - raise Exception('Unsupported case: {:s}'.format(str(service_handlers))) - service_handler,_ = list(service_handlers.values())[0] - - endpointids_to_delete = endpointids_to_raw(connection.path_hops_endpoint_ids) - connection_uuid = connection.connection_id.connection_uuid.uuid - if self._has_media_channel: - is_media_channel = service_handler.check_media_channel(connection_uuid) - if is_media_channel: + + service_handlers = self._task_executor.get_service_handlers(connection, service, **service_handler_settings) + for _, (service_handler, connection_devices) in service_handlers.items(): + + endpointids_to_delete = endpointids_to_raw(connection.path_hops_endpoint_ids) + connection_uuid = connection.connection_id.connection_uuid.uuid + if self._has_media_channel: + if isinstance(service_handler,OpenROADMServiceHandler):is_media_channel=True + else : is_media_channel = service_handler.check_media_channel(connection_uuid) + if is_media_channel: + results_deleteendpoint = service_handler.DeleteEndpoint(endpointids_to_delete, connection_uuid=connection_uuid) + errors = check_errors_deleteendpoint(endpointids_to_delete, results_deleteendpoint) + if len(errors) > 0: + MSG = 'DeleteEndpoint for OpticalConnection({:s}) from Service({:s})' + str_connection = grpc_message_to_json_string(connection) + str_service = grpc_message_to_json_string(service) + raise OperationFailedException(MSG.format(str_connection, str_service), extra_details=errors) + if is_media_channel: + self._task_executor.delete_connection(self._connection_id) + else: results_deleteendpoint = service_handler.DeleteEndpoint(endpointids_to_delete, connection_uuid=connection_uuid) errors = check_errors_deleteendpoint(endpointids_to_delete, results_deleteendpoint) - if len(errors) > 0: - MSG = 'DeleteEndpoint for OpticalConnection({:s}) from Service({:s})' - str_connection = grpc_message_to_json_string(connection) - str_service = grpc_message_to_json_string(service) - raise OperationFailedException(MSG.format(str_connection, str_service), extra_details=errors) - if is_media_channel: + if len(errors) > 0: + MSG = 'DeleteEndpoint for OpticalConnection({:s}) from Service({:s})' + str_connection = grpc_message_to_json_string(connection) + str_service = grpc_message_to_json_string(service) + raise OperationFailedException(MSG.format(str_connection, str_service), extra_details=errors) self._task_executor.delete_connection(self._connection_id) - else: - results_deleteendpoint = service_handler.DeleteEndpoint(endpointids_to_delete, connection_uuid=connection_uuid) - errors = check_errors_deleteendpoint(endpointids_to_delete, results_deleteendpoint) - if len(errors) > 0: - MSG = 'DeleteEndpoint for OpticalConnection({:s}) from Service({:s})' - str_connection = grpc_message_to_json_string(connection) - str_service = grpc_message_to_json_string(service) - raise OperationFailedException(MSG.format(str_connection, str_service), extra_details=errors) - self._task_executor.delete_connection(self._connection_id) diff --git a/src/service/service/tools/OpticalTools.py b/src/service/service/tools/OpticalTools.py index 8f03f0338e9b998557166d6f88c279cee3978dff..8ec7dcee8bb8fd0b4fa17d8a2c59fc13439e0b50 100644 --- a/src/service/service/tools/OpticalTools.py +++ b/src/service/service/tools/OpticalTools.py @@ -177,13 +177,15 @@ def get_optical_band(idx) -> str: return optical_band_uni_txt -def delete_lightpath( src, dst, bitrate, ob_id, delete_band, flow_id=None) -> str: +def DelFlexLightpath( src, dst, bitrate, ob_id, delete_band, flow_id=None) -> str: reply = "200" delete_band = 1 if delete_band else 0 base_url = get_optical_controller_base_url() if not TESTING: if flow_id is not None: - urlx = "{:s}/DelFlexLightpath/{}/{}/{}/{}/{}".format(base_url, src, dst, bitrate, ob_id, flow_id) + if ob_id is not None : + urlx = "{:s}/DelFlexLightpath/{}/{}/{}/{}/{}".format(base_url, src, dst, bitrate, flow_id,ob_id) + else : #urlx = "http://{}:{}/OpticalTFS/DelOpticalBand/{}/{}/{}".format(OPTICAL_IP, OPTICAL_PORT, src, dst, ob_id) urlx = "{:s}/DelOpticalBandSimple/{}".format(base_url, ob_id) @@ -194,15 +196,16 @@ def delete_lightpath( src, dst, bitrate, ob_id, delete_band, flow_id=None) -> st code = r.status_code return (reply, code) -def DelFlexLightpath (flow_id, src, dst, bitrate, o_band_id): +def delete_lightpath ( src, dst, bitrate, flow_id): reply = "200" base_url = get_optical_controller_base_url() if not TESTING: - urlx = "{:s}/DelFlexLightpath/{}/{}/{}/{}/{}".format(base_url, flow_id, src, dst, bitrate, o_band_id) + urlx = "{:s}/DelLightpath/{}/{}/{}/{}".format(base_url, src, dst, bitrate, flow_id) headers = {"Content-Type": "application/json"} r = requests.delete(urlx, headers=headers) - reply = r.text - return reply + reply = r.text + code = r.status_code + return (reply, code) def get_lightpaths() -> str: base_url = get_optical_controller_base_url() diff --git a/src/tests/ofc24/r_t.sh b/src/tests/ofc24/r_t.sh index ec0d57aea38d9b07c5d369186aa332fac4b5aff5..11c1fd9dfadf029d0f7fd079fd1973ba7aea6786 100755 --- a/src/tests/ofc24/r_t.sh +++ b/src/tests/ofc24/r_t.sh @@ -27,7 +27,7 @@ docker rm na2 docker network create --subnet=192.168.100.0/24 my-custom-network -screen -dmS t1 -T xterm sh -c "docker run --name t1 --net my-custom-network -p 10.0.2.4:2023:2022 -v /home/tfs/tfs-ctrl/src/tests/ofc24/tempOC/files:/files -it asgamb1/oc23bgp.img:latest bash -c 'cp /files/transponders_x4.xml demoECOC21.xml ; ./startNetconfAgent.sh'" -screen -dmS t3 -T xterm sh -c "docker run --name na3 --net my-custom-network -p 10.0.2.4:2025:2022 -v /home/tfs/tfs-ctrl/src/tests/ofc24/tempOC/files:/files -it asgamb1/flexscale-node.img:latest bash -c 'cp /files/platform_r1.xml init_openconfig-platform.xml ; ./startNetconfAgent.sh'" -screen -dmS t2 -T xterm sh -c "docker run --name t2 --net my-custom-network -p 10.0.2.4:2024:2022 -v /home/tfs/tfs-ctrl/src/tests/ofc24/tempOC/files:/files -it asgamb1/oc23bgp.img:latest bash -c 'cp /files/transponders_x4_2.xml demoECOC21.xml ; ./startNetconfAgent.sh'" -screen -dmS t4 -T xterm sh -c "docker run --name na2 --net my-custom-network -p 10.0.2.4:2026:2022 -v /home/tfs/tfs-ctrl/src/tests/ofc24/tempOC/files:/files -it asgamb1/flexscale-node.img:latest bash -c 'cp /files/platform_r2.xml init_openconfig-platform.xml ; ./startNetconfAgent.sh'" +screen -dmS t1 -T xterm sh -c "docker run --name t1 --net my-custom-network -p 2023:2022 -v /home/tfs/tfs-hackfest7/src/tests/ofc24/tempOC/files:/files -it asgamb1/oc23bgp.img:latest bash -c 'cp /files/transponders_x4.xml demoECOC21.xml ; ./startNetconfAgent.sh'" +screen -dmS t3 -T xterm sh -c "docker run --name na3 --net my-custom-network -p 2025:2022 -v /home/tfs/tfs-hackfest7/src/tests/ofc24/tempOC/files:/files -it asgamb1/flexscale-node.img:latest bash -c 'cp /files/platform_r1.xml init_openconfig-platform.xml ; ./startNetconfAgent.sh'" +screen -dmS t2 -T xterm sh -c "docker run --name t2 --net my-custom-network -p 2024:2022 -v /home/tfs/tfs-hackfest7/src/tests/ofc24/tempOC/files:/files -it asgamb1/oc23bgp.img:latest bash -c 'cp /files/transponders_x4_2.xml demoECOC21.xml ; ./startNetconfAgent.sh'" +screen -dmS t4 -T xterm sh -c "docker run --name na2 --net my-custom-network -p 2026:2022 -v /home/tfs/tfs-hackfest7/src/tests/ofc24/tempOC/files:/files -it asgamb1/flexscale-node.img:latest bash -c 'cp /files/platform_r2.xml init_openconfig-platform.xml ; ./startNetconfAgent.sh'" diff --git a/src/webui/service/__init__.py b/src/webui/service/__init__.py index bfdfbb9fbafa6fd9e48c7714e8e34407505a21e9..45dfe2c94382378dee05ff6f8355200411313596 100644 --- a/src/webui/service/__init__.py +++ b/src/webui/service/__init__.py @@ -74,12 +74,12 @@ def create_app(use_config=None, web_app_root=None): app = Flask(__name__) if use_config: app.config.from_mapping(**use_config) - + app.config.update(HEALTHZ={ 'live': liveness, 'ready': readiness }) - + app.register_blueprint(healthz, url_prefix='/healthz') from webui.service.js.routes import js # pylint: disable=import-outside-toplevel diff --git a/src/webui/service/optical_link/routes.py b/src/webui/service/optical_link/routes.py index 242573e2dcf839fe9bba56aa7bea37c1abab30db..2dfd9396407a7b5e44fed7e7b1638b32276585f0 100644 --- a/src/webui/service/optical_link/routes.py +++ b/src/webui/service/optical_link/routes.py @@ -174,7 +174,7 @@ def get_optical_bands(): try: r = requests.get(urlx, headers=headers) reply = r.json() - if (reply): + if (reply and r.status_code == 200): optical_bands=reply ob_keys = optical_bands.keys() for ob_key in ob_keys : diff --git a/src/webui/service/opticalconfig/routes.py b/src/webui/service/opticalconfig/routes.py index 042cb63dd701306db835e96910215bfc78a2016d..4473789a6d8bb33986ef218e69decd02d3ef265a 100644 --- a/src/webui/service/opticalconfig/routes.py +++ b/src/webui/service/opticalconfig/routes.py @@ -62,7 +62,8 @@ def home() : value["channels_number"] = len(value['channels']) else: if 'interfaces' in value: - value["channels_number"] = len(value['interfaces']) + + value["channels_number"] = len([i for i in value["interfaces"] if i["type"] != 'Null']) # value['operationalMode']=value['operational-mode'] # value['targetOutputPower']=value['target-output-power'] value['opticalconfig_id']=configs.opticalconfig_id @@ -87,6 +88,7 @@ def show_details(config_uuid): device_name = "" config = json.loads(opticalConfig.config) + logging.info(f"config {config}") if "device_name" in config: device_name = config["device_name"] @@ -123,16 +125,17 @@ def show_details(config_uuid): if config_type == DeviceTypeEnum.OPEN_ROADM._value_: if 'interfaces' in config : for interface in config["interfaces"]: - new_config={} - new_config["name"]=interface["name"] if "name" in interface else '' - new_config["administrative_state"]=interface[ "administrative_state"] - new_config["circuit_pack_name"]=interface["circuit_pack_name"] - new_config["port"]=interface["port"] - new_config["interface_list"]=interface["interface_list"] - new_config["frequency"]=interface["frequency"] - new_config["width"]=interface[ "width"] - new_config["type"]=interface["type"] - device_details.append(new_config) + if interface["type"] != "Null": + new_config={} + new_config["name"]=interface["name"] if "name" in interface else '' + new_config["administrative_state"]=interface[ "administrative_state"] + new_config["circuit_pack_name"]=interface["circuit_pack_name"] + new_config["port"]=interface["port"] + new_config["interface_list"]=interface["interface_list"] + new_config["frequency"]=interface["frequency"] + new_config["width"]=interface[ "width"] + new_config["type"]=interface["type"] + 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,type=config_type) @@ -329,12 +332,19 @@ def update(config_uuid, channel_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() + try: + context_client.connect() + opticalConfig_list = context_client.GetOpticalConfig(Empty()) + device_client.connect() + device_client.GetDeviceConfiguration(opticalConfig_list) + except Exception as err : + flash(f'Connection is failed: `{str(err)}`', 'danger') + return redirect(url_for("opticalconfig.home")) + + finally: + context_client.close() + device_client.close() + return home() diff --git a/src/webui/service/static/topology_icons/openroadm.png b/src/webui/service/static/topology_icons/openroadm.png new file mode 100644 index 0000000000000000000000000000000000000000..2015c839391120f8830d4ca765b1852d9f7bff83 Binary files /dev/null and b/src/webui/service/static/topology_icons/openroadm.png differ diff --git a/src/webui/service/templates/base_optical/home.html b/src/webui/service/templates/base_optical/home.html index bfa9f674c598e7b254e55cb4e9e47acd96558cc5..93eb027fd1ef1c11f405de045d2e8000273eca1a 100644 --- a/src/webui/service/templates/base_optical/home.html +++ b/src/webui/service/templates/base_optical/home.html @@ -20,6 +20,8 @@

Optical View

+
+
Optical Configuration :