From 0cf0487597bb8ab9b5a3f5de3b28d0bef408ea00 Mon Sep 17 00:00:00 2001 From: armingol <pablo.armingolrobles@telefonica.com> Date: Mon, 29 May 2023 10:05:59 +0200 Subject: [PATCH] 1) Multivendor support in L3VPN(steps 1,2 and 3) 2) Support to choose between "Jinja" and "pyangbind". 3) WebUI modification for creating services 4) Changes in ConfigRules multivendor to adapt to new features --- .../drivers/openconfig/OpenConfigDriver.py | 42 +- .../drivers/openconfig/templates/Tools.py | 4 +- .../VPN/Network_instance_multivendor.py | 15 +- .../drivers/openconfig/templates/__init__.py | 32 +- .../load_gen/RequestGenerator.py | 6 +- .../l2nm_emulated/ConfigRules.py | 24 +- .../L2NMEmulatedServiceHandler.py | 13 +- .../l2nm_openconfig/ConfigRules.py | 39 +- .../l3nm_emulated/ConfigRules.py | 17 +- .../l3nm_openconfig/ConfigRules.py | 52 +- src/webui/service/__init__.py | 2 + src/webui/service/service/forms.py | 253 +++++++++ src/webui/service/service/routes.py | 352 ++++++++---- src/webui/service/templates/service/add.html | 53 ++ .../templates/service/configure_ACL_IPV4.html | 281 ++++++++++ .../templates/service/configure_ACL_IPV6.html | 236 ++++++++ .../templates/service/configure_ACL_L2.html | 191 +++++++ .../templates/service/configure_L2VPN.html | 434 +++++++++++++++ .../templates/service/configure_L3VPN.html | 510 ++++++++++++++++++ src/webui/service/templates/service/home.html | 11 +- 20 files changed, 2380 insertions(+), 187 deletions(-) create mode 100644 src/webui/service/service/forms.py create mode 100644 src/webui/service/templates/service/add.html create mode 100644 src/webui/service/templates/service/configure_ACL_IPV4.html create mode 100644 src/webui/service/templates/service/configure_ACL_IPV6.html create mode 100644 src/webui/service/templates/service/configure_ACL_L2.html create mode 100644 src/webui/service/templates/service/configure_L2VPN.html create mode 100644 src/webui/service/templates/service/configure_L3VPN.html diff --git a/src/device/service/drivers/openconfig/OpenConfigDriver.py b/src/device/service/drivers/openconfig/OpenConfigDriver.py index 0c8ef2d68..86026350e 100644 --- a/src/device/service/drivers/openconfig/OpenConfigDriver.py +++ b/src/device/service/drivers/openconfig/OpenConfigDriver.py @@ -58,18 +58,20 @@ class NetconfSessionHandler: self.__connected = threading.Event() self.__address = address self.__port = int(port) - self.__username = settings.get('username') - self.__password = settings.get('password') - self.__vendor = settings.get('vendor') - self.__key_filename = settings.get('key_filename') - self.__hostkey_verify = settings.get('hostkey_verify', True) - self.__look_for_keys = settings.get('look_for_keys', True) - self.__allow_agent = settings.get('allow_agent', True) - self.__force_running = settings.get('force_running', False) - self.__commit_per_rule = settings.get('commit_per_rule', False) - self.__device_params = settings.get('device_params', {}) - self.__manager_params = settings.get('manager_params', {}) - self.__nc_params = settings.get('nc_params', {}) + self.__username = settings.get('username') + self.__password = settings.get('password') + self.__vendor = settings.get('vendor') + self.__version = settings.get('version', "1") + self.__key_filename = settings.get('key_filename') + self.__hostkey_verify = settings.get('hostkey_verify', True) + self.__look_for_keys = settings.get('look_for_keys', True) + self.__allow_agent = settings.get('allow_agent', True) + self.__force_running = settings.get('force_running', False) + self.__commit_per_rule = settings.get('commit_per_rule', False) + self.__device_params = settings.get('device_params', {}) + self.__manager_params = settings.get('manager_params', {}) + self.__nc_params = settings.get('nc_params', {}) + self.__message_renderer = settings.get('message_renderer','jinja') self.__manager : Manager = None self.__candidate_supported = False @@ -96,6 +98,12 @@ class NetconfSessionHandler: @property def vendor(self): return self.__vendor + + @property + def version(self): return self.__version + + @property + def message_renderer(self): return self.__message_renderer @RETRY_DECORATOR def get(self, filter=None, with_defaults=None): # pylint: disable=redefined-builtin @@ -196,22 +204,22 @@ def edit_config( format='xml' # pylint: disable=redefined-builtin ): str_method = 'DeleteConfig' if delete else 'SetConfig' - #logger.debug('[{:s}] resources = {:s}'.format(str_method, str(resources))) + logger.debug('[{:s}] resources = {:s}'.format(str_method, str(resources))) results = [None for _ in resources] 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_messages = compose_config( # get template for configuration - resource_key, resource_value, delete=delete, vendor=netconf_handler.vendor) + resource_key, resource_value, delete=delete, vendor=netconf_handler.vendor, message_renderer=netconf_handler.message_renderer) for str_config_message in str_config_messages: # configuration of the received templates if str_config_message is None: raise UnsupportedResourceKeyException(resource_key) - # logger.debug('[{:s}] str_config_message[{:d}] = {:s}'.format( - # str_method, len(str_config_message), str(str_config_message))) + 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) diff --git a/src/device/service/drivers/openconfig/templates/Tools.py b/src/device/service/drivers/openconfig/templates/Tools.py index a56fe3ea5..387cb628b 100644 --- a/src/device/service/drivers/openconfig/templates/Tools.py +++ b/src/device/service/drivers/openconfig/templates/Tools.py @@ -42,13 +42,13 @@ def generate_templates(resource_key: str, resource_value: str, delete: bool,vend 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)) + if vendor == "ADVA": 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_templates.append(create_network_instance(data)) + result_templates.append(create_network_instance(data,vendor)) elif "interface" in list_resource_key[1]: # interface rules management data: Dict[str, Any] = json.loads(resource_value) 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 2b85ba60c..aaedd4b92 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,7 +1,7 @@ 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}] +def create_network_instance(parameters,vendor): #[L2/L3] Creates a Network Instance as described in: /network_instance[{:s}] NetInstance_name = parameters['name'] #Retrieves the Name parameter of the NetInstance DEL = parameters['DEL'] #If the parameter 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 @@ -27,7 +27,7 @@ def create_network_instance(parameters): #[L2/L3] Creates a Ne #Access the entry container NetInstance_set = Network_Instance.network_instances.network_instance.add(name = NetInstance_name) NetInstance_set.config.name = NetInstance_name - NetInstance_set.config.type = NetInstance_type + if vendor == 'ADVA': NetInstance_set.config.type = NetInstance_type NetInstance_encapsulation = NetInstance_set.encapsulation.config #If the description parameter is defined [OPTIONAL-PARAMETER] @@ -59,13 +59,14 @@ def create_network_instance(parameters): #[L2/L3] Creates a Ne NetInstance_set.config.route_distinguisher = NetInstance_Route_disting #If the router-id parameter is defined [OPTIONAL-PARAMETER] - if verify.find('router_id')>0: - NetInstance_Router_ID = parameters['router_id'] - if len(NetInstance_Router_ID) != 0: NetInstance_set.config.router_id = NetInstance_Router_ID #If router-id parameter has a value + #if verify.find('router_id')>0: + #NetInstance_Router_ID = parameters['router_id'] + #if len(NetInstance_Router_ID) != 0: NetInstance_set.config.router_id = NetInstance_Router_ID #If router-id parameter has a value #Encapsulation - NetInstance_encapsulation.encapsulation_type = "MPLS" - NetInstance_encapsulation.label_allocation_mode = "INSTANCE_LABEL" + if vendor == 'ADVA': + NetInstance_encapsulation.encapsulation_type = "MPLS" + NetInstance_encapsulation.label_allocation_mode = "INSTANCE_LABEL" #Dump the entire instance as RFC 750 XML NetInstance_set = pybindIETFXMLEncoder.serialise(Network_Instance) #Specific Replace [Addition of the enabled] diff --git a/src/device/service/drivers/openconfig/templates/__init__.py b/src/device/service/drivers/openconfig/templates/__init__.py index 039cf5b55..efeff2a12 100644 --- a/src/device/service/drivers/openconfig/templates/__init__.py +++ b/src/device/service/drivers/openconfig/templates/__init__.py @@ -16,6 +16,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_templates +from .ACL.ACL_multivendor import acl_mgmt 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 @@ -23,6 +24,7 @@ from .Interfaces import parse as parse_interfaces, parse_counters from .NetworkInstances import parse as parse_network_instances from .RoutingPolicy import parse as parse_routing_policy from .Acl import parse as parse_acl +LOGGER = logging.getLogger(__name__) ALL_RESOURCE_KEYS = [ RESOURCE_ENDPOINTS, @@ -79,10 +81,28 @@ def parse(resource_key : str, xml_data : ET.Element): return parser(xml_data) def compose_config( # template generation - resource_key : str, resource_value : str, delete : bool = False, vendor : Optional[str] = None + resource_key : str, resource_value : str, delete : bool = False, vendor : Optional[str] = None, message_renderer = str ) -> str: - templates = (generate_templates(resource_key, resource_value, delete,vendor)) - return [ - '<config>{:s}</config>'.format(template) # format correction - for template in templates - ] + + if (message_renderer == "pyangbind"): + templates = (generate_templates(resource_key, resource_value, delete,vendor)) + return [ + '<config>{:s}</config>'.format(template) # format correction + for template in templates + ] + + elif (message_renderer == "jinja"): + template_name = '{:s}/edit_config.xml'.format(RE_REMOVE_FILTERS.sub('', resource_key)) + template = JINJA_ENV.get_template(template_name) + + if "acl_ruleset" in resource_key: # MANAGING ACLs + template.extend(acl_mgmt(resource_value,vendor)) # MANAGING ACLs + data : Dict[str, Any] = json.loads(resource_value) + operation = 'delete' if delete else 'merge' + LOGGER.info('Template={:s}'.format('<config>{:s}</config>'.format(template.render(**data, operation=operation, vendor=vendor).strip()))) + + return ['<config>{:s}</config>'.format(template.render(**data, operation=operation, vendor=vendor).strip())] + + else: + raise ValueError('Invalid message_renderer value: {}'.format(message_renderer)) + \ No newline at end of file diff --git a/src/load_generator/load_gen/RequestGenerator.py b/src/load_generator/load_gen/RequestGenerator.py index 5c56ea6ec..aa95f4942 100644 --- a/src/load_generator/load_gen/RequestGenerator.py +++ b/src/load_generator/load_gen/RequestGenerator.py @@ -255,8 +255,8 @@ class RequestGenerator: json_constraint_sla_latency(e2e_latency_ms), ] - vlan_id = num_request % 1000 - circuit_id = '{:03d}'.format(vlan_id + 100) + vlan_id = 300 + (num_request % 1000) + circuit_id = '{:03d}'.format(vlan_id) src_device_name = self._device_data[src_device_uuid]['name'] src_endpoint_name = self._device_endpoint_data[src_device_uuid][src_endpoint_uuid]['name'] @@ -306,7 +306,7 @@ class RequestGenerator: bgp_as = 65000 + (num_request % 10000) - vlan_id = num_request % 100 +100 + vlan_id = num_request % 1000 +300 x = num_request % 255 y = num_request % 25 * num_request % 10 route_distinguisher = '{:5d}:{:03d}'.format(bgp_as, vlan_id) diff --git a/src/service/service/service_handlers/l2nm_emulated/ConfigRules.py b/src/service/service/service_handlers/l2nm_emulated/ConfigRules.py index c2ea6e213..4eb068f7a 100644 --- a/src/service/service/service_handlers/l2nm_emulated/ConfigRules.py +++ b/src/service/service/service_handlers/l2nm_emulated/ConfigRules.py @@ -12,14 +12,20 @@ # 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 [] + if endpoint_settings is None: return [] + + json_settings : Dict = service_settings.value + json_endpoint_settings : Dict = endpoint_settings.value 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 @@ -60,7 +66,7 @@ def setup_config_rules( {'name': network_instance_name, 'type': 'L2VSI'}), json_config_rule_set( - '/interface[{:s}]/subinterface[{:d}]'.format(if_cirid_name, sub_interface_index), + '/interface[{:s}]/subinterface[{:s}]'.format(if_cirid_name, sub_interface_index), {'name': if_cirid_name, 'type': 'l2vlan', 'index': sub_interface_index, 'vlan_id': vlan_id}), json_config_rule_set( @@ -79,8 +85,14 @@ def teardown_config_rules( service_uuid : str, connection_uuid : str, device_uuid : str, endpoint_uuid : str, endpoint_name : str, service_settings : TreeNode, endpoint_settings : TreeNode ) -> List[Dict]: + + if service_settings is None: return [] + if endpoint_settings is None: return [] + + json_settings : Dict = service_settings.value + json_endpoint_settings : Dict = endpoint_settings.value - #json_settings : Dict = {} if service_settings is None else service_settings.value + 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 @@ -103,7 +115,7 @@ def teardown_config_rules( json_config_rules = [ json_config_rule_delete( - '/network_instance[{:s}]/connection_point[{:s}]'.format(network_instance_name, connection_point_id), + '/network_instance[{:s}]/connection_point[{:s}]'.format(network_instance_name, connection_point_id), {'name': network_instance_name, 'connection_point': connection_point_id}), json_config_rule_delete( @@ -112,7 +124,7 @@ def teardown_config_rules( 'subinterface': sub_interface_index}), json_config_rule_delete( - '/interface[{:s}]/subinterface[{:d}]'.format(if_cirid_name, sub_interface_index), + '/interface[{:s}]/subinterface[{:s}]'.format(if_cirid_name, sub_interface_index), {'name': if_cirid_name, 'index': sub_interface_index}), json_config_rule_delete( diff --git a/src/service/service/service_handlers/l2nm_emulated/L2NMEmulatedServiceHandler.py b/src/service/service/service_handlers/l2nm_emulated/L2NMEmulatedServiceHandler.py index 9de6c607b..748b962f6 100644 --- a/src/service/service/service_handlers/l2nm_emulated/L2NMEmulatedServiceHandler.py +++ b/src/service/service/service_handlers/l2nm_emulated/L2NMEmulatedServiceHandler.py @@ -69,16 +69,19 @@ class L2NMEmulatedServiceHandler(_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[:] + for json_config_rule in json_config_rules: + device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule)) + self.__task_executor.configure_device(device_obj) - del device_obj.device_config.config_rules[:] - for json_config_rule in json_config_rules: - device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule)) - self.__task_executor.configure_device(device_obj) results.append(True) except Exception as e: # pylint: disable=broad-except LOGGER.exception('Unable to SetEndpoint({:s})'.format(str(endpoint))) diff --git a/src/service/service/service_handlers/l2nm_openconfig/ConfigRules.py b/src/service/service/service_handlers/l2nm_openconfig/ConfigRules.py index 70ee430f0..92b93ba27 100644 --- a/src/service/service/service_handlers/l2nm_openconfig/ConfigRules.py +++ b/src/service/service/service_handlers/l2nm_openconfig/ConfigRules.py @@ -30,24 +30,29 @@ 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 - #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 - - #router_id = json_endpoint_settings.get('router_id', '0.0.0.0') # '10.95.0.10' - #route_distinguisher = json_endpoint_settings.get('route_distinguisher', '0:0' ) # '60001:801' - sub_interface_index = json_endpoint_settings.get('sub_interface_index', 0 ) # 1 - vlan_id = json_endpoint_settings.get('vlan_id', 1 ) # 400 - #address_ip = json_endpoint_settings.get('address_ip', '0.0.0.0') # '2.2.2.1' - #address_prefix = json_endpoint_settings.get('address_prefix', 24 ) # 30 - remote_router = json_endpoint_settings.get('remote_router', '5.5.5.5') # '5.5.5.5' - circuit_id = json_endpoint_settings.get('circuit_id', '111' ) # '111' + 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 + + #router_id = json_endpoint_settings.get('router_id', '0.0.0.0') # '10.95.0.10' + #route_distinguisher = json_endpoint_settings.get('route_distinguisher', '0:0' ) # '60001:801' + sub_interface_index = json_endpoint_settings.get('sub_interface_index', 0 ) # 1 + vlan_id = json_endpoint_settings.get('vlan_id', 1 ) # 400 + #address_ip = json_endpoint_settings.get('address_ip', '0.0.0.0') # '2.2.2.1' + #address_prefix = json_endpoint_settings.get('address_prefix', 24 ) # 30 + remote_router = json_endpoint_settings.get('remote_router', '5.5.5.5') # '5.5.5.5' + circuit_id = json_endpoint_settings.get('circuit_id', '111' ) # '111' + network_instance_name = json_endpoint_settings.get('ni_name', 'ELAN-AC:{:s}'.format(str(circuit_id))) #ELAN-AC:1 + + #network_interface_desc = '{:s}-NetIf'.format(service_uuid) + network_interface_desc = json_endpoint_settings.get('ni_description','') + #network_subinterface_desc = '{:s}-NetSubIf'.format(service_uuid) + network_subinterface_desc = json_endpoint_settings.get('subif_description','') - - if_cirid_name = '{:s}.{:s}'.format(endpoint_name, str(circuit_id)) - network_instance_name = 'ELAN-AC:{:s}'.format(str(circuit_id)) - connection_point_id = 'VC-1' + if_cirid_name = '{:s}.{:s}'.format(endpoint_name, vlan_id) + #connection_point_id = 'VC-{:s}'.format(str(circuit_id)) #Provisionalmente comentado, en principio se deberia usar asi + connection_point_id = 'VC-1' #Uso provisional json_config_rules = [ diff --git a/src/service/service/service_handlers/l3nm_emulated/ConfigRules.py b/src/service/service/service_handlers/l3nm_emulated/ConfigRules.py index 903ad8cd5..2e1a0859d 100644 --- a/src/service/service/service_handlers/l3nm_emulated/ConfigRules.py +++ b/src/service/service/service_handlers/l3nm_emulated/ConfigRules.py @@ -21,6 +21,12 @@ def setup_config_rules( service_settings : TreeNode, endpoint_settings : TreeNode ) -> List[Dict]: + if service_settings is None: return [] + if endpoint_settings is None: return [] + + json_settings : Dict = service_settings.value + json_endpoint_settings : Dict = endpoint_settings.value + 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 @@ -40,7 +46,7 @@ def setup_config_rules( vlan_id = json_endpoint_settings.get('vlan_id', 1 ) # 400 address_ip = json_endpoint_settings.get('address_ip', '0.0.0.0') # '2.2.2.1' address_prefix = json_endpoint_settings.get('address_prefix', 24 ) # 30 - if_subif_name = '{:s}.{:d}'.format(endpoint_name, vlan_id) + if_subif_name = '{:s}.{:s}'.format(endpoint_name, str(vlan_id)) json_config_rules = [ json_config_rule_set( @@ -54,7 +60,7 @@ def setup_config_rules( 'name': endpoint_name, 'description': network_interface_desc, 'mtu': mtu, }), json_config_rule_set( - '/interface[{:s}]/subinterface[{:d}]'.format(endpoint_name, sub_interface_index), { + '/interface[{:s}]/subinterface[{:s}]'.format(endpoint_name, sub_interface_index), { 'name': endpoint_name, 'index': sub_interface_index, 'description': network_subinterface_desc, 'vlan_id': vlan_id, 'address_ip': address_ip, 'address_prefix': address_prefix, @@ -142,6 +148,9 @@ def teardown_config_rules( service_settings : TreeNode, endpoint_settings : TreeNode ) -> List[Dict]: + if service_settings is None: return [] + if endpoint_settings is None: return [] + 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 @@ -157,7 +166,7 @@ def teardown_config_rules( #address_ip = json_endpoint_settings.get('address_ip', '0.0.0.0') # '2.2.2.1' #address_prefix = json_endpoint_settings.get('address_prefix', 24 ) # 30 - if_subif_name = '{:s}.{:d}'.format(endpoint_name, vlan_id) + if_subif_name = '{:s}.{:s}'.format(endpoint_name, vlan_id) service_short_uuid = service_uuid.split('-')[-1] network_instance_name = '{:s}-NetInst'.format(service_short_uuid) #network_interface_desc = '{:s}-NetIf'.format(service_uuid) @@ -169,7 +178,7 @@ def teardown_config_rules( 'name': network_instance_name, 'id': if_subif_name, }), json_config_rule_delete( - '/interface[{:s}]/subinterface[{:d}]'.format(endpoint_name, sub_interface_index), { + '/interface[{:s}]/subinterface[{:s}]'.format(endpoint_name, sub_interface_index), { 'name': endpoint_name, 'index': sub_interface_index, }), json_config_rule_delete( diff --git a/src/service/service/service_handlers/l3nm_openconfig/ConfigRules.py b/src/service/service/service_handlers/l3nm_openconfig/ConfigRules.py index dd48fe735..e98dd5790 100644 --- a/src/service/service/service_handlers/l3nm_openconfig/ConfigRules.py +++ b/src/service/service/service_handlers/l3nm_openconfig/ConfigRules.py @@ -12,45 +12,57 @@ # See the License for the specific language governing permissions and # limitations under the License. +import logging 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 +LOGGER = logging.getLogger(__name__) 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, endpoint_acls : List [Tuple] ) -> List[Dict]: - + if service_settings is None: return [] if endpoint_settings is None: return [] json_settings : Dict = service_settings.value json_endpoint_settings : Dict = endpoint_settings.value - service_short_uuid = service_uuid.split('-')[-1] - network_instance_name = '{:s}-NetInst'.format(service_short_uuid) - network_interface_desc = '{:s}-NetIf'.format(service_uuid) - network_subinterface_desc = '{:s}-NetSubIf'.format(service_uuid) + 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 - #address_families = json_settings.get('address_families', [] ) # ['IPV4'] - bgp_as = json_settings.get('bgp_as', 65000 ) # 65000 - route_distinguisher = json_settings.get('route_distinguisher', '0:0' ) # '60001:801' - sub_interface_index = json_endpoint_settings.get('sub_interface_index', 0 ) # 1 - router_id = json_endpoint_settings.get('router_id', '0.0.0.0') # '10.95.0.10' - vlan_id = json_endpoint_settings.get('vlan_id', 1 ) # 400 - address_ip = json_endpoint_settings.get('address_ip', '0.0.0.0') # '2.2.2.1' - address_prefix = json_endpoint_settings.get('address_prefix', 24 ) # 30 - policy_import = json_endpoint_settings.get('policy_AZ', '2' ) # 2 - policy_export = json_endpoint_settings.get('policy_ZA', '7' ) # 30 + mtu = json_settings.get('mtu', 1450 ) # 1512 + #address_families = json_settings.get('address_families', [] ) # ['IPV4'] + bgp_as = json_settings.get('bgp_as', 65000 ) # 65000 + + router_id = json_endpoint_settings.get('router_id', '0.0.0.0') # '10.95.0.10' + route_distinguisher = json_settings.get('route_distinguisher', '65000:101' ) # '60001:801' + LOGGER.warning('RD value={:s}'.format(str(route_distinguisher))) + sub_interface_index = json_endpoint_settings.get('sub_interface_index', 0 ) # 1 + vlan_id = json_endpoint_settings.get('vlan_id', 1 ) # 400 + address_ip = json_endpoint_settings.get('address_ip', '0.0.0.0') # '2.2.2.1' + address_prefix = json_endpoint_settings.get('address_prefix', 24 ) # 30 + + policy_import = json_endpoint_settings.get('policy_AZ', '2' ) # 2 + policy_export = json_endpoint_settings.get('policy_ZA', '7' ) # 30 + #network_interface_desc = '{:s}-NetIf'.format(service_uuid) + network_interface_desc = json_endpoint_settings.get('ni_description','') + #network_subinterface_desc = '{:s}-NetSubIf'.format(service_uuid) + network_subinterface_desc = json_endpoint_settings.get('subif_description','') + #service_short_uuid = service_uuid.split('-')[-1] + #network_instance_name = '{:s}-NetInst'.format(service_short_uuid) + network_instance_name = json_endpoint_settings.get('ni_name', service_uuid.split('-')[-1]) #ELAN-AC:1 - if_subif_name = '{:s}.{:d}'.format(endpoint_name, vlan_id) + if_subif_name = '{:s}.{:s}'.format(endpoint_name, vlan_id) json_config_rules = [ # Configure Interface (not used) #json_config_rule_set( # '/interface[{:s}]'.format(endpoint_name), { - # 'name': endpoint_name, 'description': network_interface_desc, 'mtu': mtu, + # 'name': endpoint_name, + # 'description': network_interface_desc, + # 'mtu': mtu, #}), #Create network instance @@ -93,7 +105,7 @@ def setup_config_rules( #Create interface with subinterface json_config_rule_set( - '/interface[{:s}]/subinterface[{:d}]'.format(if_subif_name, sub_interface_index), { + '/interface[{:s}]/subinterface[{:s}]'.format(if_subif_name, sub_interface_index), { 'name' : if_subif_name, 'type' :'l3ipvlan', 'mtu' : mtu, @@ -211,7 +223,7 @@ def teardown_config_rules( policy_import = json_endpoint_settings.get('policy_AZ', '2' ) # 2 policy_export = json_endpoint_settings.get('policy_ZA', '7' ) # 30 - if_subif_name = '{:s}.{:d}'.format(endpoint_name, vlan_id) + if_subif_name = '{:s}.{:s}'.format(endpoint_name, vlan_id) json_config_rules = [ #Delete table connections diff --git a/src/webui/service/__init__.py b/src/webui/service/__init__.py index fca107141..7f92fc2bf 100644 --- a/src/webui/service/__init__.py +++ b/src/webui/service/__init__.py @@ -16,6 +16,7 @@ import json from typing import List, Tuple, Union from flask import Flask, request, session from flask_healthz import healthz, HealthError +from common.tools.grpc.Tools import grpc_message_to_json from context.client.ContextClient import ContextClient from device.client.DeviceClient import DeviceClient @@ -97,6 +98,7 @@ def create_app(use_config=None, web_app_root=None): app.jinja_env.globals.update({ # pylint: disable=no-member 'enumerate' : enumerate, + 'grpc_message_to_json': grpc_message_to_json, 'json_to_list' : json_to_list, 'round' : round, 'get_working_context' : get_working_context, diff --git a/src/webui/service/service/forms.py b/src/webui/service/service/forms.py new file mode 100644 index 000000000..f3c5e3d17 --- /dev/null +++ b/src/webui/service/service/forms.py @@ -0,0 +1,253 @@ +# 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 re +from flask import flash, Flask +from flask_wtf import FlaskForm +from wtforms import StringField, SelectField, IntegerField, DecimalField +from wtforms.validators import InputRequired, Optional, NumberRange, ValidationError, StopValidation +#from common.proto.context_pb2 import DeviceOperationalStatusEnum +import ipaddress + +def validate_ipv4_address(form, field): #Custom validator for ensuring a valid IPv4 address is submitted + # Check for a valid IPv4 address + # print(field.data) + try: + ipaddress.IPv4Address(field.data) + except ipaddress.AddressValueError: + raise ValidationError('Invalid IPv4 address format') + +def validate_ipv6_address(form, field): #Custom validator for ensuring a valid IPv6 address is submitted + # Check for a valid IPv6 address + try: + ipaddress.IPv6Address(field.data) + except ipaddress.AddressValueError: + raise ValidationError('Invalid IPv6 address format') + +def validate_mac_address(form, field): #Custom validator for ensuring a valid MAC address is submitted + # Check for a valid MAC [L2] address + if not re.match(r'^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$', field.data): + raise ValidationError('Invalid MAC address format') + +def validate_route_distinguisher(form,field): #Custom validator for the input of Route Distinguisher value + pattern = r'^([0-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]):([0-9]|[1-9][0-9]{1,8}|[1-3][0-9]{9}|4[01][0-9]{8}|42[0-8][0-9]{7}|429[0-3][0-9]{6}|4294[0-8][0-9]{5}|42949[0-5][0-9]{4}|429496[0-6][0-9]{3}|4294967[01][0-9]{2}|42949672[0-8][0-9]|429496729[0-5])$' + if not re.match(pattern, field.data): + raise ValidationError('Invalid Route Distinguisher') + +def validate_uint32(form, field): #Custom validator for ensuring uint32 integers + if not 0 <= field.data <= 2**32-1: + raise ValidationError('Value must be a positive integer within the range of uint32') + +def validate_NI_as(form, field): #Custom validator that checks if NI_protocol_name is BGP and NI_as is not provided + if form.NI_protocol_name.data == 'BGP' and field.data == None: + raise StopValidation('AS field is required if the BGP protocol is selected.') + +def validator_ADVA(form, field): + if field.name == 'Device_1_NI_VC_ID' and form.Device_1_IF_vendor.data == 'ADVA' and form.Device_1_NI_VC_ID.data != form.Device_1_IF_vlan_id.data: + raise StopValidation('For the ADVA vendor, it is mandatory that the VC_ID is the same as the Vlan_ID.') + + if field.name == 'Device_2_NI_VC_ID' and form.Device_2_IF_vendor.data == 'ADVA' and form.Device_2_NI_VC_ID.data != form.Device_2_IF_vlan_id.data: + raise StopValidation('For the ADVA vendor, it is mandatory that the VC_ID is the same as the Vlan_ID.') + + + +class CustomInputRequired(): #Custom validator that ensures that the required data is provided + def __init__(self, message=None): #Define a constructor that takes an optional message parameter + self.message = message or "This field is required." #If message is provided, use it. Otherwise, set a default message. + + def __call__(self, form, field): #Define a __call__ method that takes in the form and field to be validated + if field.data is None or field.data == '': #Check if the field data is empty or None + raise StopValidation(self.message) #If the data is empty or None, raise a StopValidation exception with the provided message + +class AddServiceForm_1(FlaskForm): #Form-1 - Formulary Fields -> Select the type of new service to add + service_type = SelectField('Type of service', choices=[('', 'Select a type of service to add'), ('ACL_L2', 'ACL_L2'), ('ACL_IPV4', 'ACL_IPV4'), ('ACL_IPV6', 'ACL_IPV6'), ('L2VPN', 'L2VPN'), ('L3VPN', 'L3VPN')], validators=[InputRequired()]) + +class AddServiceForm_ACL_L2(FlaskForm): #ACL_L2 - Formulary Fields + #MANDATORY_PARAMETERS + name = StringField('ACL Name', validators=[CustomInputRequired("The name of the ACL is a mandatory parameter")]) #MANDATORY PARAMETER + type = SelectField('ACL Type', choices=[('ACL_L2', 'ACL_L2')], validators=[CustomInputRequired("The type of the ACL is a mandatory parameter")]) #MANDATORY PARAMETER + sequence_id = IntegerField('ACL Sequence ID', validators=[CustomInputRequired("The name of the Sequence ID of the ACL is a mandatory parameter"), validate_uint32]) #MANDATORY PARAMETER + forwarding_action = SelectField('ACL Fowarding Action', choices=[('', 'Select an action (Mandatory)'), ('ACCEPT', 'Accept'), ('DROP','Drop'),('REJECT','Reject')], validators=[CustomInputRequired("The Forwarding Action of the ACL is a mandatory parameter")]) + log_action = SelectField('ACL Log Action', choices=[(None, 'Select a log action (Optional)'), ('LOG_SYSLOG', 'Syslog'), ('LOG_NONE','None')], validators=[Optional()]) + + #PARAMETERS FOR Associating ACL to IF + interface = StringField('Interface Name', validators=[CustomInputRequired("The name of the Interface is a mandatory parameter")]) #MANDATORY PARAMETER + subinterface = StringField('Subinterface Index', validators=[Optional()]) + traffic_flow = SelectField('ACL Traffic Flow Direction', choices=[('', 'Select a direction (Mandatory)'), ('Ingress', 'Ingress'), ('Egress','Egress')], validators=[CustomInputRequired("The direction of the traffic flow is a mandatory parameter")]) #MANDATORY PARAMETER + + #SPECIFIC PARAMETERS - Creating ACL Entry [ACL_L2] + source_mac = StringField('Source MAC Address', validators=[Optional(), validate_mac_address]) + destination_mac = StringField('Destination MAC Address', validators=[Optional(), validate_mac_address]) + +class AddServiceForm_ACL_IPV4(FlaskForm): #ACL_IPV4 - Formulary Fields + #MANDATORY_PARAMETERS + name = StringField('ACL Name', validators=[CustomInputRequired("The name of the ACL is a mandatory parameter")]) #MANDATORY PARAMETER + type = SelectField('ACL Type', choices=[('ACL_IPV4', 'ACL_IPV4')], validators=[CustomInputRequired("The type of the ACL is a mandatory parameter")]) #MANDATORY PARAMETER + sequence_id = IntegerField('ACL Sequence ID', validators=[InputRequired(), NumberRange(min=1, message="Sequence ID must be greater than 0")]) #MANDATORY PARAMETER]) #MANDATORY PARAMETER + forwarding_action = SelectField('ACL Fowarding Action', choices=[(None, 'Select an action (Mandatory)'), ('ACCEPT', 'Accept'), ('DROP','Drop'),('REJECT','Reject')], validators=[InputRequired()]) + log_action = SelectField('ACL Log Action', choices=[(None, 'Select a log action (Optional)'), ('LOG_SYSLOG', 'Syslog'), ('LOG_NONE','None')], validators=[Optional()]) + + #PARAMETERS FOR Associating ACL to IF + interface = StringField('Interface Name', validators=[InputRequired()]) #MANDATORY PARAMETER + subinterface = StringField('Subinterface Index', validators=[Optional()]) + traffic_flow = SelectField('ACL Traffic Flow Direction', choices=[('', 'Select a direction (Mandatory)'), ('Ingress', 'Ingress'), ('Egress','Egress')], validators=[InputRequired()]) #MANDATORY PARAMETER + + #OPTIONAL_PARAMETERS - Creating ACL Entry [ACL_IPV4] + source_address = StringField('Source Address', validators=[Optional(), validate_ipv4_address]) + destination_address = StringField('Destination Address', validators=[Optional(), validate_ipv4_address]) + protocol = IntegerField('Protocol', validators=[Optional(),NumberRange(min=1, max=255, message="Protocol number is between 1 and 255 as defined by IANA")]) #Protocols are defined from 1 - 255 as defined in IANA (https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml) + hop_limit = IntegerField('Hop Limit', validators=[Optional(),NumberRange(min=1, max=255, message="The Hop limit value has to be between 0 and 255")]) #Max. value of Hop Limit = 255 + dscp = IntegerField('DSCP', validators=[Optional(),NumberRange(min=1, max=255, message="The DSCP value has to be between 0 and 63")]) #Max. value of DSCP = 63 + source_port = IntegerField('Source Port', validators=[Optional(),NumberRange(min=0, max=65535, message="The Port value has to be between 0 and 655535")]) #Range of existing ports in a PC + destination_port = IntegerField('Destination Port', validators=[Optional(),NumberRange(min=0, max=65535, message="The Port value has to be between 0 and 655535")]) #Range of existing ports in a PC + tcp_flags = SelectField('TCP Flags', choices=[(None, 'Select a TCP Flag (Optional)'),('TCP_SYN', 'TCP_SYN'),('TCP_ACK', 'TCP_ACK'),('TCP_RST', 'TCP_RST'),('TCP_FIN', 'TCP_FIN'),('TCP_PSH', 'TCP_PSH'),('TCP_URG', 'TCP_URG') ,('TCP_ECE', 'TCP_ECE'),('TCP_CWR', 'TCP_CWR')], validators=[Optional()]) + +class AddServiceForm_ACL_IPV6(FlaskForm): #ACL_IPV6 - Formulary Fields + #MANDATORY_PARAMETERS + name = StringField('ACL Name', validators=[InputRequired()]) #MANDATORY PARAMETER + type = SelectField('ACL Type', choices=[('ACL_IPV6', 'ACL_IPV6')], validators=[InputRequired()]) #MANDATORY PARAMETER + sequence_id = IntegerField('ACL Sequence ID', validators=[InputRequired(), NumberRange(min=1, message="Sequence ID must be greater than 0")]) #MANDATORY PARAMETER]) #MANDATORY PARAMETER + forwarding_action = SelectField('ACL Fowarding Action', choices=[(None, 'Select an action (Mandatory)'), ('ACCEPT', 'Accept'), ('DROP','Drop'),('REJECT','Reject')], validators=[InputRequired()]) + log_action = SelectField('ACL Log Action', choices=[(None, 'Select a log action (Optional)'), ('LOG_SYSLOG', 'Syslog'), ('LOG_NONE','None')], validators=[Optional()]) + + #PARAMETERS FOR Associating ACL to IF + interface = StringField('Interface Name', validators=[InputRequired()]) #MANDATORY PARAMETER + subinterface = StringField('Subinterface Index', validators=[Optional()]) + traffic_flow = SelectField('ACL Traffic Flow Direction', choices=[('', 'Select a direction (Mandatory)'), ('Ingress', 'Ingress'), ('Egress','Egress')], validators=[InputRequired()]) #MANDATORY PARAMETER + + #SPECIFIC PARAMETERS - Creating ACL Entry [ACL_IPV6] + source_address = StringField('Source Address', validators=[Optional(), validate_ipv6_address]) + destination_address = StringField('Destination Address', validators=[Optional(), validate_ipv6_address]) + protocol = IntegerField('Protocol', validators=[Optional(),NumberRange(min=1, max=255, message="Protocol number is between 1 and 255 as defined by IANA")]) #Protocols are defined from 1 - 255 as defined in IANA (https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml) + hop_limit = IntegerField('Hop Limit', validators=[Optional(),NumberRange(min=1, max=255, message="The Hop limit value has to be between 0 and 255")]) #Max. value of Hop Limit = 255 + dscp = IntegerField('DSCP', validators=[Optional(),NumberRange(min=1, max=255, message="The DSCP value has to be between 0 and 63")]) #Max. value of DSCP = 63 + +class AddServiceForm_L2VPN(FlaskForm): #L2VPN - Formulary Fields + #GENERIC SERVICE PARAMETERS (COMMON & MANDATORY) + service_name = StringField('Service Name', validators=[CustomInputRequired()]) #MANDATORY PARAMETER + service_type = SelectField('Service Type', choices=[(2, '2 (L2NM)')], validators=[CustomInputRequired()]) #MANDATORY PARAMETER - FIXED VALUE -> L2NM + service_device_1 = SelectField('Device_1', choices=[('', 'Select a device (Mandatory)')], validators=[CustomInputRequired()]) #MANDATORY PARAMETER - DEVICE-1 + service_device_2 = SelectField('Device_2', choices=[('', 'Select a device (Mandatory)')], validators=[CustomInputRequired()]) #MANDATORY PARAMETER - DEVICE-2 + service_endpoint_1 = StringField('Device_1 Endpoint', validators=[CustomInputRequired()]) #MANDATORY PARAMETER - DEVICE-1 + service_endpoint_2 = StringField('Device_2 Endpoint', validators=[CustomInputRequired()]) #MANDATORY PARAMETER - DEVICE-2 + #Device_1_IF_vendor = SelectField ('Device_1 Vendor', choices=[('', 'Select a vendor (Mandatory)'),('ADVA', 'ADVA'), ('CISCO','CISCO'), ('Huawei', 'Huawei'),('Juniper', 'Juniper'),('Nokia', 'Nokia')], validators=[CustomInputRequired()]) #MANDATORY PARAMETER - DEVICE-1 + #Device_2_IF_vendor = SelectField ('Device_2 Vendor', choices=[('', 'Select a vendor (Mandatory)'),('ADVA', 'ADVA'), ('CISCO','CISCO'), ('Huawei', 'Huawei'),('Juniper', 'Juniper'),('Nokia', 'Nokia')], validators=[CustomInputRequired()]) #MANDATORY PARAMETER - DEVICE-2 + #Device_1_Template = SelectField ('Device_1 Template', choices=[('', 'Select a type of template (Mandatory)'),('Jinja', 'Jinja'), ('Pyangbind','Pyangbind')], validators=[CustomInputRequired()]) #MANDATORY PARAMETER - DEVICE-1 + #Device_2_Template = SelectField ('Device_2 Template', choices=[('', 'Select a type of template (Mandatory)'),('Jinja', 'Jinja'), ('Pyangbind','Pyangbind')], validators=[CustomInputRequired()]) #MANDATORY PARAMETER - DEVICE-2 + + + #GENERIC SERVICE CONSTRAINT PARAMETERS (ALL OPTIONAL) + service_capacity = DecimalField('Service Capacity', places=2, default=10.00, validators=[Optional(), NumberRange(min=0)]) #OPTIONAL PARAMETER + service_latency = DecimalField('Service Latency', places=2, default=15.20, validators=[Optional(), NumberRange(min=0)]) #OPTIONAL PARAMETER + service_availability= DecimalField('Service Availability', places=2, validators=[Optional(), NumberRange(min=0)]) #OPTIONAL PARAMETER + service_isolation = SelectField('Service Isolation', choices=[('', 'Select (Optional)'), ('NO_ISOLATION', 'NO_ISOLATION'), ('PHYSICAL_ISOLATION', 'PHYSICAL_ISOLATION'), ('LOGICAL_ISOLATION', 'LOGICAL_ISOLATION'), ('PROCESS_ISOLATION', 'PROCESS_ISOLATION'), ('PHYSICAL_MEMORY_ISOLATION', 'PHYSICAL_MEMORY_ISOLATION'), ('PHYSICAL_NETWORK_ISOLATION', 'PHYSICAL_NETWORK_ISOLATION'), ('VIRTUAL_RESOURCE_ISOLATION', 'VIRTUAL_RESOURCE_ISOLATION'), ('NETWORK_FUNCTIONS_ISOLATION', 'NETWORK_FUNCTIONS_ISOLATION'), ('SERVICE_ISOLATION', 'SERVICE_ISOLATION')], validators=[Optional()]) + + #NI parameters + #Common for the service + NI_name = StringField('NI Name', validators=[CustomInputRequired()]) #MANDATORY PARAMETER + #NI_type = SelectField('NI Type', choices=[('L2VSI', 'L2VSI')], validators=[CustomInputRequired()]) #MANDATORY PARAMETER - FIXED VALUE -> L2VSI + NI_mtu = IntegerField('NI MTU', default=1500, validators=[CustomInputRequired(), NumberRange(min=0, message="MTU value can't be negative")]) #MANDATORY PARAMETER - FIXED VALUE -> 1500 + NI_description = StringField('NI Description', validators=[Optional()]) #OPTIONAL PARAMETER + #Device_1 specific + #Device_1_NI_VC_ID = IntegerField('Device_1 NI VC_ID', validators=[CustomInputRequired(), NumberRange(min=0, message="VC can't be negative"), validator_ADVA]) #MANDATORY PARAMETER + Device_1_NI_VC_ID = IntegerField('Device_1 NI VC_ID', validators=[CustomInputRequired(), NumberRange(min=0, message="VC can't be negative")]) #MANDATORY PARAMETER + Device_1_NI_remote_system = StringField('Device_1 NI remote_system', validators=[CustomInputRequired(),validate_ipv4_address]) #MANDATORY PARAMETER + Device_1_NI_connection_point = StringField('Device_1 NI conn_point', validators=[CustomInputRequired()]) #MANDATORY PARAMETER + #Device_2 specific + #Device_2_NI_VC_ID = IntegerField('Device_2 NI VC_ID', validators=[CustomInputRequired(), NumberRange(min=0, message="VC can't be negative"), validator_ADVA]) #MANDATORY PARAMETER + Device_2_NI_VC_ID = IntegerField('Device_2 NI VC_ID', validators=[CustomInputRequired(), NumberRange(min=0, message="VC can't be negative")]) #MANDATORY PARAMETER + Device_2_NI_remote_system = StringField ('Device_2 NI remote_system', validators=[CustomInputRequired(),validate_ipv4_address]) #MANDATORY PARAMETER + Device_2_NI_connection_point = StringField ('Device_2 NI conn_point', validators=[CustomInputRequired()]) #MANDATORY PARAMETER + + #Interface parameters (DEVICE SPECIFIC) + #Device-1 + #Device_1_IF_name = StringField ('Device_1 Interface Name', validators=[CustomInputRequired()]) #MANDATORY PARAMETER + #Device_1_IF_type = StringField ('Device_1 Interface Type', default="l2vlan", validators=[CustomInputRequired()]) #MANDATORY PARAMETER - FIXED VALUE -> l2vlan? + Device_1_IF_index = IntegerField('Device_1 SubIF Index', validators=[CustomInputRequired(), NumberRange(min=0, message="SubIf index can't be negative")]) #MANDATORY PARAMETER + Device_1_IF_vlan_id = IntegerField('Device_1 VLAN ID', validators=[CustomInputRequired(), NumberRange(min=0, message="VlanID can't be negative")]) #MANDATORY PARAMETER + Device_1_IF_mtu = IntegerField('Device_1 Interface MTU', validators=[Optional(), NumberRange(min=0, message="MTU value can't be negative")]) #OPTIONAL PARAMETER - FIXED VALUE -> 3000? + Device_1_IF_description = StringField ('Device_1 SubIF Description', validators=[Optional()]) #OPTIONAL PARAMETER + #Device-2 + #Device_2_IF_name = StringField ('Device_2 Interface Name', validators=[CustomInputRequired()]) #MANDATORY PARAMETER + #Device_2_IF_type = StringField ('Device_2 Interface Type', default="l2vlan", validators=[CustomInputRequired()]) #MANDATORY PARAMETER - FIXED VALUE -> l2vlan? + Device_2_IF_index = IntegerField('Device_2 SubIF Index', validators=[CustomInputRequired(), NumberRange(min=0, message="SubIf index can't be negative")]) #MANDATORY PARAMETER + Device_2_IF_vlan_id = IntegerField('Device_2 VLAN ID', validators=[CustomInputRequired(), NumberRange(min=0, message="VlanID can't be negative")]) #MANDATORY PARAMETER + Device_2_IF_mtu = IntegerField('Device_2 Interface MTU', validators=[Optional(), NumberRange(min=0, message="MTU value can't be negative")]) #OPTIONAL PARAMETER - FIXED VALUE -> 3000? + Device_2_IF_description = StringField ('Device_2 SubIF Description', validators=[Optional()]) #OPTIONAL PARAMETER + +class AddServiceForm_L3VPN(FlaskForm): #L3VPN - Formulary Fields + #GENERIC SERVICE PARAMETERS (COMMON & MANDATORY) + service_name = StringField('Service Name', validators=[CustomInputRequired()]) #MANDATORY PARAMETER + service_type = SelectField('Service Type', choices=[(1, '1 (L3NM)')], validators=[CustomInputRequired()]) #MANDATORY PARAMETER - FIXED VALUE -> L2NM + service_device_1 = SelectField('Device_1', choices=[('', 'Select a device (Mandatory)')], validators=[CustomInputRequired()]) #MANDATORY PARAMETER - DEVICE-1 + service_device_2 = SelectField('Device_2', choices=[('', 'Select a device (Mandatory)')], validators=[CustomInputRequired()]) #MANDATORY PARAMETER - DEVICE-2 + service_endpoint_1 = StringField('Device_1 Endpoint', validators=[CustomInputRequired()]) #MANDATORY PARAMETER - DEVICE-1 + service_endpoint_2 = StringField('Device_2 Endpoint', validators=[CustomInputRequired()]) #MANDATORY PARAMETER - DEVICE-2 + #Device_1_IF_vendor = SelectField ('Device_1 Vendor', choices=[('', 'Select a vendor (Mandatory)'),('ADVA', 'ADVA'), ('CISCO','CISCO'), ('Huawei', 'Huawei'),('Juniper', 'Juniper'),('Nokia', 'Nokia')], validators=[CustomInputRequired()]) #MANDATORY PARAMETER + #Device_2_IF_vendor = SelectField ('Device_2 Vendor', choices=[('', 'Select a vendor (Mandatory)'),('ADVA', 'ADVA'), ('CISCO','CISCO'), ('Huawei', 'Huawei'),('Juniper', 'Juniper'),('Nokia', 'Nokia')], validators=[CustomInputRequired()]) #MANDATORY PARAMETER + #Device_1_Template = SelectField ('Device_1 Template', choices=[('', 'Select a type of template (Mandatory)'),('Jinja', 'Jinja'), ('Pyangbind','Pyangbind')], validators=[CustomInputRequired()]) #MANDATORY PARAMETER - DEVICE-1 + #Device_2_Template = SelectField ('Device_2 Template', choices=[('', 'Select a type of template (Mandatory)'),('Jinja', 'Jinja'), ('Pyangbind','Pyangbind')], validators=[CustomInputRequired()]) #MANDATORY PARAMETER - DEVICE-2 + + #GENERIC SERVICE CONSTRAINT PARAMETERS (ALL OPTIONAL) + service_capacity = DecimalField('Service Capacity', places=2, default=10.00, validators=[Optional(), NumberRange(min=0)]) #OPTIONAL PARAMETER + service_latency = DecimalField('Service Latency', places=2, default=15.20, validators=[Optional(), NumberRange(min=0)]) #OPTIONAL PARAMETER + service_availability= DecimalField('Service Availability', places=2, validators=[Optional(), NumberRange(min=0)]) #OPTIONAL PARAMETER + service_isolation = SelectField('Service Isolation', choices=[('', 'Select (Optional)'), ('NO_ISOLATION', 'NO_ISOLATION'), ('PHYSICAL_ISOLATION', 'PHYSICAL_ISOLATION'), ('LOGICAL_ISOLATION', 'LOGICAL_ISOLATION'), ('PROCESS_ISOLATION', 'PROCESS_ISOLATION'), ('PHYSICAL_MEMORY_ISOLATION', 'PHYSICAL_MEMORY_ISOLATION'), ('PHYSICAL_NETWORK_ISOLATION', 'PHYSICAL_NETWORK_ISOLATION'), ('VIRTUAL_RESOURCE_ISOLATION', 'VIRTUAL_RESOURCE_ISOLATION'), ('NETWORK_FUNCTIONS_ISOLATION', 'NETWORK_FUNCTIONS_ISOLATION'), ('SERVICE_ISOLATION', 'SERVICE_ISOLATION')], validators=[Optional()]) + + ## Network Instance (NI) PARAMS + #Create a NI + NI_name = StringField('Name', validators=[InputRequired()]) #MANDATORY PARAMETER + #NI_type = SelectField('Type', choices=[('L3VRF', 'L3VRF')], validators=[InputRequired()]) #MANDATORY PARAMETER - FIXED VALUE -> L3VRF + NI_route_distinguisher = StringField('Route Distinguisher', validators=[InputRequired(),validate_route_distinguisher]) #MANDATORY PARAMETER + NI_router_id = StringField('Router ID', validators=[Optional(), validate_ipv4_address]) #OPTIONAL PARAMETER + NI_description = StringField('Description', validators=[Optional()]) #OPTIONAL PARAMETER + #Add a protocol to NI + NI_protocol = SelectField('Protocol', choices=[('', 'Select a type (Mandatory)'),('STATIC', 'STATIC'),('DIRECTLY_CONNECTED', 'DIRECTLY_CONNECTED'),('BGP', 'BGP')], validators=[InputRequired()]) + NI_as = IntegerField('AS', default=None, validators=[validate_NI_as, Optional(), validate_uint32]) + #Create Connections Table + #NI_src_protocol = SelectField('Source Protocol', choices=[('', 'Select a type'),('STATIC', 'STATIC'),('DIRECTLY_CONNECTED', 'DIRECTLY_CONNECTED'),('BGP', 'BGP')], validators=[InputRequired()]) + #NI_dst_protocol = SelectField('Destination Protocol', choices=[('', 'Select a type'),('STATIC', 'STATIC'),('DIRECTLY_CONNECTED', 'DIRECTLY_CONNECTED'),('BGP', 'BGP')], validators=[InputRequired()]) + NI_address_family = SelectField('Protocol Address Family', choices=[('', 'Select a type (Mandatory)'),('IPV4', 'IPV4'),('IPV6', 'IPV6')], validators=[InputRequired()]) + NI_default_import_policy = SelectField('Default Network Instance Import Policy', choices=[('', 'Select a policy (Mandatory)'),('ACCEPT_ROUTE', 'ACCEPT_ROUTE'),('REJECT_ROUTE', 'REJECT_ROUTE')], validators=[Optional()]) + #Associate RP to NI + NI_import_policy = StringField('Name of the Network Instance Import Policy', validators=[Optional()]) #OPTIONAL PARAMETER + NI_export_policy = StringField('Name of the Network Instance Export Policy', validators=[Optional()]) #OPTIONAL PARAMETER + + ## Interface (IF) PARAMS + #Device-1 + #Device_1_IF_name = StringField ('Device_1 Interface Name', validators=[CustomInputRequired()]) #MANDATORY PARAMETER + #Device_1_IF_type = StringField ('Device_1 Interface Type', default="l3ipvlan", validators=[CustomInputRequired()]) #MANDATORY PARAMETER - FIXED VALUE -> l3ipvlan? + Device_1_IF_index = IntegerField('Device_1 SubIF Index', validators=[CustomInputRequired(), NumberRange(min=0, message="SubIf index can't be negative")]) #MANDATORY PARAMETER + Device_1_IF_vlan_id = IntegerField('Device_1 VLAN ID', validators=[CustomInputRequired(), NumberRange(min=0, message="VlanID can't be negative")]) #MANDATORY PARAMETER + Device_1_IF_mtu = IntegerField('Device_1 Interface MTU', validators=[Optional(), NumberRange(min=0, message="MTU value can't be negative")]) #OPTIONAL PARAMETER - FIXED VALUE -> 3000? + Device_1_IF_address_ip = StringField('Device_1 IP Address', validators=[CustomInputRequired(), validate_ipv4_address]) #MANDATORY PARAMETER + Device_1_IF_address_prefix = IntegerField('Device_1 IP Prefix length', validators=[CustomInputRequired(), validate_uint32]) #MANDATORY PARAMETER + Device_1_IF_description = StringField ('Device_1 SubIF Description', validators=[Optional()]) #OPTIONAL PARAMETER + #Device-2 + #Device_2_IF_name = StringField ('Device_2 Interface Name', validators=[CustomInputRequired()]) #MANDATORY PARAMETER + #Device_2_IF_type = StringField ('Device_1 Interface Type', default="l3ipvlan", validators=[CustomInputRequired()]) #MANDATORY PARAMETER - FIXED VALUE -> l3ipvlan? + Device_2_IF_index = IntegerField('Device_2 SubIF Index', validators=[CustomInputRequired(), NumberRange(min=0, message="SubIf index can't be negative")]) #MANDATORY PARAMETER + Device_2_IF_vlan_id = IntegerField('Device_2 VLAN ID', validators=[CustomInputRequired(), NumberRange(min=0, message="VlanID can't be negative")]) #MANDATORY PARAMETER + Device_2_IF_mtu = IntegerField('Device_2 Interface MTU', validators=[Optional(), NumberRange(min=0, message="MTU value can't be negative")]) #MANDATORY PARAMETER - FIXED VALUE -> 3000? + Device_2_IF_address_ip = StringField('Device_2 IP Address', validators=[CustomInputRequired(), validate_ipv4_address]) #MANDATORY PARAMETER + Device_2_IF_address_prefix = IntegerField('Device_2 IP Prefix length', validators=[CustomInputRequired(), validate_uint32]) #MANDATORY PARAMETER + Device_2_IF_description = StringField ('Device_2 SubIF Description', validators=[Optional()]) #OPTIONAL PARAMETER + + ## Routing Policy (RP) parameters + #RP_policy_name = StringField('Policy Name', validators=[InputRequired()]) #MANDATORY PARAMETER + #RP_statement_name = StringField('Statement Name', validators=[InputRequired()]) #MANDATORY PARAMETER + #RP_policy_result = SelectField('Policy Result', choices=[(None, 'Not Defined'), ('ACCEPT_ROUTE', 'ACCEPT_ROUTE'),('REJECT_ROUTE', 'REJECT_ROUTE')], validators=[Optional()]) + #RP_ext_community_set_name = StringField('Ext Community Set Name', validators=[InputRequired()]) #MANDATORY PARAMETER + #RP_ext_community_member = StringField('Ext Community Member', validators=[InputRequired()]) #MANDATORY PARAMETER + diff --git a/src/webui/service/service/routes.py b/src/webui/service/service/routes.py index a58d08c46..d9b41b1ca 100644 --- a/src/webui/service/service/routes.py +++ b/src/webui/service/service/routes.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import re import grpc import base64, json, logging #, re from flask import current_app, redirect, render_template, Blueprint, flash, session, url_for, request @@ -24,6 +25,7 @@ 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 device.client.DeviceClient import DeviceClient from common.tools.object_factory.Service import ( json_service_l2nm_planned, json_service_l3nm_planned) from webui.service import json_to_list @@ -42,6 +44,7 @@ service = Blueprint('service', __name__, url_prefix='/service') 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 +device_client = DeviceClient() type = ["ACL_UNDEFINED", "ACL_IPV4","ACL_IPV6","ACL_L2","ACL_MPLS","ACL_MIXED"] f_action = ["UNDEFINED", "DROP","ACCEPT","REJECT"] @@ -186,116 +189,277 @@ def add_configure_ACL_IPV6(): @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 - ] + form_l2vpn = AddServiceForm_L2VPN() #Load the AddServiceForm_L3VPN form defined in forms.py + service_obj = Service() #Create a new instance of the Service class - 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 + context_uuid, topology_uuid = get_context_and_topology_uuids() #Get the topology and context UUIDS + if context_uuid and topology_uuid: #If the UUIDs exist + 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: #If the topology is defined + topo_device_uuids = {device_id.device_uuid.uuid for device_id in grpc_topology.device_ids} + devices = get_filtered_devices(context_client, topo_device_uuids) #Calls the fucntion that returns a list of devices that have UUIDs in the set of topology device UUIDs. + choices = get_device_choices(devices) #Returns a list of tuples, where each tuple contains the index of the device in the list and the name of the device + add_device_choices_to_form(choices, form_l2vpn.service_device_1) #Adds the device choices to the select options for the form (Device1) + add_device_choices_to_form(choices, form_l2vpn.service_device_2) #Adds the device choices to the select options for the form (Device2) + else: + flash('Context({:s})/Topology({:s}) not found'.format(str(context_uuid), str(topology_uuid)), 'danger') #If the topology is not found, display an error message and set the devices list to an empty list + else: + flash('Missing context or topology UUID', 'danger') #If the topology or context UUID is not found, display an error message + + if form_l2vpn.validate_on_submit(): #Check if the form has been submitted and is valid + try: #Calls a function that validates the selected devices and endpoints exists and are correct + [selected_device_1, selected_device_2, selected_endpoint_1, selected_endpoint_2] = validate_selected_devices_and_endpoints(form_l2vpn, devices) + 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 + #Check the specific values of the parameters dependent by the vendor of the device + [vendor_1, vendor_2] = get_device_vendor(form_l2vpn, devices) 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() + validate_params_vendor(form_l2vpn, vendor_1, 1) + validate_params_vendor(form_l2vpn, vendor_2, 2) + except Exception as e: + 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 + #Create definition of the Service: + service_uuid, service_type, endpoint_ids = set_service_parameters(service_obj, form_l2vpn, selected_device_1, selected_device_2, selected_endpoint_1, selected_endpoint_2) #Calls the function to set the Service - Endpoint UUIDS + constraints = add_constraints(form_l2vpn) #Calls the function to add the constraint parameters for defining a service + params_device_1_with_data = get_device_params(form_l2vpn, 1, service_type) #Calls the function that getst the parameters that will configure the service in the device-1 + params_device_2_with_data = get_device_params(form_l2vpn, 2, service_type) #Calls the function that getst the parameters that will configure the service in the device-2 + print(params_device_1_with_data) + print(params_device_2_with_data) + params_settings = {} #Param settings (Defined despite it has no value) -> Avoid error + config_rules = [ #Create the configuration rules from the params_with_data + json_config_rule_set( + '/settings', params_settings + ), + json_config_rule_set( + '/device[{:s}]/endpoint[{:s}]/settings'.format(str(selected_device_1.name), str(selected_endpoint_1)), params_device_1_with_data + ), + json_config_rule_set( + '/device[{:s}]/endpoint[{:s}]/settings'.format(str(selected_device_2.name), str(selected_endpoint_2)), params_device_2_with_data + ) + ] + service_client.connect() + context_client.connect() + device_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": []" + try: + process_descriptors(descriptor_json) 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 + 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 #Call the process_descriptors function to add the new service defined in the descriptor_json variable + except Exception as e: 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) + finally: + context_client.close() + device_client.close() + service_client.close() 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'] + form_l3vpn = AddServiceForm_L3VPN() #Load the AddServiceForm_L3VPN form defined in forms.py + service_obj = Service() #Create a new instance of the Service class + + context_uuid, topology_uuid = get_context_and_topology_uuids() #Get the topology and context UUIDS + if context_uuid and topology_uuid: #If the UUIDs exist + 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: #If the topology is defined + topo_device_uuids = {device_id.device_uuid.uuid for device_id in grpc_topology.device_ids} + devices = get_filtered_devices(context_client, topo_device_uuids) #Calls the fucntion that returns a list of devices that have UUIDs in the set of topology device UUIDs. + choices = get_device_choices(devices) #Returns a list of tuples, where each tuple contains the index of the device in the list and the name of the device + add_device_choices_to_form(choices, form_l3vpn.service_device_1) #Adds the device choices to the select options for the form (Device1) + add_device_choices_to_form(choices, form_l3vpn.service_device_2) #Adds the device choices to the select options for the form (Device2) + else: + flash('Context({:s})/Topology({:s}) not found'.format(str(context_uuid), str(topology_uuid)), 'danger') #If the topology is not found, display an error message and set the devices list to an empty list + else: + flash('Missing context or topology UUID', 'danger') #If the topology or context UUID is not found, display an error message + if form_l3vpn.validate_on_submit(): - flash(f'New configuration was created', 'success') - return redirect(url_for('service.home')) - print(form_l3vpn.errors) + try: + [selected_device_1, selected_device_2, selected_endpoint_1, selected_endpoint_2] = validate_selected_devices_and_endpoints(form_l3vpn, devices) #Calls a function that validates the selected devices and endpoints exists and are correct + 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_L3VPN.html', form_l3vpn=form_l3vpn, submit_text='Add New Service') #Render the L3VPN configuration form with the previously entered data and an error message + #Create definition of the Service: + service_uuid, service_type, endpoint_ids = set_service_parameters(service_obj, form_l3vpn, selected_device_1, selected_device_2, selected_endpoint_1, selected_endpoint_2) #Calls the function to set the Service - Endpoint UUIDS + constraints = add_constraints(form_l3vpn) #Calls the function to add the constraint parameters for defining a service + params_device_1_with_data = get_device_params(form_l3vpn, 1, service_type) + params_device_2_with_data = get_device_params(form_l3vpn, 2, service_type) + params_settings = {} + config_rules = [ #Create the configuration rules from the params_with_data + json_config_rule_set( + '/settings', params_settings + ), + json_config_rule_set( + '/device[{:s}]/endpoint[{:s}]/settings'.format(str(selected_device_1.name), str(selected_endpoint_1)), params_device_1_with_data + ), + json_config_rule_set( + '/device[{:s}]/endpoint[{:s}]/settings'.format(str(selected_device_2.name), str(selected_endpoint_2)), params_device_2_with_data + ) + ] + service_client.connect() + context_client.connect() + device_client.connect() + descriptor_json = json_service_l3nm_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": []" + try: + process_descriptors(descriptor_json) + 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 #Call the process_descriptors function to add the new service defined in the descriptor_json variable + except Exception as e: + 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 + finally: + context_client.close() + device_client.close() + service_client.close() return render_template('service/configure_L3VPN.html', form_l3vpn=form_l3vpn, submit_text='Add New Service') + +##Function for creating the service +DESCRIPTOR_LOADER_NUM_WORKERS = 10 + 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) # + descriptor_loader = DescriptorLoader(descriptors, num_workers=DESCRIPTOR_LOADER_NUM_WORKERS) #Creates a descriptor_loader object + results = descriptor_loader.process() #Calls the descriptor_loader.process method and saves the result in the results variable + for message,level in compose_notifications(results): #Retrieve the notifications that are obtained in the proccess + if level == 'error': + LOGGER.warning('ERROR message={:s}'.format(str(message))) #Display any error message in the LOG + flash(message, level) #Show any notification message to the user in the webUI by using flash() + +##Functions for having a higher leaver of abstraction and understanding in the code: + +def get_context_and_topology_uuids(): #Retrieve the context and topology UUIDs from the session, if they exist + context_uuid = session.get('context_uuid') + context_uuid = session.get('context_uuid') + topology_uuid = session.get('topology_uuid') + return context_uuid, topology_uuid #Return the UUIDs as a tuple, or None if either is missing + +def get_filtered_devices(context_client, topo_device_uuids): #Call the ListDevices() method on the context client to retrieve a list of all devices + grpc_devices = context_client.ListDevices(Empty()) + return [device for device in grpc_devices.devices if device.device_id.device_uuid.uuid in topo_device_uuids] #Filter the list of devices to only include those with UUIDs that appear in the topology + +def get_device_choices(devices): #Create the tuple (Number, Device) that will be added to the form + return [(i, str(device.name)) for i, device in enumerate(devices)] + +def add_device_choices_to_form(choices, form): #Add the device choices (tuple) to the select options of the correspondent form + form.choices += choices + +def validate_selected_devices_and_endpoints(form, devices): #Validates that the 2 selected devices and 2 endpoints exist and are valid. Then it returns them + selected_device_1 = devices[int(form.service_device_1.data)] #Selected_Device1 will be the one selected by the user in the previously defined form field + selected_device_2 = devices[int(form.service_device_2.data)] #Selected_Device2 will be the one selected by the user in the previously defined form field + if selected_device_1 == selected_device_2: + raise ValidationError('The devices must be different!. Please select two valid and different devices') # If it is not a valid endpoint -> Raise a Validation Error + elif form.service_endpoint_1.data not in [endpoint.name for endpoint in selected_device_1.device_endpoints]: # Check if the endpoint submitted by the user is a valid endpoint of the selected device + raise ValidationError('The selected endpoint: ' + form.service_endpoint_1.data + ' is not a valid endpoint for: '+ selected_device_1.name + '. Please select an endpoint that is available for this device') + elif form.service_endpoint_2.data not in [endpoint.name for endpoint in selected_device_2.device_endpoints]: # Check if the endpoint submitted by the user is a valid endpoint of the selected device + raise ValidationError('The selected endpoint: ' + form.service_endpoint_2.data + ' is not a valid endpoint for: '+ selected_device_2.name + '. Please select an endpoint that is available for this device') + else: + selected_endpoint_1 = form.service_endpoint_1.data #If the selected endpoint is valid, save it in a variable + selected_endpoint_2 = form.service_endpoint_2.data #If the selected endpoint is valid, save it in a variable + return selected_device_1, selected_device_2, selected_endpoint_1, selected_endpoint_2 #Return the devices and endpoints + +def get_device_vendor(form, devices): + selected_device_1 = devices[int(form.service_device_1.data)] #Selected_Device1 will be the one selected by the user in the previously defined form field + selected_device_2 = devices[int(form.service_device_2.data)] #Selected_Device2 will be the one selected by the user in the previously defined form field + + for config_rule in selected_device_1.device_config.config_rules: + if "vendor" in config_rule.custom.resource_value: + vendor_config_rule_1 = config_rule.custom.resource_value + + for config_rule in selected_device_2.device_config.config_rules: + if "vendor" in config_rule.custom.resource_value: + vendor_config_rule_2 = config_rule.custom.resource_value + + config_rule_dict_1 = json.loads(vendor_config_rule_1) + config_rule_dict_2 = json.loads(vendor_config_rule_2) + vendor_value_1 = config_rule_dict_1["vendor"] + vendor_value_2 = config_rule_dict_2["vendor"] + return vendor_value_1, vendor_value_2 + +def validate_params_vendor(form, vendor, device_num): #num is an auxiliar variable that can be 1 or 2 for knowing if it corresponds to the first or second device + if vendor == "ADVA": + if form.NI_name.data != f"ELAN-AC:{getattr(form, f'Device_{device_num}_IF_vlan_id').data}": + raise ValidationError('For an ADVA device, the name of the Network Instance should have this name: "ELAN-AC:vlanID"') + + elif getattr(form, f'Device_{device_num}_NI_VC_ID').data != getattr(form, f'Device_{device_num}_IF_vlan_id').data: + raise ValidationError('For an ADVA device, the value of the VlanID and the value of the VC_ID must be the same') + else: + None + return None + +def set_service_parameters(service_obj, form, selected_device_1, selected_device_2, selected_endpoint_1, selected_endpoint_2): #Function to retrieve and set the service parameters for defining the service + #Service UUID: + service_obj.service_id.service_uuid.uuid = str(form.service_name.data) #Create the Service UUID (Unique Identifier of the service) from the service name + service_uuid = service_obj.service_id.service_uuid.uuid + #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.service_type.data) #Set the Service type as selected by the user in the form + service_type = service_obj.service_type + # Set the endpoint IDs + endpoint_ids = [ #Create a list containing a element that represents the Selected Device ID and the Selected Endpoint + json_endpoint_id(json_device_id(selected_device_1.name), str(selected_endpoint_1)), + json_endpoint_id(json_device_id(selected_device_2.name), str(selected_endpoint_2)) + ] + return service_uuid, service_type, endpoint_ids + +def add_constraints(form): #Function to add the constraints for a definition of a service + constraints = [] #Constraints -> Creates a list in which the constraints for the service will be added + if form.service_capacity.data: + constraints.append(json_constraint_sla_capacity(float(form.service_capacity.data))) #Capacity [Gbps] + if form.service_latency.data: + constraints.append(json_constraint_sla_latency(float(form.service_latency.data))) #Latency [ms] + if form.service_availability.data: + constraints.append(json_constraint_sla_availability(1, True, float(form.service_availability.data))) #Availability [%] + if form.service_isolation.data is not None and form.service_isolation.data != '': + constraints.append(json_constraint_sla_isolation([getattr(IsolationLevelEnum, str(form.service_isolation.data))])) #Isolation (Predefined values) + + return constraints #Returns a list with the constraints and values + +def get_device_params(form, device_num, form_type): #Function to retrieve and set the device parameters for defining the service + if form_type == 2: #Type2 = L2NM + device_params = { + 'ni_name': str(getattr(form, 'NI_name').data), + 'sub_interface_index': str(getattr(form, f'Device_{device_num}_IF_index').data), + 'vlan_id': str(getattr(form, f'Device_{device_num}_IF_vlan_id').data), + 'remote_router': str(getattr(form, f'Device_{device_num}_NI_remote_system').data), + 'circuit_id': str(getattr(form, f'Device_{device_num}_NI_VC_ID').data), + 'mtu': str(getattr(form, f'Device_{device_num}_IF_mtu').data), + 'ni_description': str(getattr(form, 'NI_description').data), + 'subif_description': str(getattr(form, f'Device_{device_num}_IF_description').data), + } + elif form_type == 1: #Type1 = L3NM + if device_num == 1: + policy_az_field = 'NI_import_policy' + policy_za_field = 'NI_export_policy' + elif device_num == 2: + policy_az_field = 'NI_export_policy' + policy_za_field = 'NI_import_policy' + device_params = { + 'ni_name': str(getattr(form, 'NI_name').data), + 'bgp_as':str(getattr(form, 'NI_as').data), + 'route_distinguisher': str(getattr(form, 'NI_route_distinguisher').data), + 'sub_interface_index': str(getattr(form, f'Device_{device_num}_IF_index').data), + 'router_id': str(getattr(form, 'NI_router_id').data), + 'vlan_id': str(getattr(form, f'Device_{device_num}_IF_vlan_id').data), + 'address_ip': str(getattr(form, f'Device_{device_num}_IF_address_ip').data), + 'address_prefix': str(getattr(form, f'Device_{device_num}_IF_address_prefix').data), + 'policy_AZ': str(getattr(form, policy_az_field).data), + 'policy_ZA': str(getattr(form, policy_za_field).data), + 'mtu': str(getattr(form, f'Device_{device_num}_IF_mtu').data), + 'ni_description': str(getattr(form, 'NI_description').data), + 'subif_description': str(getattr(form, f'Device_{device_num}_IF_description').data), + } + else: + raise ValueError(f'Unsupported form type: {form_type}') + params_with_data = {k: v for k, v in device_params.items() if v is not None and str(v) != 'None' and v != ''} #Retrieve the params that do not have value (None or ' ') + return params_with_data diff --git a/src/webui/service/templates/service/add.html b/src/webui/service/templates/service/add.html new file mode 100644 index 000000000..4f9aaa699 --- /dev/null +++ b/src/webui/service/templates/service/add.html @@ -0,0 +1,53 @@ +<!-- + 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>Add New Service</h1> +<form method="POST" action="{{ url_for('service.add') }}"> + <fieldset> + + <div class="row mb-3"> + {{ form_1.hidden_tag() }} + </div> + <div class="row mb-3"> + {{ form_1.service_type.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_1.service_type.errors %} + {{ form_1.service_type(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_1.service_type.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_1.service_type(class="form-control") }} + {% endif %} + </div> + </div> + <button type="submit" class="btn btn-primary"> + <i class="bi bi-plus-circle-fill"></i> + {{ submit_text }} + </button> + <button type="button" class="btn btn-block btn-secondary" onclick="javascript: history.back()"> + <i class="bi bi-box-arrow-in-left"></i> + Cancel + </button> + </div> + </fieldset> +</form> +{% endblock %} + diff --git a/src/webui/service/templates/service/configure_ACL_IPV4.html b/src/webui/service/templates/service/configure_ACL_IPV4.html new file mode 100644 index 000000000..eb854b35e --- /dev/null +++ b/src/webui/service/templates/service/configure_ACL_IPV4.html @@ -0,0 +1,281 @@ +<!-- + 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>Add New Service [ACL-IPV4]</h1> +<form method="POST" action="{{ url_for('service.add_configure_ACL_IPV4') }}"> + <fieldset> + <div class="row mb-3"> + {{ form_acl.hidden_tag() }} + </div> + <h4>Generic ACL Parameters</h4> + {% if form_acl.acl_params is not none %} + <div class="row mb-3"> + {{ form_acl.name.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.name.errors %} + {{ form_acl.name(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_acl.name.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.name(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_acl.type.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.type.errors %} + {{ form_acl.type(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_acl.type.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.type(class="form-control") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_acl.sequence_id.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.sequence_id.errors %} + {{ form_acl.sequence_id(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_acl.sequence_id.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.sequence_id(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_acl.forwarding_action.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.forwarding_action.errors %} + {{ form_acl.forwarding_action(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_acl.forwarding_action.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.forwarding_action(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_acl.log_action.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.log_action.errors %} + {{ form_acl.log_action(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_acl.log_action.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.log_action(class="form-control") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_acl.traffic_flow.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.traffic_flow.errors %} + {{ form_acl.traffic_flow(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_acl.traffic_flow.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.traffic_flow(class="form-control") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_acl.interface.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.interface.errors %} + {{ form_acl.interface(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_acl.interface.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.interface(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_acl.subinterface.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.subinterface.errors %} + {{ form_acl.subinterface(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_acl.subinterface.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.subinterface(class="form-control") }} + {% endif %} + </div> + </div> + </br> + <h4>Specific ACL_IPV4 Parameters</h4> + <div class="row mb-3"> + {{ form_acl.source_address.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.source_address.errors %} + {{ form_acl.source_address(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_acl.source_address.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.source_address(class="form-control") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_acl.destination_address.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.destination_address.errors %} + {{ form_acl.destination_address(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_acl.destination_address.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.destination_address(class="form-control") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_acl.protocol.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.protocol.errors %} + {{ form_acl.protocol(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_acl.protocol.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.protocol(class="form-control") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_acl.hop_limit.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.hop_limit.errors %} + {{ form_acl.hop_limit(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_acl.hop_limit.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.hop_limit(class="form-control") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_acl.dscp.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.dscp.errors %} + {{ form_acl.dscp(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_acl.dscp.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.dscp(class="form-control") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_acl.source_port.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.source_port.errors %} + {{ form_acl.source_port(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_acl.source_port.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.source_port(class="form-control") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_acl.destination_port.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.destination_port.errors %} + {{ form_acl.destination_port(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_acl.destination_port.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.destination_port(class="form-control") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_acl.tcp_flags.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.tcp_flags.errors %} + {{ form_acl.tcp_flags(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_acl.tcp_flags.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.tcp_flags(class="form-control") }} + {% endif %} + </div> + </div> + {% endif %} + <button type="submit" class="btn btn-primary"> + <i class="bi bi-plus-circle-fill"></i> + {{ submit_text }} + </button> + <button type="button" class="btn btn-block btn-secondary" onclick="javascript: history.back()"> + <i class="bi bi-box-arrow-in-left"></i> + Cancel + </button> + </fieldset> + </form> + {% endblock %} diff --git a/src/webui/service/templates/service/configure_ACL_IPV6.html b/src/webui/service/templates/service/configure_ACL_IPV6.html new file mode 100644 index 000000000..cd6b13fad --- /dev/null +++ b/src/webui/service/templates/service/configure_ACL_IPV6.html @@ -0,0 +1,236 @@ +<!-- + 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>Add New Service [ACL-IPV6]</h1> +<form method="POST" action="{{ url_for('service.add_configure_ACL_IPV6') }}"> + <fieldset> + <div class="row mb-3"> + {{ form_acl.hidden_tag() }} + </div> + <h4>Generic ACL Parameters</h4> + {% if form_acl.acl_params is not none %} + <div class="row mb-3"> + {{ form_acl.name.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.name.errors %} + {{ form_acl.name(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_acl.name.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.name(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_acl.type.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.type.errors %} + {{ form_acl.type(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_acl.type.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.type(class="form-control") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_acl.sequence_id.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.sequence_id.errors %} + {{ form_acl.sequence_id(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_acl.sequence_id.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.sequence_id(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_acl.forwarding_action.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.forwarding_action.errors %} + {{ form_acl.forwarding_action(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_acl.forwarding_action.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.forwarding_action(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_acl.log_action.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.log_action.errors %} + {{ form_acl.log_action(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_acl.log_action.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.log_action(class="form-control") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_acl.traffic_flow.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.traffic_flow.errors %} + {{ form_acl.traffic_flow(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_acl.traffic_flow.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.traffic_flow(class="form-control") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_acl.interface.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.interface.errors %} + {{ form_acl.interface(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_acl.interface.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.interface(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_acl.subinterface.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.subinterface.errors %} + {{ form_acl.subinterface(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_acl.subinterface.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.subinterface(class="form-control") }} + {% endif %} + </div> + </div> + </br> + <h4>Specific ACL_IPV6 Parameters</h4> + <div class="row mb-3"> + {{ form_acl.source_address.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.source_address.errors %} + {{ form_acl.source_address(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_acl.source_address.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.source_address(class="form-control") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_acl.destination_address.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.destination_address.errors %} + {{ form_acl.destination_address(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_acl.destination_address.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.destination_address(class="form-control") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_acl.protocol.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.protocol.errors %} + {{ form_acl.protocol(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_acl.protocol.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.protocol(class="form-control") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_acl.hop_limit.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.hop_limit.errors %} + {{ form_acl.hop_limit(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_acl.hop_limit.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.hop_limit(class="form-control") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_acl.dscp.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.dscp.errors %} + {{ form_acl.dscp(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_acl.dscp.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.dscp(class="form-control") }} + {% endif %} + </div> + </div> + {% endif %} + <button type="submit" class="btn btn-primary"> + <i class="bi bi-plus-circle-fill"></i> + {{ submit_text }} + </button> + <button type="button" class="btn btn-block btn-secondary" onclick="javascript: history.back()"> + <i class="bi bi-box-arrow-in-left"></i> + Cancel + </button> + </fieldset> + </form> + {% endblock %} diff --git a/src/webui/service/templates/service/configure_ACL_L2.html b/src/webui/service/templates/service/configure_ACL_L2.html new file mode 100644 index 000000000..c41fe9d20 --- /dev/null +++ b/src/webui/service/templates/service/configure_ACL_L2.html @@ -0,0 +1,191 @@ +<!-- + 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>Add New Service [ACL-L2]</h1> +<form method="POST" action="{{ url_for('service.add_configure_ACL_L2') }}"> + <fieldset> + <div class="row mb-3"> + {{ form_acl.hidden_tag() }} + </div> + <h4>Generic ACL Parameters</h4> + {% if form_acl.acl_params is not none %} + <div class="row mb-3"> + {{ form_acl.name.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.name.errors %} + {{ form_acl.name(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_acl.name.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.name(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_acl.type.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.type.errors %} + {{ form_acl.type(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_acl.type.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.type(class="form-control") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_acl.sequence_id.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.sequence_id.errors %} + {{ form_acl.sequence_id(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_acl.sequence_id.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.sequence_id(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_acl.forwarding_action.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.forwarding_action.errors %} + {{ form_acl.forwarding_action(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_acl.forwarding_action.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.forwarding_action(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_acl.log_action.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.log_action.errors %} + {{ form_acl.log_action(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_acl.log_action.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.log_action(class="form-control") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_acl.traffic_flow.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.traffic_flow.errors %} + {{ form_acl.traffic_flow(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_acl.traffic_flow.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.traffic_flow(class="form-control") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_acl.interface.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.interface.errors %} + {{ form_acl.interface(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_acl.interface.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.interface(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_acl.subinterface.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.subinterface.errors %} + {{ form_acl.subinterface(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_acl.subinterface.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.subinterface(class="form-control") }} + {% endif %} + </div> + </div> + </br> + <h4>Specific ACL_L2 Parameters</h4> + <div class="row mb-3"> + {{ form_acl.source_mac.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.source_mac.errors %} + {{ form_acl.source_mac(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_acl.source_mac.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.source_mac(class="form-control") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_acl.destination_mac.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_acl.destination_mac.errors %} + {{ form_acl.destination_mac(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_acl.destination_mac.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_acl.destination_mac(class="form-control") }} + {% endif %} + </div> + </div> + {% endif %} + <button type="submit" class="btn btn-primary"> + <i class="bi bi-plus-circle-fill"></i> + {{ submit_text }} + </button> + <button type="button" class="btn btn-block btn-secondary" onclick="javascript: history.back()"> + <i class="bi bi-box-arrow-in-left"></i> + Cancel + </button> + </fieldset> + </form> + {% endblock %} diff --git a/src/webui/service/templates/service/configure_L2VPN.html b/src/webui/service/templates/service/configure_L2VPN.html new file mode 100644 index 000000000..a0039704b --- /dev/null +++ b/src/webui/service/templates/service/configure_L2VPN.html @@ -0,0 +1,434 @@ +<!-- + 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>Add New Service [L2VPN]</h1> +<form method="POST" action="{{ url_for('service.add_configure_L2VPN') }}"> + <fieldset> + <div class="row mb-3"> + {{ form_l2vpn.hidden_tag() }} + </div> + {% if form_l2vpn.l2vpn_params is not none %} + <h3>Generic Service Parameters</h3> + <div class="row mb-3"> + {{ form_l2vpn.service_name.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_l2vpn.service_name.errors %} + {{ form_l2vpn.service_name(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l2vpn.service_name.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l2vpn.service_name(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_l2vpn.service_type.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_l2vpn.service_type.errors %} + {{ form_l2vpn.service_type(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l2vpn.service_type.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l2vpn.service_type(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_l2vpn.service_device_1.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-4"> + {% if form_l2vpn.service_device_1.errors %} + {{ form_l2vpn.service_device_1(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l2vpn.service_device_1.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l2vpn.service_device_1(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + {{ form_l2vpn.service_device_2.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-4"> + {% if form_l2vpn.service_device_2.errors %} + {{ form_l2vpn.service_device_2(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l2vpn.service_device_2.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l2vpn.service_device_2(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_l2vpn.service_endpoint_1.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-4"> + {% if form_l2vpn.service_endpoint_1.errors %} + {{ form_l2vpn.service_endpoint_1(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l2vpn.service_endpoint_1.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l2vpn.service_endpoint_1(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + {{ form_l2vpn.service_endpoint_2.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-4"> + {% if form_l2vpn.service_endpoint_2.errors %} + {{ form_l2vpn.service_endpoint_2(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l2vpn.service_endpoint_2.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l2vpn.service_endpoint_2(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + <h3>Generic Service Constraints</h3> + <div class="row mb-3"> + {{ form_l2vpn.service_capacity.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_l2vpn.service_capacity.errors %} + {{ form_l2vpn.service_capacity(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_l2vpn.service_capacity.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l2vpn.service_capacity(class="form-control") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_l2vpn.service_latency.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_l2vpn.service_latency.errors %} + {{ form_l2vpn.service_latency(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_l2vpn.service_latency.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l2vpn.service_latency(class="form-control") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_l2vpn.service_availability.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_l2vpn.service_availability.errors %} + {{ form_l2vpn.service_availability(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_l2vpn.service_availability.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l2vpn.service_availability(class="form-control") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_l2vpn.service_isolation.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_l2vpn.service_isolation.errors %} + {{ form_l2vpn.service_isolation(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_l2vpn.service_isolation.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l2vpn.service_isolation(class="form-control") }} + {% endif %} + </div> + </div> + </br> + <h3>Specific Service Parameters</h3> + </br> + <h4>Network Instance (NI) Parameters</h4> + <div class="row mb-3"> + {{ form_l2vpn.NI_name.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_l2vpn.NI_name.errors %} + {{ form_l2vpn.NI_name(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l2vpn.NI_name.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l2vpn.NI_name(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_l2vpn.NI_mtu.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_l2vpn.NI_mtu.errors %} + {{ form_l2vpn.NI_mtu(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_l2vpn.NI_mtu.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l2vpn.NI_mtu(class="form-control") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_l2vpn.NI_description.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_l2vpn.NI_description.errors %} + {{ form_l2vpn.NI_description(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_l2vpn.NI_description.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l2vpn.NI_description(class="form-control") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_l2vpn.Device_1_NI_VC_ID.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-4"> + {% if form_l2vpn.Device_1_NI_VC_ID.errors %} + {{ form_l2vpn.Device_1_NI_VC_ID(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l2vpn.Device_1_NI_VC_ID.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l2vpn.Device_1_NI_VC_ID(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + {{ form_l2vpn.Device_2_NI_VC_ID.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-4"> + {% if form_l2vpn.Device_2_NI_VC_ID.errors %} + {{ form_l2vpn.Device_2_NI_VC_ID(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l2vpn.Device_2_NI_VC_ID.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l2vpn.Device_2_NI_VC_ID(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_l2vpn.Device_1_NI_remote_system.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-4"> + {% if form_l2vpn.Device_1_NI_remote_system.errors %} + {{ form_l2vpn.Device_1_NI_remote_system(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l2vpn.Device_1_NI_remote_system.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l2vpn.Device_1_NI_remote_system(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + {{ form_l2vpn.Device_2_NI_remote_system.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-4"> + {% if form_l2vpn.Device_2_NI_remote_system.errors %} + {{ form_l2vpn.Device_2_NI_remote_system(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l2vpn.Device_2_NI_remote_system.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l2vpn.Device_2_NI_remote_system(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_l2vpn.Device_1_NI_connection_point.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-4"> + {% if form_l2vpn.Device_1_NI_connection_point.errors %} + {{ form_l2vpn.Device_1_NI_connection_point(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l2vpn.Device_1_NI_connection_point.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l2vpn.Device_1_NI_connection_point(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + {{ form_l2vpn.Device_2_NI_connection_point.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-4"> + {% if form_l2vpn.Device_2_NI_connection_point.errors %} + {{ form_l2vpn.Device_2_NI_connection_point(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l2vpn.Device_2_NI_connection_point.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l2vpn.Device_2_NI_connection_point(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + + <h4>Interface Parameters</h4> + <div class="row mb-3"> + {{ form_l2vpn.Device_1_IF_index.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-4"> + {% if form_l2vpn.Device_1_IF_index.errors %} + {{ form_l2vpn.Device_1_IF_index(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l2vpn.Device_1_IF_index.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l2vpn.Device_1_IF_index(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + {{ form_l2vpn.Device_2_IF_index.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-4"> + {% if form_l2vpn.Device_2_IF_index.errors %} + {{ form_l2vpn.Device_2_IF_index(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l2vpn.Device_2_IF_index.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l2vpn.Device_2_IF_index(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_l2vpn.Device_1_IF_vlan_id.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-4"> + {% if form_l2vpn.Device_1_IF_vlan_id.errors %} + {{ form_l2vpn.Device_1_IF_vlan_id(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l2vpn.Device_1_IF_vlan_id.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l2vpn.Device_1_IF_vlan_id(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + {{ form_l2vpn.Device_2_IF_vlan_id.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-4"> + {% if form_l2vpn.Device_2_IF_vlan_id.errors %} + {{ form_l2vpn.Device_2_IF_vlan_id(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l2vpn.Device_2_IF_vlan_id.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l2vpn.Device_2_IF_vlan_id(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_l2vpn.Device_1_IF_mtu.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-4"> + {% if form_l2vpn.Device_1_IF_mtu.errors %} + {{ form_l2vpn.Device_1_IF_mtu(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_l2vpn.Device_1_IF_mtu.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l2vpn.Device_1_IF_mtu(class="form-control") }} + {% endif %} + </div> + {{ form_l2vpn.Device_2_IF_mtu.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-4"> + {% if form_l2vpn.Device_2_IF_mtu.errors %} + {{ form_l2vpn.Device_2_IF_mtu(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_l2vpn.Device_2_IF_mtu.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l2vpn.Device_2_IF_mtu(class="form-control") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_l2vpn.Device_1_IF_description.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-4"> + {% if form_l2vpn.Device_1_IF_description.errors %} + {{ form_l2vpn.Device_1_IF_description(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_l2vpn.Device_1_IF_description.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l2vpn.Device_1_IF_description(class="form-control") }} + {% endif %} + </div> + {{ form_l2vpn.Device_2_IF_description.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-4"> + {% if form_l2vpn.Device_2_IF_description.errors %} + {{ form_l2vpn.Device_2_IF_description(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_l2vpn.Device_2_IF_description.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l2vpn.Device_2_IF_description(class="form-control") }} + {% endif %} + </div> + </div> + {% endif %} + <button type="submit" class="btn btn-primary"> + <i class="bi bi-plus-circle-fill"></i> + {{ submit_text }} + </button> + <button type="button" class="btn btn-block btn-secondary" onclick="javascript: history.back()"> + <i class="bi bi-box-arrow-in-left"></i> + Cancel + </button> + </div> + </fieldset> +</form> +{% endblock %} \ No newline at end of file diff --git a/src/webui/service/templates/service/configure_L3VPN.html b/src/webui/service/templates/service/configure_L3VPN.html new file mode 100644 index 000000000..575eec10a --- /dev/null +++ b/src/webui/service/templates/service/configure_L3VPN.html @@ -0,0 +1,510 @@ +<!-- + 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>Add New Service [L3VPN]</h1> +<form method="POST" action="{{ url_for('service.add_configure_L3VPN') }}"> + <fieldset> + <div class="row mb-3"> + {{ form_l3vpn.hidden_tag() }} + </div> + {% if form_l3vpn.l3vpn_params is not none %} + <h3>Generic Service Parameters</h3> + <div class="row mb-3"> + {{ form_l3vpn.service_name.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_l3vpn.service_name.errors %} + {{ form_l3vpn.service_name(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l3vpn.service_name.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l3vpn.service_name(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_l3vpn.service_type.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_l3vpn.service_type.errors %} + {{ form_l3vpn.service_type(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l3vpn.service_type.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l3vpn.service_type(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_l3vpn.service_device_1.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-4"> + {% if form_l3vpn.service_device_1.errors %} + {{ form_l3vpn.service_device_1(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l3vpn.service_device_1.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l3vpn.service_device_1(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + {{ form_l3vpn.service_device_2.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-4"> + {% if form_l3vpn.service_device_2.errors %} + {{ form_l3vpn.service_device_2(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l3vpn.service_device_2.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l3vpn.service_device_2(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_l3vpn.service_endpoint_1.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-4"> + {% if form_l3vpn.service_endpoint_1.errors %} + {{ form_l3vpn.service_endpoint_1(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l3vpn.service_endpoint_1.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l3vpn.service_endpoint_1(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + {{ form_l3vpn.service_endpoint_2.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-4"> + {% if form_l3vpn.service_endpoint_2.errors %} + {{ form_l3vpn.service_endpoint_2(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l3vpn.service_endpoint_2.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l3vpn.service_endpoint_2(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + <h3>Generic Service Constraints</h3> + <div class="row mb-3"> + {{ form_l3vpn.service_capacity.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_l3vpn.service_capacity.errors %} + {{ form_l3vpn.service_capacity(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_l3vpn.service_capacity.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l3vpn.service_capacity(class="form-control") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_l3vpn.service_latency.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_l3vpn.service_latency.errors %} + {{ form_l3vpn.service_latency(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_l3vpn.service_latency.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l3vpn.service_latency(class="form-control") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_l3vpn.service_availability.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_l3vpn.service_availability.errors %} + {{ form_l3vpn.service_availability(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_l3vpn.service_availability.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l3vpn.service_availability(class="form-control") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_l3vpn.service_isolation.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_l3vpn.service_isolation.errors %} + {{ form_l3vpn.service_isolation(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_l3vpn.service_isolation.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l3vpn.service_isolation(class="form-control") }} + {% endif %} + </div> + </div> + </br> + <h3>Specific Service Parameters</h3> + </br> + <h4>Network Instance Parameters</h4> + <div class="row mb-3"> + {{ form_l3vpn.NI_name.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_l3vpn.NI_name.errors %} + {{ form_l3vpn.NI_name(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l3vpn.NI_name.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l3vpn.NI_name(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_l3vpn.NI_route_distinguisher.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_l3vpn.NI_route_distinguisher.errors %} + {{ form_l3vpn.NI_route_distinguisher(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l3vpn.NI_route_distinguisher.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l3vpn.NI_route_distinguisher(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_l3vpn.NI_protocol.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_l3vpn.NI_protocol.errors %} + {{ form_l3vpn.NI_protocol(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l3vpn.NI_protocol.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l3vpn.NI_protocol(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_l3vpn.NI_as.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_l3vpn.NI_as.errors %} + {{ form_l3vpn.NI_as(class="form-control is-invalid", placeholder="Mandatory if BGP protocol is selected") }} + <div class="invalid-feedback"> + {% for error in form_l3vpn.NI_as.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l3vpn.NI_as(class="form-control", placeholder="Mandatory if BGP protocol is selected") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_l3vpn.NI_address_family.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_l3vpn.NI_address_family.errors %} + {{ form_l3vpn.NI_address_family(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l3vpn.NI_address_family.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l3vpn.NI_address_family(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_l3vpn.NI_default_import_policy.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_l3vpn.NI_default_import_policy.errors %} + {{ form_l3vpn.NI_default_import_policy(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_l3vpn.NI_default_import_policy.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l3vpn.NI_default_import_policy(class="form-control") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_l3vpn.NI_import_policy.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_l3vpn.NI_import_policy.errors %} + {{ form_l3vpn.NI_import_policy(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l3vpn.NI_import_policy.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l3vpn.NI_import_policy(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_l3vpn.NI_export_policy.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_l3vpn.NI_export_policy.errors %} + {{ form_l3vpn.NI_export_policy(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l3vpn.NI_export_policy.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l3vpn.NI_export_policy(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_l3vpn.NI_router_id.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_l3vpn.NI_router_id.errors %} + {{ form_l3vpn.NI_router_id(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_l3vpn.NI_router_id.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l3vpn.NI_router_id(class="form-control") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_l3vpn.NI_description.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form_l3vpn.NI_description.errors %} + {{ form_l3vpn.NI_description(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_l3vpn.NI_description.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l3vpn.NI_description(class="form-control") }} + {% endif %} + </div> + </div> + <h4>Interface Parameters</h4> + <div class="row mb-3"> + {{ form_l3vpn.Device_1_IF_index.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-4"> + {% if form_l3vpn.Device_1_IF_index.errors %} + {{ form_l3vpn.Device_1_IF_index(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l3vpn.Device_1_IF_index.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l3vpn.Device_1_IF_index(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + {{ form_l3vpn.Device_2_IF_index.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-4"> + {% if form_l3vpn.Device_2_IF_index.errors %} + {{ form_l3vpn.Device_2_IF_index(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l3vpn.Device_2_IF_index.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l3vpn.Device_2_IF_index(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_l3vpn.Device_1_IF_vlan_id.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-4"> + {% if form_l3vpn.Device_1_IF_vlan_id.errors %} + {{ form_l3vpn.Device_1_IF_vlan_id(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l3vpn.Device_1_IF_vlan_id.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l3vpn.Device_1_IF_vlan_id(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + {{ form_l3vpn.Device_2_IF_vlan_id.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-4"> + {% if form_l3vpn.Device_2_IF_vlan_id.errors %} + {{ form_l3vpn.Device_2_IF_vlan_id(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l3vpn.Device_2_IF_vlan_id.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l3vpn.Device_2_IF_vlan_id(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_l3vpn.Device_1_IF_address_ip.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-4"> + {% if form_l3vpn.Device_1_IF_address_ip.errors %} + {{ form_l3vpn.Device_1_IF_address_ip(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l3vpn.Device_1_IF_address_ip.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l3vpn.Device_1_IF_address_ip(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + {{ form_l3vpn.Device_2_IF_address_ip.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-4"> + {% if form_l3vpn.Device_2_IF_address_ip.errors %} + {{ form_l3vpn.Device_2_IF_address_ip(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l3vpn.Device_2_IF_address_ip.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l3vpn.Device_2_IF_address_ip(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_l3vpn.Device_1_IF_address_prefix.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-4"> + {% if form_l3vpn.Device_1_IF_address_prefix.errors %} + {{ form_l3vpn.Device_1_IF_address_prefix(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l3vpn.Device_1_IF_address_prefix.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l3vpn.Device_1_IF_address_prefix(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + {{ form_l3vpn.Device_2_IF_address_prefix.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-4"> + {% if form_l3vpn.Device_2_IF_address_prefix.errors %} + {{ form_l3vpn.Device_2_IF_address_prefix(class="form-control is-invalid", placeholder="Mandatory") }} + <div class="invalid-feedback"> + {% for error in form_l3vpn.Device_2_IF_address_prefix.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l3vpn.Device_2_IF_address_prefix(class="form-control", placeholder="Mandatory") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_l3vpn.Device_1_IF_mtu.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-4"> + {% if form_l3vpn.Device_1_IF_mtu.errors %} + {{ form_l3vpn.Device_1_IF_mtu(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_l3vpn.Device_1_IF_mtu.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l3vpn.Device_1_IF_mtu(class="form-control") }} + {% endif %} + </div> + {{ form_l3vpn.Device_2_IF_mtu.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-4"> + {% if form_l3vpn.Device_2_IF_mtu.errors %} + {{ form_l3vpn.Device_2_IF_mtu(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_l3vpn.Device_2_IF_mtu.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l3vpn.Device_2_IF_mtu(class="form-control") }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form_l3vpn.Device_1_IF_description.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-4"> + {% if form_l3vpn.Device_1_IF_description.errors %} + {{ form_l3vpn.Device_1_IF_description(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_l3vpn.Device_1_IF_description.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l3vpn.Device_1_IF_description(class="form-control") }} + {% endif %} + </div> + {{ form_l3vpn.Device_2_IF_description.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-4"> + {% if form_l3vpn.Device_2_IF_description.errors %} + {{ form_l3vpn.Device_2_IF_description(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form_l3vpn.Device_2_IF_description.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form_l3vpn.Device_2_IF_description(class="form-control") }} + {% endif %} + </div> + </div> + {% endif %} + <button type="submit" class="btn btn-primary"> + <i class="bi bi-plus-circle-fill"></i> + {{ submit_text }} + </button> + <button type="button" class="btn btn-block btn-secondary" onclick="javascript: history.back()"> + <i class="bi bi-box-arrow-in-left"></i> + Cancel + </button> + </div> + </fieldset> +</form> +{% endblock %} \ No newline at end of file diff --git a/src/webui/service/templates/service/home.html b/src/webui/service/templates/service/home.html index 79b55c962..6633c977e 100644 --- a/src/webui/service/templates/service/home.html +++ b/src/webui/service/templates/service/home.html @@ -19,17 +19,17 @@ {% block content %} <h1>Services</h1> - <div class="row"> - <!-- <div class="col"> - <a href="{{ url_for('service.add') }}" class="btn btn-primary" style="margin-bottom: 10px;"> + <div class="row"> <!-- Button for adding a New Service --> + <div class="col"> + <a href="{{ url_for('service.add') }}" class="btn btn-primary" style="margin-bottom: 10px;"> <i class="bi bi-plus"></i> Add New Service </a> - </div> --> + </div> <div class="col"> {{ services | length }} services found in context <i>{{ session['context_uuid'] }}</i> </div> - <!-- <div class="col"> + <!-- <div class="col"> Search engine for a service in the table of services <form> <div class="input-group"> <input type="text" aria-label="Search" placeholder="Search..." class="form-control"/> @@ -39,7 +39,6 @@ </div> --> </div> - <table class="table table-striped table-hover"> <thead> <tr> -- GitLab