diff --git a/proto/context.proto b/proto/context.proto index 8a6b019dc99863da32631425d954afbaf167f1b7..55c4121190690a3a57c16a1d241987d0a590fd1d 100644 --- a/proto/context.proto +++ b/proto/context.proto @@ -80,6 +80,7 @@ service ContextService { rpc GetOpticalConfig (Empty ) returns (OpticalConfigList ) {} rpc SetOpticalConfig (OpticalConfig ) returns (OpticalConfigId ) {} rpc SelectOpticalConfig(OpticalConfigId) returns (OpticalConfig ) {} + rpc DeleteOpticalConfig(OpticalConfigId) returns (Empty ) {} rpc SetOpticalLink (OpticalLink ) returns (Empty ) {} rpc GetOpticalLink (OpticalLinkId ) returns (OpticalLink ) {} @@ -638,6 +639,12 @@ message OpticalConfigList { repeated OpticalConfig opticalconfigs = 1; } +message OpticalConfigEvent { + Event event = 1; + OpticalConfigId opticalconfig_id = 2; +} + + // ---- Optical Link ---- message OpticalLinkId { diff --git a/src/common/tools/context_queries/OpticalConfig.py b/src/common/tools/context_queries/OpticalConfig.py new file mode 100644 index 0000000000000000000000000000000000000000..52727c45df00ca84e1fc6070d922fc1eaecc51c9 --- /dev/null +++ b/src/common/tools/context_queries/OpticalConfig.py @@ -0,0 +1,58 @@ + +from common.method_wrappers.ServiceExceptions import InvalidArgumentsException +from typing import Optional, Union +from uuid import UUID, uuid4, uuid5 + +# Generate a UUIDv5-like from the SHA-1 of "TFS" and no namespace to be used as the NAMESPACE for all +# the context UUIDs generated. For efficiency purposes, the UUID is hardcoded; however, it is produced +# using the following code: +# from hashlib import sha1 +# from uuid import UUID +# hash = sha1(bytes('TFS', 'utf-8')).digest() +# NAMESPACE_TFS = UUID(bytes=hash[:16], version=5) +NAMESPACE_TFS = UUID('200e3a1f-2223-534f-a100-758e29c37f40') + +def get_uuid_from_string(str_uuid_or_name : Union[str, UUID], prefix_for_name : Optional[str] = None) -> str: + # if UUID given, assume it is already a valid UUID + if isinstance(str_uuid_or_name, UUID): return str_uuid_or_name + if not isinstance(str_uuid_or_name, str): + MSG = 'Parameter({:s}) cannot be used to produce a UUID' + raise Exception(MSG.format(str(repr(str_uuid_or_name)))) + try: + # try to parse as UUID + return str(UUID(str_uuid_or_name)) + except: # pylint: disable=bare-except + # produce a UUID within TFS namespace from parameter + if prefix_for_name is not None: + str_uuid_or_name = '{:s}/{:s}'.format(prefix_for_name, str_uuid_or_name) + return str(uuid5(NAMESPACE_TFS, str_uuid_or_name)) + +def get_uuid_random() -> str: + # Generate random UUID. No need to use namespace since "namespace + random = random". + return str(uuid4()) + +def channel_get_uuid( + channel_name :str , allow_random : bool = False +) -> str: + + + if len(channel_name) > 0: + return get_uuid_from_string(channel_name) + if allow_random: return get_uuid_random() + + raise InvalidArgumentsException([ + ('channel uuid', channel_name), + + ], extra_details=['Channel name is required to produce a channel UUID']) + +def opticalconfig_get_uuid( + device_name : str = '', allow_random : bool = False +) -> str: + + if len(device_name) > 0: + return get_uuid_from_string(device_name) + if allow_random: return get_uuid_random() + + raise InvalidArgumentsException([ + ('name', device_name), + ], extra_details=['At least one is required to produce a OpticalConfig UUID']) diff --git a/src/context/client/ContextClient.py b/src/context/client/ContextClient.py index 48a635cccd19f5985721a6a737fb8a2e5bd260b2..3ca4ea16a72c7d2eca8c6b8784f06f992cde69c2 100644 --- a/src/context/client/ContextClient.py +++ b/src/context/client/ContextClient.py @@ -461,3 +461,9 @@ class ContextClient: response = self.stub.SelectOpticalConfig(request) LOGGER.debug('SelectOpticalConfig result: {:s}'.format(grpc_message_to_json_string(response))) return response + @RETRY_DECORATOR + def DeleteOpticalConfig(self,request : OpticalConfigId) -> Empty: + LOGGER.debug('DeleteOpticalConfig request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.DeleteOpticalConfig(request) + LOGGER.debug('DeleteOpticalConfig result: {:s}'.format(grpc_message_to_json_string(response))) + return response diff --git a/src/context/service/ContextServiceServicerImpl.py b/src/context/service/ContextServiceServicerImpl.py index 1be4cdac8009481c99ea6dd0de6114f4b49e45ce..2b6c61ebeb0960d73621d34e31c32efccf7fbbe0 100644 --- a/src/context/service/ContextServiceServicerImpl.py +++ b/src/context/service/ContextServiceServicerImpl.py @@ -24,7 +24,7 @@ from common.proto.context_pb2 import ( Service, ServiceEvent, ServiceFilter, ServiceId, ServiceIdList, ServiceList, Slice, SliceEvent, SliceFilter, SliceId, SliceIdList, SliceList, Topology, TopologyDetails, TopologyEvent, TopologyId, TopologyIdList, TopologyList, - OpticalConfigList, OpticalConfigId, OpticalConfig + OpticalConfigList, OpticalConfigId, OpticalConfig ) from common.proto.policy_pb2 import PolicyRuleIdList, PolicyRuleId, PolicyRuleList, PolicyRule from common.proto.context_pb2_grpc import ContextServiceServicer @@ -45,7 +45,7 @@ from .database.Slice import ( slice_delete, slice_get, slice_list_ids, slice_list_objs, slice_select, slice_set, slice_unset) from .database.Topology import ( topology_delete, topology_get, topology_get_details, topology_list_ids, topology_list_objs, topology_set) -from .database.OpticalConfig import set_opticalconfig, select_opticalconfig, get_opticalconfig +from .database.OpticalConfig import set_opticalconfig, select_opticalconfig, get_opticalconfig ,delete_opticalconfig LOGGER = logging.getLogger(__name__) @@ -318,3 +318,9 @@ class ContextServiceServicerImpl(ContextServiceServicer, ContextPolicyServiceSer optical_config_id = OpticalConfigId() optical_config_id.CopyFrom(result.opticalconfig_id) return OpticalConfig(config=result.config, opticalconfig_id=optical_config_id) + + @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) + def DeleteOpticalConfig (self, request : OpticalConfigId, context : grpc.ServicerContext) -> Empty: + delete_opticalconfig(self.db_engine,self.messagebroker, request) + + return Empty() diff --git a/src/context/service/database/Events.py b/src/context/service/database/Events.py index 36774a5170ba20914555b0adc47a5c2faa592799..a53413592372243143dc136eeda1f3249dcd672f 100644 --- a/src/context/service/database/Events.py +++ b/src/context/service/database/Events.py @@ -17,7 +17,8 @@ from typing import Dict, Iterator, Set from common.message_broker.Message import Message from common.message_broker.MessageBroker import MessageBroker from common.proto.context_pb2 import ( - ConnectionEvent, ContextEvent, DeviceEvent, EventTypeEnum, LinkEvent, ServiceEvent, SliceEvent, TopologyEvent) + ConnectionEvent, ContextEvent, DeviceEvent, EventTypeEnum, LinkEvent, ServiceEvent, SliceEvent, TopologyEvent , + OpticalConfigEvent) class EventTopicEnum(enum.Enum): CONNECTION = 'connection' @@ -28,6 +29,8 @@ class EventTopicEnum(enum.Enum): SERVICE = 'service' SLICE = 'slice' TOPOLOGY = 'topology' + OPTICALCONFIG = 'optical-config' + TOPIC_TO_EVENTCLASS = { EventTopicEnum.CONNECTION.value : ConnectionEvent, @@ -38,6 +41,8 @@ TOPIC_TO_EVENTCLASS = { EventTopicEnum.SERVICE.value : ServiceEvent, EventTopicEnum.SLICE.value : SliceEvent, EventTopicEnum.TOPOLOGY.value : TopologyEvent, + EventTopicEnum.OPTICALCONFIG.value : OpticalConfigEvent + } CONSUME_TIMEOUT = 0.5 # seconds @@ -60,6 +65,10 @@ def notify_event_topology(messagebroker : MessageBroker, event_type : EventTypeE def notify_event_device(messagebroker : MessageBroker, event_type : EventTypeEnum, device_id : Dict) -> None: notify_event(messagebroker, EventTopicEnum.DEVICE, event_type, {'device_id': device_id}) + +def notify_event_opticalconfig(messagebroker : MessageBroker, event_type : EventTypeEnum, opticalconfig_id : Dict) -> None: + notify_event(messagebroker, EventTopicEnum.DEVICE, event_type, {'opticalconfig_id': opticalconfig_id}) + def notify_event_link(messagebroker : MessageBroker, event_type : EventTypeEnum, link_id : Dict) -> None: notify_event(messagebroker, EventTopicEnum.LINK, event_type, {'link_id': link_id}) diff --git a/src/context/service/database/OpticalConfig.py b/src/context/service/database/OpticalConfig.py index 973e93c3357a3cd32dc3714a1d70c397fbdae8c3..8251af887b56d6083df0e569d54702f240fe77da 100644 --- a/src/context/service/database/OpticalConfig.py +++ b/src/context/service/database/OpticalConfig.py @@ -14,12 +14,14 @@ import json, logging from sqlalchemy.dialects.postgresql import insert +from common.message_broker.MessageBroker import MessageBroker from sqlalchemy.engine import Engine from sqlalchemy.orm import Session, sessionmaker from sqlalchemy_cockroachdb import run_transaction -from common.proto.context_pb2 import OpticalConfig, OpticalConfigId +from common.proto.context_pb2 import OpticalConfig, OpticalConfigId , Empty , EventTypeEnum from .models.OpticalConfigModel import OpticalConfigModel , OpticalChannelModel from context.service.database.uuids.OpticalConfig import channel_get_uuid +from .Events import notify_event_opticalconfig LOGGER = logging.getLogger(__name__) @@ -94,12 +96,20 @@ def set_opticalconfig(db_engine : Engine, request : OpticalConfig): stmt = insert(OpticalChannelModel).values(channels) - stmt = stmt.on_conflict_do_nothing( + stmt = stmt.on_conflict_do_update( index_elements=[OpticalChannelModel.channel_uuid , OpticalConfigModel.opticalconfig_uuid], + set_=dict( + channel_name= stmt.excluded.channel_name , + frequency = stmt.excluded.frequency, + operational_mode=stmt.excluded.operational_mode, + target_output_power=stmt.excluded.target_output_power, + + ) ) stmt = stmt.returning(OpticalChannelModel.channel_uuid) opticalChannel_id = session.execute(stmt).fetchone() + LOGGER.info(f"new optical channel config {opticalChannel_id}") opticalconfig_id = run_transaction(sessionmaker(bind=db_engine), callback) return {'opticalconfig_uuid': opticalconfig_id} @@ -114,3 +124,21 @@ def select_opticalconfig(db_engine:Engine,request:OpticalConfigId): result.opticalconfig_id.opticalconfig_uuid = obj.opticalconfig_uuid return result return run_transaction(sessionmaker(bind=db_engine, expire_on_commit=False), callback) + + +def delete_opticalconfig(db_engine : Engine ,messagebroker : MessageBroker, request : OpticalConfigId): + opticalconfig_uuid = request.opticalconfig_uuid + def callback(session : Session): + query = session.query(OpticalConfigModel) + + num_deleted = session.query(OpticalConfigModel).filter_by(opticalconfig_uuid=opticalconfig_uuid).delete() + return num_deleted > 0 + deleted = run_transaction(sessionmaker(bind=db_engine), callback) + + if deleted: + notify_event_opticalconfig(messagebroker, EventTypeEnum.EVENTTYPE_REMOVE, opticalconfig_uuid) + + + + + return Empty() diff --git a/src/context/service/database/models/OpticalConfigModel.py b/src/context/service/database/models/OpticalConfigModel.py index 8531f37b270af80696ecdf22675e5aba42d6d0ee..9937f342df3c669f36401850e6ea9685b667214f 100644 --- a/src/context/service/database/models/OpticalConfigModel.py +++ b/src/context/service/database/models/OpticalConfigModel.py @@ -64,7 +64,7 @@ class OpticalChannelModel(_Base): return { "name" :{'index':self.channel_name}, "frequency" : self.frequency, - "target_output_power" : self.target_output_power, - "operational_mode" : self.operational_mode, + "target-output-power" : self.target_output_power, + "operational-mode" : self.operational_mode, } diff --git a/src/device/service/OpenConfigServicer.py b/src/device/service/OpenConfigServicer.py index 1060449f185330faa0a71ff062ad2bbd0086b2e4..e0b7c5be9c30e7d6fda7b9624c3a98b4b0755b4b 100644 --- a/src/device/service/OpenConfigServicer.py +++ b/src/device/service/OpenConfigServicer.py @@ -77,17 +77,27 @@ class OpenConfigServicer(DeviceServiceServicer): def ConfigureOpticalDevice (self, request : OpticalConfig, context : grpc.ServicerContext) -> Empty: device_uuid = request.opticalconfig_id.opticalconfig_uuid resources=[] + is_all_good=True config =json.loads(request.config) + LOGGER.info(f" config from openconfigservicer {config}") try: context_client = ContextClient() device = get_device( context_client, device_uuid, rw_copy=True, include_endpoints=True, include_components=False, include_config_rules=False) + LOGGER.info(f"device is {device}") if device is None: raise NotFoundException('Device', device_uuid, extra_details='loading in ConfigureDevice') resources,conditions=extract_resources(config=config,device=device) + LOGGER.info(f"from openconfigservicer {resources} and conditions {conditions}") driver : _Driver = get_driver(self.driver_instance_cache, device) - result = driver.SetConfig(resources=resources,conditions=conditions) + results = driver.SetConfig(resources=resources,conditions=conditions) + for result in results: + if not result : + is_all_good=False + LOGGER.info(f"resluts {results} and is_all_good {is_all_good}") + if is_all_good: + driver.GetConfig(resource_keys=[]) #TODO: add a control with the NETCONF get #driver.GetConfig(resource_keys=filter_fields) except Exception as e: diff --git a/src/device/service/drivers/oc_driver/OCDriver.py b/src/device/service/drivers/oc_driver/OCDriver.py index 4b49c7e4122d5c625666cef3a9eb77a56d19c273..0f94b1c695228a7ccb7ff0c73f41133bd20fafab 100644 --- a/src/device/service/drivers/oc_driver/OCDriver.py +++ b/src/device/service/drivers/oc_driver/OCDriver.py @@ -130,12 +130,19 @@ class NetconfSessionHandler: ): - + 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) - + logging.info(f"resonse from edit {response}") + str_respones = str(response) + if re.search(r'<ok/>', str_respones): + + return True + + return False + @RETRY_DECORATOR def locked(self, target): @@ -157,8 +164,7 @@ def edit_config( results = [] str_config_messages=[] - - + if (conditions['edit_type']=='optical-channel'): #transponder str_config_messages = create_optical_channel(resources) @@ -170,19 +176,21 @@ def edit_config( str_config_messages=create_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") - netconf_handler.edit_config( # configure the device + 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(True) + results.append(result) + + return results class OCDriver(_Driver): diff --git a/src/device/service/drivers/oc_driver/templates/VPN/physical.py b/src/device/service/drivers/oc_driver/templates/VPN/physical.py index 355858d2da891d96777d111f481c070fe3036d44..7835b47789494f01760fff11ddde7c4edf071288 100644 --- a/src/device/service/drivers/oc_driver/templates/VPN/physical.py +++ b/src/device/service/drivers/oc_driver/templates/VPN/physical.py @@ -41,7 +41,7 @@ def create_optical_channel(resources): data={"name":i["value"] for i in resources if i["resource_key"]=="channel_name"} data["channel_namespace"]=next((i["value"] for i in resources if i["resource_key"] == "channel_namespace"), None) config,ports,index=seperate_port_config(resources,unwanted_keys=unwanted_keys) - + logging.info(f"ports are {ports}") port_val = "" if 'destination_port' in ports and ports['destination_port'][0] is not None: port_val = ports['destination_port'][0] diff --git a/src/tests/ofc24/copy.sh b/src/tests/ofc24/copy.sh new file mode 100644 index 0000000000000000000000000000000000000000..a7d69af37c5b1c915815506bfb3d38e230fd0713 --- /dev/null +++ b/src/tests/ofc24/copy.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +docker stop -t 1 tna1 +docker stop -t 1 tna2 + +docker rm tna1 +docker rm tna2 + + + +screen -dmS tt1 -T xterm sh -c "docker run --name tna1 -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/platform_t1.xml demoECOC21.xml ; ./startNetconfAgent.sh'" +screen -dmS tt2 -T xterm sh -c "docker run --name tna2 -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/platform_t2.xml demoECOC21.xml ; ./startNetconfAgent.sh'" \ No newline at end of file diff --git a/src/tests/ofc24/run_test.sh b/src/tests/ofc24/run_test.sh index 660436552cebca50c7e628478ab4e66beed6f08d..a4cb46bb4e2448cad7e563719102c63503251771 100644 --- a/src/tests/ofc24/run_test.sh +++ b/src/tests/ofc24/run_test.sh @@ -1,7 +1,7 @@ #!/bin/bash docker stop na1 docker rm na1 - screen -dmS t1 -T xterm sh -c "docker run -p 10.0.2.10:2023:2022 -v ~/tfs-ctrl/tempOC/files:/files --name na1 -it asgamb1/oc23bgp.img:latest + screen -dmS t1 -T xterm sh -c "docker run -p 10.0.2.10:2023:2022 -v ~/tfs-ctrl/src/tests/ofc24/tempOC/files:/files --name na1 -it asgamb1/oc23bgp.img:latest " sleep 2 if [ "$( docker container inspect -f '{{.State.Running}}' na1)" = "true" ]; then diff --git a/src/tests/ofc24/startExtraNetConfigAgent.sh b/src/tests/ofc24/startExtraNetConfigAgent.sh index f8638a51f289af6718e388db583d94da40653efb..e0d2f781c957de699defa195ae2a835b679980d3 100755 --- a/src/tests/ofc24/startExtraNetConfigAgent.sh +++ b/src/tests/ofc24/startExtraNetConfigAgent.sh @@ -15,10 +15,14 @@ +docker stop na1 +docker rm na1 +docker stop na2 +docker rm na2 -screen -dmS t1 -T xterm sh -c "docker run -p 10.0.2.4:2023:2022 -v ~/tfs-ctrl/src/tests/ofc24/tempOC/files:/files --name na1 -it asgamb1/oc23bgp.img:latest bash" -screen -dmS t2 -T xterm sh -c "docker run -p 10.0.2.4:2024:2022 -v ~/tfs-ctrl/src/tests/ofc24/tempOC/files:/files --name na2 -it asgamb1/oc23bgp.img:latest bash" +screen -dmS t1 -T xterm sh -c "docker run -p 10.0.2.4:2023:2022 -v ~/tfs-ctrl/src/tests/ofc24/tempOC/files:/files --name na1 -it asgamb1/oc23bgp.img:latest sh" +screen -dmS t2 -T xterm sh -c "docker run -p 10.0.2.4:2024:2022 -v ~/tfs-ctrl/src/tests/ofc24/tempOC/files:/files --name na2 -it asgamb1/oc23bgp.img:latest sh" @@ -26,7 +30,7 @@ sleep 4 echo "starting transponder1 " if [ "$( docker container inspect -f '{{.State.Running}}' na1)" = "true" ]; then - docker exec na1 sh -c " cp /files/platform_t1.xml demoECOC21.xml ; + docker exec na1 bash -c " cp /files/platform_t1.xml demoECOC21.xml ; /confd/examples.confd/OC23/startNetconfAgent.sh;" else @@ -36,7 +40,7 @@ fi echo "starting transponder2 " if [ "$( docker container inspect -f '{{.State.Running}}' na2)" = "true" ]; then - docker exec na2 sh -c " cp /files/platform_t2.xml demoECOC21.xml; + docker exec na2 bash -c " cp /files/platform_t2.xml demoECOC21.xml; /confd/examples.confd/OC23/startNetconfAgent.sh " else diff --git a/src/webui/service/main/routes.py b/src/webui/service/main/routes.py index 75f036befd4bed3bb3bd743b9f423bf21c014e55..1fd7e006ce6de12737e95d2027fa34e9c9b26759 100644 --- a/src/webui/service/main/routes.py +++ b/src/webui/service/main/routes.py @@ -48,7 +48,9 @@ def process_descriptors(descriptors): descriptor_loader = DescriptorLoader(descriptors, num_workers=DESCRIPTOR_LOADER_NUM_WORKERS) results = descriptor_loader.process() for message,level in compose_notifications(results): + if level == 'error': LOGGER.warning('ERROR message={:s}'.format(str(message))) + flash(message, level) @main.route('/', methods=['GET', 'POST']) diff --git a/src/webui/service/opticalconfig/routes.py b/src/webui/service/opticalconfig/routes.py index 5445d140e587418b8df9ce819756fb92d6ff16ea..b20c15e0dbb50ad9ad7113c6a0e15e82b7532381 100644 --- a/src/webui/service/opticalconfig/routes.py +++ b/src/webui/service/opticalconfig/routes.py @@ -1,18 +1,14 @@ import base64, json, logging #, re -from flask import jsonify, redirect, render_template, Blueprint, flash, session, url_for, request -from common.proto.context_pb2 import (ContextList, Empty, TopologyId, TopologyList -,DeviceId,DeviceList ,OpticalConfig, OpticalConfigId ,OpticalConfigList) -from common.tools.descriptor.Loader import DescriptorLoader, compose_notifications -from common.tools.grpc.Tools import grpc_message_to_json_string -from common.tools.object_factory.Context import json_context_id -from common.tools.object_factory.Topology import json_topology_id +from flask import request, redirect, render_template, Blueprint, flash, session, url_for, current_app ,make_response +from common.proto.context_pb2 import ( Empty +,DeviceId ,OpticalConfig, OpticalConfigId ,OpticalConfigList) + from context.client.ContextClient import ContextClient from device.client.DeviceClient import DeviceClient from service.client.ServiceClient import ServiceClient from slice.client.SliceClient import SliceClient -from webui.service.main.forms import ContextTopologyForm, DescriptorForm from .forms import UpdateDeviceForm ,AddTrancseiver ,UpdateInterfaceForm - +from common.tools.context_queries.OpticalConfig import opticalconfig_get_uuid opticalconfig = Blueprint('opticalconfig', __name__,url_prefix="/opticalconfig") @@ -64,31 +60,142 @@ def show_details(config_uuid): opticalconfigId=OpticalConfigId() opticalconfigId.opticalconfig_uuid=config_uuid device_details=[] + interfaces=[] context_client.connect() response = context_client.SelectOpticalConfig(opticalconfigId) context_client.close() - LOGGER.info("response %s",response) - opticalConfig = OpticalConfig() - opticalConfig.CopyFrom(response) - - config =json.loads(opticalConfig.config) - LOGGER.info("config details %s",config) - interfaces=config["interfaces"] - new_config={} + if (response and response.opticalconfig_id.opticalconfig_uuid !=''): + LOGGER.info("response %s",response) + opticalConfig = OpticalConfig() + opticalConfig.CopyFrom(response) + + config =json.loads(opticalConfig.config) + LOGGER.info("config details %s",config) + interfaces=config["interfaces"] + new_config={} - for channel in config['channels'] : + for channel in config['channels'] : + + new_config["name"]=channel['name'] + new_config['operationalMode']=channel['operational-mode'] if 'operational-mode' in channel else '' + new_config['targetOutputPower']=channel['target-output-power'] if 'target-output-power' in channel else '' + new_config["frequency"]=channel['frequency'] if 'frequency' in channel else '' + new_config['line_port']=channel["line-port"] if 'line-port' in channel else '' - new_config["name"]=channel['name'] - new_config['operationalMode']=channel['operational_mode'] if 'operational_mode' in channel else '' - new_config['targetOutputPower']=channel['target_output_power'] if 'target_output_power' in channel else '' - new_config["frequency"]=channel['frequency'] if 'frequency' in channel else '' - new_config['line_port']=channel["line-port"] if 'line-port' in channel else '' - - device_details.append(new_config) - LOGGER.info("config details %s",device_details) + device_details.append(new_config) + LOGGER.info("config details %s",device_details) + return render_template('opticalconfig/details.html', device=device_details,config_id=config_uuid,interfaces=interfaces) + +@opticalconfig.route('<path:opticalconfig_uuid>/delete', methods=['GET']) +def delete_opitcalconfig (opticalconfig_uuid) : + try : + opticalconfigId=OpticalConfigId() + opticalconfigId.opticalconfig_uuid=opticalconfig_uuid + context_client.connect() + context_client.DeleteOpticalConfig(opticalconfigId) + context_client.close() + flash(f'OpticalConfig "{opticalconfig_uuid}" deleted successfully!', 'success') + except Exception as e: # pylint: disable=broad-except + flash(f'Problem deleting optical config {opticalconfig_uuid}', 'danger') + current_app.logger.exception(e) + return redirect(url_for('opticalconfig.home')) + +@opticalconfig.route('/update_opticalconfig', methods=['POST']) +def update_externally () : + + if (request.method == 'POST'): + device_list= [] + data = request.get_json() + LOGGER.info(f"data {data}") + devices=data.get('devices') + LOGGER.info(f"devices {devices}") + myResponse =[] + status_code='' + for device in devices : + port = device.get("port") + channel_name= f"channel-{port}" + device_name=device.get("device_name") + LOGGER.info(f"device from post {device}") + + if (device_name): + opticalconfig_uuid = opticalconfig_get_uuid(device_name=device_name) + opticalconfigId=OpticalConfigId() + opticalconfigId.opticalconfig_uuid=opticalconfig_uuid + context_client.connect() + opticalconfig = context_client.SelectOpticalConfig(opticalconfigId) + context_client.close() + LOGGER.info(f"opticalconfig {opticalconfig}") + LOGGER.info(f"opticalconfig opticalconfig_uuid {opticalconfig.opticalconfig_id.opticalconfig_uuid}") + LOGGER.info(f"device is existed {opticalconfig.opticalconfig_id.opticalconfig_uuid != ''}") + if opticalconfig and opticalconfig.opticalconfig_id.opticalconfig_uuid != '' : + + new_opticalconfig = OpticalConfig() + new_opticalconfig.CopyFrom(opticalconfig) + config =json.loads(opticalconfig.config) + target_channel =next((item for item in config['channels'] if item["name"]['index'] == channel_name) , None) + LOGGER.info(f"target channel {target_channel}") + target_power=device.get( "target-output-power") + freq = device.get("frequency") + mode = device.get("operational-mode") + LOGGER.info(f"target power {target_power} freq {freq} mode {mode}") + if target_channel: + if target_power : + + target_channel["target-output-power"] =target_power + if freq : + + target_channel["frequency"] =freq + if mode : + + target_channel["operational-mode"] =mode + del target_channel['name'] + config["new_config"]=target_channel + config["new_config"]["channel_name"]=channel_name + config["flow"]=[(port,'0')] + opticalconfig.config =json.dumps(config) + LOGGER.info(f"new Config {config}") + try: + device_client.connect() + device_client.ConfigureOpticalDevice(opticalconfig) + device_client.close() + + myResponse.append(f"device {device_name} port {port} is updated successfully") + status_code = 200 + + + except Exception as e: # pylint: disable=broad-except + + myResponse.append(f"Problem updating the device. {e}") + status_code = 500 + break + + else : + + myResponse.append(f"requested channel {channel_name} is not existed") + status_code = 400 + break + + else : + + myResponse.append(f"requested device {device_name} is not existed") + status_code = 400 + break + + + + response=make_response(f'{myResponse}') + response.status_code=status_code + + return response + + + #return redirect(url_for('opticalconfig.show_details',config_uuid=opticalconfig_uuid)) + #return redirect(url_for('opticalconfig.home')) + @opticalconfig.route('<path:config_uuid>/<path:channel_name>/update', methods=['GET', 'POST']) def update(config_uuid,channel_name): + form = UpdateDeviceForm() opticalconfigId=OpticalConfigId() @@ -187,6 +294,7 @@ def add_transceiver (config_uuid): except Exception as e: # pylint: disable=broad-except flash(f'Problem updating the device. {e}', 'danger') return render_template('opticalconfig/add_transceiver.html',form=addtrancseiver, submit_text='Add Trancseiver') + diff --git a/src/webui/service/templates/opticalconfig/add_transceiver.html b/src/webui/service/templates/opticalconfig/add_transceiver.html index 836bf7d5d0546f5671e65b97db93936e9ce4455d..200626fad5ae3a0f405cd5ec8c4533cd374fc108 100644 --- a/src/webui/service/templates/opticalconfig/add_transceiver.html +++ b/src/webui/service/templates/opticalconfig/add_transceiver.html @@ -1,3 +1,18 @@ +<!-- + Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> {% extends 'base.html' %} diff --git a/src/webui/service/templates/opticalconfig/details.html b/src/webui/service/templates/opticalconfig/details.html index 922f61c674bbd64a03247c06947c75dc46e0e19f..58f4bb6b143f3648fd91b47fdbfa3e63d9da5251 100644 --- a/src/webui/service/templates/opticalconfig/details.html +++ b/src/webui/service/templates/opticalconfig/details.html @@ -1,15 +1,46 @@ + +<!-- + Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + + + {% extends 'base.html' %} {% block content %} + + <h1>My Configurations</h1> <div class="row"> + {% if device %} <div class="col-sm-12"> <span>Device ID:</span> <h5>{{config_id}}</h5> </div> <div class="col-sm-12"> + <div class="col-sm-12"> + <div class="col-sm-3"> + <!-- <button type="button" class="btn btn-danger"><i class="bi bi-x-square"></i>Delete device</button> --> + <button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#deleteModal"> + <i class="bi bi-x-square"></i> + Delete Optical Config + </button> + </div> + </div> <div class="col-sm-12"> <span>Interface:</span> <span> @@ -26,18 +57,7 @@ </ul> </span> </div> - <div class="col-sm-12"> - - <div class="col-sm-3"> - <a id="update" class="btn btn-secondary" - href="{{ url_for('opticalconfig.update_interface',config_uuid=config_id,interface_name=interfaces.interface.name) }}"> - <i class="bi bi-pencil-square"></i> - Update interface - </a> - </div> - - <div class="col-sm-12 hr bg-primary" style="height:1px; margin:5px 0;"></div> - </div> + </div> {% for channel in device %} <div class="col-sm-12"> @@ -62,16 +82,6 @@ <span class="font-weight-bold" style="font-weight: 700;">{{channel.line_port}}</span> </div> - <div class="col-sm-12"> - - <div class="col-sm-3"> - <a id="update" class="btn btn-secondary" - href="{{ url_for('opticalconfig.update',config_uuid=config_id,channel_name=channel.name) }}"> - <i class="bi bi-pencil-square"></i> - Update channel - </a> - </div> - </div> <div class="col-sm-12 hr bg-primary" style="height:1px; margin:5px 0;"></div> {% endfor %} @@ -83,27 +93,31 @@ <h4 colspan="7">No devices found</h4> </div> {% endif %} -<!-- <div class="col"> - <a href="{{ url_for('service.add') }}" class="btn btn-primary" style="margin-bottom: 10px;"> - <i class="bi bi-plus"></i> - Add New Service - </a> - </div> --> - -<!-- Only display XR service addition button if there are XR constellations. Otherwise it might confuse - user, as other service types do not have GUI to add service yet. --> - -<!-- <div class="col"> - <form> - <div class="input-group"> - <input type="text" aria-label="Search" placeholder="Search..." class="form-control"/> - <button type="submit" class="btn btn-primary">Search</button> - </div> - </form> - </div> --> +<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 Optical Config?</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 Optical Config "{{config_id}}"? + </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('opticalconfig.delete_opitcalconfig', opticalconfig_uuid=config_id) }}"><i + class="bi bi-exclamation-diamond"></i>Yes</a> + </div> + </div> + </div> +</div> </div> -{% endblock %} \ No newline at end of file +{% endblock %} + + diff --git a/src/webui/service/templates/opticalconfig/home.html b/src/webui/service/templates/opticalconfig/home.html index 2e0a5da67c73c38f6d12664edc2da6b0be66f8cf..c1253a7b500512881f0b7d5cdaa5b6e7151b7715 100644 --- a/src/webui/service/templates/opticalconfig/home.html +++ b/src/webui/service/templates/opticalconfig/home.html @@ -1,3 +1,19 @@ +<!-- + Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + {% extends 'base.html' %} {% block content %} diff --git a/src/webui/service/templates/opticalconfig/update_interface.html b/src/webui/service/templates/opticalconfig/update_interface.html index 38709ac4752355f19a30505f72ae0fa78be3311f..f09c724c12314b669c6ae1c72dbcc9cfae1f5f45 100644 --- a/src/webui/service/templates/opticalconfig/update_interface.html +++ b/src/webui/service/templates/opticalconfig/update_interface.html @@ -1,3 +1,19 @@ +<!-- + Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + {% extends 'base.html' %} {% block content %}