diff --git a/proto/context.proto b/proto/context.proto index 27d0cdafd41ac346eddb8a57747318abc07ae25a..685c757937d82cafe662e383d8cf30cbb677930b 100644 --- a/proto/context.proto +++ b/proto/context.proto @@ -665,6 +665,8 @@ message AuthenticationResult { bool authenticated = 2; } + + // ---------------- Experimental ------------------------ message OpticalConfigId { string opticalconfig_uuid = 1; @@ -685,6 +687,8 @@ message OpticalConfigEvent { } + + // ---- Optical Link ---- message OpticalEndPointId { @@ -719,6 +723,29 @@ message OpticalLink { } +message ChannelId { + Uuid channel_uuid = 1; +} + +message OpticalBandId { + Uuid opticalband_uuid = 1; +} + + +message OpticalBand { + + OpticalBandId opticalband_id = 1 ; + ConnectionId connection_id =2 ; + ChannelId channel_id = 3; + ServiceId service_id =4; + +} + +message OpticalBandList { + repeated OpticalBand opticalbands =1 ; + +} + ////////////////// Config Rule Delete //////////// diff --git a/src/common/tools/descriptor/Loader.py b/src/common/tools/descriptor/Loader.py index 4a5fc37dbfe216db194bf760b1a8f8f1e6f543d1..3ddc34f8039fc7c9201aa772814b387f9558c494 100644 --- a/src/common/tools/descriptor/Loader.py +++ b/src/common/tools/descriptor/Loader.py @@ -509,6 +509,7 @@ class DescriptorLoader: self._unload_normal_mode() def compose_notifications(results : TypeResults) -> TypeNotificationList: + notifications = [] for entity_name, action_name, num_ok, error_list in results: entity_name_singluar,entity_name_plural = ENTITY_TO_TEXT[entity_name] diff --git a/src/context/client/ContextClient.py b/src/context/client/ContextClient.py index b8eed67aa8ceee4e043aa0c654b6287cb2ebbf10..d3513c36a0f79a82b92669b42eb8fffd53e69c52 100644 --- a/src/context/client/ContextClient.py +++ b/src/context/client/ContextClient.py @@ -27,7 +27,8 @@ from common.proto.context_pb2 import ( OpticalConfig, OpticalConfigId, OpticalConfigList , OpticalLink, OpticalLinkList, Service, ServiceConfigRule, ServiceEvent, ServiceFilter, ServiceId, ServiceIdList, ServiceList, Slice, SliceEvent, SliceFilter, SliceId, SliceIdList, SliceList, - Topology, TopologyDetails, TopologyEvent, TopologyId, TopologyIdList, TopologyList, + Topology, TopologyDetails, TopologyEvent, TopologyId, TopologyIdList, TopologyList,OpticalBand ,OpticalBandId, + OpticalBandList ) from common.proto.context_pb2_grpc import ContextServiceStub from common.proto.context_policy_pb2_grpc import ContextPolicyServiceStub @@ -484,6 +485,21 @@ class ContextClient: LOGGER.debug('DeleteOpticalChannel result: {:s}'.format(grpc_message_to_json_string(response))) return response + + @RETRY_DECORATOR + def GetOpticalBand(self, request : Empty) -> OpticalBandList: + LOGGER.debug('GetOpticalBand request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.GetOpticalBand(request) + LOGGER.debug('GetOpticalBand result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + def SetOpticalBand(self,request : OpticalBand) -> Empty: + LOGGER.debug('SetOpticalBand request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.SetOpticalBand(request) + LOGGER.debug('SetOpticalBand result: {:s}'.format(grpc_message_to_json_string(response))) + return response + #--------------------------- Optical Link ------------------------ def GetOpticalLinkList(self, request: Empty) -> OpticalLinkList: LOGGER.debug('ListOpticalLinks request: {:s}'.format(grpc_message_to_json_string(request))) diff --git a/src/context/service/ContextServiceServicerImpl.py b/src/context/service/ContextServiceServicerImpl.py index fa6b6a34f1cb3d4be8b61859e98a33b92b884bff..d789b7863a4b4fc0635008a3a5d46eb7f88be03f 100644 --- a/src/context/service/ContextServiceServicerImpl.py +++ b/src/context/service/ContextServiceServicerImpl.py @@ -25,7 +25,7 @@ from common.proto.context_pb2 import ( Slice, SliceEvent, SliceFilter, SliceId, SliceIdList, SliceList, Topology, TopologyDetails, TopologyEvent, TopologyId, TopologyIdList, TopologyList, OpticalConfigList, OpticalConfigId, OpticalConfig, OpticalLink, OpticalLinkList, - ServiceConfigRule + ServiceConfigRule,OpticalBand,OpticalBandId,OpticalBandList ) from common.proto.policy_pb2 import PolicyRuleIdList, PolicyRuleId, PolicyRuleList, PolicyRule from common.proto.context_pb2_grpc import ContextServiceServicer @@ -69,6 +69,10 @@ from .database.OpticalLink import ( optical_link_delete, optical_link_get, optical_link_list_objs, optical_link_set ) from .database.ConfigRule import delete_config_rule +from .database.OpticalBand import ( + get_optical_band,set_optical_band + ) + LOGGER = logging.getLogger(__name__) METRICS_POOL = MetricsPool('Context', 'RPC') @@ -357,6 +361,17 @@ class ContextServiceServicerImpl(ContextServiceServicer, ContextPolicyServiceSer def DeleteOpticalChannel(self, request : OpticalConfig, context : grpc.ServicerContext) -> Empty: delete_opticalchannel(self.db_engine, self.messagebroker, request) return Empty() + + @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) + def GetOpticalBand(self, request : Empty, context : grpc.ServicerContext) -> OpticalBandList: + result = get_optical_band(self.db_engine) + return OpticalBandList(opticalbands=result) + + @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) + def SetOpticalBand(self, request : OpticalBand, context : grpc.ServicerContext) -> Empty: + result = set_optical_band(self.db_engine, request) + return Empty() + #--------------------- Experimental Optical Link ------------------- diff --git a/src/context/service/database/OpticalBand.py b/src/context/service/database/OpticalBand.py new file mode 100644 index 0000000000000000000000000000000000000000..75c4e029c7e9f4405bc9e1d332bbfbb515c3ce91 --- /dev/null +++ b/src/context/service/database/OpticalBand.py @@ -0,0 +1,54 @@ +# 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 +from sqlalchemy.engine import Engine +from sqlalchemy.orm import Session, selectinload, sessionmaker +from sqlalchemy_cockroachdb import run_transaction +from sqlalchemy.dialects.postgresql import insert + +from typing import Dict, List +from common.proto.context_pb2 import OpticalBand,OpticalBandId,OpticalBandList +from .models.OpticalConfig.OpticalBandModel import OpticalBandModel + + +LOGGER = logging.getLogger(__name__) + + + +def get_optical_band(db_engine : Engine): + def callback(session:Session): + results = session.query(OpticalBandModel).all() + + return {"opticalbands":[obj.dump_id() for obj in results]} + obj = run_transaction(sessionmaker(bind=db_engine), callback) + return obj + + +def set_optical_band(db_engine : Engine, ob_data ): + + def callback(session : Session) -> List[Dict]: + if len(ob_data) > 0: + stmt = insert(OpticalBandModel).values(ob_data) + stmt = stmt.on_conflict_do_update( + index_elements=[OpticalBandModel.ob_uuid], + set_=dict( + connection_uuid = stmt.excluded.connection_uuid + ) + ) + stmt = stmt.returning(OpticalBandModel.ob_uuid) + ob_id = session.execute(stmt).fetchone() + + ob_id = run_transaction(sessionmaker(bind=db_engine), callback) + return {'ob_id': ob_id} diff --git a/src/context/service/database/OpticalConfig.py b/src/context/service/database/OpticalConfig.py index 2876ff073e155e2cf0573ab0f7daa6812e7d2f8d..583b2d2c9209317dced20f3c5ac08d837a8f7eaa 100644 --- a/src/context/service/database/OpticalConfig.py +++ b/src/context/service/database/OpticalConfig.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import json, logging +import json, logging ,datetime from sqlalchemy.dialects.postgresql import insert from common.message_broker.MessageBroker import MessageBroker from common.DeviceTypes import DeviceTypeEnum @@ -25,11 +25,12 @@ from .models.OpticalConfig.TransponderModel import TransponderTypeModel, Optica from .models.OpticalConfig.RoadmModel import RoadmTypeModel, ChannelModel, ORInterfaceModel from context.service.database.uuids.OpticalConfig import ( channel_get_uuid , opticalconfig_get_uuid ,transponder_get_uuid,roadm_get_uuid, - interface_get_uuid + interface_get_uuid , ob_get_uuid ) from .Events import notify_event_opticalconfig - +from .OpticalBand import set_optical_band LOGGER = logging.getLogger(__name__) +now = datetime.datetime.utcnow() def get_opticalconfig(db_engine : Engine): def callback(session:Session): @@ -285,9 +286,11 @@ def update_opticalconfig(db_engine : Engine, request : OpticalConfig): channel_namespace = None OpticalConfig_data = [] config_type = None + optical_bands = [] #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) @@ -357,6 +360,7 @@ def update_opticalconfig(db_engine : Engine, request : OpticalConfig): if channel_namespace is None and 'channel_namespace' in config['new_config']: channel_namespace=config['new_config']['channel_namespace'] if 'is_opticalband' in config and not config['is_opticalband']: + is_optical_band=config['is_opticalband'] #channels = [channel['name']['index'] for channel in config['channels']] if 'flow_handled' in config and len(config['flow_handled'])>0 : num = 0 @@ -380,7 +384,9 @@ def update_opticalconfig(db_engine : Engine, request : OpticalConfig): "channel_index" : str(channel_index) if channel_index is not None else None }) if 'is_opticalband' in config and config['is_opticalband']: + is_optical_band=config['is_opticalband'] #channels = [channel['name']['index'] for channel in config['channels']] + if 'flow_handled' in config and len(config['flow_handled']) > 0: ob_id = config['new_config']['ob_id'] if 'ob_id' in config['new_config'] else None num = 0 @@ -401,6 +407,13 @@ def update_opticalconfig(db_engine : Engine, request : OpticalConfig): "type" : 'optical_band', "channel_index" : str( channel_index) if channel_index is not None else None }) + optical_bands.append ({ + "channel_uuid" : channel_get_uuid(f'optical_bands_{channel_index}',device_uuid), + 'connection_uuid' : config['connection_uuid'], + "ob_uuid" : ob_get_uuid (f'ob_{channel_index}' ), + 'created_at':now + + }) roadms.append({ "roadm_uuid" : roadm_get_uuid(device_id), @@ -481,8 +494,10 @@ def update_opticalconfig(db_engine : Engine, request : OpticalConfig): ) stmt = stmt.returning(ChannelModel.channel_uuid) opticalChannel_id = session.execute(stmt).fetchone() - + opticalconfig_id = run_transaction(sessionmaker(bind=db_engine), callback) + if is_optical_band: set_optical_band(db_engine,optical_bands) + return {'opticalconfig_uuid': opticalconfig_id} def select_opticalconfig(db_engine : Engine, request : OpticalConfigId): diff --git a/src/context/service/database/models/OpticalConfig/OpticalBandModel.py b/src/context/service/database/models/OpticalConfig/OpticalBandModel.py new file mode 100644 index 0000000000000000000000000000000000000000..0e3351388d623c96b68f164b2d89a5ea40256b16 --- /dev/null +++ b/src/context/service/database/models/OpticalConfig/OpticalBandModel.py @@ -0,0 +1,47 @@ +# 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 json, logging, operator +from sqlalchemy import Column, DateTime, ForeignKey, Integer, CheckConstraint, String +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.orm import relationship +from typing import Dict +from context.service.database.models._Base import _Base + +LOGGER = logging.getLogger(__name__) + +class OpticalBandModel(_Base): + __tablename__ = 'opticalband' + + ob_uuid = Column(UUID(as_uuid=False), primary_key=True) + connection_uuid = Column(ForeignKey('connection.connection_uuid', ondelete='RESTRICT'), nullable=False) + channel_uuid = Column(ForeignKey('channel.channel_uuid', ondelete='RESTRICT'), nullable=False) + + created_at = Column(DateTime, nullable=False) + + + ob_channel = relationship('ChannelModel') # back_populates='connections' + ob_connection = relationship('ConnectionModel') # lazy='joined', back_populates='connection' + + def dump_id(self) -> Dict: + return {'opticalband_uuid': {'uuid': self.ob_uuid}} + + def dump(self) -> Dict: + return { + 'opticalband_id' : self.dump_id(), + 'channel_id' : self.ob_channel.dump_id(), + 'connection_id' : self.ob_connection.dump_id(), + 'service_id' : self.ob_connection.connection_service.dump_id() + } + diff --git a/src/context/service/database/uuids/OpticalConfig.py b/src/context/service/database/uuids/OpticalConfig.py index a23c1370e2541f8bfc5decf8c746543ec85c1f5e..e11f835975fca86aba9e02eb6bbc15c6598627bb 100644 --- a/src/context/service/database/uuids/OpticalConfig.py +++ b/src/context/service/database/uuids/OpticalConfig.py @@ -15,6 +15,7 @@ from common.method_wrappers.ServiceExceptions import InvalidArgumentsException from common.proto.context_pb2 import DeviceId from ._Builder import get_uuid_from_string, get_uuid_random +import logging def channel_get_uuid( channel_name : str , device_id : str, allow_random : bool = False @@ -73,3 +74,20 @@ def opticalconfig_get_uuid( 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 +) -> str: + + if ( ob_name is not None): + + + result = get_uuid_from_string(ob_name) + logging.info(f'get_ob_uuid {result}') + return result + if allow_random: return get_uuid_random() + + raise InvalidArgumentsException([ + ('ob_name ', ob_name), + ], extra_details=['ob_name is required to produce a Optical band UUID']) diff --git a/src/device/service/drivers/oc_driver/OCDriver.py b/src/device/service/drivers/oc_driver/OCDriver.py index bfc84bf1a9b95f8b4f5c1e935b23e5bdde2136d4..5fede091ff6d9f2057d05582a810b5f8e73d60cf 100644 --- a/src/device/service/drivers/oc_driver/OCDriver.py +++ b/src/device/service/drivers/oc_driver/OCDriver.py @@ -68,7 +68,7 @@ class NetconfSessionHandler: self.__port = int(port) self.__username = settings.get('username') self.__password = settings.get('password') - self.__vendor = settings.get('vendor') + 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) @@ -155,117 +155,121 @@ def edit_config( str_method = 'DeleteConfig' if delete else 'SetConfig' results = [] - logging.info(f"commmit per rule {commit_per_rule}") + str_config_messages=[] - if str_method == 'SetConfig': - if (conditions['edit_type']=='optical-channel'): - #transponder - str_config_messages = edit_optical_channel(resources) - 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_v2(resources) - #Disabling of the Configuration - else: - # Device type is Transponder - if (conditions['edit_type'] == "optical-channel"): - _,ports,_=seperate_port_config(resources) - str_config_messages=change_optical_channel_status(state="DISABLED",ports=ports) - - # Device type is Roadm - elif (conditions['edit_type']=='optical-band'): - str_config_messages=delete_optical_band(resources) - else : - str_config_messages=disable_media_channel(resources) - - for str_config_message in 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=target, default_operation=default_operation, - 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) - - if netconf_handler.vendor == "CISCO": - if "L2VSI" in resources[0][1]: + if netconf_handler.vendor is None : + if str_method == 'SetConfig': + if (conditions['edit_type']=='optical-channel'): + #transponder + str_config_messages = edit_optical_channel(resources) + 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_v2(resources) + #Disabling of the Configuration + else: + # Device type is Transponder + if (conditions['edit_type'] == "optical-channel"): + _,ports,_=seperate_port_config(resources) + str_config_messages=change_optical_channel_status(state="DISABLED",ports=ports) + + # Device type is Roadm + elif (conditions['edit_type']=='optical-band'): + 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") + result= netconf_handler.edit_config( # configure the device + 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 + + #results[i] = True + results.append(result) + + else : + if netconf_handler.vendor == "CISCO": + if "L2VSI" in resources[0][1]: + #Configure by CLI + logger.warning("CLI Configuration") + cli_compose_config(resources, + delete=delete, + host= netconf_handler._NetconfSessionHandler__address, + user=netconf_handler._NetconfSessionHandler__username, + passw=netconf_handler._NetconfSessionHandler__password) + for i,resource in enumerate(resources): + results.append(True) + else: + logger.warning("CLI Configuration CISCO INTERFACE") + cisco_interface(resources, + delete=delete, + host= netconf_handler._NetconfSessionHandler__address + , user=netconf_handler._NetconfSessionHandler__username, + passw=netconf_handler._NetconfSessionHandler__password) + for i,resource in enumerate(resources): + results.append(True) + elif netconf_handler.vendor == "UFISPACE": #Configure by CLI - logger.warning("CLI Configuration") - cli_compose_config(resources, - delete=delete, - host= netconf_handler._NetconfSessionHandler__address, - user=netconf_handler._NetconfSessionHandler__username, - passw=netconf_handler._NetconfSessionHandler__password) + logger.warning("CLI Configuration: {:s}".format(resources)) + ufi_interface(resources, + delete=delete, + host= netconf_handler._NetconfSessionHandler__address, + user=netconf_handler._NetconfSessionHandler__username, + passw=netconf_handler._NetconfSessionHandler__password) for i,resource in enumerate(resources): results.append(True) - else: - logger.warning("CLI Configuration CISCO INTERFACE") - cisco_interface(resources, - delete=delete, - host= netconf_handler._NetconfSessionHandler__address - , user=netconf_handler._NetconfSessionHandler__username, - passw=netconf_handler._NetconfSessionHandler__password) + else: for i,resource in enumerate(resources): - results.append(True) - elif netconf_handler.vendor == "UFISPACE": - #Configure by CLI - logger.warning("CLI Configuration: {:s}".format(resources)) - ufi_interface(resources, - delete=delete, - host= netconf_handler._NetconfSessionHandler__address, - user=netconf_handler._NetconfSessionHandler__username, - passw=netconf_handler._NetconfSessionHandler__password) - for i,resource in enumerate(resources): - results.append(True) - else: - for i,resource in enumerate(resources): - str_resource_name = 'resources[#{:d}]'.format(i) - try: - logger.debug('[{:s}] resource = {:s}'.format(str_method, str(resource))) - chk_type(str_resource_name, resource, (list, tuple)) - 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 - 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 - 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 - if 'table_connections' in resource_key: - time.sleep(5) # CPU usage might exceed critical level after route redistribution, BGP daemon needs time to reload - - #results[i] = True - results.append(True) - except Exception as e: # pylint: disable=broad-except - str_operation = 'preparing' if target == 'candidate' else ('deleting' if delete else 'setting') - msg = '[{:s}] Exception {:s} {:s}: {:s}' - logger.exception(msg.format(str_method, str_operation, str_resource_name, str(resource))) - #results[i] = e # if validation fails, store the exception - results.append(e) - - if not commit_per_rule: - try: - netconf_handler.commit() - except Exception as e: # pylint: disable=broad-except - msg = '[{:s}] Exception committing: {:s}' - str_operation = 'preparing' if target == 'candidate' else ('deleting' if delete else 'setting') - logger.exception(msg.format(str_method, str_operation, str(resources))) - results = [e for _ in resources] # if commit fails, set exception in each resource + str_resource_name = 'resources[#{:d}]'.format(i) + try: + logger.debug('[{:s}] resource = {:s}'.format(str_method, str(resource))) + chk_type(str_resource_name, resource, (list, tuple)) + 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 + 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 + 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 + if 'table_connections' in resource_key: + time.sleep(5) # CPU usage might exceed critical level after route redistribution, BGP daemon needs time to reload + + #results[i] = True + results.append(True) + except Exception as e: # pylint: disable=broad-except + str_operation = 'preparing' if target == 'candidate' else ('deleting' if delete else 'setting') + msg = '[{:s}] Exception {:s} {:s}: {:s}' + logger.exception(msg.format(str_method, str_operation, str_resource_name, str(resource))) + #results[i] = e # if validation fails, store the exception + results.append(e) + + if not commit_per_rule: + try: + netconf_handler.commit() + except Exception as e: # pylint: disable=broad-except + msg = '[{:s}] Exception committing: {:s}' + str_operation = 'preparing' if target == 'candidate' else ('deleting' if delete else 'setting') + 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 diff --git a/src/device/service/drivers/oc_driver/templates/VPN/roadms.py b/src/device/service/drivers/oc_driver/templates/VPN/roadms.py index e5362f48243dbca9bb2b439855e2d8ed84f14eee..f3befa00137f6fc846ccabfe380ae41dcf22d5c7 100644 --- a/src/device/service/drivers/oc_driver/templates/VPN/roadms.py +++ b/src/device/service/drivers/oc_driver/templates/VPN/roadms.py @@ -156,7 +156,7 @@ def create_media_channel_v2 (resources): if resource['resource_key'] == "index": with tag('index'):text(str(int(index)+n)) elif resource['resource_key']== 'optical-band-parent' : - with tag('optical-band-parent',xmlns=optical_band_namespaces):text(int(resource['value'])+int(n)) + with tag('optical-band-parent',xmlns=optical_band_namespaces):text(int(resource['value'])) elif resource['resource_key']== 'admin-state' : with tag('admin-status'):text(resource['value']) else: diff --git a/src/service/service/ServiceServiceServicerImpl.py b/src/service/service/ServiceServiceServicerImpl.py index 9120d475b8b2bb88bfcf7d7da3cadba2bf9931e4..ba43bdbc8635959d843f164ce02c6509001b2baf 100644 --- a/src/service/service/ServiceServiceServicerImpl.py +++ b/src/service/service/ServiceServiceServicerImpl.py @@ -312,7 +312,7 @@ class ServiceServiceServicerImpl(ServiceServiceServicer): devices, _service, reply_json, context_uuid_x, topology_uuid_x, optical_band_txt ) - tasks_scheduler.compose_from_pathcompreply( + tasks_scheduler.compose_from_opticalcontroller_reply( optical_reply, is_delete=False) else: if len(service_with_uuids.service_endpoint_ids) >= num_expected_endpoints: diff --git a/src/service/service/service_handler_api/_ServiceHandler.py b/src/service/service/service_handler_api/_ServiceHandler.py index 9e90ade1da362b0d4f5bb043541bf96cd26a985d..f8ef8d55188b9f7f9ae87b36444e6f23caad31d3 100644 --- a/src/service/service/service_handler_api/_ServiceHandler.py +++ b/src/service/service/service_handler_api/_ServiceHandler.py @@ -150,3 +150,25 @@ class _ServiceHandler: the processing must be returned. """ raise NotImplementedError() + + + def SetOpticalConfig ( + self, endpoints : List[Tuple[str, str, Optional[str]]], + connection_uuid : Optional[str] = None + ) -> List[Union[bool, Exception]]: + + """ Create/Update Optical configuration related to optical service. + Parameters: + endpoints: List[Tuple[str, str, Optional[str]]] + List of tuples, each containing a device_uuid, + endpoint_uuid and, optionally, the topology_uuid + of the endpoint to be added. + connection_uuid : Optional[str] + If specified, is the UUID of the connection this endpoint is associated to. + Returns: + results: List[Union[bool, Exception]] + List of results for configurations changes requested. + Return values must be in the same order as the requested + configurtations. + """ + diff --git a/src/service/service/service_handlers/oc/OCServiceHandler.py b/src/service/service/service_handlers/oc/OCServiceHandler.py index 49212d1aa5e82097627709dc77fe6d99914b6d98..1f2211e094f980ecf14bb65fbc576aa131020a21 100644 --- a/src/service/service/service_handlers/oc/OCServiceHandler.py +++ b/src/service/service/service_handlers/oc/OCServiceHandler.py @@ -48,26 +48,7 @@ class OCServiceHandler(_ServiceHandler): chk_type('endpoints', endpoints, list) if len(endpoints) == 0: return [] - is_opticalband =False - #service_uuid = self.__service.service_id.service_uuid.uuid - settings=None - - if self.__settings_handler.get('/settings-ob_{}'.format(connection_uuid)): - is_opticalband=True - settings = self.__settings_handler.get('/settings-ob_{}'.format(connection_uuid)) - else: - settings = self.__settings_handler.get('/settings') - - bidir = settings.value.get("bidir") - LOGGER.debug(f"Bidir bvalue is: {bidir}") - # settings = self.__settings_handler.get('/settings') - - #flow is the new variable that stores input-output relationship - flows = convert_endpoints_to_flows(endpoints) - LOGGER.info(f"endpoints {endpoints} is_opticalband {is_opticalband} ") - #flows = endpoints_to_flows(endpoints, bidir, is_opticalband) - #handled_flows=handle_flows_names(flows=flows,task_executor=self.__task_executor) - + results = [] for endpoint in endpoints: try: @@ -91,16 +72,50 @@ class OCServiceHandler(_ServiceHandler): LOGGER.exception('Unable to SetEndpoint({:s})'.format(str(endpoint))) results.append(e) - LOGGER.info(f"flows {flows} ") - LOGGER.info(f"settings {settings} ") + + return results + + + @metered_subclass_method(METRICS_POOL) + def SetOpticalConfig( + 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 + #service_uuid = self.__service.service_id.service_uuid.uuid + settings=None + + if self.__settings_handler.get('/settings-ob_{}'.format(connection_uuid)): + is_opticalband=True + settings = self.__settings_handler.get('/settings-ob_{}'.format(connection_uuid)) + else: + settings = self.__settings_handler.get('/settings') + + bidir = settings.value.get("bidir") + LOGGER.debug(f"Bidir bvalue is: {bidir}") + # settings = self.__settings_handler.get('/settings') + + #flow is the new variable that stores input-output relationship + flows = convert_endpoints_to_flows(endpoints) + LOGGER.info(f"endpoints {endpoints} is_opticalband {is_opticalband} ") + #flows = endpoints_to_flows(endpoints, bidir, is_opticalband) + #handled_flows=handle_flows_names(flows=flows,task_executor=self.__task_executor) + + results = [] + #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: - self.__task_executor.configure_optical_device(device_obj, settings, dev_flows, is_opticalband) + if settings is not None: + self.__task_executor.configure_optical_device(device_obj, settings, dev_flows + , is_opticalband ,connection_uuid) results.append(True) except Exception as e: # pylint: disable=broad-except LOGGER.exception('Unable to configure Device({:s})'.format(str(device_uuid))) @@ -108,6 +123,7 @@ class OCServiceHandler(_ServiceHandler): return results + @metered_subclass_method(METRICS_POOL) def DeleteEndpoint( self, endpoints : List[Tuple[str, str, Optional[str]]], connection_uuid : Optional[str] = None diff --git a/src/service/service/task_scheduler/TaskExecutor.py b/src/service/service/task_scheduler/TaskExecutor.py index 6fb1eca3497fef311605e2cd202ca74bd9cb730f..5391eca4883771fde998c838bc5f698b43ef0bfe 100644 --- a/src/service/service/task_scheduler/TaskExecutor.py +++ b/src/service/service/task_scheduler/TaskExecutor.py @@ -127,7 +127,8 @@ class TaskExecutor: # New function Andrea for Optical Devices def configure_optical_device( - self, device : Device, settings : str, flows : list, is_opticalband : bool + self, device : Device, settings : str, flows : list, is_opticalband : bool, + connection_uuid:str ): device_key = get_device_key(device.device_id) optical_config_id = OpticalConfigId() @@ -142,6 +143,7 @@ class TaskExecutor: result = self._context_client.SelectOpticalConfig(optical_config_id) new_config = json.loads(result.config) + if is_opticalband : new_config['connection_uuid']=connection_uuid if 'type' in new_config: config_type=new_config['type'] if config_type == 'optical-transponder': @@ -292,6 +294,12 @@ class TaskExecutor: device_type = DeviceTypeEnum._value2member_map_[controller.device_type] devices.setdefault(device_type, dict())[controller.device_id.device_uuid.uuid] = controller return devices + + + def set_optical_band(self, connection : ConnectionId,device:DeviceId) -> None: + device_key = get_device_key(device.device_id) + self._device_client.ConfigureDevice(device) + self._store_grpc_object(CacheableObjectType.DEVICE, device_key, device) # ----- Service-related methods ------------------------------------------------------------------------------------ diff --git a/src/service/service/task_scheduler/TaskScheduler.py b/src/service/service/task_scheduler/TaskScheduler.py index e849e855f9d91c2dc51a99237bcfa389dc0b414d..9017149d653a6508d3f3dfeeb61955249bf14ab4 100644 --- a/src/service/service/task_scheduler/TaskScheduler.py +++ b/src/service/service/task_scheduler/TaskScheduler.py @@ -23,7 +23,9 @@ from context.client.ContextClient import ContextClient from service.service.tools.ObjectKeys import get_connection_key, get_service_key from .tasks._Task import _Task from .tasks.Task_ConnectionConfigure import Task_ConnectionConfigure +from .tasks.Task_OpticalConnectionConfigure import Task_OpticalConnectionConfigure from .tasks.Task_OpticalConnectionDeconfigure import Task_OpticalConnectionDeconfigure + from .tasks.Task_OpticalServiceDelete import Task_OpticalServiceDelete from .tasks.Task_ConnectionDeconfigure import Task_ConnectionDeconfigure from .tasks.Task_ServiceDelete import Task_ServiceDelete @@ -85,6 +87,19 @@ class TasksScheduler: # deleting a service requires the service is in removing state self._dag.add(service_delete_key, service_removing_key) return service_removing_key, service_delete_key + + + def _optical_service_create(self, service_id : ServiceId , has_media_channel : bool + , has_optical_band = True) -> Tuple[str, str]: + service_planned_key = self._add_task_if_not_exists(Task_ServiceSetStatus( + self._executor, service_id, ServiceStatusEnum.SERVICESTATUS_PLANNED)) + + service_active_key = self._add_task_if_not_exists(Task_ServiceSetStatus( + self._executor, service_id, ServiceStatusEnum.SERVICESTATUS_ACTIVE)) + + # activating a service requires the service is in planning state + self._dag.add(service_active_key, service_planned_key) + return service_planned_key, service_active_key def _optical_service_remove( self, service_id : ServiceId, has_media_channel : bool, has_optical_band = True @@ -135,6 +150,29 @@ class TasksScheduler: self._dag.add(service_delete_key, connection_deconfigure_key) return connection_deconfigure_key + + + def _optical_connection_configure(self, connection_id : ConnectionId + , service_id : ServiceId , + has_media_channel : bool, has_optical_band = True) -> str: + optical_connection_configure_key = self._add_task_if_not_exists(Task_OpticalConnectionConfigure( + self._executor, connection_id)) + + + # the connection configuration depends on its connection's service being in planning state + service_planned_key = self._add_task_if_not_exists(Task_ServiceSetStatus( + self._executor, service_id, ServiceStatusEnum.SERVICESTATUS_PLANNED)) + self._dag.add(optical_connection_configure_key, service_planned_key) + + + + # the connection's service depends on the connection configuration to transition to active state + service_active_key = self._add_task_if_not_exists(Task_ServiceSetStatus( + self._executor, service_id, ServiceStatusEnum.SERVICESTATUS_ACTIVE)) + self._dag.add(service_active_key, optical_connection_configure_key) + + + return optical_connection_configure_key def _optical_connection_deconfigure( self, connection_id : ConnectionId, service_id : ServiceId, @@ -218,10 +256,57 @@ class TasksScheduler: else : has_optical_band = True return (has_media_channel, has_optical_band) - - def compose_from_optical_service( - self, service : Service, params : dict, is_delete : bool = False + + + + def compose_from_opticalcontroller_reply( + self, pathcomp_reply : PathCompReply, is_delete : bool = False ) -> None: + t0 = time.time() + include_service = self._optical_service_remove if is_delete else self._optical_service_create + include_connection = self._optical_connection_deconfigure if is_delete else self._optical_connection_configure + + #pending_items_to_explore.put(service) + has_media_channel = None + has_optical_band = None + + for service in pathcomp_reply.services: + + connections = self._context_client.ListConnections(service.service_id) + has_media_channel, has_optical_band = self.check_service_for_media_channel( + connections=connections, item=service.service_id + ) + + + include_service(service.service_id , has_media_channel=has_media_channel, has_optical_band=has_optical_band) + self._add_service_to_executor_cache(service) + + for connection in connections.connections: + self._add_connection_to_executor_cache(connection) + + + + 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)) + + def compose_from_optical_service(self, service : Service, params:dict, is_delete : bool = False) -> None: t0 = time.time() include_service = self._optical_service_remove if is_delete else self._service_create include_connection = self._optical_connection_deconfigure if is_delete else self._connection_configure @@ -230,103 +315,125 @@ class TasksScheduler: explored_items = set() pending_items_to_explore = queue.Queue() pending_items_to_explore.put(service) - has_media_channel = None - has_optical_band = None - reply = None - code = 0 - reply_not_allowed = "DELETE_NOT_ALLOWED" + has_media_channel=None + has_optical_band=None + reply=None + code=0 + reply_not_allowed="DELETE_NOT_ALLOWED" while not pending_items_to_explore.empty(): try: item = pending_items_to_explore.get(block=False) + except queue.Empty: break - + if isinstance(item, Service): + str_item_key = grpc_message_to_json_string(item.service_id) 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 - ) - + has_media_channel,has_optical_band=self.check_service_for_media_channel(connections=connections,item=item.service_id) + if len(service.service_config.config_rules) > 0: - reply, code = delete_lightpath( - 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: + + + reply,code = delete_lightpath( + 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) - include_service( - item.service_id, has_media_channel=has_media_channel, has_optical_band=has_optical_band - ) + include_service(item.service_id,has_media_channel=has_media_channel,has_optical_band=has_optical_band) self._add_service_to_executor_cache(item) - + + for connection in connections.connections: - self._add_connection_to_executor_cache(connection) - pending_items_to_explore.put(connection) + self._add_connection_to_executor_cache(connection) + pending_items_to_explore.put(connection) explored_items.add(str_item_key) + elif isinstance(item, ServiceId): - if code == 400 and reply_not_allowed in reply: break + + if code == 400 and reply_not_allowed in reply:break + str_item_key = grpc_message_to_json_string(item) if str_item_key in explored_items: continue connections = self._context_client.ListConnections(item) - has_media_channel, has_optical_band = self.check_service_for_media_channel( - connections=connections, item=item - ) - include_service( - item, has_media_channel=has_media_channel, has_optical_band=has_optical_band - ) - self._executor.get_service(item) + has_media_channel,has_optical_band=self.check_service_for_media_channel(connections=connections,item=item) + + include_service(item,has_media_channel=has_media_channel,has_optical_band=has_optical_band) + + + self._executor.get_service(item) + for connection in connections.connections: + 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 : - connections_list = ConnectionList() - connections_list.connections.append(item) - is_media_channel, _ = self.check_service_for_media_channel( - connections=connections_list, item=service - ) - if has_optical_band and is_media_channel: - include_service_config( - item.connection_id, item.service_id - ) + connections_list = ConnectionList() + connections_list.connections.append(item) + + is_media_channel,_=self.check_service_for_media_channel(connections=connections_list,item=service) + + if has_optical_band and is_media_channel: + include_service_config(item.connection_id + , item.service_id + + ) + + self._executor.get_service(item.service_id) pending_items_to_explore.put(item.service_id) - + + 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) + + explored_items.add(str_item_key) + + else: MSG = 'Unsupported item {:s}({:s})' raise Exception(MSG.format(type(item).__name__, grpc_message_to_json_string(item))) - + t1 = time.time() LOGGER.debug('[compose_from_service] elapsed_time: {:f} sec'.format(t1-t0)) + def compose_from_service(self, service : Service, is_delete : bool = False) -> None: t0 = time.time() include_service = self._service_remove if is_delete else self._service_create diff --git a/src/service/service/task_scheduler/tasks/Task_OpticalConnectionConfigure.py b/src/service/service/task_scheduler/tasks/Task_OpticalConnectionConfigure.py new file mode 100644 index 0000000000000000000000000000000000000000..1665c41b18dba0a7c09ae124092e018afb4c6fb2 --- /dev/null +++ b/src/service/service/task_scheduler/tasks/Task_OpticalConnectionConfigure.py @@ -0,0 +1,88 @@ +# 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 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.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 ._Task import _Task + +if TYPE_CHECKING: + from service.service.service_handler_api._ServiceHandler import _ServiceHandler + +KEY_TEMPLATE = 'optical_Connection ({connection_id:s}):configure' + +class Task_OpticalConnectionConfigure(_Task): + def __init__(self, task_executor : TaskExecutor, connection_id : ConnectionId) -> None: + super().__init__(task_executor) + self._connection_id = connection_id + + @property + def connection_id(self) -> ConnectionId: return self._connection_id + + @staticmethod + def build_key(connection_id : ConnectionId) -> str: # pylint: disable=arguments-differ + str_connection_id = get_connection_key(connection_id) + return KEY_TEMPLATE.format(connection_id=str_connection_id) + + @property + def key(self) -> str: return self.build_key(self._connection_id) + + def execute(self) -> None: + connection = self._task_executor.get_connection(self._connection_id) + service = self._task_executor.get_service(connection.service_id) + + service_handler_settings = {} + service_handlers : Dict[DeviceTypeEnum, Tuple['_ServiceHandler', Dict[str, Device]]] = \ + self._task_executor.get_service_handlers(connection, service, **service_handler_settings) + + connection_uuid = connection.connection_id.connection_uuid.uuid + endpointids_to_set = endpointids_to_raw(connection.path_hops_endpoint_ids) + + errors = list() + for _, (service_handler, connection_devices) in service_handlers.items(): + _endpointids_to_set = [ + (device_uuid, endpoint_uuid, topology_uuid) + for device_uuid, endpoint_uuid, topology_uuid in endpointids_to_set + if device_uuid in connection_devices + ] + results_setendpoint = service_handler.SetEndpoint( + _endpointids_to_set, connection_uuid=connection_uuid + ) + errors.extend(check_errors_setendpoint(endpointids_to_set, results_setendpoint)) + + if len(errors) > 0: + MSG = 'SetEndpoint for Connection({: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.set_connection(connection) + + results_setendOpticalConfigs = service_handler.SetOpticalConfig( + _endpointids_to_set, connection_uuid=connection_uuid + ) + errors.extend(check_errors_setendpoint(endpointids_to_set, results_setendOpticalConfigs)) + + if len(errors) > 0: + MSG = 'SetOpticalConfigs for Optical Connection({:s}) from Optical 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) + diff --git a/src/service/service/tools/OpticalTools.py b/src/service/service/tools/OpticalTools.py index b7df3dd1428718f1d8141c557fddf8e572312afc..774f4f6713ea131fb124782e782b336a6842025d 100644 --- a/src/service/service/tools/OpticalTools.py +++ b/src/service/service/tools/OpticalTools.py @@ -26,7 +26,10 @@ from common.Settings import ( find_environment_variables, get_env_var_name ) from service.service.tools.replies import ( - reply_uni_txt, optical_band_uni_txt, reply_bid_txt, optical_band_bid_txt + reply_uni_txt + , optical_band_uni_txt + , reply_bid_txt + , optical_band_bid_txt ) log = logging.getLogger(__name__) diff --git a/src/webui/requirements.in b/src/webui/requirements.in index f5bbee5bc954aa82392d86d653fda57725cda40b..e80851b4a2a89898cdfef36c292e23b62b9f1514 100644 --- a/src/webui/requirements.in +++ b/src/webui/requirements.in @@ -18,3 +18,4 @@ flask-healthz<2 flask-unittest==0.1.3 lorem-text==2.1 werkzeug==2.3.7 +requests==2.27.1 diff --git a/src/webui/service/__main__.py b/src/webui/service/__main__.py index e604b0d9030c436bd07c02d792b22d5be0bd0701..44a8f2e015ac516484722acd1326a586c125bd54 100644 --- a/src/webui/service/__main__.py +++ b/src/webui/service/__main__.py @@ -17,7 +17,8 @@ from prometheus_client import start_http_server from common.Constants import ServiceNameEnum from common.Settings import ( ENVVAR_SUFIX_SERVICE_HOST, ENVVAR_SUFIX_SERVICE_PORT_GRPC, get_env_var_name, get_log_level, get_metrics_port, - get_service_baseurl_http, get_service_port_http, get_setting, wait_for_environment_variables) + get_service_baseurl_http, get_service_port_http, get_setting, wait_for_environment_variables + ) from webui.service import create_app from webui.Config import MAX_CONTENT_LENGTH, HOST, SECRET_KEY, DEBUG @@ -41,6 +42,8 @@ def main(): get_env_var_name(ServiceNameEnum.DEVICE, ENVVAR_SUFIX_SERVICE_PORT_GRPC), get_env_var_name(ServiceNameEnum.SERVICE, ENVVAR_SUFIX_SERVICE_HOST ), get_env_var_name(ServiceNameEnum.SERVICE, ENVVAR_SUFIX_SERVICE_PORT_GRPC), + get_env_var_name(ServiceNameEnum.OPTICALCONTROLLER, ENVVAR_SUFIX_SERVICE_HOST ), + get_env_var_name(ServiceNameEnum.OPTICALCONTROLLER, ENVVAR_SUFIX_SERVICE_PORT_GRPC), ]) logger.info('Starting...') @@ -60,7 +63,9 @@ def main(): 'SECRET_KEY': SECRET_KEY, 'MAX_CONTENT_LENGTH': MAX_CONTENT_LENGTH, 'SESSION_COOKIE_NAME': create_unique_session_cookie_name(), + 'WTF_CSRF_ENABLED':False }, web_app_root=web_app_root) + #app = create_app(use_config=None, web_app_root=web_app_root) app.run(host=host, port=service_port, debug=debug) logger.info(f'Bye ') diff --git a/src/webui/service/main/routes.py b/src/webui/service/main/routes.py index 2e23c28a67956f6a274a8f9c61df1ce2a7deb6ed..c3e4fe1a3ed6b47622733f8541f065c268fce547 100644 --- a/src/webui/service/main/routes.py +++ b/src/webui/service/main/routes.py @@ -57,7 +57,7 @@ def home(): device_client.connect() context_topology_form = ContextTopologyForm() context_topology_form.context_topology.choices.append(('', 'Select...')) - + logging.info(f"CSRF Token in Session: {session}") contexts : ContextList = context_client.ListContexts(Empty()) for context_ in contexts.contexts: #context_uuid : str = context_.context_id.context_uuid.uuid @@ -74,6 +74,8 @@ def home(): context_topology_form.context_topology.choices.append(context_topology_entry) if context_topology_form.validate_on_submit(): + logging.info(f"CSRF Token from Form: {request.form.get('csrf_token')}") + logging.info(f"CSRF Token in Session: {session.get('csrf_token')}") context_topology_uuid = context_topology_form.context_topology.data if len(context_topology_uuid) > 0: b64_values = context_topology_uuid.split(',') @@ -117,7 +119,8 @@ def home(): finally: context_client.close() device_client.close() - + + LOGGER.info(f"erorr {context_topology_form.errors.items()}") return render_template( 'main/home.html', context_topology_form=context_topology_form, descriptor_form=descriptor_form) diff --git a/src/webui/service/optical_link/routes.py b/src/webui/service/optical_link/routes.py index 764d4c266948998b7389f276126f384bff7e7ba8..87ca15de1c0bfc1228bc6efdab361ac9a56e1b5d 100644 --- a/src/webui/service/optical_link/routes.py +++ b/src/webui/service/optical_link/routes.py @@ -18,6 +18,27 @@ from common.proto.context_pb2 import Empty, OpticalLink, LinkId, OpticalLinkList from common.tools.context_queries.EndPoint import get_endpoint_names from common.tools.context_queries.Topology import get_topology from context.client.ContextClient import ContextClient +import functools ,requests ,logging +from common.Constants import ServiceNameEnum +from common.Settings import ( + ENVVAR_SUFIX_SERVICE_BASEURL_HTTP, ENVVAR_SUFIX_SERVICE_HOST, ENVVAR_SUFIX_SERVICE_PORT_GRPC, + find_environment_variables, get_env_var_name +) +get_optical_controller_setting = functools.partial(get_env_var_name, ServiceNameEnum.OPTICALCONTROLLER) +VAR_NAME_OPTICAL_CTRL_HOST = get_optical_controller_setting(ENVVAR_SUFIX_SERVICE_HOST) +VAR_NAME_OPTICAL_CTRL_PORT = get_optical_controller_setting(ENVVAR_SUFIX_SERVICE_PORT_GRPC) +VAR_NAME_OPTICAL_CTRL_BASEURL_HTTP = get_optical_controller_setting(ENVVAR_SUFIX_SERVICE_BASEURL_HTTP) +VAR_NAME_OPTICAL_CTRL_SCHEMA = get_optical_controller_setting('SCHEMA') + +settings = find_environment_variables([ + VAR_NAME_OPTICAL_CTRL_BASEURL_HTTP, + VAR_NAME_OPTICAL_CTRL_SCHEMA, + VAR_NAME_OPTICAL_CTRL_HOST, + VAR_NAME_OPTICAL_CTRL_PORT, + ]) + +host = settings.get(VAR_NAME_OPTICAL_CTRL_HOST) +port = settings.get(VAR_NAME_OPTICAL_CTRL_PORT, 80) optical_link = Blueprint('optical_link', __name__, url_prefix='/optical_link') context_client = ContextClient() @@ -106,3 +127,74 @@ def delete_all(): except Exception as e: flash(f"Problem in delete all optical link => {e}",'danger') return redirect(url_for('optical_link.home')) + + +@optical_link.route('getopticallinks', methods=(['GET'])) +def get_optical_links (): + + + urlx = "http://{:s}:{:s}/OpticalTFS/GetLinks".format(host,port) + + headers = {"Content-Type": "application/json"} + optical_links =[] + try: + r = requests.get(urlx, headers=headers) + reply = r.json() + if (reply and 'optical_links' in reply): optical_links = reply["optical_links"] + + logging.info(f"reply {optical_links}") + except Exception as e : + logging.info(f"error {e}") + finally: + + return render_template( + 'opticalconfig/opticallinks.html', optical_links=optical_links + ) + + + + +@optical_link.route('getopticalbands', methods=(['GET'])) +def get_optical_bands(): + + + urlx = "http://{:s}:{:s}/OpticalTFS/GetOpticalBands".format(host,port) + + headers = {"Content-Type": "application/json"} + optical_bands ={} + try: + r = requests.get(urlx, headers=headers) + reply = r.json() + if (reply):optical_bands=reply + + logging.info(f"optical bands {reply}") + except Exception as e : + logging.info(f"error {e}") + finally: + + return render_template( + 'opticalconfig/opticalbands.html',optical_bands=optical_bands + ) + +@optical_link.route('getlightpath', methods=(['GET'])) +def get_lightpath(): + + + urlx = "http://{:s}:{:s}/OpticalTFS/GetLightpaths".format(host,port) + + headers = {"Content-Type": "application/json"} + light_paths ={} + try: + r = requests.get(urlx, headers=headers) + reply = r.json() + if (reply):light_paths=reply + + + logging.info(f"lightpaths {reply}") + except Exception as e : + logging.info(f"error {e}") + finally: + + return render_template( + 'opticalconfig/lightpaths.html',light_paths=light_paths + ) \ No newline at end of file diff --git a/src/webui/service/templates/base_optical/home.html b/src/webui/service/templates/base_optical/home.html index c087af016dd18410958edfde9f0b452bf9f0bd83..490d40ffe00ed3cb1670fda30adf1390011d5af9 100644 --- a/src/webui/service/templates/base_optical/home.html +++ b/src/webui/service/templates/base_optical/home.html @@ -27,10 +27,22 @@ </a> </div> <div class="col"> - <a href="{{ url_for('optical_link.home') }}" class="btn btn-primary" style="margin-bottom: 10px;"> + <a href="{{ url_for('optical_link.get_optical_links') }}" class="btn btn-primary" style="margin-bottom: 10px;"> Optical Links </a> </div> + <div class="col"> + <a href="{{ url_for('optical_link.get_optical_bands') }}" class="btn btn-primary" style="margin-bottom: 10px;"> + + Optical Bands + </a> + </div> + <div class="col"> + <a href="{{ url_for('optical_link.get_lightpath') }}" class="btn btn-primary" style="margin-bottom: 10px;"> + + Light Paths + </a> + </div> </div> {% endblock %} diff --git a/src/webui/service/templates/optical_link/detail.html b/src/webui/service/templates/optical_link/detail.html index 2f7cd2326589dbcc93c8d4d232257d5267363b87..3bcb1a34b7ddac0a95feed5f08d1e0bd5e4c9827 100644 --- a/src/webui/service/templates/optical_link/detail.html +++ b/src/webui/service/templates/optical_link/detail.html @@ -17,7 +17,6 @@ {% extends 'base.html' %} {% block content %} -<h1>Link {{ link.name }} ({{ link.link_id.link_uuid.uuid }})</h1> <div class="row mb-3"> <div class="col-sm-3"> <button type="button" class="btn btn-success" onclick="window.location.href='{{ url_for('optical_link.home') }}'"> @@ -25,29 +24,26 @@ Back to link list </button> </div> - <div class="col-sm-3"> - <!-- <button type="button" class="btn btn-danger"><i class="bi bi-x-square"></i>Delete link</button> --> - <button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#deleteModal"> - <i class="bi bi-x-square"></i> - Delete link - </button> - </div> + </div> +{% for link in optical_links %} +<h1>Link : {{ link.name }} </h1> + <br> <div class="row mb-3"> <div class="col-sm-4"> <b>UUID: </b>{{ link.link_id.link_uuid.uuid }}<br> - <b>Name: </b>{{ link.name }}<br> + </div> <div class="col-sm-8"> <table class="table table-striped table-hover"> <thead> <tr> <th scope="col">Endpoint UUID</th> - <th scope="col">Name</th> - <th scope="col">Device</th> - <th scope="col">Endpoint Type</th> + + <th scope="col">Device UUID </th> + </tr> </thead> <tbody> @@ -56,21 +52,12 @@ <td> {{ endpoint.endpoint_uuid.uuid }} </td> + <td> - {{ endpoints_data.get(endpoint.endpoint_uuid.uuid, (endpoint.endpoint_uuid.uuid, ''))[0] }} - </td> - <td> - <a href="{{ url_for('device.detail', device_uuid=endpoint.device_id.device_uuid.uuid) }}"> - {{ device_names.get(endpoint.device_id.device_uuid.uuid, endpoint.device_id.device_uuid.uuid) }} - <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-eye" viewBox="0 0 16 16"> - <path d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z"/> - <path d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z"/> - </svg> - </a> - </td> - <td> - {{ endpoints_data.get(endpoint.endpoint_uuid.uuid, ('', '-'))[1] }} + {{endpoint.device_id.device_uuid.uuid}} + </td> + </tr> {% endfor %} </tbody> @@ -89,28 +76,20 @@ </tr> </thead> <tbody> - {% for field_descriptor, field_value in link.optical_details.ListFields() %} - {% if field_descriptor.name != "c_slots" and field_descriptor.name != "s_slots" and field_descriptor.name != "l_slots" %} + {% for field_descriptor, field_value in link.optical_details.items() %} + <tr> + <td> - {{ field_descriptor.name }} + {{ field_descriptor }} </td> <td> {{ field_value }} </td> </tr> - {%endif%} + {% endfor %} - {%if c_slots %} - <tr> - <td> - c_slots - </td> - <td> - {{ c_slots }} - </td> - </tr> - {%endif%} + {%if l_slots %} <tr> <td> @@ -133,26 +112,9 @@ {%endif%} </tbody> </table> + +{% endfor %} <!-- Modal --> -<div class="modal fade" id="deleteModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" - aria-labelledby="staticBackdropLabel" aria-hidden="true"> - <div class="modal-dialog"> - <div class="modal-content"> - <div class="modal-header"> - <h5 class="modal-title" id="staticBackdropLabel">Delete link?</h5> - <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> - </div> - <div class="modal-body"> - Are you sure you want to delete the link "{{ link.link_id.link_uuid.uuid }}"? - </div> - <div class="modal-footer"> - <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">No</button> - <a type="button" class="btn btn-danger" - href="{{ url_for('optical_link.delete', link_uuid=link.link_id.link_uuid.uuid) }}"><i - class="bi bi-exclamation-diamond"></i>Yes</a> - </div> - </div> - </div> -</div> + {% endblock %} diff --git a/src/webui/service/templates/opticalconfig/lightpaths.html b/src/webui/service/templates/opticalconfig/lightpaths.html new file mode 100644 index 0000000000000000000000000000000000000000..5ae6734615b9a743e656380b4815c0f718420f7d --- /dev/null +++ b/src/webui/service/templates/opticalconfig/lightpaths.html @@ -0,0 +1,73 @@ +<!-- + 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. + --> + +{% extends 'base.html' %} + +{% block content %} +<div class="row mb-3"> + <div class="col-sm-3"> + <button type="button" class="btn btn-success" onclick="window.location.href='{{ url_for('optical_link.home') }}'"> + <i class="bi bi-box-arrow-in-left"></i> + Back to link list + </button> + </div> + +</div> + +<h1>Light Paths </h1> +{% for field_descriptor, field_value in light_paths.items() %} + + +<div class="row mt-3"> + <div class="col-sm-3"> + </div> + +</div> + +<b>Name : {{field_descriptor}}</b> + +<table class="table table-striped table-hover"> + <thead> + <tr> + <th scope="col">Key</th> + <th scope="col">Value</th> + </tr> + </thead> + <tbody> + {% for k, v in field_value.items() %} + + <tr> + + <td> + {{ k }} + </td> + <td> + {{ v }} + </td> + </tr> + + {% endfor %} + + + </tbody> +</table> + +{% endfor %} +<!-- Modal --> +<!-- Modal --> + + +{% endblock %} diff --git a/src/webui/service/templates/opticalconfig/opticalbands.html b/src/webui/service/templates/opticalconfig/opticalbands.html new file mode 100644 index 0000000000000000000000000000000000000000..94e4ec559f090a3d4caf49d9aaeae653528e264c --- /dev/null +++ b/src/webui/service/templates/opticalconfig/opticalbands.html @@ -0,0 +1,74 @@ +<!-- + 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. + --> + +{% extends 'base.html' %} + +{% block content %} +<div class="row mb-3"> + <div class="col-sm-3"> + <button type="button" class="btn btn-success" onclick="window.location.href='/base_optical'"> + <i class="bi bi-box-arrow-in-left"></i> + Back to link list + </button> + </div> + +</div> + +<h1>Optical Bands </h1> +{% for field_descriptor, field_value in optical_bands.items() %} + + +<div class="row mt-3"> + <div class="col-sm-3"> + </div> + +</div> + + +<b>Name : {{field_descriptor}}</b> + +<table class="table table-striped table-hover"> + <thead> + <tr> + <th scope="col">Key</th> + <th scope="col">Value</th> + </tr> + </thead> + <tbody> + {% for k, v in field_value.items() %} + + <tr> + + <td> + {{ k }} + </td> + <td> + {{ v }} + </td> + </tr> + + {% endfor %} + + + </tbody> +</table> + +{% endfor %} +<!-- Modal --> + + +{% endblock %} + diff --git a/src/webui/service/templates/opticalconfig/opticallinks.html b/src/webui/service/templates/opticalconfig/opticallinks.html new file mode 100644 index 0000000000000000000000000000000000000000..3bcb1a34b7ddac0a95feed5f08d1e0bd5e4c9827 --- /dev/null +++ b/src/webui/service/templates/opticalconfig/opticallinks.html @@ -0,0 +1,120 @@ +<!-- + 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. + --> + +{% extends 'base.html' %} + +{% block content %} +<div class="row mb-3"> + <div class="col-sm-3"> + <button type="button" class="btn btn-success" onclick="window.location.href='{{ url_for('optical_link.home') }}'"> + <i class="bi bi-box-arrow-in-left"></i> + Back to link list + </button> + </div> + +</div> +{% for link in optical_links %} +<h1>Link : {{ link.name }} </h1> + + +<br> +<div class="row mb-3"> + <div class="col-sm-4"> + <b>UUID: </b>{{ link.link_id.link_uuid.uuid }}<br> + + </div> + <div class="col-sm-8"> + <table class="table table-striped table-hover"> + <thead> + <tr> + <th scope="col">Endpoint UUID</th> + + <th scope="col">Device UUID </th> + + </tr> + </thead> + <tbody> + {% for endpoint in link.link_endpoint_ids %} + <tr> + <td> + {{ endpoint.endpoint_uuid.uuid }} + </td> + + <td> + {{endpoint.device_id.device_uuid.uuid}} + + </td> + + </tr> + {% endfor %} + </tbody> + </table> + </div> +</div> + + + +<b>Optical Link Detail:</b> +<table class="table table-striped table-hover"> + <thead> + <tr> + <th scope="col">Key</th> + <th scope="col">Value</th> + </tr> + </thead> + <tbody> + {% for field_descriptor, field_value in link.optical_details.items() %} + + <tr> + + <td> + {{ field_descriptor }} + </td> + <td> + {{ field_value }} + </td> + </tr> + + {% endfor %} + + {%if l_slots %} + <tr> + <td> + l_slots + </td> + <td> + {{ l_slots }} + </td> + </tr> + {%endif%} + {%if s_slots %} + <tr> + <td> + s_slots + </td> + <td> + {{ s_slots }} + </td> + </tr> + {%endif%} + </tbody> +</table> + +{% endfor %} +<!-- Modal --> + + +{% endblock %}