diff --git a/src/device/service/drivers/openconfig/OpenConfigDriver.py b/src/device/service/drivers/openconfig/OpenConfigDriver.py index 0c8ef2d685edda826bad0513704dabeb61365945..86026350e2d6c135c142719af58d1abc55623ab2 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 a56fe3ea5e936d78a85fcd36d5a49094e112a37b..387cb628b4441fd0546791e9a6be5886b1ffd029 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 2b85ba60c3d27bb351c462dff1b30ad41d5cdf05..aaedd4b920dd6ad181c8dbfc7b7c0dcffa76a17a 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 039cf5b5549061a8c6a8e785e47001ac0e639363..efeff2a12461e2c0d3f99ca32f5a34e5794efd11 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 5c56ea6ec603f4e9bb3fc72d5baa47f05ea0c991..aa95f49424b797b2d2bc6908fbcfea9d981d4026 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 c2ea6e213ee8d18b4507089fb2762c913e03039a..4eb068f7a8a7a13e006e5fc60e180b0715c72d1d 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 9de6c607b1336a4b3fb43867efc16d30048177e0..748b962f66a5122b675b79f3a5ca15a73cdaf658 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 70ee430f0d82fe520b4f6b8e77a519c990d71e76..92b93ba277b1cec37e909628fb0e470a4960e667 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 903ad8cd5ae442a03d54fb49083f3837a3c8187c..2e1a0859de80cb6e1347bd02d876ec4cd6a2bff7 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 dd48fe7351e03328ddad8fdb29abf0fae09bc7b7..e98dd57904bfb99694715fa053cfc63e8ef85963 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 fca1071419b3b2b61739c2a0d1d8bfa45aba5119..7f92fc2bf98d2bd33ac04c983df5e38dbc3369a8 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 0000000000000000000000000000000000000000..f3c5e3d17abebe23e8df56aa23f1452cdc12d056 --- /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 a58d08c46cfc805caf6d270bbea92ddbc67ee235..d9b41b1ca25beb56ead0d02c2216c93a5c0d3335 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 0000000000000000000000000000000000000000..4f9aaa699fcef32a22ae2048163dff1c54198b57 --- /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 0000000000000000000000000000000000000000..eb854b35e017616eccb3d26349b73ff0d16a2b56 --- /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 0000000000000000000000000000000000000000..cd6b13fadb6cd2f1861cea3a474079f6667f3451 --- /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 0000000000000000000000000000000000000000..c41fe9d20163118ce85f9cb600d517bff0e79336 --- /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 0000000000000000000000000000000000000000..a0039704bcb09140d5cd150a7c8f5628fe266381 --- /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 0000000000000000000000000000000000000000..575eec10ad3042ea1ed154a5f21fb6811f4d4ed0 --- /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 79b55c962dcdd0af4a380928c180f6c9def75ba7..6633c977ea63752b916cb220feab80dbf510800b 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>