diff --git a/src/device/Dockerfile b/src/device/Dockerfile index 7ddf719a389b4a059a03c4ab845b94a349955b43..6566625527f8ceaa8de4639d558c92572c4835cb 100644 --- a/src/device/Dockerfile +++ b/src/device/Dockerfile @@ -16,7 +16,7 @@ FROM python:3.9-slim # Install dependencies RUN apt-get --yes --quiet --quiet update && \ - apt-get --yes --quiet --quiet install wget g++ && \ + apt-get --yes --quiet --quiet install wget g++ git && \ rm -rf /var/lib/apt/lists/* # Set Python to show logs as they occur diff --git a/src/device/requirements.in b/src/device/requirements.in index ec29fc7a30278625e950f3eed608281f8c7c5cb8..4e927d65cdb0c3ec6da07db461d52ec03a7bed83 100644 --- a/src/device/requirements.in +++ b/src/device/requirements.in @@ -29,7 +29,8 @@ xmltodict==0.12.0 tabulate ipaddress macaddress - +pyang +git+https://github.com/robshakir/pyangbind.git # pip's dependency resolver does not take into account installed packages. # p4runtime does not specify the version of grpcio/protobuf it needs, so it tries to install latest one # adding here again grpcio==1.47.* and protobuf==3.20.* with explicit versions to prevent collisions diff --git a/src/device/service/Tools.py b/src/device/service/Tools.py index 571e8acdab7fc243c22923a69202c89db88c8ce3..b63bac9659521f47117ef97b3ae6d97be653adcf 100644 --- a/src/device/service/Tools.py +++ b/src/device/service/Tools.py @@ -16,7 +16,7 @@ import json, logging from typing import Any, Dict, List, Tuple, Union from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME from common.method_wrappers.ServiceExceptions import InvalidArgumentException -from common.proto.context_pb2 import ConfigActionEnum, Device, DeviceConfig +from common.proto.context_pb2 import ConfigActionEnum, ConfigRule_ACL, Device, DeviceConfig from common.proto.device_pb2 import MonitoringSettings from common.proto.kpi_sample_types_pb2 import KpiSampleType from common.tools.grpc.ConfigRules import update_config_rule_custom @@ -143,6 +143,7 @@ def _raw_config_rules_to_grpc( if resource_value is None: continue resource_value = json.loads(resource_value) if isinstance(resource_value, str) else resource_value + if isinstance(resource_value, ConfigRule_ACL): resource_value = grpc_message_to_json(resource_value) resource_value = {field_name : (field_value, False) for field_name,field_value in resource_value.items()} update_config_rule_custom(device_config.config_rules, resource_key, resource_value, new_action=config_action) @@ -162,21 +163,36 @@ def populate_initial_config_rules(device_uuid : str, device_config : DeviceConfi def compute_rules_to_add_delete( device : Device, request : Device ) -> Tuple[List[Tuple[str, Any]], List[Tuple[str, Any]]]: - # convert config rules from context into a dictionary - # TODO: add support for non-custom config rules - context_config_rules = { - config_rule.custom.resource_key: config_rule.custom.resource_value - for config_rule in device.device_config.config_rules - if config_rule.WhichOneof('config_rule') == 'custom' - } - - # convert config rules from request into a list - # TODO: add support for non-custom config rules - request_config_rules = [ - (config_rule.action, config_rule.custom.resource_key, config_rule.custom.resource_value) - for config_rule in request.device_config.config_rules - if config_rule.WhichOneof('config_rule') == 'custom' - ] + # convert config rules from context into a dictionary + context_config_rules = {} + for config_rule in device.device_config.config_rules: + config_rule_kind = config_rule.WhichOneof('config_rule') + if config_rule_kind == 'custom': # process "custom" rules + context_config_rules[config_rule.custom.resource_key] = config_rule.custom.resource_value # get the resource value of the rule resource + elif config_rule_kind == 'acl': # process "custom" rules + device_uuid = config_rule.acl.endpoint_id.device_id.device_uuid.uuid # get the device name + endpoint_uuid = config_rule.acl.endpoint_id.endpoint_uuid.uuid # get the endpoint name + acl_ruleset_name = config_rule.acl.rule_set.name # get the acl name + ACL_KEY_TEMPLATE = '/device[{:s}]/endpoint[{:s}]/acl_ruleset[{:s}]' + key_or_path = ACL_KEY_TEMPLATE.format(device_uuid, endpoint_uuid, acl_ruleset_name) + context_config_rules[key_or_path] = config_rule.acl # get the resource value of the acl + + request_config_rules = [] + for config_rule in request.device_config.config_rules: + config_rule_kind = config_rule.WhichOneof('config_rule') + if config_rule_kind == 'custom': # resource management of "custom" rule + request_config_rules.append(( + config_rule.action, config_rule.custom.resource_key, config_rule.custom.resource_value + )) + elif config_rule_kind == 'acl': # resource management of "acl" rule + device_uuid = config_rule.acl.endpoint_id.device_id.device_uuid.uuid + endpoint_uuid = config_rule.acl.endpoint_id.endpoint_uuid.uuid + acl_ruleset_name = config_rule.acl.rule_set.name + ACL_KEY_TEMPLATE = '/device[{:s}]/endpoint[{:s}]/acl_ruleset[{:s}]' + key_or_path = ACL_KEY_TEMPLATE.format(device_uuid, endpoint_uuid, acl_ruleset_name) + request_config_rules.append(( + config_rule.action, key_or_path, config_rule.acl + )) resources_to_set : List[Tuple[str, Any]] = [] # key, value resources_to_delete : List[Tuple[str, Any]] = [] # key, value diff --git a/src/device/service/drivers/openconfig/OpenConfigDriver.py b/src/device/service/drivers/openconfig/OpenConfigDriver.py index ac03527529b603089c4f8233cb185f6427e0c360..0c8ef2d685edda826bad0513704dabeb61365945 100644 --- a/src/device/service/drivers/openconfig/OpenConfigDriver.py +++ b/src/device/service/drivers/openconfig/OpenConfigDriver.py @@ -190,7 +190,7 @@ def do_sampling( except: # pylint: disable=bare-except logger.exception('Error retrieving samples') -def edit_config( +def edit_config( # edit the configuration of openconfig devices netconf_handler : NetconfSessionHandler, logger : logging.Logger, resources : List[Tuple[str, Any]], delete=False, commit_per_rule=False, target='running', default_operation='merge', test_option=None, error_option=None, format='xml' # pylint: disable=redefined-builtin @@ -201,21 +201,22 @@ def edit_config( for i,resource in enumerate(resources): str_resource_name = 'resources[#{:d}]'.format(i) try: - logger.debug('[{:s}] resource = {:s}'.format(str_method, str(resource))) + # 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_message = compose_config( + str_config_messages = compose_config( # get template for configuration resource_key, resource_value, delete=delete, vendor=netconf_handler.vendor) - 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( - 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() + 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 results[i] = True except Exception as e: # pylint: disable=broad-except str_operation = 'preparing' if target == 'candidate' else ('deleting' if delete else 'setting') diff --git a/src/device/service/drivers/openconfig/templates/ACL/ACL_multivendor.py b/src/device/service/drivers/openconfig/templates/ACL/ACL_multivendor.py index c8151d67586fef62cdc38e94594ae9a59d748334..4d332e5d0a10fb10a4c68912287eda962fc581c9 100755 --- a/src/device/service/drivers/openconfig/templates/ACL/ACL_multivendor.py +++ b/src/device/service/drivers/openconfig/templates/ACL/ACL_multivendor.py @@ -1,77 +1,92 @@ -from openconfig_acl import openconfig_acl +from .openconfig_acl import openconfig_acl from pyangbind.lib.serialise import pybindIETFXMLEncoder - -def acl_set_mgmt(parameters): - Acl_name = parameters['name'] - Acl_type = parameters['type'] - ID = parameters['sequence_id'] - DEL = parameters['DEL'] +from common.tools.grpc.Tools import grpc_message_to_json +import logging +def acl_mgmt(parameters,vendor): # acl templates management + acl = [] + data = grpc_message_to_json(parameters,use_integers_for_enums=True) # acl rule parameters management + acl.append(acl_set_mgmt(data,vendor)) # acl_set template + acl.append(acl_interface(data,vendor)) # acl interface template + return acl + +def acl_set_mgmt(parameters,vendor): + type = ["ACL_UNDEFINED", "ACL_IPV4","ACL_IPV6","ACL_L2","ACL_MPLS","ACL_MIXED"] + f_action = ["UNDEFINED", "DROP","ACCEPT","REJECT"] + l_action = ["UNDEFINED", "LOG_NONE","LOG_SYSLOG"] + + Acl_data = parameters["rule_set"] + Acl_name = Acl_data['name'] + Acl_type = type[Acl_data['type']] + Acl_desc = Acl_data['description'] + Acl_entries = Acl_data['entries'] # Create an instance of the YANG model acl_instance = openconfig_acl() # Access the entry container - acl_set = acl_instance.acl.acl_sets.acl_set.add(name = Acl_name, type=Acl_type) - acl_entrie = acl_set.acl_entries.acl_entry.add(ID) + acl_set = acl_instance.acl.acl_sets.acl_set.add(name = Acl_name, type=Acl_type) + acl_set.config.name = Acl_name + acl_set.config.type = Acl_type + acl_set.config.description = Acl_desc + LOGGER = logging.getLogger(__name__) - if DEL: - # Dump the entire instance as RFC 7950 XML - acl_set = pybindIETFXMLEncoder.serialise(acl_instance) - #Delete replace - acl_set = acl_set.replace('<acl-entry>','<acl-entry xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" nc:operation="delete">') - #Generic replaces - acl_set = acl_set.replace('<openconfig-acl xmlns="http://openconfig.net/yang/acl">',"") - acl_set = acl_set.replace('<acl>','<acl xmlns="http://openconfig.net/yang/acl">') - acl_set = acl_set.replace('</openconfig-acl>','') - - else: - acl_set.config.name = Acl_name - acl_set.config.type = Acl_type + LOGGER.warning("VALORES DE ENTRIES",Acl_entries) + + for entry in Acl_entries: + ID = entry['sequence_id'] + desc = entry['description'] + match = entry['match'] + action = entry['action'] + + acl_entrie = acl_set.acl_entries.acl_entry.add(ID) acl_entrie.config.sequence_id = ID + acl_entrie.config.description= desc # Configuration per type if "L2" in Acl_type: - for variable, valor in parameters.items(): - if "source_mac" in variable and len(valor) != 0: acl_entrie.l2.config.source_mac = valor - elif "destination_mac" in variable and len(valor) != 0: acl_entrie.l2.config.destination_mac = valor + for key, value in match.items(): + if "src_address" in key and len(value) != 0: acl_entrie.l2.config.source_mac = value + elif "dst_address" in key and len(value) != 0: acl_entrie.l2.config.destination_mac = value elif "IPV4" in Acl_type: - for variable, valor in parameters.items(): - if "source_address" in variable and len(valor) != 0: acl_entrie.ipv4.config.source_address = valor - elif "destination_address" in variable and len(valor) != 0: acl_entrie.ipv4.config.destination_address = valor - elif "protocol" in variable and len(valor) != 0: acl_entrie.ipv4.config.protocol = valor - elif "hop_limit" in variable and len(valor) != 0: acl_entrie.ipv4.config.hop_limit = valor - elif "dscp" in variable and len(valor) != 0: acl_entrie.ipv4.config.dscp = valor - - for variable, valor in parameters.items(): - if "source_port" in variable and len(valor) != 0: acl_entrie.transport.config.source_port = valor - elif "destination_port" in variable and len(valor) != 0: acl_entrie.transport.config.destination_port = valor - elif "tcp_flags" in variable and len(valor) != 0: acl_entrie.transport.config.tcp_flags = valor + for key, value in match.items(): + if "src_address" in key and len(value) != 0: acl_entrie.ipv4.config.source_address = value + elif "dst_address" in key and len(value) != 0: acl_entrie.ipv4.config.destination_address = value + elif "protocol" in key : acl_entrie.ipv4.config.protocol = value + elif "hop_limit" in key : acl_entrie.ipv4.config.hop_limit = value + elif "dscp" in key : acl_entrie.ipv4.config.dscp = value + + for key, value in match.items(): + if "src_port" in key : acl_entrie.transport.config.source_port = value + elif "dst_port" in key : acl_entrie.transport.config.destination_port = value + elif "tcp_flags" in key : acl_entrie.transport.config.tcp_flags = value elif "IPV6" in Acl_type: - for variable, valor in parameters.items(): - if "source_address" in variable and len(valor) != 0: acl_entrie.ipv6.config.source_address = valor - elif "destination_address" in variable and len(valor) != 0: acl_entrie.ipv6.config.destination_address = valor - elif "protocol" in variable and len(valor) != 0: acl_entrie.ipv6.config.protocol = valor - elif "hop_limit" in variable and len(valor) != 0: acl_entrie.ipv6.config.hop_limit = valor - elif "dscp" in variable and len(valor) != 0: acl_entrie.ipv6.config.dscp = valor + for key, value in match.items(): + if "src_address" in key and len(value) != 0: acl_entrie.ipv6.config.source_address = value + elif "dst_address" in key and len(value) != 0: acl_entrie.ipv6.config.destination_address = value + elif "protocol" in key : acl_entrie.ipv6.config.protocol = value + elif "hop_limit" in key : acl_entrie.ipv6.config.hop_limit = value + elif "dscp" in key : acl_entrie.ipv6.config.dscp = value - for variable, valor in parameters.items(): - if "forwarding_action" in variable and len(valor) != 0: acl_entrie.actions.config.forwarding_action = valor - elif "log_action" in variable and len(valor) != 0: acl_entrie.actions.config.log_action = valor + for key, value in action.items(): + if "forward_action" in key : acl_entrie.actions.config.forwarding_action = f_action[value] + elif "log_action" in key : acl_entrie.actions.config.log_action = l_action[value] - # Dump the entire instance as RFC 7950 XML - acl_set = pybindIETFXMLEncoder.serialise(acl_instance) - acl_set = acl_set.replace('<openconfig-acl xmlns="http://openconfig.net/yang/acl">',"") - acl_set = acl_set.replace('<acl>','<acl xmlns="http://openconfig.net/yang/acl">') - acl_set = acl_set.replace('</openconfig-acl>','') + # Dump the entire instance as RFC 7950 XML + acl_set = pybindIETFXMLEncoder.serialise(acl_instance) + acl_set = acl_set.replace('<openconfig-acl xmlns="http://openconfig.net/yang/acl">',"") + acl_set = acl_set.replace('<acl>','<acl xmlns="http://openconfig.net/yang/acl">') + acl_set = acl_set.replace('</openconfig-acl>','') return(acl_set) -def acl_interface(parameters): - ID = parameters['id'] - DEL = parameters['DEL'] - verify = str(parameters) #Verify transforms the received parameters into a string format for later making verifications and modifications +def acl_interface(parameters,vendor): + type = ["ACL_UNDEFINED", "ACL_IPV4","ACL_IPV6","ACL_L2","ACL_MPLS","ACL_MIXED"] + ID = parameters['endpoint_id']['endpoint_uuid']['uuid'] + Acl_data = parameters["rule_set"] + Acl_name = Acl_data['name'] + Acl_type = type[Acl_data['type']] # Create an instance of the YANG model acl_instance = openconfig_acl() @@ -79,48 +94,24 @@ def acl_interface(parameters): # Access the entry container interface = acl_instance.acl.interfaces.interface.add(id = ID) - if DEL: - acl_set = pybindIETFXMLEncoder.serialise(acl_instance) - acl_set = acl_set.replace('<interface>','<interface xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" nc:operation="delete">') + #Config - Interface + interface.config.id = ID + + #If the Interface parameter is defined [OPTIONAL-PARAMETER] + interface.interface_ref.config.interface = ID # Interface parameter + + #TODO: add subinterface management + if vendor == "ADVA" : interface.interface_ref.config.subinterface = '0' # Subinterface parameter + elif vendor == "Juniper": interface.interface_ref.config.subinterface = '0' + else: interface.interface_ref.config.subinterface = Acl_data['subinterface'] + # Configuration ingress type + ingress= interface.ingress_acl_sets.ingress_acl_set.add(set_name = Acl_name, type = Acl_type) + ingress.config.set_name = Acl_name + ingress.config.type = Acl_type - # Dump the entire instance as RFC 7950 XML - acl_set = acl_set.replace('<openconfig-acl xmlns="http://openconfig.net/yang/acl">'," ") - acl_set = acl_set.replace('<acl>','<acl xmlns="http://openconfig.net/yang/acl">') - acl_set = acl_set.replace('</openconfig-acl>','') - else: - #Config - Interface - interface.config.id = ID - #If the Interface parameter is defined [OPTIONAL-PARAMETER] - if verify.find('interface')>0: - Interface = parameters['interface'] - if len(Interface) != 0: interface.interface_ref.config.interface = Interface #If interface parameter has a value - - #If the Subinterface parameter is defined [OPTIONAL-PARAMETER] - if verify.find('subinterface')>0: - Subinterface = parameters['subinterface'] - if len(Interface) != 0: interface.interface_ref.config.subinterface = Subinterface #If subinterface parameter has a value - - # Configuration per type - if verify.find('set_name_ingress')>0: #If set_name_ingress is defined - Ingress_name = parameters['set_name_ingress'] - if len(Ingress_name) != 0: - Ingress_type = parameters['type_ingress'] - ingress= interface.ingress_acl_sets.ingress_acl_set.add(set_name = Ingress_name, type = Ingress_type) - ingress.config.set_name = Ingress_name - ingress.config.type = Ingress_type - - if verify.find('set_name_egress')>0: #If set_name_egress is defined - Egress_name = parameters['set_name_egress'] - if len(Egress_name) != 0: - Egress_name = parameters['set_name_egress'] - Egress_type = parameters['type_egress'] - egress= interface.egress_acl_sets.egress_acl_set.add(set_name = Egress_name, type = Egress_type) - egress.config.set_name = Egress_name - egress.config.type = Egress_type - - # Dump the entire instance as RFC 7950 XML - acl_set = pybindIETFXMLEncoder.serialise(acl_instance) - acl_set = acl_set.replace('<openconfig-acl xmlns="http://openconfig.net/yang/acl">',"") - acl_set = acl_set.replace('<acl>','<acl xmlns="http://openconfig.net/yang/acl">') - acl_set = acl_set.replace('</openconfig-acl>','') + # Dump the entire instance as RFC 7950 XML + acl_set = pybindIETFXMLEncoder.serialise(acl_instance) + acl_set = acl_set.replace('<openconfig-acl xmlns="http://openconfig.net/yang/acl">',"") + acl_set = acl_set.replace('<acl>','<acl xmlns="http://openconfig.net/yang/acl">') + acl_set = acl_set.replace('</openconfig-acl>','') return(acl_set) diff --git a/src/device/service/drivers/openconfig/templates/Tools.py b/src/device/service/drivers/openconfig/templates/Tools.py index d71c00742ebb8f224ad8308f657cef87000b88c5..a56fe3ea5e936d78a85fcd36d5a49094e112a37b 100644 --- a/src/device/service/drivers/openconfig/templates/Tools.py +++ b/src/device/service/drivers/openconfig/templates/Tools.py @@ -15,10 +15,10 @@ import json import lxml.etree as ET from typing import Collection, Dict, Any -from ACL.ACL_multivendor import acl_set_mgmt, acl_interface -from VPN.Network_instance_multivendor import create_network_instance, associate_virtual_circuit, associate_RP_to_NI, add_protocol_NI, create_table_conns, associate_If_to_NI -from VPN.Interfaces_multivendor import create_If_SubIf -from VPN.Routing_policy import create_rp_def, create_rp_statement +from .ACL.ACL_multivendor import acl_mgmt +from .VPN.Network_instance_multivendor import create_network_instance, associate_virtual_circuit, associate_RP_to_NI, add_protocol_NI, create_table_conns, associate_If_to_NI +from .VPN.Interfaces_multivendor import create_If_SubIf +from .VPN.Routing_policy import create_rp_def, create_rp_statement def add_value_from_tag(target : Dict, field_name: str, field_value : ET.Element, cast=None) -> None: if field_value is None or field_value.text is None: return @@ -30,43 +30,41 @@ def add_value_from_collection(target : Dict, field_name: str, field_value : Coll if field_value is None or len(field_value) == 0: return target[field_name] = field_value -def generate_template(resource_key: str, resource_value: str, delete: bool) -> str: - data: Dict[str, Any] = json.loads(resource_value) - data['DEL'] = delete +def generate_templates(resource_key: str, resource_value: str, delete: bool,vendor:str) -> str: # template management to be configured - list_resource_key = resource_key.split("/") - - if "network_instance" in list_resource_key[1]: - if "connection_point" in list_resource_key: - result_template = associate_virtual_circuit(data) - elif "inter_instance_policies" in list_resource_key: - result_template = associate_RP_to_NI(data) - elif "protocolos" in list_resource_key: - result_template = add_protocol_NI(data) - elif "table_connections" in list_resource_key: - result_template = create_table_conns(data) - elif "interface" in list_resource_key: - result_template = associate_If_to_NI(data) + result_templates = [] + list_resource_key = resource_key.split("/") # the rule resource key management + if "network_instance" in list_resource_key[1]: # network instance rules management + data: Dict[str, Any] = json.loads(resource_value) + data['DEL'] = delete + if "connection_point" in resource_key: + result_templates.append(associate_virtual_circuit(data)) + elif "inter_instance_policies" in resource_key: + result_templates.append(associate_RP_to_NI(data)) + elif "protocols" in resource_key: + result_templates.append(add_protocol_NI(data)) + elif "table_connections" in resource_key: + result_templates.append(create_table_conns(data)) + elif "interface" in resource_key: + result_templates.append(associate_If_to_NI(data)) else: - result_template = create_network_instance(data) - - elif "interface" in list_resource_key[1]: - if "subinterface" in list_resource_key: - result_template = create_If_SubIf(data) + result_templates.append(create_network_instance(data)) - elif "acl" in list_resource_key[1]: - if "acl_set" in list_resource_key: - result_template = acl_set_mgmt(data) - else: - result_template = acl_interface(data) + elif "interface" in list_resource_key[1]: # interface rules management + data: Dict[str, Any] = json.loads(resource_value) + data['DEL'] = delete + if "subinterface" in resource_key: + result_templates.append(create_If_SubIf(data)) - elif "routing_policy" in list_resource_key[1]: - if "bgp_defined_set" in list_resource_key: - result_template = create_rp_def(data) + elif "routing_policy" in list_resource_key[1]: # routing policy rules management + data: Dict[str, Any] = json.loads(resource_value) + data['DEL'] = delete + if "bgp_defined_set" in resource_key: + result_templates.append(create_rp_def(data)) else: - result_template = create_rp_statement(data) - + result_templates.append(create_rp_statement(data)) else: - result_template = "" + if "acl_ruleset" in resource_key: # acl rules management + result_templates.extend(acl_mgmt(resource_value,vendor)) - return result_template \ No newline at end of file + return result_templates \ No newline at end of file diff --git a/src/device/service/drivers/openconfig/templates/VPN/Interfaces_multivendor.py b/src/device/service/drivers/openconfig/templates/VPN/Interfaces_multivendor.py index 201c691249712e831a96c27da3062b549d676ed8..6cfe525d77ec28a5714248ef5cf481a60f460d9b 100644 --- a/src/device/service/drivers/openconfig/templates/VPN/Interfaces_multivendor.py +++ b/src/device/service/drivers/openconfig/templates/VPN/Interfaces_multivendor.py @@ -1,4 +1,4 @@ -from openconfig_interfaces import openconfig_interfaces +from .openconfig_interfaces import openconfig_interfaces from pyangbind.lib.serialise import pybindIETFXMLEncoder def set_vlan(OptionalParams): #[L2/L3] Sets a VLANID and a VENDOR that will be requested for executing the following methods @@ -11,32 +11,28 @@ def set_vlan(OptionalParams): #[L2/L3] Sets a #If the VlanID parameter is defined [OPTIONAL-PARAMETER] if verify.find('vlan_id')>0: VlanID = OptionalParams['vlan_id'] - if VlanID == 0 and "ADVA" in Vendor: - vlan = '</description>\n \t <untagged-allowed xmlns="http://www.advaoptical.com/cim/adva-dnos-oc-interfaces">true</untagged-allowed></config>' - elif VlanID != 0: - vlan = '</description>\n </config>\n\t <vlan xmlns="http://openconfig.net/yang/vlan"> \n\t <match> \n\t <single-tagged> \n \t\t<config>\n \t\t <vlan-id>'+str(VlanID)+'</vlan-id> \n \t\t</config> \n \t </single-tagged> \n \t </match> \n \t </vlan>' - else: - vlan = '</description>\n </config>' - else: - vlan = '</description>\n </config>' + if VlanID == 0 and "ADVA" in Vendor: vlan = ' <untagged-allowed xmlns="http://www.advaoptical.com/cim/adva-dnos-oc-interfaces">true</untagged-allowed></config> \n </config>\n </subinterface>' + elif VlanID != 0: vlan = '</config>\n <vlan xmlns="http://openconfig.net/yang/vlan"> \n\t <match> \n\t <single-tagged> \n \t\t<config>\n \t\t <vlan-id>'+str(VlanID)+'</vlan-id> \n \t\t</config> \n \t </single-tagged> \n \t </match> \n \t </vlan> \n </subinterface>' + else: vlan = '</subinterface>\n </config>' + else: vlan = '</subinterface>\n </config>' return vlan def set_ip(OptionalParams): #[L3] Sets a IPAddress that will be requested for executing the following L3VPN methods - verify = str(OptionalParams) #Verify transforms the received parameters into a string format for later making verifications and modifications + verify = str(OptionalParams) # Verify transforms the received parameters into a string format for later making verifications and modifications #If the Address_ip parameter is defined [OPTIONAL-PARAMETER] if verify.find('address_ip')>0: - IP = OptionalParams['address_ip'] - Prefix = OptionalParams['address_prefix'] + IP = OptionalParams['address_ip'] + Prefix = OptionalParams['address_prefix'] address = ' <ipv4 xmlns="http://openconfig.net/yang/interfaces/ip"> \n\t <addresses> \n\t <address> \n \t\t<ip>'+IP+'</ip> \n \t\t<config>\n \t\t <ip>'+IP+'</ip> \n \t\t <prefix-length>'+str(Prefix)+'</prefix-length> \n \t\t</config> \n \t </address> \n \t </addresses> \n \t </ipv4> \n \t</subinterface>' else: address ='</subinterface>' return address -def create_If_SubIf(parameters): #[L2/L3] Creates a Interface with a Subinterface as described in /interface[{:s}]/subinterface[{:d}] +def create_If_SubIf(parameters): # [L2/L3] Creates a Interface with a Subinterface as described in /interface[{:s}]/subinterface[{:d}] Interface_name = parameters['name'] - DEL = parameters['DEL'] #If the parameters DEL is set to "TRUE" that will mean that is for making a DELETE, ELSE is for creating - verify = str(parameters) #Verify transforms the received parameters into a string format for later making verifications and modifications + DEL = parameters['DEL'] # If the parameters DEL is set to "TRUE" that will mean that is for making a DELETE, ELSE is for creating + verify = str(parameters) # Verify transforms the received parameters into a string format for later making verifications and modifications #Create an instance of the YANG model InterfaceInstance = openconfig_interfaces() @@ -66,7 +62,7 @@ def create_If_SubIf(parameters): #[L2/L3] Creates InterfaceInstance_set.config.enabled = True #SubIntefaces-Config - SubInterfaceInstance = InterfaceInstance_set.subinterfaces.subinterface.add(index = SubInterface_Index) + SubInterfaceInstance = InterfaceInstance_set.subinterfaces.subinterface.add(index = SubInterface_Index) SubInterfaceInstance.config.index = SubInterface_Index #If the description parameter is defined [OPTIONAL-PARAMETER] @@ -85,7 +81,7 @@ def create_If_SubIf(parameters): #[L2/L3] Creates #Replaces for adding the Interface Type InterfaceInstance_set = InterfaceInstance_set.replace('</config>\n <subinterfaces>',' <type xmlns:ianaift="urn:ietf:params:xml:ns:yang:iana-if-type">ianaift:'+Interface_type+'</type>\n </config>\n <subinterfaces>') vlan = set_vlan(parameters) - InterfaceInstance_set = InterfaceInstance_set.replace('</description>\n </config>',vlan) + InterfaceInstance_set = InterfaceInstance_set.replace('</config>\n </subinterface>',vlan) if "l3ipvlan" in Interface_type: ip = set_ip(parameters) @@ -97,4 +93,3 @@ def create_If_SubIf(parameters): #[L2/L3] Creates InterfaceInstance_set = InterfaceInstance_set.replace('</openconfig-interfaces>','') return (InterfaceInstance_set) - diff --git a/src/device/service/drivers/openconfig/templates/VPN/Network_instance_multivendor.py b/src/device/service/drivers/openconfig/templates/VPN/Network_instance_multivendor.py index 22c365f7c0b468c7f0e44b2c57e3e04587d9eac4..2b85ba60c3d27bb351c462dff1b30ad41d5cdf05 100644 --- a/src/device/service/drivers/openconfig/templates/VPN/Network_instance_multivendor.py +++ b/src/device/service/drivers/openconfig/templates/VPN/Network_instance_multivendor.py @@ -1,4 +1,4 @@ -from openconfig_network_instance import openconfig_network_instance +from .openconfig_network_instance import openconfig_network_instance from pyangbind.lib.serialise import pybindIETFXMLEncoder def create_network_instance(parameters): #[L2/L3] Creates a Network Instance as described in: /network_instance[{:s}] @@ -51,7 +51,7 @@ def create_network_instance(parameters): #[L2/L3] Creates a Ne #Dump the entire instance as RFC 750 XML NetInstance_set = pybindIETFXMLEncoder.serialise(Network_Instance) #Specific Replace [Addition of the enabled and MTU variables] - NetInstance_set = NetInstance_set.replace('</name>','</name>\n <mtu>'+str(NetInstance_MTU)+'</mtu>\n <enabled>true</enabled>') + NetInstance_set = NetInstance_set.replace('</type>','</type>\n <mtu>'+str(NetInstance_MTU)+'</mtu>\n <enabled>true</enabled>') #Configuration for L3VRF elif "L3VRF" in NetInstance_type: diff --git a/src/device/service/drivers/openconfig/templates/VPN/Routing_policy.py b/src/device/service/drivers/openconfig/templates/VPN/Routing_policy.py index 21d6f1560c5365464409124e4c17a11811cff178..895385a65a3f71cd1e3daa42b56d32fbd7ceec0b 100644 --- a/src/device/service/drivers/openconfig/templates/VPN/Routing_policy.py +++ b/src/device/service/drivers/openconfig/templates/VPN/Routing_policy.py @@ -1,4 +1,4 @@ -from openconfig_routing_policy import openconfig_routing_policy +from .openconfig_routing_policy import openconfig_routing_policy from pyangbind.lib.serialise import pybindIETFXMLEncoder def create_rp_statement(parameters): #[L3] Creates a Routing Policy Statement diff --git a/src/device/service/drivers/openconfig/templates/__init__.py b/src/device/service/drivers/openconfig/templates/__init__.py index fc3b8db150f8b2ac26a1b040af48d15e440bb578..039cf5b5549061a8c6a8e785e47001ac0e639363 100644 --- a/src/device/service/drivers/openconfig/templates/__init__.py +++ b/src/device/service/drivers/openconfig/templates/__init__.py @@ -15,7 +15,7 @@ import json, logging, lxml.etree as ET, re from typing import Any, Dict, Optional from jinja2 import Environment, PackageLoader, select_autoescape -from Tools import generate_template +from .Tools import generate_templates from device.service.driver_api._Driver import ( RESOURCE_ENDPOINTS, RESOURCE_INTERFACES, RESOURCE_NETWORK_INSTANCES, RESOURCE_ROUTING_POLICIES, RESOURCE_ACL) from .EndPoints import parse as parse_endpoints @@ -78,9 +78,11 @@ def parse(resource_key : str, xml_data : ET.Element): if parser is None: return [(resource_key, xml_data)] return parser(xml_data) -def compose_config( +def compose_config( # template generation resource_key : str, resource_value : str, delete : bool = False, vendor : Optional[str] = None ) -> str: - template = (generate_template(resource_key, resource_value, delete)) - - return '<config>{:s}</config>'.format(template) + templates = (generate_templates(resource_key, resource_value, delete,vendor)) + return [ + '<config>{:s}</config>'.format(template) # format correction + for template in templates + ] diff --git a/src/service/service/service_handler_api/SettingsHandler.py b/src/service/service/service_handler_api/SettingsHandler.py index 85dd3a12851bf8c5ba697180fe00d0467e7a76b5..255e60b061373e4fedd42f90eadb2e64a67f7d55 100644 --- a/src/service/service/service_handler_api/SettingsHandler.py +++ b/src/service/service/service_handler_api/SettingsHandler.py @@ -16,8 +16,8 @@ import anytree, json, logging from typing import Any, List, Optional, Tuple, Union from common.proto.context_pb2 import ConfigActionEnum, ConfigRule, Device, EndPoint, ServiceConfig from common.tools.grpc.Tools import grpc_message_to_json, grpc_message_to_json_string -from service.service.service_handler_api.AnyTreeTools import TreeNode, delete_subnode, get_subnode, set_subnode_value - +from service.service.service_handler_api.Tools import extract_endpoint_index, extract_index +from service.service.service_handler_api.AnyTreeTools import TreeNode, delete_subnode, get_subnode, set_subnode_value, dump_subtree LOGGER = logging.getLogger(__name__) class SettingsHandler: @@ -41,9 +41,10 @@ class SettingsHandler: elif kind == 'acl': device_uuid = config_rule.acl.endpoint_id.device_id.device_uuid.uuid endpoint_uuid = config_rule.acl.endpoint_id.endpoint_uuid.uuid + endpoint_name, endpoint_index = extract_endpoint_index(endpoint_uuid) acl_ruleset_name = config_rule.acl.rule_set.name - ACL_KEY_TEMPLATE = '/device[{:s}]/endpoint[{:s}]/acl_ruleset[{:s}]' - key_or_path = ACL_KEY_TEMPLATE.format(device_uuid, endpoint_uuid, acl_ruleset_name) + ACL_KEY_TEMPLATE = '/device[{:s}]/endpoint[{:s}]/index[{:d}]/acl_ruleset[{:s}]' + key_or_path = ACL_KEY_TEMPLATE.format(device_uuid, endpoint_name,endpoint_index, acl_ruleset_name) value = grpc_message_to_json(config_rule.acl) else: MSG = 'Unsupported Kind({:s}) in ConfigRule({:s})' @@ -66,6 +67,28 @@ class SettingsHandler: if endpoint_settings is not None: return endpoint_settings return None + + def get_endpoint_acls(self, device : Device, endpoint : EndPoint) -> List [Tuple]: + endpoint_name = endpoint.name + device_keys = device.device_id.device_uuid.uuid, device.name + endpoint_keys = endpoint.endpoint_id.endpoint_uuid.uuid, endpoint.name + acl_rules = [] + for device_key in device_keys: + for endpoint_key in endpoint_keys: + endpoint_settings_uri = '/device[{:s}]/endpoint[{:s}]'.format(device_key, endpoint_key) + endpoint_settings = self.get(endpoint_settings_uri) + if endpoint_settings is None: continue + endpoint_name, endpoint_index = extract_endpoint_index(endpoint_name) + ACL_RULE_PREFIX = '/device[{:s}]/endpoint[{:s}]/'.format(device_key, endpoint_name) + + results = dump_subtree(endpoint_settings) + for res_key, res_value in results: + if not res_key.startswith(ACL_RULE_PREFIX): continue + if not "acl_ruleset" in res_key: continue + acl_index = extract_index(res_value) + if not 'index[{:d}]'.format(acl_index) in res_key: continue + acl_rules.append((res_key, res_value)) + return acl_rules def set(self, key_or_path : Union[str, List[str]], value : Any) -> None: set_subnode_value(self.__resolver, self.__config, key_or_path, value) diff --git a/src/service/service/service_handler_api/Tools.py b/src/service/service/service_handler_api/Tools.py index 222cd8968cd490d488dbbfc0082b6c3d4f5c1035..787b0f499a2d4b3ad76bfe4b7d41f072bbe6c50c 100644 --- a/src/service/service/service_handler_api/Tools.py +++ b/src/service/service/service_handler_api/Tools.py @@ -11,12 +11,12 @@ # 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 functools +import functools, re from typing import Any, List, Optional, Tuple, Union from common.method_wrappers.ServiceExceptions import NotFoundException from common.proto.context_pb2 import Device, EndPoint from common.type_checkers.Checkers import chk_length, chk_type +from common.tools.grpc.Tools import grpc_message_to_json ACTION_MSG_SET_ENDPOINT = 'Set EndPoint(device_uuid={:s}, endpoint_uuid={:s}, topology_uuid={:s})' ACTION_MSG_DELETE_ENDPOINT = 'Delete EndPoint(device_uuid={:s}, endpoint_uuid={:s}, topology_uuid={:s})' @@ -58,3 +58,18 @@ def get_device_endpoint_uuids(endpoint : Tuple[str, str, Optional[str]]) -> Tupl chk_length('endpoint', endpoint, min_length=2, max_length=3) device_uuid, endpoint_uuid = endpoint[0:2] # ignore topology_uuid by now return device_uuid, endpoint_uuid + +def extract_endpoint_index(endpoint_name : str, default_index=0) -> Tuple[str, int]: + RE_PATTERN = '^(eth\-[0-9]+(?:\/[0-9]+)*)(?:\.([0-9]+))?$' + m = re.match(RE_PATTERN, endpoint_name) + if m is None: return endpoint_name, default_index + endpoint_name, index = m.groups() + if index is not None: index = int(index) + return endpoint_name, index + +def extract_index(res_value : str) -> int: + acl_value = grpc_message_to_json(res_value,use_integers_for_enums=True) + endpoint = acl_value.split("'endpoint_uuid': {'uuid': '") + endpoint = endpoint[1].split("'}") + _ , index = extract_endpoint_index(endpoint[0]) + return index diff --git a/src/service/service/service_handlers/l2nm_openconfig/ConfigRules.py b/src/service/service/service_handlers/l2nm_openconfig/ConfigRules.py index 07e78d73631342d101d77697098e83961c7dcf26..70ee430f0d82fe520b4f6b8e77a519c990d71e76 100644 --- a/src/service/service/service_handlers/l2nm_openconfig/ConfigRules.py +++ b/src/service/service/service_handlers/l2nm_openconfig/ConfigRules.py @@ -12,13 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Dict, List +from typing import Dict, List, Tuple from common.tools.object_factory.ConfigRule import json_config_rule_delete, json_config_rule_set from service.service.service_handler_api.AnyTreeTools import TreeNode def setup_config_rules( service_uuid : str, connection_uuid : str, device_uuid : str, endpoint_uuid : str, endpoint_name : str, - service_settings : TreeNode, endpoint_settings : TreeNode + service_settings : TreeNode, endpoint_settings : TreeNode, endpoint_acls : List [Tuple] ) -> List[Dict]: if service_settings is None: return [] @@ -30,7 +30,7 @@ def setup_config_rules( json_settings : Dict = {} if service_settings is None else service_settings.value json_endpoint_settings : Dict = {} if endpoint_settings is None else endpoint_settings.value - #mtu = json_settings.get('mtu', 1450 ) # 1512 + mtu = json_settings.get('mtu', 1450 ) # 1512 #address_families = json_settings.get('address_families', [] ) # ['IPV4'] #bgp_as = json_settings.get('bgp_as', 0 ) # 65000 #bgp_route_target = json_settings.get('bgp_route_target', '0:0') # 65000:333 @@ -79,6 +79,10 @@ def setup_config_rules( 'remote_system': remote_router }), ] + for res_key, res_value in endpoint_acls: + json_config_rules.append( + {'action': 1, 'acl': res_value} + ) return json_config_rules def teardown_config_rules( diff --git a/src/service/service/service_handlers/l2nm_openconfig/L2NMOpenConfigServiceHandler.py b/src/service/service/service_handlers/l2nm_openconfig/L2NMOpenConfigServiceHandler.py index d511c8947ecb43052fd154ab3ce3293a468b4263..467f4e4c842ecdfb5f9e3410c47d4334ee1cb9bb 100644 --- a/src/service/service/service_handlers/l2nm_openconfig/L2NMOpenConfigServiceHandler.py +++ b/src/service/service/service_handlers/l2nm_openconfig/L2NMOpenConfigServiceHandler.py @@ -69,11 +69,12 @@ class L2NMOpenConfigServiceHandler(_ServiceHandler): device_obj = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid))) endpoint_obj = get_endpoint_matching(device_obj, endpoint_uuid) endpoint_settings = self.__settings_handler.get_endpoint_settings(device_obj, endpoint_obj) + endpoint_acls = self.__settings_handler.get_endpoint_acls(device_obj, endpoint_obj) endpoint_name = endpoint_obj.name json_config_rules = setup_config_rules( service_uuid, connection_uuid, device_uuid, endpoint_uuid, endpoint_name, - settings, endpoint_settings) + settings, endpoint_settings, endpoint_acls) if len(json_config_rules) > 0: del device_obj.device_config.config_rules[:] diff --git a/src/service/service/service_handlers/l3nm_openconfig/ConfigRules.py b/src/service/service/service_handlers/l3nm_openconfig/ConfigRules.py index ef93dcdda8145cab15ff21c24b6318e9eb00e098..dd48fe7351e03328ddad8fdb29abf0fae09bc7b7 100644 --- a/src/service/service/service_handlers/l3nm_openconfig/ConfigRules.py +++ b/src/service/service/service_handlers/l3nm_openconfig/ConfigRules.py @@ -12,13 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Dict, List +from typing import Dict, List, Tuple from common.tools.object_factory.ConfigRule import json_config_rule_delete, json_config_rule_set from service.service.service_handler_api.AnyTreeTools import TreeNode def setup_config_rules( service_uuid : str, connection_uuid : str, device_uuid : str, endpoint_uuid : str, endpoint_name : str, - service_settings : TreeNode, endpoint_settings : TreeNode + service_settings : TreeNode, endpoint_settings : TreeNode, endpoint_acls : List [Tuple] ) -> List[Dict]: if service_settings is None: return [] @@ -177,6 +177,10 @@ def setup_config_rules( }), ] + for res_key, res_value in endpoint_acls: + json_config_rules.append( + {'action': 1, 'acl': res_value} + ) return json_config_rules def teardown_config_rules( diff --git a/src/service/service/service_handlers/l3nm_openconfig/L3NMOpenConfigServiceHandler.py b/src/service/service/service_handlers/l3nm_openconfig/L3NMOpenConfigServiceHandler.py index b2639ddad58e4c453f1b1e2dc87fce8861ad79a2..eae2f3cbec0cad805c44b338bfbac3fb06cb84b3 100644 --- a/src/service/service/service_handlers/l3nm_openconfig/L3NMOpenConfigServiceHandler.py +++ b/src/service/service/service_handlers/l3nm_openconfig/L3NMOpenConfigServiceHandler.py @@ -69,11 +69,12 @@ class L3NMOpenConfigServiceHandler(_ServiceHandler): device_obj = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid))) endpoint_obj = get_endpoint_matching(device_obj, endpoint_uuid) endpoint_settings = self.__settings_handler.get_endpoint_settings(device_obj, endpoint_obj) + endpoint_acls = self.__settings_handler.get_endpoint_acls(device_obj, endpoint_obj) endpoint_name = endpoint_obj.name json_config_rules = setup_config_rules( service_uuid, connection_uuid, device_uuid, endpoint_uuid, endpoint_name, - settings, endpoint_settings) + settings, endpoint_settings, endpoint_acls) if len(json_config_rules) > 0: del device_obj.device_config.config_rules[:] diff --git a/src/webui/requirements.in b/src/webui/requirements.in index b4a158d394bc2de67af1e0e99e922df08104f736..d9de647f1225f0f2a58bf4322ae5ca4b19f5329c 100644 --- a/src/webui/requirements.in +++ b/src/webui/requirements.in @@ -17,3 +17,4 @@ Flask-WTF==1.0.0 flask-healthz==0.0.3 flask-unittest==0.1.2 lorem-text==2.1 +APScheduler==3.8.1 diff --git a/src/webui/service/service/routes.py b/src/webui/service/service/routes.py index defbe2cb003cc97830d6ec24db01bf8734a7f530..a58d08c46cfc805caf6d270bbea92ddbc67ee235 100644 --- a/src/webui/service/service/routes.py +++ b/src/webui/service/service/routes.py @@ -13,30 +13,51 @@ # limitations under the License. import grpc -from flask import current_app, redirect, render_template, Blueprint, flash, session, url_for +import base64, json, logging #, re +from flask import current_app, redirect, render_template, Blueprint, flash, session, url_for, request from common.proto.context_pb2 import ( - IsolationLevelEnum, Service, ServiceId, ServiceTypeEnum, ServiceStatusEnum, Connection) + IsolationLevelEnum, Device, Service, ServiceId, ServiceTypeEnum, ServiceStatusEnum, Connection, DeviceList, Empty) from common.tools.context_queries.Context import get_context from common.tools.context_queries.EndPoint import get_endpoint_names from common.tools.context_queries.Service import get_service +from common.tools.context_queries.Topology import get_topology +from wtforms.validators import ValidationError from context.client.ContextClient import ContextClient from service.client.ServiceClient import ServiceClient +from common.tools.object_factory.Service import ( + json_service_l2nm_planned, json_service_l3nm_planned) +from webui.service import json_to_list +from common.tools.object_factory.Constraint import ( + json_constraint_sla_availability, json_constraint_sla_capacity, json_constraint_sla_isolation, + json_constraint_sla_latency) +from common.tools.descriptor.Loader import DescriptorLoader, compose_notifications +from common.tools.object_factory.ConfigRule import json_config_rule_set +from common.tools.object_factory.Device import json_device_id +from common.tools.object_factory.EndPoint import json_endpoint_id +from webui.service.service.forms import AddServiceForm_1, AddServiceForm_ACL_L2, AddServiceForm_ACL_IPV4, AddServiceForm_ACL_IPV6, AddServiceForm_L2VPN, AddServiceForm_L3VPN +from common.tools.grpc.Tools import grpc_message_to_json -service = Blueprint('service', __name__, url_prefix='/service') +LOGGER = logging.getLogger(__name__) +service = Blueprint('service', __name__, url_prefix='/service') #Define a flask Blueprint called "service" behind the url "/service" -context_client = ContextClient() -service_client = ServiceClient() +context_client = ContextClient() #Create an instance of ContextClient class as defined in /src/service/client/ContextClient.py +service_client = ServiceClient() #Create an instance of ServiceClient class as defined in /src/service/client/ServiceClient.py -@service.get('/') +type = ["ACL_UNDEFINED", "ACL_IPV4","ACL_IPV6","ACL_L2","ACL_MPLS","ACL_MIXED"] +f_action = ["UNDEFINED", "DROP","ACCEPT","REJECT"] +l_action = ["UNDEFINED", "LOG_NONE","LOG_SYSLOG"] + +@service.get('/') #Route for the homepage of the created "service" blueprint def home(): - if 'context_uuid' not in session or 'topology_uuid' not in session: - flash("Please select a context!", "warning") + if 'context_uuid' not in session or 'topology_uuid' not in session: #Check if context_uuid and topology_uuid are defined in the current seession + flash("Please select a context!", "warning") #If they are not defined in the current session [Return to main page - STOP] return redirect(url_for("main.home")) - context_uuid = session['context_uuid'] + context_uuid = session['context_uuid'] #If both are they are present in the current session, the value of the "context_uuid" + #is retrieved from the session and stored in the local variable "context_uuid" - context_client.connect() + context_client.connect() #Creates a connection, as specified in the connect method of the ContextClient() class - context_obj = get_context(context_client, context_uuid, rw_copy=False) + context_obj = get_context(context_client, context_uuid, rw_copy=False) #Using the get_context function, defined in /src/common/ if context_obj is None: flash('Context({:s}) not found'.format(str(context_uuid)), 'danger') services, device_names, endpoints_data = list(), list(), list() @@ -59,15 +80,7 @@ def home(): 'service/home.html', services=services, device_names=device_names, endpoints_data=endpoints_data, ste=ServiceTypeEnum, sse=ServiceStatusEnum) - -@service.route('add', methods=['GET', 'POST']) -def add(): - flash('Add service route called', 'danger') - raise NotImplementedError() - #return render_template('service/home.html') - - -@service.get('<path:service_uuid>/detail') +@service.get('<path:service_uuid>/detail') #Route for display all the details about a specific service def detail(service_uuid: str): if 'context_uuid' not in session or 'topology_uuid' not in session: flash("Please select a context!", "warning") @@ -76,9 +89,9 @@ def detail(service_uuid: str): try: context_client.connect() - endpoint_ids = list() service_obj = get_service(context_client, service_uuid, rw_copy=False) + if service_obj is None: flash('Context({:s})/Service({:s}) not found'.format(str(context_uuid), str(service_uuid)), 'danger') service_obj = Service() @@ -94,17 +107,15 @@ def detail(service_uuid: str): device_names, endpoints_data = dict(), dict() context_client.close() - return render_template( 'service/detail.html', service=service_obj, connections=connections, device_names=device_names, - endpoints_data=endpoints_data, ste=ServiceTypeEnum, sse=ServiceStatusEnum, ile=IsolationLevelEnum) + endpoints_data=endpoints_data, ste=ServiceTypeEnum, sse=ServiceStatusEnum, ile=IsolationLevelEnum, type = type, f_action = f_action, l_action = l_action) except Exception as e: flash('The system encountered an error and cannot show the details of this service.', 'warning') current_app.logger.exception(e) return redirect(url_for('service.home')) - -@service.get('<path:service_uuid>/delete') +@service.get('<path:service_uuid>/delete') #Route for deleting a specific service def delete(service_uuid: str): if 'context_uuid' not in session or 'topology_uuid' not in session: flash("Please select a context!", "warning") @@ -124,3 +135,167 @@ def delete(service_uuid: str): flash('Problem deleting service "{:s}": {:s}'.format(service_uuid, str(e.details())), 'danger') current_app.logger.exception(e) return redirect(url_for('service.home')) + +#Added routes for creating a new Service +@service.route('add', methods=['GET', 'POST']) #Route for adding a new service [Selecting the type of operation to be performed - First Form] +def add(): + form_1 = AddServiceForm_1() + if form_1.validate_on_submit(): + #store the selected service type in session + #session['service_type'] = form_1.service_type.data + #redirect to the same page to display the second form + if form_1.service_type.data == 'ACL_L2': + return redirect(url_for('service.add_configure_ACL_L2')) + elif form_1.service_type.data == 'ACL_IPV4': + return redirect(url_for('service.add_configure_ACL_IPV4')) + elif form_1.service_type.data == 'ACL_IPV6': + return redirect(url_for('service.add_configure_ACL_IPV6')) + elif form_1.service_type.data == 'L2VPN': + return redirect(url_for('service.add_configure_L2VPN')) + elif form_1.service_type.data == 'L3VPN': + return redirect(url_for('service.add_configure_L3VPN')) + # display the first form + return render_template('service/add.html', form_1=form_1, submit_text='Continue to configuraton') + +@service.route('add/configure/ACL_L2', methods=['GET', 'POST']) #Route for adding a new ACL_L2 service [Setting the parameters for defining the service] +def add_configure_ACL_L2(): + form_acl = AddServiceForm_ACL_L2() + if form_acl.validate_on_submit(): + flash(f'New configuration was created', 'success') + return redirect(url_for('service.home')) + print(form_acl.errors) + return render_template('service/configure_ACL_L2.html', form_acl=form_acl, submit_text='Add New Service') + +@service.route('add/configure/ACL_IPV4', methods=['GET', 'POST']) #Route for adding a new ACL_IPV4 service [Setting the parameters for defining the service] +def add_configure_ACL_IPV4(): + form_acl = AddServiceForm_ACL_IPV4() + if form_acl.validate_on_submit(): + flash(f'New configuration was created', 'success') + return redirect(url_for('service.home')) + print(form_acl.errors) + return render_template('service/configure_ACL_IPV4.html', form_acl=form_acl, submit_text='Add New Service') + +@service.route('add/configure/ACL_IPV6', methods=['GET', 'POST']) #Route for adding a new ACL_IPV6 service [Setting the parameters for defining the service] +def add_configure_ACL_IPV6(): + form_acl = AddServiceForm_ACL_IPV6() + if form_acl.validate_on_submit(): + flash(f'New configuration was created', 'success') + return redirect(url_for('service.home')) + print(form_acl.errors) + return render_template('service/configure_ACL_IPV6.html', form_acl=form_acl, submit_text='Add New Service') + +@service.route('add/configure/L2VPN', methods=['GET', 'POST']) #Route for adding a new L2VPN service [Setting the parameters for defining the service] +def add_configure_L2VPN(): + form_l2vpn = AddServiceForm_L2VPN() #Load the AddServiceForm_L2VPN form defined in forms.py + service_obj = Service() #Create a new instance of the Service class + context_uuid = session['context_uuid'] #Retrieves the context UUID from the session + topology_uuid = session['topology_uuid'] #Retrieves the topology UUID from the session + context_client.connect() #Connects to the context service using the context_client object + grpc_topology = get_topology(context_client, topology_uuid, context_uuid=context_uuid, rw_copy=False) #Call the get_topology() function to retrieve the topology information for the given context and topology UUIDs + if grpc_topology is None: #If the topology is not found, display an error message and set the devices list to an empty list + flash('Context({:s})/Topology({:s}) not found'.format(str(context_uuid), str(topology_uuid)), 'danger') + devices = [] + else: #If the topology is found + topo_device_uuids = {device_id.device_uuid.uuid for device_id in grpc_topology.device_ids} #Retrieve the device UUIDs from the topology information + grpc_devices: DeviceList = context_client.ListDevices(Empty()) #Call the ListDevices() method on the context_client to retrieve a list of all devices + devices = [ #Filter the list of devices to only include those with UUIDs that appear in the topology + device for device in grpc_devices.devices + if device.device_id.device_uuid.uuid in topo_device_uuids + ] + + for i, device in enumerate(devices): #Iterate over a range of numbers from 0 to the length of the devices list (The number of devices in the topology) + new_choice = (i, str(device.name)) #Create a new tuple consisting of the index and the name of the device at the current index. + form_l2vpn.service_device_1.choices.append(new_choice) #Add the device to a select option in the form_l2vpn service_device_1 part. + + if form_l2vpn.validate_on_submit(): #Check if the form has been submitted and is valid + selected_device = devices[int(form_l2vpn.service_device_1.data)] #Selected_Device will be the one selected by the user in the previously defined form field + try: + if form_l2vpn.service_endpoint_1.data not in [endpoint.name for endpoint in selected_device.device_endpoints]: # Check if the endpoint submitted by the user is a valid endpoint of the selected device + raise ValidationError('The selected endpoint: ' + form_l2vpn.service_endpoint_1.data + ' is not a valid endpoint for: '+ selected_device.name + '. Please select an endpoint that is available for this device') # If it is not a valid endpoint -> Raise a Validation Error + else: + selected_endpoint = form_l2vpn.service_endpoint_1.data #If the selected endpoint is valid, save it in a variable + except Exception as e: # Catch any exception raised during the validation process + flash('{:s}'.format(str(e.args[0])), 'danger') + current_app.logger.exception(e) + return render_template('service/configure_L2VPN.html', form_l2vpn=form_l2vpn, submit_text='Add New Service') #Render the L2VPN configuration form with the previously entered data and an error message + + #SET THE MANDATORY PARAMETERS FOR WHEN DEFINING A SERVICE [APART FROM THE ONES DEFINING THE TYPE OF SERVICE] + #Service UUID: + service_obj.service_id.service_uuid.uuid = str(form_l2vpn.service_name.data) #Create the Service UUID (Unique Identifier of the service) from the service name + + #Service type [OPTIONS Defined in Context.proto]: 0(Unknown), 1(L3NM), 2(L2NM), 3(TAPI_CONNECTIVITY_SERVICE), 4(ACL) : + service_obj.service_type = int(form_l2vpn.service_type.data) #Set the Service type as selected by the user in the form [Fixed value: L2NM] + + #Validate the Context + try: + if 'context_uuid' not in session or 'topology_uuid' not in session: #Check if the context_uuid and topology_uuid are in session + flash("Please select a context!", "warning") + return redirect(url_for("main.home")) #If not -> Warning message & redirect to home page + context_uuid = session['context_uuid'] #Retrieves the context UUID from the session + + service_uuid = service_obj.service_id.service_uuid.uuid #Set the service UUID + endpoint_ids = [ + json_endpoint_id(json_device_id(selected_device.name), str(selected_endpoint)) #Create a list containing a element that represents the Selected Device ID and the Selected Endpoint + ] + + constraints = [] #Constraints -> Creates a list in which the constraints for the service will be added + if form_l2vpn.service_capacity.data: + constraints.append(json_constraint_sla_capacity(float(form_l2vpn.service_capacity.data))) #Capacity [Gbps] + if form_l2vpn.service_latency.data: + constraints.append(json_constraint_sla_latency(float(form_l2vpn.service_latency.data))) #Latency [ms] + if form_l2vpn.service_availability.data: + constraints.append(json_constraint_sla_availability(1, True, float(form_l2vpn.service_availability.data))) #Availability [%] + if form_l2vpn.service_isolation.data is not None and form_l2vpn.service_isolation.data != '': + constraints.append(json_constraint_sla_isolation([getattr(IsolationLevelEnum, str(form_l2vpn.service_isolation.data))])) #Isolation (Predefined values) + + params = { #Parameters for defining the L2VPN [Value given in the webui form] -> /service/service_handlers/l2nm_emulated/ConfigRules.py + 'sub_interface_index': str(form_l2vpn.IF_index.data), #sub_interface_index -> IF_index + 'vlan_id': str(form_l2vpn.IF_vlan_id.data), #vlan_id -> IF_vlan_id + 'remote_router': str(form_l2vpn.NI_remote_system.data), #remote_router -> NI_remote_system + 'circuit_id': str(form_l2vpn.NI_VC_ID.data), #circuit_id -> NI_VC_ID + 'mtu':str(form_l2vpn.IF_mtu.data), #mtu -> NI_mtu + } + params_with_data = {k: v for k, v in params.items() if v is not None and str(v) != 'None' and v != ''} #The parameters that have no value are removed and the rest are stored in params_with_data + config_rules = [ #Create the configuration rules from the params_with_data + json_config_rule_set( + '/device[{:s}]/endpoint[{:s}]/settings'.format(str(selected_device.name), str(selected_endpoint)), params_with_data + ) + ] + service_client.connect() + context_client.connect() + + descriptor_json = json_service_l2nm_planned(service_uuid = service_uuid, endpoint_ids = endpoint_ids, constraints = constraints, config_rules = config_rules, context_uuid= context_uuid) + descriptor_json = {"services": [descriptor_json]} #Wrap the descriptor between the tag: "services": []" + process_descriptors(descriptor_json) #Call the process_descriptors function to add the new service defined in the descriptor_json variable + + service_client.close() + context_client.close() + + flash('Service "{:s}" added successfully!'.format(service_obj.service_id.service_uuid.uuid), 'success') #If the service was added succesfully -> Flash success message with newly added service UUID. + return redirect(url_for('service.home', service_uuid=service_obj.service_id.service_uuid.uuid)) #If the service was added succesfully -> Redirect to the service.home URL + + except Exception as e: #If the service was NOT added succesfully -> Catch any exceptions that occur when adding a new service + flash('Problem adding service: {:s}'.format((str(e.args[0]))), 'danger') #If the service was NOT added succesfully -> Include the exception message in a flashed message + current_app.logger.exception(e) #If the service was NOT added succesfully -> Log the exception using Flask's logger + return render_template('service/configure_L2VPN.html', form_l2vpn=form_l2vpn, submit_text='Add New Service') #If the service was NOT added succesfully -> Redirect to service/configure_L2VPN.html + print(form_l2vpn.errors) + return render_template('service/configure_L2VPN.html', form_l2vpn=form_l2vpn, submit_text='Add New Service') + +@service.route('add/configure/L3VPN', methods=['GET', 'POST']) #Route for adding a new L3VPN service [Setting the parameters for defining the service] +def add_configure_L3VPN(): + form_l3vpn = AddServiceForm_L3VPN() + #form_l3vpn.l3vpn_params = SERVICE_PARAMETERS['L3VPN'] + if form_l3vpn.validate_on_submit(): + flash(f'New configuration was created', 'success') + return redirect(url_for('service.home')) + print(form_l3vpn.errors) + return render_template('service/configure_L3VPN.html', form_l3vpn=form_l3vpn, submit_text='Add New Service') + +def process_descriptors(descriptors): #The function receives a "descriptors" parameter which has to be a JSON descriptor object + descriptor_loader = DescriptorLoader(descriptors) #Creates a descriptor_loader object + 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) # + diff --git a/src/webui/service/templates/service/detail.html b/src/webui/service/templates/service/detail.html index bee2e93c53896a8eeac826703a60afe02a5aa825..1c5335db36deb5f5aef0077106243b61e7935666 100644 --- a/src/webui/service/templates/service/detail.html +++ b/src/webui/service/templates/service/detail.html @@ -211,6 +211,69 @@ </td> </tr> {% endif %} + {% if config.WhichOneof('config_rule') == 'acl' %} + <tr> + <td> + {% if config.acl %} + {% set endpoint_id = config.acl.endpoint_id %} + {% set rule_set_name = config.acl.rule_set.name %} + /device[{{ endpoint_id.device_id.device_uuid.uuid }}]/endpoint[{{ endpoint_id.endpoint_uuid.uuid }}]/acl_ruleset[{{ rule_set_name }}] + {% endif %} + </td> + <td> + <ul> + {% if config.acl.rule_set.name %} + <li><b>name: </b>{{ config.acl.rule_set.name }}</li> + {% endif %} + {% if config.acl.rule_set.type %} + <li><b>type: </b>{{ type[config.acl.rule_set.type] }}</li> + {% endif %} + {% if config.acl.rule_set.description %} + <li><b>description: </b>{{ config.acl.rule_set.description }}</li> + {% endif %} + {% if config.acl.rule_set.user_id %} + <li><b>user_id: </b>{{ config.acl.rule_set.user_id }}</li> + {% endif %} + {% for entry in config.acl.rule_set.entries %} + {% if entry.description %} + <li><b>entry {{ entry.sequence_id }} description: </b>{{ entry.description }}</li> + {% endif %} + {% if entry.match.protocol %} + <li><b>entry {{ entry.sequence_id }} protocol: </b>{{ entry.match.protocol }}</li> + {% endif %} + {% if entry.match.dscp %} + <li><b>entry {{ entry.sequence_id }} dscp:</b>{{ entry.match.dscp }}</li> + {% endif %} + {% if entry.match.src_address %} + <li><b>entry {{ entry.sequence_id }} src_address: </b>{{ entry.match.src_address }}</li> + {% endif %} + {% if entry.match.dst_address %} + <li><b>entry {{ entry.sequence_id }} dst_address: </b>{{ entry.match.dst_address }}</li> + {% endif %} + {% if entry.match.src_port %} + <li><b>entry {{ entry.sequence_id }} src_port: </b>{{ entry.match.src_port }}</li> + {% endif %} + {% if entry.match.dst_port %} + <li><b>entry {{ entry.sequence_id }} dst_port: </b>{{ entry.match.dst_port }}</li> + {% endif %} + {% if entry.match.start_mpls_label %} + <li><b>entry {{ entry.sequence_id }} start mpls label: </b>{{ entry.match.start_mpls_label }}</li> + {% endif %} + {% if entry.match.end_mpls_label %} + <li><b>entry {{ entry.sequence_id }} end mpls label: </b> {{ entry.match.end_mpls_label }}</li> + {% endif %} + {% if entry.action.forward_action %} + <li><b>entry {{ entry.sequence_id }} forward_action: </b>{{ f_action[entry.action.forward_action] }}</li> + {% endif %} + {% if entry.action.log_action %} + <li><b>entry {{ entry.sequence_id }} log_action: </b>{{l_action[entry.action.log_action] }}</li> + {% endif %} + {% endfor %} + + </ul> + </td> + </tr> + {% endif %} {% endfor %} </tbody> </table>