diff --git a/proto/context.proto b/proto/context.proto index 49d16229cdac5de84f25cfaa7d196d25184f46f0..bfbe8ab2f1f1ed7ccc9055405778affa5393fb29 100644 --- a/proto/context.proto +++ b/proto/context.proto @@ -191,6 +191,7 @@ enum DeviceDriverEnum { DEVICEDRIVER_IETF_NETWORK_TOPOLOGY = 4; DEVICEDRIVER_ONF_TR_352 = 5; DEVICEDRIVER_XR = 6; + DEVICEDRIVER_BGPLS = 7; } enum DeviceOperationalStatusEnum { diff --git a/src/common/DeviceTypes.py b/src/common/DeviceTypes.py index 99255defdb6b5ee155607536a2e13d23b97b2d3a..a47b6be3c6acac5be0ebbe262ba0988f536bf083 100644 --- a/src/common/DeviceTypes.py +++ b/src/common/DeviceTypes.py @@ -28,6 +28,7 @@ class DeviceTypeEnum(Enum): EMULATED_P4_SWITCH = 'emu-p4-switch' EMULATED_PACKET_ROUTER = 'emu-packet-router' EMULATED_PACKET_SWITCH = 'emu-packet-switch' + EMULATED_BGPLS_ASNUMBER = 'emu-bgpls-asnumber' # Real device types DATACENTER = 'datacenter' @@ -38,4 +39,5 @@ class DeviceTypeEnum(Enum): P4_SWITCH = 'p4-switch' PACKET_ROUTER = 'packet-router' PACKET_SWITCH = 'packet-switch' - XR_CONSTELLATION = 'xr-constellation' \ No newline at end of file + XR_CONSTELLATION = 'xr-constellation' + BGPLS_ASNUMBER = 'bgpls-asnumber' \ No newline at end of file diff --git a/src/common/tools/object_factory/Device.py b/src/common/tools/object_factory/Device.py index 0cc4555d455bf28ac2143a5d58b87e084a8360c7..d35cb684cb55181b7bc6d665e00f45ebf351c758 100644 --- a/src/common/tools/object_factory/Device.py +++ b/src/common/tools/object_factory/Device.py @@ -43,6 +43,9 @@ DEVICE_MICROWAVE_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY] DEVICE_P4_TYPE = DeviceTypeEnum.P4_SWITCH.value DEVICE_P4_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_P4] +DEVICE_BGPLS_TYPE = DeviceTypeEnum.BGPLS_ASNUMBER.value +DEVICE_BGPLS_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_BGPLS] + def json_device_id(device_uuid : str): return {'device_uuid': {'uuid': device_uuid}} diff --git a/src/common/type_checkers/Assertions.py b/src/common/type_checkers/Assertions.py index c0442d8770c682ac1eea032980b58e7028be90c4..e20b2d1f870be0327d1528180e39422d7eea06e0 100644 --- a/src/common/type_checkers/Assertions.py +++ b/src/common/type_checkers/Assertions.py @@ -33,6 +33,7 @@ def validate_device_driver_enum(message): 'DEVICEDRIVER_IETF_NETWORK_TOPOLOGY', 'DEVICEDRIVER_ONF_TR_352', 'DEVICEDRIVER_XR', + 'DEVICEDRIVER_BGPLS', ] def validate_device_operational_status_enum(message): diff --git a/src/context/service/database/models/enums/DeviceDriver.py b/src/context/service/database/models/enums/DeviceDriver.py index 6997e7dfbff6bc1d4b6452a28f11cdac9aae412f..9893add28e828eba93a67dffa0f599065f9c5cbf 100644 --- a/src/context/service/database/models/enums/DeviceDriver.py +++ b/src/context/service/database/models/enums/DeviceDriver.py @@ -24,6 +24,7 @@ class ORM_DeviceDriverEnum(enum.Enum): IETF_NETWORK_TOPOLOGY = DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY ONF_TR_352 = DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352 XR = DeviceDriverEnum.DEVICEDRIVER_XR + BGPLS = DeviceDriverEnum.DEVICEDRIVER_BGPLS grpc_to_enum__device_driver = functools.partial( grpc_to_enum, DeviceDriverEnum, ORM_DeviceDriverEnum) diff --git a/src/device/service/DeviceServiceServicerImpl.py b/src/device/service/DeviceServiceServicerImpl.py index be40e64ecd25a5c46c23d5ec0a73a2484b65691d..76acd203e4a63e0e7e8ad925a8af1a9edd0d56df 100644 --- a/src/device/service/DeviceServiceServicerImpl.py +++ b/src/device/service/DeviceServiceServicerImpl.py @@ -30,6 +30,7 @@ from .Tools import ( check_connect_rules, check_no_endpoints, compute_rules_to_add_delete, configure_rules, deconfigure_rules, populate_config_rules, populate_endpoint_monitoring_resources, populate_endpoints, populate_initial_config_rules, subscribe_kpi, unsubscribe_kpi, update_endpoints) +from .drivers.bgpls.Tools import config_BGPLSDriver LOGGER = logging.getLogger(__name__) @@ -70,7 +71,7 @@ class DeviceServiceServicerImpl(DeviceServiceServicer): self.mutex_queues.wait_my_turn(device_uuid) try: driver : _Driver = get_driver(self.driver_instance_cache, device) - + LOGGER.info("_DRIVER get_driver: %s",driver) errors = [] if len(device.device_endpoints) == 0: @@ -106,6 +107,7 @@ class DeviceServiceServicerImpl(DeviceServiceServicer): try: context_client = ContextClient() device = get_device(context_client, device_uuid, rw_copy=True) + # LOGGER.info("device get_device: %s", device ) if device is None: raise NotFoundException('Device', device_uuid, extra_details='loading in ConfigureDevice') @@ -123,6 +125,13 @@ class DeviceServiceServicerImpl(DeviceServiceServicer): device_id = context_client.SetDevice(device) device = context_client.GetDevice(device_id) + # Checkear aqui si son dos driver y si uno es bgpls Connect ****tid-bgp-speaker**** + if DeviceDriverEnum.DEVICEDRIVER_BGPLS in device.device_drivers: + LOGGER.info("Es BGPLS (ConfigureDevice)") + # config_BGPLSDriver.driverSettings(context_client) + # config_BGPLSDriver.driverConnect(self.driver_instance_cache,device,device_uuid) + + if request.device_operational_status != DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_UNDEFINED: device.device_operational_status = request.device_operational_status diff --git a/src/device/service/drivers/__init__.py b/src/device/service/drivers/__init__.py index 469abcad387dc055ba17770e4f405db1d1ceaa3b..17fb8c8f12e7988a89bec8aede92d38b3c31944a 100644 --- a/src/device/service/drivers/__init__.py +++ b/src/device/service/drivers/__init__.py @@ -37,6 +37,7 @@ DRIVERS.append( DeviceTypeEnum.EMULATED_P4_SWITCH, DeviceTypeEnum.EMULATED_PACKET_ROUTER, DeviceTypeEnum.EMULATED_PACKET_SWITCH, + DeviceTypeEnum.EMULATED_BGPLS_ASNUMBER, #DeviceTypeEnum.DATACENTER, #DeviceTypeEnum.MICROWAVE_RADIO_SYSTEM, @@ -127,3 +128,13 @@ if LOAD_ALL_DEVICE_DRIVERS: FilterFieldEnum.DRIVER : DeviceDriverEnum.DEVICEDRIVER_XR, } ])) +if LOAD_ALL_DEVICE_DRIVERS: + from .bgpls.BGPLSDriver import BGPLSDriver # pylint: disable=wrong-import-position + DRIVERS.append( + (BGPLSDriver, [ + { + # Values :TODO ¿ + FilterFieldEnum.DEVICE_TYPE: DeviceTypeEnum.BGPLS_ASNUMBER, + FilterFieldEnum.DRIVER : DeviceDriverEnum.DEVICEDRIVER_BGPLS, + } + ])) \ No newline at end of file diff --git a/src/device/service/drivers/bgpls/BGPLSDriver.py b/src/device/service/drivers/bgpls/BGPLSDriver.py new file mode 100644 index 0000000000000000000000000000000000000000..be7b4656e4f2e274c182b0ed9686e4127e81ae97 --- /dev/null +++ b/src/device/service/drivers/bgpls/BGPLSDriver.py @@ -0,0 +1,233 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json, logging,threading, queue,time,signal +from datetime import datetime, timedelta +from typing import Any, Iterator, List, Optional, Tuple, Union +# from apscheduler.executors.pool import ThreadPoolExecutor +# from apscheduler.job import Job +# from apscheduler.jobstores.memory import MemoryJobStore +# from apscheduler.schedulers.background import BackgroundScheduler +# from common.method_wrappers.Decorator import MetricTypeEnum, MetricsPool, metered_subclass_method, INF +# from common.type_checkers.Checkers import chk_float, chk_length, chk_string, chk_type + + +import logging,anytree, json, pytz, queue, re, threading +import grpc +from .Tools import grpcComms,Link,Node,UpdateInfo +from .protos import grpcService_pb2_grpc +from .protos import grpcService_pb2 + + +from concurrent import futures +from lxml import etree +import os +import subprocess +from multiprocessing import Pool + +from device.service.driver_api._Driver import _Driver +from apscheduler.executors.pool import ThreadPoolExecutor +from apscheduler.job import Job +from apscheduler.jobstores.memory import MemoryJobStore +from apscheduler.schedulers.background import BackgroundScheduler +from common.method_wrappers.Decorator import MetricTypeEnum, MetricsPool, metered_subclass_method, INF +from common.type_checkers.Checkers import chk_float, chk_length, chk_string, chk_type +from device.service.driver_api._Driver import _Driver +from device.service.driver_api.AnyTreeTools import TreeNode, dump_subtree, get_subnode, set_subnode_value + +SERVER_ADDRESS = 'localhost:2021' +SERVER_ID = 1 +_ONE_DAY_IN_SECONDS = 60 * 60 * 24 + +XML_FILE="BGP4Parameters_3.xml" +XML_CONFIG_FILE="TMConfiguration_guillermo.xml" + +LOGGER = logging.getLogger(__name__) + + +# Esto tiene que heredar de _Driver¿ +class BGPLSDriver(_Driver): + """ + This class gets the current topology from a bgps speaker module in java + and updates the posible new devices to add in the context topology. + Needs the address, port and as_number from the device giving de information via bgpls + to the java module. + """ + def __init__(self, address : str, port : int, asNumber : int,**settings) -> None: # pylint: disable=super-init-not-called + self.__lock = threading.Lock() + self.__started = threading.Event() + self.__terminate = threading.Event() + self.__out_samples = queue.Queue() + # self.__scheduler = BackgroundScheduler(daemon=True) # scheduler used to emulate sampling events + # self.__scheduler.configure( + # jobstores = {'default': MemoryJobStore()}, + # executors = {'default': ThreadPoolExecutor(max_workers=1)}, + # job_defaults = {'coalesce': False, 'max_instances': 3}, + # timezone=pytz.utc) + # TODO: atributos necesarios + # self.__server=asyncio.run(grpc.aio.server()) + + self.__address=address + self.__port=port + self.__asNumber=asNumber + self.__configFile=XML_CONFIG_FILE + self.__process=0 + self.__comms=grpcComms + + + + async def Connect(self) -> bool: + # TODO: Metodos necesarios para conectarte al speaker + LOGGER.info("CONNECT BGPLSDriver") + # If started, assume it is already connected + if self.__started.is_set(): return True + # self.__scheduler.start() + self.__started.set() #notifyAll -->event.is_set() + # 10 workers ? + with self.__lock: + self.__server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) + grpcService_pb2_grpc.add_updateServiceServicer_to_server(self, self.__server) + self.__server.add_insecure_port(SERVER_ADDRESS) + # server.add_secure_port(SERVER_ADDRESS) + LOGGER.info("Starting server on %s", SERVER_ADDRESS) + await self.__server.start() + try: + while True: + time.sleep(_ONE_DAY_IN_SECONDS) + except KeyboardInterrupt: + LOGGER.info("DISCONNECT") + self.Disconnect() + return True + + + def Disconnect(self) -> bool: + # TODO: channel grpc close + self.__terminate.set() + # If not started, assume it is already disconnected + if not self.__started.is_set(): return True + + LOGGER.info("Keyboard interrupt, stop server") + self.__server.stop(0) + # Disconnect triggers deactivation of sampling events + # self.__scheduler.shutdown() + exit(0) + return True + + # @metered_subclass_method(METRICS_POOL) + def update(self,request, context) -> bool: + """ + Processes the messages recived by de grpc server + """ + with self.__lock: + #TODO: Get update + LOGGER.info("(server)SimpleMethod called by client the message: %s" % (request)) + response = grpcService_pb2.updateResponse(greeting="OK") + link=None + node=None # inicializar en otro lado¿ + for linkIn in request.link: + link=Link(linkIn.remoteID,linkIn.localID,linkIn.remoteIP,linkIn.localIP) + for nodeIn in request.node: + node=Node(nodeIn.nodeName,nodeIn.nodeID) + up=UpdateInfo(link,node) + return response + + def GetState(self, blocking=False, terminate : Optional[threading.Event] = None) -> Iterator[Tuple[str, Any]]: + while True: + if self.__terminate.is_set(): break + if terminate is not None and terminate.is_set(): break + try: + sample = self.__out_samples.get(block=blocking, timeout=0.1) + except queue.Empty: + if blocking: continue + return + if sample is None: continue + yield sample + + def setPeer(self) -> bool: + """ + Sets XML existing config file with peer address and port. TODO: as_number + """ + + XMLParser = etree.XMLParser(remove_blank_text=False) + tree = etree.parse(XML_FILE, parser=XMLParser) + root = tree.getroot() + peerAddress = root.find(".//peer") + peerAddress.text=self.__address + peerPort = root.find(".//peerPort") + peerPort.text=str(self.__port) + tree.write(XML_FILE) #with ... as .. + return True + + def execBGPSpeaker(self) -> bool: + """ + Executes java BGPLS speaker + """ + # CHECKEAR muchas cosas + LOGGER.debug("Before exec") + with subprocess.Popen(['java -jar bgp_ls.jar '+ XML_CONFIG_FILE], + shell=True,start_new_session=True) as self.__process: + # # logging.debug(self.__process.stdout.read()) + return True + + def endBGPSpeaker(self) -> bool: + """ + Starts timer to kill java BGPLS Speaker + """ + LOGGER.debug("end to sleep") + time.sleep(15) + LOGGER.debug("PID: %d",self.__process.pid) + LOGGER.debug("Group PID: %d",os.getpgid(self.__process.pid)) + os.killpg(os.getpgid(self.__process.pid), signal.SIGKILL) + self.__process.kill() + return True + + def runThreads(self): + # with futures.ThreadPoolExecutor(max_workers=4) as executor: + # executor.submit(bgpDriver.ConnectNotWait) + # executor.submit(bgpDriver.execBGPSpeaker) + # executor.submit(bgpDriver.endBGPSpeaker) + t1=threading.Thread(name="t1",target=bgpDriver.Connect) + t2=threading.Thread(name="t2",target=bgpDriver.execBGPSpeaker) + t3=threading.Thread(name="t3",target=bgpDriver.endBGPSpeaker) + t1.start() + t2.start() + t3.start() + return True + + def getCurrentTopo(): + # import common.tools.Device + # get_devices_in_topology( + # context_client : ContextClient, context_id : ContextId, topology_uuid : str) + + return True + +def quit(signal, _frame): + LOGGER.info("Interrupted by %d, shutting down" % signal) + bgpDriver.Disconnect() + +if __name__ == "__main__": + + logging.basicConfig(level=logging.DEBUG) + for sig in ('TERM', 'HUP', 'INT'): + signal.signal(getattr(signal, 'SIG'+sig), quit) + # TODO: add port connection speaker + bgpDriver=BGPLSDriver("10.95.90.76",179,65006) + bgpDriver.setPeer() + bgpDriver.runThreads() + + + + + + diff --git a/src/device/service/drivers/bgpls/Tools.py b/src/device/service/drivers/bgpls/Tools.py new file mode 100644 index 0000000000000000000000000000000000000000..61f52ad51c8b6dd0639a63dd2b297e508f642b5e --- /dev/null +++ b/src/device/service/drivers/bgpls/Tools.py @@ -0,0 +1,111 @@ + +from .protos import grpcService_pb2_grpc +from .protos import grpcService_pb2 + +import logging +LOGGER = logging.getLogger(__name__) +import os + +class UpdateInfo: + def __init__(self,link,node): + self.link=link + self.node=node +class Link: + def __init__(self,rID,lID,rIP,lIP): + self.rID=rID + self.lID=lID + self.rIP=rIP + self.lIP=lIP +class Node: + def __init__(self,name,nid): + self.Name=name + self.ID=nid + +class grpcComms(grpcService_pb2_grpc.updateServiceServicer): + + def update(self,request, context) -> bool: + """ + Processes the messages recived by de grpc server + """ + with self.__lock: + #TODO: Get update + print("(server)SimpleMethod called by client the message: %s" % (request)) + response = grpcService_pb2.updateResponse(greeting="OK") + link=None + node=None # inicializar en otro lado¿ + for linkIn in request.link: + link=Link(linkIn.remoteID,linkIn.localID,linkIn.remoteIP,linkIn.localIP) + for nodeIn in request.node: + node=Node(nodeIn.nodeName,nodeIn.nodeID) + up=UpdateInfo(link,node) + return response + +from common.proto.context_pb2 import Device ,DeviceDriverEnum,ContextId,Empty +from device.service.driver_api.DriverInstanceCache import DriverInstanceCache +from device.service.driver_api.FilterFields import FilterFieldEnum, get_device_driver_filter_fields +from device.service.Tools import get_connect_rules +from . import BGPLSDriver as bgpls +from context.client.ContextClient import ContextClient +from common.Constants import DEFAULT_CONTEXT_NAME +from common.tools.object_factory.Context import json_context_id + +ADMIN_CONTEXT_ID = ContextId(**json_context_id(DEFAULT_CONTEXT_NAME)) + +class config_BGPLSDriver(): + + + def driverSettings( context_client : ContextClient): + + list_topo=[] + LOGGER.info("Context (driverSettings) **************%s",context_client) + # context_uuid = context_id['context_uuid']['uuid'] + # response = context.GetContext(ADMIN_CONTEXT_ID) + contexts : ContextList = context_client.ListContexts(Empty()) + for context_ in contexts.contexts: + context_uuid : str = context_.context_id.context_uuid.uuid + context_name : str = context_.name + topologies : TopologyList = context_client.ListTopologies(context_.context_id) + # topologies : TopologyList=context_client.ListTopologies(context_client) + for topology_ in topologies.topologies: + #topology_uuid : str = topology_.topology_id.topology_uuid.uuid + topology_name : str = topology_.name + context_topology_name = 'Context({:s}):Topology({:s})'.format(context_name, topology_name) + # Topos=context.GetTopology(list_topo.topology_id) + LOGGER.info("topo (driverSettings) %s",topology_) + details=context_client.GetTopologyDetails(topology_.topology_id) + LOGGER.info("details (driverSettings) %s",details) + devices=context_client.ListDevices(Empty()) + # LOGGER.info("devices (driverSettings) %s",devices) + for device_ in devices.devices: + LOGGER.info("device_ (driverSettings) %s",device_.name) + for config_rule_ in device_.device_config.config_rules: + if config_rule_.custom.resource_key == "_connect/address": + LOGGER.info("device_.resource_value-addr (driverSettings) %s", + config_rule_.custom.resource_value) + + # LOGGER.info("response getContext (driverSettings) %s",response) + # list_topo=context.ListTopologies(response.context_id) + # LOGGER.info("list_topo (driverSettings) %s",list_topo) + # Topos=context.GetTopology(list_topo.topology_id) + # LOGGER.info("topo (driverSettings) %s",Topos) + return + + def driverConnect(driver_instance_cache : DriverInstanceCache,device : Device, + device_uuid): + bgpls_instance=[driver for driver in device.device_drivers if DeviceDriverEnum.DEVICEDRIVER_BGPLS] + LOGGER.info(" (driverConnect) class bgpls: %s",bgpls_instance) + driver_filter_fields = get_device_driver_filter_fields(device) + connect_rules = get_connect_rules(device.device_config) + address = connect_rules.get('address', '127.0.0.1') + port = connect_rules.get('port', '0') + settings = connect_rules.get('settings', '{}') + driver : _Driver = driver_instance_cache.get( + device_uuid, filter_fields=driver_filter_fields, address=address, port=port, settings=settings) + LOGGER.info(" (driverConnect) driver: %s",driver) + bgpDriver=bgpls.BGPLSDriver("10.95.90.76",179,65006) + cwd = os.getcwd() + LOGGER.info("Current working directory:", cwd) + bgpDriver.setPeer() + bgpDriver.runThreads() + driver.Connect() + return diff --git a/src/device/service/drivers/bgpls/__init__.py b/src/device/service/drivers/bgpls/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..1549d9811aa5d1c193a44ad45d0d7773236c0612 --- /dev/null +++ b/src/device/service/drivers/bgpls/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/src/device/service/drivers/bgpls/protos/grpcService.proto b/src/device/service/drivers/bgpls/protos/grpcService.proto new file mode 100644 index 0000000000000000000000000000000000000000..ef3642c30a301087189db9d1e1a2abba09ba209c --- /dev/null +++ b/src/device/service/drivers/bgpls/protos/grpcService.proto @@ -0,0 +1,40 @@ +syntax = "proto3"; +package src.main.proto; + +//el modulo java abre la comunicacion +//cliente(java) manda la info al servidor(python) +//el modulo en python responde con ok + +message updateRequest { + + repeated nodeInfo node = 1; + // repeated : se da la posibilidad de mandar 0 o varios + repeated linkInfo link = 2; + + // There are many more basics types, like Enum, Map + // See https://developers.google.com/protocol-buffers/docs/proto3 + // for more information. +} + +message nodeInfo{ + string nodeName=1; + string nodeID=2; +} + +message linkInfo{ + string remoteID=1; + string localID=2; + + string remoteIP=3; + string localIP=4; +} + +message updateResponse { + string greeting = 1; +} + +// Defining a Service, a Service can have multiple RPC operations +service updateService { + // MODIFY HERE: Update the return to streaming return. + rpc update(updateRequest) returns (updateResponse); +} \ No newline at end of file diff --git a/src/device/service/drivers/bgpls/protos/grpcService_pb2.py b/src/device/service/drivers/bgpls/protos/grpcService_pb2.py new file mode 100644 index 0000000000000000000000000000000000000000..47f75875c38186d9e578a96b103a3697a1cfeadf --- /dev/null +++ b/src/device/service/drivers/bgpls/protos/grpcService_pb2.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: grpcService.proto +"""Generated protocol buffer code.""" +from google.protobuf.internal import builder as _builder +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x11grpcService.proto\x12\x0esrc.main.proto\"_\n\rupdateRequest\x12&\n\x04node\x18\x01 \x03(\x0b\x32\x18.src.main.proto.nodeInfo\x12&\n\x04link\x18\x02 \x03(\x0b\x32\x18.src.main.proto.linkInfo\",\n\x08nodeInfo\x12\x10\n\x08nodeName\x18\x01 \x01(\t\x12\x0e\n\x06nodeID\x18\x02 \x01(\t\"P\n\x08linkInfo\x12\x10\n\x08remoteID\x18\x01 \x01(\t\x12\x0f\n\x07localID\x18\x02 \x01(\t\x12\x10\n\x08remoteIP\x18\x03 \x01(\t\x12\x0f\n\x07localIP\x18\x04 \x01(\t\"\"\n\x0eupdateResponse\x12\x10\n\x08greeting\x18\x01 \x01(\t2X\n\rupdateService\x12G\n\x06update\x12\x1d.src.main.proto.updateRequest\x1a\x1e.src.main.proto.updateResponseb\x06proto3') + +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'grpcService_pb2', globals()) +if _descriptor._USE_C_DESCRIPTORS == False: + + DESCRIPTOR._options = None + _UPDATEREQUEST._serialized_start=37 + _UPDATEREQUEST._serialized_end=132 + _NODEINFO._serialized_start=134 + _NODEINFO._serialized_end=178 + _LINKINFO._serialized_start=180 + _LINKINFO._serialized_end=260 + _UPDATERESPONSE._serialized_start=262 + _UPDATERESPONSE._serialized_end=296 + _UPDATESERVICE._serialized_start=298 + _UPDATESERVICE._serialized_end=386 +# @@protoc_insertion_point(module_scope) diff --git a/src/device/service/drivers/bgpls/protos/grpcService_pb2_grpc.py b/src/device/service/drivers/bgpls/protos/grpcService_pb2_grpc.py new file mode 100644 index 0000000000000000000000000000000000000000..c8bbda558d60b1108bfcb1ff60fbe755bb2d75c3 --- /dev/null +++ b/src/device/service/drivers/bgpls/protos/grpcService_pb2_grpc.py @@ -0,0 +1,70 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +from . import grpcService_pb2 as grpcService__pb2 + + +class updateServiceStub(object): + """Defining a Service, a Service can have multiple RPC operations + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.update = channel.unary_unary( + '/src.main.proto.updateService/update', + request_serializer=grpcService__pb2.updateRequest.SerializeToString, + response_deserializer=grpcService__pb2.updateResponse.FromString, + ) + + +class updateServiceServicer(object): + """Defining a Service, a Service can have multiple RPC operations + """ + + def update(self, request, context): + """MODIFY HERE: Update the return to streaming return. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_updateServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'update': grpc.unary_unary_rpc_method_handler( + servicer.update, + request_deserializer=grpcService__pb2.updateRequest.FromString, + response_serializer=grpcService__pb2.updateResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'src.main.proto.updateService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class updateService(object): + """Defining a Service, a Service can have multiple RPC operations + """ + + @staticmethod + def update(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/src.main.proto.updateService/update', + grpcService__pb2.updateRequest.SerializeToString, + grpcService__pb2.updateResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/src/pathcomp/frontend/service/algorithms/tools/ConstantsMappings.py b/src/pathcomp/frontend/service/algorithms/tools/ConstantsMappings.py index cd1956a873dd2170c7a75db0c677db34162449ee..b38907668fd2faec199b5e2821efb1b8919e55da 100644 --- a/src/pathcomp/frontend/service/algorithms/tools/ConstantsMappings.py +++ b/src/pathcomp/frontend/service/algorithms/tools/ConstantsMappings.py @@ -101,6 +101,9 @@ DEVICE_TYPE_TO_LAYER = { DeviceTypeEnum.OPTICAL_ROADM.value : DeviceLayerEnum.OPTICAL_DEVICE, DeviceTypeEnum.EMULATED_OPTICAL_TRANSPONDER.value : DeviceLayerEnum.OPTICAL_DEVICE, DeviceTypeEnum.OPTICAL_TRANSPONDER.value : DeviceLayerEnum.OPTICAL_DEVICE, + + DeviceTypeEnum.BGPLS_ASNUMBER.value : DeviceLayerEnum.APPLICATION_CONTROLLER, #TODO: value + DeviceTypeEnum.EMULATED_BGPLS_ASNUMBER.value : DeviceLayerEnum.APPLICATION_CONTROLLER, #TODO: } DEVICE_LAYER_TO_SERVICE_TYPE = { diff --git a/src/service/service/service_handler_api/FilterFields.py b/src/service/service/service_handler_api/FilterFields.py index a73ec53f37d68e0414eeb1df146373c6906273c5..6f3e226cfe162e1165aa10dd82c9d39df27fcf78 100644 --- a/src/service/service/service_handler_api/FilterFields.py +++ b/src/service/service/service_handler_api/FilterFields.py @@ -33,7 +33,8 @@ DEVICE_DRIVER_VALUES = { DeviceDriverEnum.DEVICEDRIVER_P4, DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY, DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352, - DeviceDriverEnum.DEVICEDRIVER_XR + DeviceDriverEnum.DEVICEDRIVER_XR, + DeviceDriverEnum.DEVICEDRIVER_BGPLS } # Map allowed filter fields to allowed values per Filter field. If no restriction (free text) None is specified diff --git a/src/webui/service/__init__.py b/src/webui/service/__init__.py index fca1071419b3b2b61739c2a0d1d8bfa45aba5119..5ba5a35e52d45f578656610636d5897e996d8f65 100644 --- a/src/webui/service/__init__.py +++ b/src/webui/service/__init__.py @@ -91,6 +91,9 @@ def create_app(use_config=None, web_app_root=None): from webui.service.device.routes import device # pylint: disable=import-outside-toplevel app.register_blueprint(device) + + from webui.service.topology.routes import topology # pylint: disable=import-outside-toplevel + app.register_blueprint(topology) from webui.service.link.routes import link # pylint: disable=import-outside-toplevel app.register_blueprint(link) diff --git a/src/webui/service/device/routes.py b/src/webui/service/device/routes.py index ebf77a35ffdf9c2546ddbdd1bac0c8c1f54a2b56..32fc6babc8990bbb5dfd2868f60b11e35c369119 100644 --- a/src/webui/service/device/routes.py +++ b/src/webui/service/device/routes.py @@ -120,6 +120,8 @@ def add(): device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352) if form.device_drivers_xr.data: device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_XR) + if form.device_drivers_bgpls.data: + device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_BGPLS) device_obj.device_drivers.extend(device_drivers) # pylint: disable=no-member try: diff --git a/src/webui/service/templates/base.html b/src/webui/service/templates/base.html index 1dfa3687198d8a33db346ba2bbcd2989f6f109bb..daed737a493f1266288eca920075c0642aaafd8c 100644 --- a/src/webui/service/templates/base.html +++ b/src/webui/service/templates/base.html @@ -96,6 +96,13 @@ <a class="nav-link" href="{{ url_for('load_gen.home') }}">Load Generator</a> {% endif %} </li> + <li class="nav-item"> + {% if '/topology/' in request.path %} + <a class="nav-link active" aria-current="page" href="{{ url_for('topology.home') }}">Topology</a> + {% else %} + <a class="nav-link" href="{{ url_for('topology.home') }}">Topology</a> + {% endif %} + </li> <!-- <li class="nav-item"> <a class="nav-link" href="#">Context</a> diff --git a/src/webui/service/templates/topology/detail.html b/src/webui/service/templates/topology/detail.html new file mode 100644 index 0000000000000000000000000000000000000000..aad221c85e73b98869b90f59117228b77c02fa43 --- /dev/null +++ b/src/webui/service/templates/topology/detail.html @@ -0,0 +1,37 @@ +<!-- + 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>Device {{ device.name }} ({{ device.device_id.device_uuid.uuid }})</h1> + <!-- TEST --> + <h5>Type: {{ device.device_type }}</h5> + <div> + <label for="nombre">Device IP:</label> + <input type="text" id="ip" name="ip"> + <label for="nombre">Device User:</label> + <input type="text" id="user" name="user"> + <label for="nombre">Device Password:</label> + <input type="text" id="pass" name="pass"> + </div> + + <script src="https://d3js.org/d3.v4.min.js"></script> + <div id="topology"></div> + <script src="{{ url_for('js.topology_js') }}"></script> + +{% endblock %} + \ No newline at end of file diff --git a/src/webui/service/templates/topology/home.html b/src/webui/service/templates/topology/home.html new file mode 100644 index 0000000000000000000000000000000000000000..27b864d3bb6aefb9d1586838a2466238cbe83299 --- /dev/null +++ b/src/webui/service/templates/topology/home.html @@ -0,0 +1,68 @@ +<!-- + 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>Topology</h1> + + <div class="row"> + + <div class="col"> + {{ devices | length }} devices found in context <i>{{ session['context_uuid'] }}</i> + </div> + </div> + + + <table class="table table-striped table-hover"> + <thead> + <tr> + <th scope="col">Name</th> + <th scope="col">Config Rules</th> + <th scope="col"></th> + </tr> + </thead> + <tbody> + {% if devices %} + {% for device in devices %} + <tr> + <td>{{ device.name }}</td> + <td>{{ device.device_config.config_rules | length }}</td> + <td> + <div class="col"> + <a href="{{ url_for('topology.detail', device_uuid=device.device_id.device_uuid.uuid) }}"> + <i class="bi bi-plus"></i> + Add Device + </a> + </div> + + + </td> + </tr> + {% endfor %} + {% else %} + <tr> + <td colspan="3">No devices found</td> + </tr> + {% endif %} + </tbody> + </table> + + <script src="https://d3js.org/d3.v4.min.js"></script> + <div id="topology"></div> + <script src="{{ url_for('js.topology_js') }}"></script> + +{% endblock %} \ No newline at end of file diff --git a/src/webui/service/topology/__init__.py b/src/webui/service/topology/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..1549d9811aa5d1c193a44ad45d0d7773236c0612 --- /dev/null +++ b/src/webui/service/topology/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/src/webui/service/topology/forms.py b/src/webui/service/topology/forms.py new file mode 100644 index 0000000000000000000000000000000000000000..ca039a9e2edbbd0176f513c150db6b120e3432ae --- /dev/null +++ b/src/webui/service/topology/forms.py @@ -0,0 +1,80 @@ +# 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. + +# external imports +from flask_wtf import FlaskForm +from flask_wtf.file import FileAllowed +from wtforms import SelectField, FileField, SubmitField + +from wtforms import StringField, SelectField, TextAreaField, SubmitField, BooleanField, Form +from wtforms.validators import DataRequired, Length, NumberRange, Regexp, ValidationError +from common.proto.context_pb2 import DeviceOperationalStatusEnum +from webui.utils.form_validators import key_value_validator + +class AddDeviceForm(FlaskForm): + device_id = StringField('ID', + validators=[DataRequired(), Length(min=5)]) + device_type = SelectField('Type', choices = []) + operational_status = SelectField('Operational Status', + # choices=[(-1, 'Select...'), (0, 'Undefined'), (1, 'Disabled'), (2, 'Enabled')], + coerce=int, + validators=[NumberRange(min=0)]) + device_drivers_undefined = BooleanField('UNDEFINED / EMULATED') + device_drivers_openconfig = BooleanField('OPENCONFIG') + device_drivers_transport_api = BooleanField('TRANSPORT_API') + device_drivers_p4 = BooleanField('P4') + device_drivers_ietf_network_topology = BooleanField('IETF_NETWORK_TOPOLOGY') + device_drivers_onf_tr_352 = BooleanField('ONF_TR_352') + device_drivers_xr = BooleanField('XR') + device_drivers_bgpls = BooleanField('BGPLS') + device_config_address = StringField('connect/address',default='127.0.0.1',validators=[DataRequired(), Length(min=5)]) + device_config_port = StringField('connect/port',default='0',validators=[DataRequired(), Length(min=1)]) + device_config_settings = TextAreaField('connect/settings',default='{}',validators=[DataRequired(), Length(min=2)]) + submit = SubmitField('Add') + + def validate_operational_status(form, field): + if field.data not in DeviceOperationalStatusEnum.DESCRIPTOR.values_by_number: + raise ValidationError('The operational status value selected is incorrect!') + +class ConfigForm(FlaskForm): + device_key_config = StringField('Key configuration') + device_value_config = StringField('Value configuration') + submit = SubmitField('Add') + + +class UpdateDeviceForm(FlaskForm): + update_operational_status = SelectField('Operational Status', + choices=[(-1, 'Select...'), (0, 'Undefined'), (1, 'Disabled'), (2, 'Enabled')], + coerce=int, + validators=[NumberRange(min=0)]) + + submit = SubmitField('Update') + +class ContextTopologyForm(FlaskForm): + context_topology = SelectField( + 'Ctx/Topo', + choices=[], + validators=[ + DataRequired(), + Length(min=1) + ]) + submit = SubmitField('Submit') + +class DescriptorForm(FlaskForm): + descriptors = FileField( + 'Descriptors', + validators=[ + FileAllowed(['json'], 'JSON Descriptors only!') + ]) + submit = SubmitField('Submit') \ No newline at end of file diff --git a/src/webui/service/topology/routes.py b/src/webui/service/topology/routes.py new file mode 100644 index 0000000000000000000000000000000000000000..710461b47b5dd500995beacf5f6244be41b04886 --- /dev/null +++ b/src/webui/service/topology/routes.py @@ -0,0 +1,232 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +from flask import current_app, render_template, Blueprint, flash, session, redirect, url_for +from common.proto.context_pb2 import ( + ConfigActionEnum, Device, DeviceDriverEnum, DeviceId, DeviceList, DeviceOperationalStatusEnum, Empty, TopologyId) +from common.tools.object_factory.Context import json_context_id +from common.tools.object_factory.Topology import json_topology_id +from context.client.ContextClient import ContextClient +from device.client.DeviceClient import DeviceClient +from webui.service.device.forms import AddDeviceForm +from common.DeviceTypes import DeviceTypeEnum +from webui.service.topology.forms import ConfigForm +from webui.service.topology.forms import UpdateDeviceForm + +topology = Blueprint('topology', __name__, url_prefix='/topoloy') +context_client = ContextClient() +device_client = DeviceClient() + +@topology.get('/') +def home(): + if 'context_uuid' not in session or 'topology_uuid' not in session: + flash("Please select a context!", "warning") + return redirect(url_for("main.home")) + + context_uuid = session['context_uuid'] + topology_uuid = session['topology_uuid'] + + context_client.connect() + json_topo_id = json_topology_id(topology_uuid, context_id=json_context_id(context_uuid)) + grpc_topology = context_client.GetTopology(TopologyId(**json_topo_id)) + topo_device_uuids = {device_id.device_uuid.uuid for device_id in grpc_topology.device_ids} + grpc_devices: DeviceList = context_client.ListDevices(Empty()) + context_client.close() + + devices = [ + device for device in grpc_devices.devices + if device.device_id.device_uuid.uuid in topo_device_uuids + ] + + return render_template( + 'topology/home.html', devices=devices, dde=DeviceDriverEnum, + dose=DeviceOperationalStatusEnum) + +@topology.route('add', methods=['GET', 'POST']) +def add(): + form = AddDeviceForm() + + # listing enum values + form.operational_status.choices = [] + for key, _ in DeviceOperationalStatusEnum.DESCRIPTOR.values_by_name.items(): + form.operational_status.choices.append( + (DeviceOperationalStatusEnum.Value(key), key.replace('DEVICEOPERATIONALSTATUS_', ''))) + + # items for Device Type field + for device_type in DeviceTypeEnum: + form.device_type.choices.append((device_type.value,device_type.value)) + + if form.validate_on_submit(): + device_obj = Device() + # Device UUID: + device_obj.device_id.device_uuid.uuid = form.device_id.data + + # Device type: + device_obj.device_type = str(form.device_type.data) + + # Device configurations: + config_rule = device_obj.device_config.config_rules.add() + config_rule.action = ConfigActionEnum.CONFIGACTION_SET + config_rule.custom.resource_key = '_connect/address' + config_rule.custom.resource_value = form.device_config_address.data + + config_rule = device_obj.device_config.config_rules.add() + config_rule.action = ConfigActionEnum.CONFIGACTION_SET + config_rule.custom.resource_key = '_connect/port' + config_rule.custom.resource_value = form.device_config_port.data + + config_rule = device_obj.device_config.config_rules.add() + config_rule.action = ConfigActionEnum.CONFIGACTION_SET + config_rule.custom.resource_key = '_connect/settings' + + try: + device_config_settings = json.loads(form.device_config_settings.data) + except: # pylint: disable=bare-except + device_config_settings = form.device_config_settings.data + + if isinstance(device_config_settings, dict): + config_rule.custom.resource_value = json.dumps(device_config_settings) + else: + config_rule.custom.resource_value = str(device_config_settings) + + # Device status: + device_obj.device_operational_status = form.operational_status.data + + # Device drivers: + if form.device_drivers_undefined.data: + device_obj.device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_UNDEFINED) + if form.device_drivers_openconfig.data: + device_obj.device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_OPENCONFIG) + if form.device_drivers_transport_api.data: + device_obj.device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_TRANSPORT_API) + if form.device_drivers_p4.data: + device_obj.device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_P4) + if form.device_drivers_ietf_network_topology.data: + device_obj.device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY) + if form.device_drivers_onf_tr_352.data: + device_obj.device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352) + if form.device_drivers_xr.data: + device_obj.device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_XR) + if form.device_drivers_bgpls.data: + device_obj.device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_BGPLS) + + try: + device_client.connect() + response: DeviceId = device_client.AddDevice(device_obj) + device_client.close() + flash(f'New device was created with ID "{response.device_uuid.uuid}".', 'success') + return redirect(url_for('device.home')) + except Exception as e: + flash(f'Problem adding the device. {e.details()}', 'danger') + + return render_template('device/add.html', form=form, + submit_text='Add New Device') + +@topology.route('detail/<path:device_uuid>', methods=['GET', 'POST']) +def detail(device_uuid: str): + request = DeviceId() + request.device_uuid.uuid = device_uuid + context_client.connect() + response = context_client.GetDevice(request) + context_client.close() + return render_template('topology/detail.html', device=response, + dde=DeviceDriverEnum, + dose=DeviceOperationalStatusEnum) + +@topology.get('<path:device_uuid>/delete') +def delete(device_uuid): + try: + + # first, check if device exists! + # request: DeviceId = DeviceId() + # request.device_uuid.uuid = device_uuid + # response: Device = client.GetDevice(request) + # TODO: finalize implementation + + request = DeviceId() + request.device_uuid.uuid = device_uuid + device_client.connect() + response = device_client.DeleteDevice(request) + device_client.close() + + flash(f'Device "{device_uuid}" deleted successfully!', 'success') + except Exception as e: + flash(f'Problem deleting device "{device_uuid}": {e.details()}', 'danger') + current_app.logger.exception(e) + return redirect(url_for('device.home')) + +@topology.route('<path:device_uuid>/addconfig', methods=['GET', 'POST']) +def addconfig(device_uuid): + form = ConfigForm() + request = DeviceId() + request.device_uuid.uuid = device_uuid + context_client.connect() + response = context_client.GetDevice(request) + context_client.close() + + if form.validate_on_submit(): + device = Device() + device.CopyFrom(response) + config_rule = device.device_config.config_rules.add() + config_rule.action = ConfigActionEnum.CONFIGACTION_SET + config_rule.custom.resource_key = form.device_key_config.data + config_rule.custom.resource_value = form.device_value_config.data + try: + device_client.connect() + response: DeviceId = device_client.ConfigureDevice(device) + device_client.close() + flash(f'New configuration was created with ID "{response.device_uuid.uuid}".', 'success') + return redirect(url_for('device.home')) + except Exception as e: + flash(f'Problem adding the device. {e.details()}', 'danger') + + return render_template('topology/addconfig.html', form=form, submit_text='Add New Configuration') + +@topology.route('updateconfig', methods=['GET', 'POST']) +def updateconfig(): + + + return render_template('topology/updateconfig.html') + + +@topology.route('<path:device_uuid>/update', methods=['GET', 'POST']) +def update(device_uuid): + form = UpdateDeviceForm() + request = DeviceId() + request.device_uuid.uuid = device_uuid + context_client.connect() + response = context_client.GetDevice(request) + context_client.close() + + # listing enum values + form.update_operational_status.choices = [] + for key, value in DeviceOperationalStatusEnum.DESCRIPTOR.values_by_name.items(): + form.update_operational_status.choices.append((DeviceOperationalStatusEnum.Value(key), key.replace('DEVICEOPERATIONALSTATUS_', ''))) + + form.update_operational_status.default = response.device_operational_status + + if form.validate_on_submit(): + device = Device() + device.CopyFrom(response) + device.device_operational_status = form.update_operational_status.data + try: + device_client.connect() + response: DeviceId = device_client.ConfigureDevice(device) + device_client.close() + flash(f'Status of device with ID "{response.device_uuid.uuid}" was updated.', 'success') + return redirect(url_for('device.home')) + except Exception as e: + flash(f'Problem updating the device. {e.details()}', 'danger') + return render_template('topology/update.html', device=response, form=form, submit_text='Update Device')