diff --git a/proto/acl.proto b/proto/acl.proto
index 3dba735dccf44d584a998eb02b4835bac7ceddd1..f691c3dbd102927636515fd22575b3f263943406 100644
--- a/proto/acl.proto
+++ b/proto/acl.proto
@@ -46,6 +46,7 @@ message AclMatch {
uint32 dst_port = 6;
uint32 start_mpls_label = 7;
uint32 end_mpls_label = 8;
+ string flags = 9;
}
message AclAction {
diff --git a/proto/context.proto b/proto/context.proto
index 8a6b019dc99863da32631425d954afbaf167f1b7..472d55975a925d7760d57ff93f2ac24fd6437c54 100644
--- a/proto/context.proto
+++ b/proto/context.proto
@@ -510,6 +510,7 @@ message ConfigRule_Custom {
message ConfigRule_ACL {
EndPointId endpoint_id = 1;
acl.AclRuleSet rule_set = 2;
+ string interface = 3;
}
message ConfigRule {
diff --git a/src/device/service/drivers/openconfig/OpenConfigDriver.py b/src/device/service/drivers/openconfig/OpenConfigDriver.py
index 99ae1c8db2978e9a9fa3341eeed911f72b41130d..8c6e07b3f00a975a909161006e59e89de0ceaaf3 100644
--- a/src/device/service/drivers/openconfig/OpenConfigDriver.py
+++ b/src/device/service/drivers/openconfig/OpenConfigDriver.py
@@ -226,11 +226,8 @@ def edit_config(
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, message_renderer='pyangbind')
- # str_config_messages = compose_config( # get template for configuration
- # resource_key, resource_value, delete=delete, vendor=netconf_handler.vendor, message_renderer=netconf_handler.message_renderer)
+ 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(
diff --git a/src/device/service/drivers/openconfig/templates/Tools.py b/src/device/service/drivers/openconfig/templates/Tools.py
index 3e804368085537526728168f1edc09df1c57c1f9..79bebef5179b3464c33ce7fa0663b0cd35a51fc0 100644
--- a/src/device/service/drivers/openconfig/templates/Tools.py
+++ b/src/device/service/drivers/openconfig/templates/Tools.py
@@ -61,8 +61,7 @@ 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, vendor, delete))
- # if vendor == "ADVA": result_templates.append(add_protocol_NI(data, vendor, delete))
+ if vendor == "ADVA": result_templates.append(add_protocol_NI(data, vendor, delete))
elif "table_connections" in resource_key:
result_templates.append(create_table_conns(data, delete))
elif "interface" in resource_key:
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 e36955a0d95e66f481289b7412ddaa327374e4ab..c4d494ea61a307fbb5a53780f4ab37af2e7091a4 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
@@ -116,9 +116,6 @@ def add_protocol_NI(parameters,vendor, DEL):
else:
with tag('network-instance'):
with tag('name'):text(parameters['name'])
- with tag('config'):
- with tag('name'): text(parameters['name'])
- with tag('type', 'xmlns:oc-ni-types="http://openconfig.net/yang/network-instance-types"'): text('oc-ni-types:DEFAULT_INSTANCE')
with tag('protocols'):
with tag('protocol'):
with tag('identifier', 'xmlns:oc-pol-types="http://openconfig.net/yang/policy-types"'):text('oc-pol-types:',parameters['identifier'])
@@ -126,41 +123,14 @@ def add_protocol_NI(parameters,vendor, DEL):
with tag('config'):
with tag('identifier', 'xmlns:oc-pol-types="http://openconfig.net/yang/policy-types"'):text('oc-pol-types:',parameters['identifier'])
with tag('name') :text(parameters['protocol_name'])
- with tag('enabled'): text('true')
if "BGP" in parameters['identifier']:
with tag('bgp'):
with tag('global'):
- with tag('afi-safis'):
- with tag('afi-safi'):
- with tag('afi-safi-name', 'xmlns:oc-bgp-types="http://openconfig.net/yang/bgp-types"'): text('oc-bgp-types:IPV4_UNICAST')
- with tag('config'):
- with tag('afi-safi-name', 'xmlns:oc-bgp-types="http://openconfig.net/yang/bgp-types"'): text('oc-bgp-types:IPV4_UNICAST')
- with tag('enabled'): text('true')
with tag('config'):
with tag('as') :text(parameters['as'])
- with tag('peer-groups'):
- with tag('peer-group'):
- with tag('peer-group-name'): text('IBGP')
- with tag('config'):
- with tag('peer-group-name'): text('IBGP')
- with tag('peer-as'): text(parameters['protocol_name'])
- with tag('afi-safis'):
- with tag('afi-safi'):
- with tag('afi-safi-name', 'xmlns:oc-bgp-types="http://openconfig.net/yang/bgp-types"'): text('oc-bgp-types:IPV4_UNICAST')
- with tag('config'):
- with tag('afi-safi-name', 'xmlns:oc-bgp-types="http://openconfig.net/yang/bgp-types"'): text('oc-bgp-types:IPV4_UNICAST')
- with tag('enabled'): text('true')
-
- if 'neighbors' in parameters:
- with tag('neighbors'):
- for neighbor in parameters['neighbors']:
- with tag('neighbor'):
- with tag('neighbor-address'): text(neighbor['ip_address'])
- with tag('config'):
- with tag('neighbor-address'): text(neighbor['ip_address'])
- with tag('peer-group'): text('IBGP')
- # if vendor == "ADVA":
- if True:
+ if "router-id" in parameters:
+ with tag('router-id'):text(parameters['router-id'])
+ if vendor == "ADVA":
with tag('tables'):
with tag('table'):
with tag('protocol', 'xmlns:oc-pol-types="http://openconfig.net/yang/policy-types"'):text('oc-pol-types:',parameters['identifier'])
diff --git a/src/device/service/drivers/openconfig/templates/__init__.py b/src/device/service/drivers/openconfig/templates/__init__.py
index 87eea1f0b6673c4bff3222598d81a16b383b4c3b..8df8fa28f53b291866dcc515e0211eec96d8e1f5 100644
--- a/src/device/service/drivers/openconfig/templates/__init__.py
+++ b/src/device/service/drivers/openconfig/templates/__init__.py
@@ -27,6 +27,9 @@ from .NetworkInstances import parse as parse_network_instances
from .RoutingPolicy import parse as parse_routing_policy
from .Acl import parse as parse_acl
from .Inventory import parse as parse_inventory
+from .acl.acl_adapter import acl_cr_to_dict
+from .acl.acl_adapter_ipinfusion_proprietary import acl_cr_to_dict_ipinfusion_proprietary
+
LOGGER = logging.getLogger(__name__)
ALL_RESOURCE_KEYS = [
@@ -113,16 +116,30 @@ def compose_config( # template generation
elif (message_renderer == "jinja"):
templates =[]
- template_name = '{:s}/edit_config.xml'.format(RE_REMOVE_FILTERS.sub('', resource_key))
- templates.append(JINJA_ENV.get_template(template_name))
-
if "acl_ruleset" in resource_key: # MANAGING ACLs
- templates =[]
- templates.append(JINJA_ENV.get_template('acl/acl-set/acl-entry/edit_config.xml'))
- templates.append(JINJA_ENV.get_template('acl/interfaces/ingress/edit_config.xml'))
- data : Dict[str, Any] = json.loads(resource_value)
+ if True: #vendor == 'ipinfusion': #! ipinfusion proprietary netconf receipe is used temporarily
+ enable_ingress_filter_path = 'acl/interfaces/ingress/enable_ingress_filter.xml'
+ acl_entry_path = 'acl/acl-set/acl-entry/edit_config_ipinfusion_proprietary.xml'
+ acl_ingress_path = 'acl/interfaces/ingress/edit_config_ipinfusion_proprietary.xml'
+ data : Dict[str, Any] = acl_cr_to_dict_ipinfusion_proprietary(resource_value, delete=delete)
+ else:
+ enable_ingress_filter_path = 'acl/interfaces/ingress/enable_ingress_filter.xml'
+ acl_entry_path = 'acl/acl-set/acl-entry/edit_config.xml'
+ acl_ingress_path = 'acl/interfaces/ingress/edit_config.xml'
+ data : Dict[str, Any] = acl_cr_to_dict(resource_value, delete=delete)
+ if delete: # unpair acl and interface before removing acl
+ templates.append(JINJA_ENV.get_template(acl_ingress_path))
+ templates.append(JINJA_ENV.get_template(acl_entry_path))
+ templates.append(JINJA_ENV.get_template(enable_ingress_filter_path))
+ else:
+ templates.append(JINJA_ENV.get_template(enable_ingress_filter_path))
+ templates.append(JINJA_ENV.get_template(acl_entry_path))
+ templates.append(JINJA_ENV.get_template(acl_ingress_path))
+ else:
+ template_name = '{:s}/edit_config.xml'.format(RE_REMOVE_FILTERS.sub('', resource_key))
+ templates.append(JINJA_ENV.get_template(template_name))
+ data : Dict[str, Any] = json.loads(resource_value)
operation = 'delete' if delete else 'merge'
-
return [
'{:s}'.format(
template.render(**data, operation=operation, vendor=vendor).strip())
diff --git a/src/device/service/drivers/openconfig/templates/acl/__init__.py b/src/device/service/drivers/openconfig/templates/acl/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..f80ccfd52ebfd4fa1783267201c52eb7381741bf
--- /dev/null
+++ b/src/device/service/drivers/openconfig/templates/acl/__init__.py
@@ -0,0 +1,13 @@
+# 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.
\ No newline at end of file
diff --git a/src/device/service/drivers/openconfig/templates/acl/acl-set/acl-entry/edit_config_ipinfusion_proprietary.xml b/src/device/service/drivers/openconfig/templates/acl/acl-set/acl-entry/edit_config_ipinfusion_proprietary.xml
new file mode 100644
index 0000000000000000000000000000000000000000..d0210a66c1b5d7de1a4be479cd79e9b48131e2a0
--- /dev/null
+++ b/src/device/service/drivers/openconfig/templates/acl/acl-set/acl-entry/edit_config_ipinfusion_proprietary.xml
@@ -0,0 +1,34 @@
+
+
+
+ {{name}}
+ {% if type is defined %}{{type}}{% endif %}
+
+ {{name}}
+ {% if type is defined %}{{type}}{% endif %}
+
+ {% if operation != 'delete' %}
+
+
+ {{sequence_id}}
+
+ {{sequence_id}}
+
+
+
+ {{source_address}}
+ {{destination_address}}
+ {{dscp}}
+
+ {{source_port}}
+ {{destination_port}}
+ {{tcp_flags}}
+ {{forwarding_action}}
+
+
+
+
+ {% endif %}
+
+
+
\ No newline at end of file
diff --git a/src/device/service/drivers/openconfig/templates/acl/acl_adapter.py b/src/device/service/drivers/openconfig/templates/acl/acl_adapter.py
new file mode 100644
index 0000000000000000000000000000000000000000..244c4b61609ee7566ec36758c47150588d580aaf
--- /dev/null
+++ b/src/device/service/drivers/openconfig/templates/acl/acl_adapter.py
@@ -0,0 +1,75 @@
+# 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.
+
+from typing import Dict, TypedDict
+
+from ..ACL.ACL_multivendor import RULE_TYPE_MAPPING, FORWARDING_ACTION_MAPPING, LOG_ACTION_MAPPING
+
+class ACLRequestData(TypedDict):
+ name: str # acl-set name
+ type: str # acl-set type
+ sequence_id: int # acl-entry sequence-id
+ source_address: str
+ destination_address: str
+ forwarding_action: str
+ id: str # interface id
+ interface: str
+ subinterface: int
+ set_name_ingress: str # ingress-acl-set name
+ type_ingress: str # ingress-acl-set type
+ all: bool
+ dscp: int
+ protocol: int
+ tcp_flags: str
+ source_port: int
+ destination_port: int
+
+def acl_cr_to_dict(acl_cr_dict: Dict, subinterface:int = 0) -> Dict:
+ rule_set = acl_cr_dict['rule_set']
+ rule_set_entry = rule_set['entries'][0]
+ rule_set_entry_match = rule_set_entry['match']
+ rule_set_entry_action = rule_set_entry['action']
+
+ name: str = rule_set['name']
+ type: str = RULE_TYPE_MAPPING[rule_set["type"]]
+ sequence_id = rule_set_entry['sequence_id']
+ source_address = rule_set_entry_match['src_address']
+ destination_address = rule_set_entry_match['dst_address']
+ forwarding_action: str = FORWARDING_ACTION_MAPPING[rule_set_entry_action['forward_action']]
+ interface_id = acl_cr_dict['interface']
+ interface = interface_id
+ set_name_ingress = name
+ type_ingress = type
+
+ return ACLRequestData(
+ name=name,
+ type=type,
+ sequence_id=sequence_id,
+ source_address=source_address,
+ destination_address=destination_address,
+ forwarding_action=forwarding_action,
+ id=interface_id,
+ interface=interface,
+ # subinterface=subinterface,
+ set_name_ingress=set_name_ingress,
+ type_ingress=type_ingress,
+ all=True,
+ dscp=18,
+ protocol=6,
+ tcp_flags='TCP_SYN',
+ source_port=22,
+ destination_port=80
+ )
+
+
\ No newline at end of file
diff --git a/src/device/service/drivers/openconfig/templates/acl/acl_adapter_ipinfusion_proprietary.py b/src/device/service/drivers/openconfig/templates/acl/acl_adapter_ipinfusion_proprietary.py
new file mode 100644
index 0000000000000000000000000000000000000000..79db6ad98120377fca2e2ead0039370c8d2e6645
--- /dev/null
+++ b/src/device/service/drivers/openconfig/templates/acl/acl_adapter_ipinfusion_proprietary.py
@@ -0,0 +1,65 @@
+# 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.
+
+from typing import Dict, TypedDict
+
+
+RULE_TYPE_MAPPING = {
+ 'ACLRULETYPE_IPV4' : 'ip',
+}
+
+FORWARDING_ACTION_MAPPING = {
+ 'ACLFORWARDINGACTION_DROP' : 'deny',
+ 'ACLFORWARDINGACTION_ACCEPT' : 'permit',
+}
+
+class ACLRequestData(TypedDict):
+ name: str # acl-set name
+ type: str # acl-set type
+ sequence_id: int # acl-entry sequence-id
+ source_address: str
+ destination_address: str
+ forwarding_action: str
+ interface: str
+ dscp: int
+ tcp_flags: str
+ source_port: int
+ destination_port: int
+
+def acl_cr_to_dict_ipinfusion_proprietary(acl_cr_dict: Dict, delete: bool = False) -> Dict:
+ rule_set = acl_cr_dict['rule_set']
+ name: str = rule_set['name']
+ type: str = RULE_TYPE_MAPPING[rule_set["type"]]
+ interface = acl_cr_dict['interface'][5:] # remove preceding `PORT-` characters
+ if delete:
+ return ACLRequestData(name=name, type=type, interface=interface)
+ rule_set_entry = rule_set['entries'][0]
+ rule_set_entry_match = rule_set_entry['match']
+ rule_set_entry_action = rule_set_entry['action']
+
+ return ACLRequestData(
+ name=name,
+ type=type,
+ sequence_id=rule_set_entry['sequence_id'],
+ source_address=rule_set_entry_match['src_address'],
+ destination_address=rule_set_entry_match['dst_address'],
+ forwarding_action=FORWARDING_ACTION_MAPPING[rule_set_entry_action['forward_action']],
+ interface=interface,
+ dscp=rule_set_entry_match["dscp"],
+ tcp_flags=rule_set_entry_match["flags"],
+ source_port=rule_set_entry_match['src_port'],
+ destination_port=rule_set_entry_match['dst_port']
+ )
+
+
\ No newline at end of file
diff --git a/src/device/service/drivers/openconfig/templates/acl/interfaces/ingress/edit_config_ipinfusion_proprietary.xml b/src/device/service/drivers/openconfig/templates/acl/interfaces/ingress/edit_config_ipinfusion_proprietary.xml
new file mode 100644
index 0000000000000000000000000000000000000000..6e502154f16a7a9d4ce0afc0c49ab96b3a2bd979
--- /dev/null
+++ b/src/device/service/drivers/openconfig/templates/acl/interfaces/ingress/edit_config_ipinfusion_proprietary.xml
@@ -0,0 +1,26 @@
+
+
+
+ {{interface}}
+
+ {{interface}}
+
+
+
+ {% if type is defined %}{{type}}{% endif %}
+
+
+ {{name}}
+
+ {{name}}
+
+
+
+
+ {% if type is defined %}{{type}}{% endif %}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/device/service/drivers/openconfig/templates/acl/interfaces/ingress/enable_ingress_filter.xml b/src/device/service/drivers/openconfig/templates/acl/interfaces/ingress/enable_ingress_filter.xml
new file mode 100644
index 0000000000000000000000000000000000000000..274028657547dd31d20654e2a59ac11554cb01d5
--- /dev/null
+++ b/src/device/service/drivers/openconfig/templates/acl/interfaces/ingress/enable_ingress_filter.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/nbi/requirements.in b/src/nbi/requirements.in
index 6e3eb94404f9d12431c715080cf210a02c7c82f4..7a7b1cffb6b1d1d02a30893135f3294f1502fe73 100644
--- a/src/nbi/requirements.in
+++ b/src/nbi/requirements.in
@@ -24,3 +24,4 @@ pyang==2.6.0
git+https://github.com/robshakir/pyangbind.git
requests==2.27.1
werkzeug==2.3.7
+pydantic==2.6.3
diff --git a/src/nbi/service/__main__.py b/src/nbi/service/__main__.py
index 8834e45a2779c8d422ba1f9878c435f14a2f43db..2a8a2251dbe7bd66d89c2f37b1b189aa565650a6 100644
--- a/src/nbi/service/__main__.py
+++ b/src/nbi/service/__main__.py
@@ -26,6 +26,7 @@ from .rest_server.nbi_plugins.ietf_l2vpn import register_ietf_l2vpn
from .rest_server.nbi_plugins.ietf_l3vpn import register_ietf_l3vpn
from .rest_server.nbi_plugins.ietf_network import register_ietf_network
from .rest_server.nbi_plugins.ietf_network_slice import register_ietf_nss
+from .rest_server.nbi_plugins.ietf_acl import register_ietf_acl
terminate = threading.Event()
LOGGER = None
@@ -68,6 +69,7 @@ def main():
register_ietf_l3vpn(rest_server) # Registering L3VPN entrypoint
register_ietf_network(rest_server)
register_ietf_nss(rest_server) # Registering NSS entrypoint
+ register_ietf_acl(rest_server)
rest_server.start()
# Wait for Ctrl+C or termination signal
diff --git a/src/nbi/service/rest_server/nbi_plugins/etsi_bwm/Resources.py b/src/nbi/service/rest_server/nbi_plugins/etsi_bwm/Resources.py
index 394b50de8abd5a3ee7d8b8c3ffceafbb0546c2b0..3fccbbb55c8959ae0c478526d2ea24882ebfef1f 100644
--- a/src/nbi/service/rest_server/nbi_plugins/etsi_bwm/Resources.py
+++ b/src/nbi/service/rest_server/nbi_plugins/etsi_bwm/Resources.py
@@ -13,9 +13,7 @@
# limitations under the License.
import copy, deepmerge, json, logging
-from typing import Dict
from common.Constants import DEFAULT_CONTEXT_NAME
-from werkzeug.exceptions import UnsupportedMediaType
from context.client.ContextClient import ContextClient
from flask_restful import Resource, request
from service.client.ServiceClient import ServiceClient
@@ -39,20 +37,15 @@ class BwInfo(_Resource):
return bw_allocations
def post(self):
- if not request.is_json:
- raise UnsupportedMediaType('JSON payload is required')
- request_data: Dict = request.get_json()
- service = bwInfo_2_service(self.client, request_data)
+ bwinfo = request.get_json()
+ service = bwInfo_2_service(self.client, bwinfo)
stripped_service = copy.deepcopy(service)
stripped_service.ClearField('service_endpoint_ids')
stripped_service.ClearField('service_constraints')
stripped_service.ClearField('service_config')
- try:
- response = format_grpc_to_json(self.service_client.CreateService(stripped_service))
- response = format_grpc_to_json(self.service_client.UpdateService(service))
- except Exception as e: # pylint: disable=broad-except
- return e
+ response = format_grpc_to_json(self.service_client.CreateService(stripped_service))
+ response = format_grpc_to_json(self.service_client.UpdateService(service))
return response
diff --git a/src/nbi/service/rest_server/nbi_plugins/etsi_bwm/Tools.py b/src/nbi/service/rest_server/nbi_plugins/etsi_bwm/Tools.py
index d3be769c93614cfd46a08fbd238743d90f24ca85..a78d2819317623b9ccaaec62e808f6435c88b630 100644
--- a/src/nbi/service/rest_server/nbi_plugins/etsi_bwm/Tools.py
+++ b/src/nbi/service/rest_server/nbi_plugins/etsi_bwm/Tools.py
@@ -14,35 +14,22 @@
import json
import logging
-import re
import time
from decimal import ROUND_HALF_EVEN, Decimal
from flask.json import jsonify
from common.proto.context_pb2 import (
- ContextId, Empty, EndPointId, ServiceId, ServiceTypeEnum, Service, ServiceStatusEnum, Constraint, Constraint_SLA_Capacity,
+ ContextId, Empty, EndPointId, ServiceId, ServiceTypeEnum, Service, Constraint, Constraint_SLA_Capacity,
ConfigRule, ConfigRule_Custom, ConfigActionEnum)
from common.tools.grpc.Tools import grpc_message_to_json
-from common.tools.grpc.ConfigRules import update_config_rule_custom
from common.tools.object_factory.Context import json_context_id
from common.tools.object_factory.Service import json_service_id
LOGGER = logging.getLogger(__name__)
-ENDPOINT_SETTINGS_KEY = '/device[{:s}]/endpoint[{:s}]/vlan[{:d}]/settings'
-RE_CONFIG_RULE_IF_SUBIF = re.compile(r'^\/interface\[([^\]]+)\]\/subinterface\[([^\]]+)\]$')
-MEC_CONSIDERED_FIELDS = ['requestType', 'sessionFilter', 'fixedAllocation', 'allocationDirection']
-ALLOCATION_DIRECTION_DESCRIPTIONS = {
- '00' : 'Downlink (towards the UE)',
- '01' : 'Uplink (towards the application/session)',
- '10' : 'Symmetrical'}
-VLAN_TAG = 0
-PREFIX_LENGTH = 24
-BGP_AS = 65000
-policy_AZ = 'srv_{:d}_a'.format(VLAN_TAG)
-policy_ZA = 'srv_{:d}_b'.format(VLAN_TAG)
def service_2_bwInfo(service: Service) -> dict:
response = {}
+ # allocationDirection = '??' # String: 00 = Downlink (towards the UE); 01 = Uplink (towards the application/session); 10 = Symmetrical
response['appInsId'] = service.service_id.service_uuid.uuid # String: Application instance identifier
for constraint in service.service_constraints:
if constraint.WhichOneof('constraint') == 'sla_capacity':
@@ -68,108 +55,47 @@ def service_2_bwInfo(service: Service) -> dict:
return response
-def bwInfo_2_service(client, bw_info: dict) -> Service:
- # add description to allocationDirection code
- if ad_code := bw_info.get('allocationDirection'):
- bw_info['allocationDirection'] = {'code': ad_code, 'description': ALLOCATION_DIRECTION_DESCRIPTIONS[ad_code]}
- if 'sessionFilter' in bw_info:
- bw_info['sessionFilter'] = bw_info['sessionFilter'][0] # Discard other items in sessionFilter field
-
+def bwInfo_2_service(client, bwInfo: dict) -> Service:
service = Service()
-
- service_config_rules = service.service_config.config_rules
-
- route_distinguisher = '{:5d}:{:03d}'.format(BGP_AS, VLAN_TAG)
- settings_cr_key = '/settings'
- settings_cr_value = {'bgp_as':(BGP_AS, True), 'route_distinguisher': (route_distinguisher, True)}
- update_config_rule_custom(service_config_rules, settings_cr_key, settings_cr_value)
-
- request_cr_key = '/request'
- request_cr_value = {k:bw_info[k] for k in MEC_CONSIDERED_FIELDS}
-
- config_rule = ConfigRule()
- config_rule.action = ConfigActionEnum.CONFIGACTION_SET
- config_rule_custom = ConfigRule_Custom()
- config_rule_custom.resource_key = request_cr_key
- config_rule_custom.resource_value = json.dumps(request_cr_value)
- config_rule.custom.CopyFrom(config_rule_custom)
- service_config_rules.append(config_rule)
-
- if 'sessionFilter' in bw_info:
- a_ip = bw_info['sessionFilter']['sourceIp']
- z_ip = bw_info['sessionFilter']['dstAddress']
+ for key in ['allocationDirection', 'fixedBWPriority', 'requestType', 'timeStamp', 'sessionFilter']:
+ if key not in bwInfo:
+ continue
+ config_rule = ConfigRule()
+ config_rule.action = ConfigActionEnum.CONFIGACTION_SET
+ config_rule_custom = ConfigRule_Custom()
+ config_rule_custom.resource_key = key
+ if key != 'sessionFilter':
+ config_rule_custom.resource_value = str(bwInfo[key])
+ else:
+ config_rule_custom.resource_value = json.dumps(bwInfo[key])
+ config_rule.custom.CopyFrom(config_rule_custom)
+ service.service_config.config_rules.append(config_rule)
+
+ if 'sessionFilter' in bwInfo:
+ a_ip = bwInfo['sessionFilter'][0]['sourceIp']
+ z_ip = bwInfo['sessionFilter'][0]['dstAddress']
devices = client.ListDevices(Empty()).devices
- router_id_counter = 1
for device in devices:
- device_endpoint_uuids = {ep.name:ep.endpoint_id.endpoint_uuid.uuid for ep in device.device_endpoints}
for cr in device.device_config.config_rules:
- if cr.WhichOneof('config_rule') != 'custom':
- continue
- match_subif = RE_CONFIG_RULE_IF_SUBIF.match(cr.custom.resource_key)
- if not match_subif:
- continue
- address_ip = json.loads(cr.custom.resource_value).get('address_ip')
- if address_ip not in [a_ip, z_ip]:
- continue
- port_name = 'PORT-' + match_subif.groups(0)[0] # `PORT-` added as prefix
- ep_id = EndPointId()
- ep_id.endpoint_uuid.uuid = device_endpoint_uuids[port_name]
- ep_id.device_id.device_uuid.uuid = device.device_id.device_uuid.uuid
- service.service_endpoint_ids.append(ep_id)
-
- # add interface config rules
- endpoint_settings_key = ENDPOINT_SETTINGS_KEY.format(device.name, port_name, VLAN_TAG)
- if address_ip == a_ip:
- field_updates = {
- 'address_ip': (address_ip, True),
- # 'router_id': ('.'.join([str(router_id_counter)]*4), True),
- 'router_id': ('200.1.1.1', True),
- 'neighbor_address_ip': ('192.168.150.2', True),
- 'route_distinguisher': (route_distinguisher, True),
- 'sub_interface_index': (0, True),
- 'vlan_id' : (VLAN_TAG, True),
- # 'bgp_as': (BGP_AS+router_id_counter, True),
- 'bgp_as': (BGP_AS, True),
- 'ip_address': (address_ip, True),
- 'prefix_length': (PREFIX_LENGTH, True),
- 'policy_AZ' : (policy_AZ, True),
- 'policy_ZA' : (policy_ZA, True),
- 'address_prefix' : (PREFIX_LENGTH, True),
- }
- elif address_ip == z_ip:
- field_updates = {
- 'address_ip': (address_ip, True),
- # 'router_id': ('.'.join([str(router_id_counter)]*4), True),
- 'router_id': ('200.1.1.2', True),
- 'neighbor_address_ip': ('192.168.150.1', True),
- 'route_distinguisher': (route_distinguisher, True),
- 'sub_interface_index': (0, True),
- 'vlan_id' : (VLAN_TAG, True),
- # 'bgp_as': (BGP_AS+router_id_counter, True),
- 'bgp_as': (BGP_AS, True),
- 'ip_address': (address_ip, True),
- 'prefix_length': (PREFIX_LENGTH, True),
- 'policy_AZ' : (policy_ZA, True),
- 'policy_ZA' : (policy_AZ, True),
- 'address_prefix' : (PREFIX_LENGTH, True),
- }
- router_id_counter += 1
- LOGGER.debug(f'BEFORE UPDATE -> device.device_config.config_rules: {service_config_rules}')
- update_config_rule_custom(service_config_rules, endpoint_settings_key, field_updates)
- LOGGER.debug(f'AFTER UPDATE -> device.device_config.config_rules: {service_config_rules}')
-
- service.service_status.service_status = ServiceStatusEnum.SERVICESTATUS_PLANNED
+ if cr.WhichOneof('config_rule') == 'custom' and cr.custom.resource_key == '_connect/settings':
+ for ep in json.loads(cr.custom.resource_value)['endpoints']:
+ if 'ip' in ep and (ep['ip'] == a_ip or ep['ip'] == z_ip):
+ ep_id = EndPointId()
+ ep_id.endpoint_uuid.uuid = ep['uuid']
+ ep_id.device_id.device_uuid.uuid = device.device_id.device_uuid.uuid
+ service.service_endpoint_ids.append(ep_id)
+
service.service_type = ServiceTypeEnum.SERVICETYPE_L3NM
- if 'appInsId' in bw_info:
- service.service_id.service_uuid.uuid = bw_info['appInsId']
+ if 'appInsId' in bwInfo:
+ service.service_id.service_uuid.uuid = bwInfo['appInsId']
service.service_id.context_id.context_uuid.uuid = 'admin'
- service.name = bw_info['appInsId']
+ service.name = bwInfo['appInsId']
- if 'fixedAllocation' in bw_info:
+ if 'fixedAllocation' in bwInfo:
capacity = Constraint_SLA_Capacity()
- capacity.capacity_gbps = float(bw_info['fixedAllocation']) / 1.e9
+ capacity.capacity_gbps = float(bwInfo['fixedAllocation']) / 1.e9
constraint = Constraint()
constraint.sla_capacity.CopyFrom(capacity)
service.service_constraints.append(constraint)
diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_acl/__init__.py b/src/nbi/service/rest_server/nbi_plugins/ietf_acl/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..6c1353bff1ee848a176106c698c5d42d90806d56
--- /dev/null
+++ b/src/nbi/service/rest_server/nbi_plugins/ietf_acl/__init__.py
@@ -0,0 +1,42 @@
+# 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.
+
+from flask_restful import Resource
+
+from nbi.service.rest_server.nbi_plugins.ietf_acl.acl_service import ACL
+from nbi.service.rest_server.nbi_plugins.ietf_acl.acl_services import ACLs
+from nbi.service.rest_server.RestServer import RestServer
+
+URL_PREFIX = "/restconf/data"
+
+
+def __add_resource(rest_server: RestServer, resource: Resource, *urls, **kwargs):
+ urls = [(URL_PREFIX + url) for url in urls]
+ rest_server.add_resource(resource, *urls, **kwargs)
+
+
+def register_ietf_acl(rest_server: RestServer):
+ __add_resource(
+ rest_server,
+ ACLs,
+ "/device=/ietf-access-control-list:acls",
+ "/device=/ietf-access-control-list:acls",
+ )
+
+ __add_resource(
+ rest_server,
+ ACL,
+ "/device=/ietf-access-control-list:acl=",
+ "/device=/ietf-access-control-list:acl=/",
+ )
diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_acl/acl_service.py b/src/nbi/service/rest_server/nbi_plugins/ietf_acl/acl_service.py
new file mode 100644
index 0000000000000000000000000000000000000000..466a68efc8b6c966dd3a282fcd5a394f4dae70a8
--- /dev/null
+++ b/src/nbi/service/rest_server/nbi_plugins/ietf_acl/acl_service.py
@@ -0,0 +1,98 @@
+# 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 logging
+import re
+import json
+
+from flask_restful import Resource
+from werkzeug.exceptions import NotFound
+
+from nbi.service.rest_server.nbi_plugins.tools.Authentication import HTTP_AUTH
+from common.proto.acl_pb2 import AclRuleTypeEnum
+from common.proto.context_pb2 import (
+ ConfigActionEnum,
+ ConfigRule,
+ Device,
+ DeviceId,
+)
+from common.tools.object_factory.Device import json_device_id
+from common.tools.grpc.Tools import grpc_message_to_json_string
+from context.client.ContextClient import ContextClient
+from device.client.DeviceClient import DeviceClient
+
+
+from .ietf_acl_parser import ietf_acl_from_config_rule_resource_value
+
+LOGGER = logging.getLogger(__name__)
+
+ACL_CONIG_RULE_KEY = r'\/device\[.+\]\/endpoint\[(.+)\]/acl_ruleset\[{}\]'
+
+
+class ACL(Resource):
+ # @HTTP_AUTH.login_required
+ def get(self, device_uuid: str, acl_name: str):
+ RE_ACL_CONIG_RULE_KEY = re.compile(ACL_CONIG_RULE_KEY.format(acl_name))
+
+ context_client = ContextClient()
+ device_client = DeviceClient()
+
+ _device = context_client.GetDevice(DeviceId(**json_device_id(device_uuid)))
+
+
+ for cr in _device.device_config.config_rules:
+ if cr.WhichOneof('config_rule') == 'custom':
+ if ep_uuid_match := RE_ACL_CONIG_RULE_KEY.match(cr.custom.resource_key):
+ endpoint_uuid = ep_uuid_match.groups(0)[0]
+ resource_value_dict = json.loads(cr.custom.resource_value)
+ LOGGER.debug(f'P99: {resource_value_dict}')
+ return ietf_acl_from_config_rule_resource_value(resource_value_dict)
+ else:
+ raise NotFound(f'ACL not found')
+
+ # @HTTP_AUTH.login_required
+ def delete(self, device_uuid: str, acl_name: str):
+ RE_ACL_CONIG_RULE_KEY = re.compile(ACL_CONIG_RULE_KEY.format(acl_name))
+
+ context_client = ContextClient()
+ device_client = DeviceClient()
+
+ _device = context_client.GetDevice(DeviceId(**json_device_id(device_uuid)))
+
+
+ for cr in _device.device_config.config_rules:
+ if cr.WhichOneof('config_rule') == 'custom':
+ if ep_uuid_match := RE_ACL_CONIG_RULE_KEY.match(cr.custom.resource_key):
+ endpoint_uuid = ep_uuid_match.groups(0)[0]
+ resource_value_dict = json.loads(cr.custom.resource_value)
+ type_str = resource_value_dict['rule_set']['type']
+ interface = resource_value_dict['interface']
+ break
+ else:
+ raise NotFound(f'ACL not found')
+
+ acl_config_rule = ConfigRule()
+ acl_config_rule.action = ConfigActionEnum.CONFIGACTION_DELETE
+ acl_config_rule.acl.rule_set.name = acl_name
+ acl_config_rule.acl.interface = interface
+ acl_config_rule.acl.rule_set.type = getattr(AclRuleTypeEnum, type_str)
+ acl_config_rule.acl.endpoint_id.device_id.device_uuid.uuid = device_uuid
+ acl_config_rule.acl.endpoint_id.endpoint_uuid.uuid = endpoint_uuid
+
+ device = Device()
+ device.CopyFrom(_device)
+ del device.device_config.config_rules[:]
+ device.device_config.config_rules.append(acl_config_rule)
+ response = device_client.ConfigureDevice(device)
+ return (response.device_uuid.uuid).strip("\"\n")
diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_acl/acl_services.py b/src/nbi/service/rest_server/nbi_plugins/ietf_acl/acl_services.py
new file mode 100644
index 0000000000000000000000000000000000000000..2d03e61b6ebb518d661e0e6147e84a4d16b99a17
--- /dev/null
+++ b/src/nbi/service/rest_server/nbi_plugins/ietf_acl/acl_services.py
@@ -0,0 +1,68 @@
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+from typing import Dict
+
+from flask import request
+from flask_restful import Resource
+from werkzeug.exceptions import NotFound
+
+from common.proto.context_pb2 import Device, DeviceId
+from common.tools.grpc.Tools import grpc_message_to_json_string
+from common.tools.object_factory.Device import json_device_id
+from context.client.ContextClient import ContextClient
+from device.client.DeviceClient import DeviceClient
+
+from nbi.service.rest_server.nbi_plugins.tools.Authentication import HTTP_AUTH
+
+from .ietf_acl_parser import config_rule_from_ietf_acl
+
+LOGGER = logging.getLogger(__name__)
+
+class ACLs(Resource):
+ # @HTTP_AUTH.login_required
+ def get(self):
+ return {}
+
+ # @HTTP_AUTH.login_required
+ def post(self, device_uuid: str):
+ if not request.is_json:
+ raise UnsupportedMediaType("JSON pyload is required")
+ request_data: Dict = request.json
+ LOGGER.debug("Request: {:s}".format(str(request_data)))
+ attached_interface = request_data["ietf-access-control-list"]["acls"]['attachment-points']['interface'][0]['interface-id']
+
+ context_client = ContextClient()
+ device_client = DeviceClient()
+
+ _device = context_client.GetDevice(DeviceId(**json_device_id(device_uuid)))
+
+ for ep in _device.device_endpoints:
+ if ep.name == attached_interface:
+ endpoint_uuid = ep.endpoint_id.endpoint_uuid.uuid
+ break
+ else:
+ raise NotFound(f'interface {attached_interface} not found in device {device_uuid}')
+
+ acl_config_rule = config_rule_from_ietf_acl(request_data, device_uuid, endpoint_uuid, sequence_id=1, subinterface=0)
+
+ LOGGER.info(f"ACL Config Rule: {grpc_message_to_json_string(acl_config_rule)}")
+
+ device = Device()
+ device.CopyFrom(_device)
+ del device.device_config.config_rules[:]
+ device.device_config.config_rules.append(acl_config_rule)
+ response = device_client.ConfigureDevice(device)
+ return (response.device_uuid.uuid).strip("\"\n")
diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_acl/ietf_acl_client.py b/src/nbi/service/rest_server/nbi_plugins/ietf_acl/ietf_acl_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..79ec388a2c8cb8e3b4352bfe17866e33c7763585
--- /dev/null
+++ b/src/nbi/service/rest_server/nbi_plugins/ietf_acl/ietf_acl_client.py
@@ -0,0 +1,69 @@
+# 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 requests
+import json
+import time
+
+BASE_URL = "/restconf/data"
+POST_URL = "/device={}/ietf-access-control-list:acls"
+DELETE_URL = "/device={}/ietf-access-control-list:acl={}"
+
+class IetfTfsClient:
+ def __init__(self,
+ tfs_host: str = "10.1.1.119",
+ tfs_port: int = 80,
+ username: str = "admin",
+ password: str = "admin",
+ timeout: int = 10,
+ allow_redirects: bool = True,
+ ) -> None:
+ self.host = tfs_host
+ self.port = tfs_port
+ self.username = username
+ self.password = password
+ self.timeout = timeout
+ self.allow_redirects = allow_redirects
+
+ def post(self, device_uuid: str, ietf_acl_data: dict) -> str:
+ request_url = "http://{:s}:{:d}{:s}{:s}".format(self.host, self.port, BASE_URL, POST_URL.format(device_uuid))
+ reply = requests.request("post", request_url, timeout=self.timeout, json=ietf_acl_data, allow_redirects=self.allow_redirects)
+ return reply.text
+
+ def get(self, device_uuid: str, acl_name: str) -> str:
+ request_url = "http://{:s}:{:d}{:s}{:s}".format(self.host, self.port, BASE_URL, DELETE_URL.format(device_uuid, acl_name))
+ reply = requests.request("get", request_url, timeout=self.timeout, allow_redirects=self.allow_redirects)
+ return reply.text
+
+ def delete(self, device_uuid: str, acl_name: str) -> str:
+ request_url = "http://{:s}:{:d}{:s}{:s}".format(self.host, self.port, BASE_URL, DELETE_URL.format(device_uuid, acl_name))
+ reply = requests.request("delete", request_url, timeout=self.timeout, allow_redirects=self.allow_redirects)
+ return reply.text
+
+if __name__ == "__main__":
+ csg1_device_uuid = 'b71fd62f-e3d4-5956-93b9-3139094836cf'
+ acl_name = 'sample-ipv4-acl'
+ acl_request_path = 'src/nbi/tests/data/ietf_acl.json'
+ with open(acl_request_path, 'r') as afile:
+ acl_request_data = json.load(afile)
+
+ ietf_tfs_client = IetfTfsClient()
+ post_response = ietf_tfs_client.post(csg1_device_uuid, acl_request_data)
+ print(f"post response: {post_response}")
+ time.sleep(.5)
+ get_response = ietf_tfs_client.get(csg1_device_uuid, acl_name)
+ print(f"get response: {get_response}")
+ time.sleep(.5)
+ delete_response = ietf_tfs_client.delete(csg1_device_uuid, acl_name)
+ print(f"delete response: {delete_response}")
\ No newline at end of file
diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_acl/ietf_acl_parser.py b/src/nbi/service/rest_server/nbi_plugins/ietf_acl/ietf_acl_parser.py
new file mode 100644
index 0000000000000000000000000000000000000000..b378153f82eb4d1138a16ae0300bb8ca0a21444e
--- /dev/null
+++ b/src/nbi/service/rest_server/nbi_plugins/ietf_acl/ietf_acl_parser.py
@@ -0,0 +1,164 @@
+# 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.
+
+from typing import List, Dict, Optional, TypedDict
+from pydantic import BaseModel, Field
+
+from common.proto.acl_pb2 import AclForwardActionEnum, AclRuleTypeEnum, AclEntry
+from common.proto.context_pb2 import ConfigActionEnum, ConfigRule
+
+class Ipv4(BaseModel):
+ dscp: int = 0
+ source_ipv4_network: str = Field(serialization_alias="source-ipv4-network", default="")
+ destination_ipv4_network: str = Field(serialization_alias="destination-ipv4-network", default="")
+
+class Port(BaseModel):
+ port: int = 0
+ operator: str = "eq"
+
+class Tcp(BaseModel):
+ flags: str = ""
+ source_port: Port = Field(serialization_alias="source-port", default_factory=lambda: Port())
+ destination_port: Port = Field(serialization_alias="destination-port", default_factory=lambda: Port())
+
+class Matches(BaseModel):
+ ipv4: Ipv4 = Ipv4()
+ tcp: Tcp = Tcp()
+
+class Action(BaseModel):
+ forwarding: str = ""
+
+class Ace(BaseModel):
+ name: str = "custom_rule"
+ matches: Matches = Matches()
+ actions: Action = Action()
+
+class Aces(BaseModel):
+ ace: List[Ace] = [Ace()]
+
+class Acl(BaseModel):
+ name: str = ""
+ type: str = ""
+ aces: Aces = Aces()
+
+class Name(BaseModel):
+ name: str = ""
+
+class AclSet(BaseModel):
+ acl_set: List[Name] = Field(serialization_alias="acl-set", default=[Name()])
+
+class AclSets(BaseModel):
+ acl_sets: AclSet = Field(serialization_alias="acl-sets", default=AclSet())
+
+class Ingress(BaseModel):
+ ingress: AclSets = AclSets()
+
+class Interface(BaseModel):
+ interface_id: str = Field(serialization_alias="interface-id", default="")
+ ingress: Ingress = Ingress()
+
+class Interfaces(BaseModel):
+ interface: List[Interface] = [Interface()]
+
+class AttachmentPoints(BaseModel):
+ attachment_points: Interfaces = Field(serialization_alias="attachment-points", default=Interfaces())
+
+class Acls(BaseModel):
+ acl: List[Acl] = [Acl()]
+ attachment_points: AttachmentPoints = Field(serialization_alias="attachment-points", default=AttachmentPoints())
+
+class IETF_ACL(BaseModel):
+ acls: Acls = Acls()
+
+
+IETF_TFS_RULE_TYPE_MAPPING = {
+ "ipv4-acl-type": "ACLRULETYPE_IPV4",
+ "ipv6-acl-type": "ACLRULETYPE_IPV6",
+}
+
+IETF_TFS_FORWARDING_ACTION_MAPPING = {
+ "drop": "ACLFORWARDINGACTION_DROP",
+ "accept": "ACLFORWARDINGACTION_ACCEPT",
+}
+
+TFS_IETF_RULE_TYPE_MAPPING = {
+ "ACLRULETYPE_IPV4": "ipv4-acl-type",
+ "ACLRULETYPE_IPV6": "ipv6-acl-type",
+}
+
+TFS_IETF_FORWARDING_ACTION_MAPPING = {
+ "ACLFORWARDINGACTION_DROP": "drop",
+ "ACLFORWARDINGACTION_ACCEPT": "accept",
+}
+
+def config_rule_from_ietf_acl(
+ request: Dict,
+ device_uuid: str,
+ endpoint_uuid: str,
+ sequence_id: int,
+ subinterface: int,
+) -> ConfigRule:
+ the_acl = request["ietf-access-control-list"]["acls"]["acl"][0]
+ acl_ip_data = the_acl["aces"]["ace"][0]["matches"]["ipv4"]
+ acl_tcp_data = the_acl["aces"]["ace"][0]["matches"]["tcp"]
+ attachemnt_interface = request["ietf-access-control-list"]["acls"]['attachment-points']['interface'][0]
+ source_address = acl_ip_data["source-ipv4-network"]
+ destination_address = acl_ip_data["destination-ipv4-network"]
+ source_port = acl_tcp_data['source-port']['port']
+ destination_port = acl_tcp_data['destination-port']['port']
+ ietf_action = the_acl["aces"]["ace"][0]["actions"]["forwarding"]
+ interface_id = attachemnt_interface['interface-id']
+
+ acl_config_rule = ConfigRule()
+ acl_config_rule.action = ConfigActionEnum.CONFIGACTION_SET
+ acl_config_rule.acl.interface = interface_id
+ acl_endpoint_id = acl_config_rule.acl.endpoint_id
+ acl_endpoint_id.device_id.device_uuid.uuid = device_uuid
+ acl_endpoint_id.endpoint_uuid.uuid = endpoint_uuid
+ acl_rule_set = acl_config_rule.acl.rule_set
+ acl_rule_set.name = the_acl["name"]
+ acl_rule_set.type = getattr(AclRuleTypeEnum, IETF_TFS_RULE_TYPE_MAPPING[the_acl['type']])
+ acl_rule_set.description = (
+ f'{ietf_action} {the_acl["type"]}: {source_address}:{source_port}->{destination_address}:{destination_port}'
+ )
+ acl_entry = AclEntry()
+ acl_entry.sequence_id = sequence_id
+ acl_entry.match.src_address = source_address
+ acl_entry.match.dst_address = destination_address
+ acl_entry.match.src_port = source_port
+ acl_entry.match.dst_port = destination_port
+ acl_entry.match.dscp = acl_ip_data["dscp"]
+ acl_entry.match.flags = acl_tcp_data["flags"]
+ acl_entry.action.forward_action = getattr(AclForwardActionEnum, IETF_TFS_FORWARDING_ACTION_MAPPING[ietf_action])
+ acl_rule_set.entries.append(acl_entry)
+
+ return acl_config_rule
+
+def ietf_acl_from_config_rule_resource_value(config_rule_rv: Dict) -> Dict:
+ rule_set = config_rule_rv['rule_set']
+ acl_entry = rule_set['entries'][0]
+ match_ = acl_entry['match']
+
+ ipv4 = Ipv4(dscp=match_["dscp"], source_ipv4_network=match_["src_address"], destination_ipv4_network=match_["dst_address"])
+ tcp = Tcp(flags=match_["flags"], source_port=Port(port=match_["src_port"]), destination_port=Port(port=match_["dst_port"]))
+ matches = Matches(ipvr=ipv4, tcp=tcp)
+ aces = Aces(ace=[Ace(matches=matches, actions=Action(forwarding=TFS_IETF_FORWARDING_ACTION_MAPPING[acl_entry["action"]["forward_action"]]))])
+ acl = Acl(name=rule_set["name"], type=TFS_IETF_RULE_TYPE_MAPPING[rule_set["type"]], aces=aces)
+ acl_sets = AclSets(acl_sets=AclSet(acl_set=[Name(name=rule_set["name"])]))
+ ingress = Ingress(ingress=acl_sets)
+ interfaces = Interfaces(interface=[Interface(interface_id=config_rule_rv["interface"], ingress=ingress)])
+ acls = Acls(acl=[acl], attachment_points=AttachmentPoints(attachment_points=interfaces))
+ ietf_acl = IETF_ACL(acls=acls)
+
+ return ietf_acl.model_dump(by_alias=True)
\ No newline at end of file
diff --git a/src/nbi/tests/data/ietf_acl.json b/src/nbi/tests/data/ietf_acl.json
new file mode 100644
index 0000000000000000000000000000000000000000..3cbdd0c6705a8797c051a21aecec98f14576fcbd
--- /dev/null
+++ b/src/nbi/tests/data/ietf_acl.json
@@ -0,0 +1,56 @@
+{
+ "ietf-access-control-list": {
+ "acls": {
+ "acl": [
+ {
+ "name": "sample-ipv4-acl",
+ "type": "ipv4-acl-type",
+ "aces": {
+ "ace": [
+ {
+ "name": "rule1",
+ "matches": {
+ "ipv4": {
+ "dscp": 18,
+ "source-ipv4-network": "192.168.10.6/24",
+ "destination-ipv4-network": "192.168.20.6/24"
+ },
+ "tcp": {
+ "flags": "syn",
+ "source-port": {
+ "port": 1444,
+ "operator": "eq"
+ },
+ "destination-port": {
+ "port": 1333,
+ "operator": "eq"
+ }
+ }
+ },
+ "actions": {
+ "forwarding": "drop"
+ }
+ }
+ ]
+ }
+ }
+ ],
+ "attachment-points": {
+ "interface": [
+ {
+ "interface-id": "PORT-ce1",
+ "ingress": {
+ "acl-sets": {
+ "acl-set": [
+ {
+ "name": "sample-ipv4-acl"
+ }
+ ]
+ }
+ }
+ }
+ ]
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/pathcomp/frontend/service/algorithms/tools/ComposeConfigRules.py b/src/pathcomp/frontend/service/algorithms/tools/ComposeConfigRules.py
index e67b4aba0ad40ef7ee7e055b62509128dc35c80b..2d4ff4fd59187e3581c8426435f80bca958ad655 100644
--- a/src/pathcomp/frontend/service/algorithms/tools/ComposeConfigRules.py
+++ b/src/pathcomp/frontend/service/algorithms/tools/ComposeConfigRules.py
@@ -185,23 +185,6 @@ def compose_device_config_rules(
device_endpoint_keys = set(itertools.product(device_keys, endpoint_keys))
if len(device_endpoint_keys.intersection(endpoints_traversed)) == 0: continue
subservice_config_rules.append(config_rule)
-
- match = RE_ENDPOINT_VLAN_SETTINGS.match(config_rule.custom.resource_key)
- if match is not None:
- device_uuid_or_name = match.group(1)
- device_name_or_uuid = device_name_mapping[device_uuid_or_name]
- device_keys = {device_uuid_or_name, device_name_or_uuid}
-
- endpoint_uuid_or_name = match.group(2)
- endpoint_name_or_uuid_1 = endpoint_name_mapping[(device_uuid_or_name, endpoint_uuid_or_name)]
- endpoint_name_or_uuid_2 = endpoint_name_mapping[(device_name_or_uuid, endpoint_uuid_or_name)]
- endpoint_keys = {endpoint_uuid_or_name, endpoint_name_or_uuid_1, endpoint_name_or_uuid_2}
-
- device_endpoint_keys = set(itertools.product(device_keys, endpoint_keys))
- if len(device_endpoint_keys.intersection(endpoints_traversed)) == 0: continue
- # ! check later: vlan removed from config_rule
- config_rule.custom.resource_key = re.sub('\/vlan\[[^\]]+\]', '', config_rule.custom.resource_key)
- subservice_config_rules.append(config_rule)
else:
continue
diff --git a/src/service/service/service_handlers/l3nm_openconfig/ConfigRules.py b/src/service/service/service_handlers/l3nm_openconfig/ConfigRules.py
index 0369d6207ff659f381db76ac0742579a9eb7703f..1e4425cdbcaa6ac1f423f2c3c65889e0e8017789 100644
--- a/src/service/service/service_handlers/l3nm_openconfig/ConfigRules.py
+++ b/src/service/service/service_handlers/l3nm_openconfig/ConfigRules.py
@@ -37,7 +37,6 @@ 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
- neighbor_address_ip = json_endpoint_settings.get('neighbor_address_ip', '0.0.0.0') # '2.2.2.1'
policy_import = json_endpoint_settings.get('policy_AZ', '2' ) # 2
policy_export = json_endpoint_settings.get('policy_ZA', '7' ) # 30
@@ -47,21 +46,18 @@ def setup_config_rules(
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
- network_instance_name = 'default'
+ 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}'.format(endpoint_name[5:])
+ if_subif_name = '{:s}.{:d}'.format(endpoint_name, vlan_id)
json_config_rules = [
- # # Configure Interface (not used)
- # json_config_rule_set(
+ # Configure Interface (not used)
+ #json_config_rule_set(
# '/interface[{:s}]'.format(endpoint_name), {
# 'name': endpoint_name,
# 'description': network_interface_desc,
# 'mtu': mtu,
- # }),
+ #}),
#Create network instance
json_config_rule_set(
@@ -78,10 +74,8 @@ def setup_config_rules(
json_config_rule_set(
'/network_instance[{:s}]/protocols[BGP]'.format(network_instance_name), {
'name': network_instance_name,
- # 'protocol_name': 'BGP',
- 'protocol_name': bgp_as,
+ 'protocol_name': 'BGP',
'identifier': 'BGP',
- # 'identifier': bgp_as,
'as': bgp_as,
'router_id': router_id,
}),
@@ -107,8 +101,7 @@ def setup_config_rules(
json_config_rule_set(
'/interface[{:s}]/subinterface[{:d}]'.format(if_subif_name, sub_interface_index), {
'name' : if_subif_name,
- # 'type' :'l3ipvlan',
- 'type' :'ethernetCsmacd',
+ 'type' :'l3ipvlan',
'mtu' : mtu,
'index' : sub_interface_index,
'description' : network_subinterface_desc,
@@ -190,40 +183,6 @@ def setup_config_rules(
}),
]
- '''
- if_subif_name = '{:s}'.format(endpoint_name[5:])
-
- json_config_rules = [
-
- #Add DIRECTLY CONNECTED protocol to network instance
- json_config_rule_set(
- '/network_instance[{:s}]/protocols[DIRECTLY_CONNECTED]'.format(network_instance_name), {
- 'name': network_instance_name,
- 'identifier': 'DIRECTLY_CONNECTED',
- 'protocol_name': 'DIRECTLY_CONNECTED',
- }),
-
- # Add BGP neighbors
- json_config_rule_set(
- '/network_instance[{:s}]/protocols[BGP]'.format(network_instance_name), {
- 'name': network_instance_name,
- 'protocol_name': bgp_as,
- 'identifier': 'BGP',
- 'as': bgp_as,
- 'router_id': router_id,
- 'neighbors': [{'ip_address': neighbor_address_ip, 'remote_as': bgp_as}]
- }),
- json_config_rule_set(
- '/network_instance[{:s}]/table_connections[DIRECTLY_CONNECTED][BGP][IPV4]'.format(network_instance_name), {
- 'name' : network_instance_name,
- 'src_protocol' : 'DIRECTLY_CONNECTED',
- 'dst_protocol' : 'BGP',
- 'address_family' : 'IPV4',
- 'default_import_policy': 'ACCEPT_ROUTE',
- }),
-
- ]
-
for res_key, res_value in endpoint_acls:
json_config_rules.append(
{'action': 1, 'acl': res_value}
@@ -242,8 +201,7 @@ def teardown_config_rules(
json_endpoint_settings : Dict = endpoint_settings.value
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_short_uuid) #ELAN-AC: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)
@@ -304,10 +262,10 @@ def teardown_config_rules(
#Delete interface; automatically deletes:
# - /interface[]/subinterface[]
- # json_config_rule_delete('/interface[{:s}]/subinterface[0]'.format(if_subif_name),
- # {
- # 'name': if_subif_name,
- # }),
+ json_config_rule_delete('/interface[{:s}]/subinterface[0]'.format(if_subif_name),
+ {
+ 'name': if_subif_name,
+ }),
#Delete network instance; automatically deletes:
# - /network_instance[]/interface[]