diff --git a/scripts/run_tests_locally-device-openconfig-ocnos.sh b/scripts/run_tests_locally-device-openconfig-ocnos.sh new file mode 100755 index 0000000000000000000000000000000000000000..60af6768d37199c957d17c6804c8af1072d0b0e1 --- /dev/null +++ b/scripts/run_tests_locally-device-openconfig-ocnos.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (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. + + +PROJECTDIR=`pwd` + +cd $PROJECTDIR/src +RCFILE=$PROJECTDIR/coverage/.coveragerc + +# Run unitary tests and analyze coverage of code at same time +# helpful pytest flags: --log-level=INFO -o log_cli=true --verbose --maxfail=1 --durations=0 +coverage run --rcfile=$RCFILE --append -m pytest --log-level=INFO -o log_cli=true --verbose \ + device/tests/test_unitary_openconfig_ocnos.py diff --git a/src/device/service/drivers/openconfig/OpenConfigDriver.py b/src/device/service/drivers/openconfig/OpenConfigDriver.py index a592b51576acc21e6dc055fe9f41e720f28aae1c..fd36e2dc40e38a125f1812f00eeb304106a40c8a 100644 --- a/src/device/service/drivers/openconfig/OpenConfigDriver.py +++ b/src/device/service/drivers/openconfig/OpenConfigDriver.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import time import json import anytree, copy, logging, pytz, queue, re, threading #import lxml.etree as ET @@ -237,6 +238,8 @@ def edit_config( test_option=test_option, error_option=error_option, format=format) if commit_per_rule: netconf_handler.commit() # configuration commit + if 'table_connections' in resource_key: + time.sleep(5) # CPU usage might exceed critical level after route redistribution, BGP daemon needs time to reload #results[i] = True results.append(True) diff --git a/src/device/service/drivers/openconfig/templates/Tools.py b/src/device/service/drivers/openconfig/templates/Tools.py index b7a5ba9c1a962032fe13b4ec5cb70eae7ff604a1..c4ef22b1e3c11f1e512026bea8e2122ab703a9e5 100644 --- a/src/device/service/drivers/openconfig/templates/Tools.py +++ b/src/device/service/drivers/openconfig/templates/Tools.py @@ -61,7 +61,8 @@ 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: - if vendor == "ADVA": result_templates.append(add_protocol_NI(data, vendor, delete)) + if vendor is None or 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/Interfaces_multivendor.py b/src/device/service/drivers/openconfig/templates/VPN/Interfaces_multivendor.py index 09e3b618a69c738b57b4d2268c0429e6f8119147..ab57ce3bd26e9183f931a1a6e13a44a9a85bef7d 100644 --- a/src/device/service/drivers/openconfig/templates/VPN/Interfaces_multivendor.py +++ b/src/device/service/drivers/openconfig/templates/VPN/Interfaces_multivendor.py @@ -54,7 +54,7 @@ def create_If_SubIf(data,vendor, DEL): with tag('enabled'):text('true') with tag('subinterfaces'): with tag('subinterface'): - if vendor == 'ADVA': + if vendor is None or vendor == 'ADVA': with tag('index'): text('0') with tag('config'): with tag('index'): text('0') @@ -65,8 +65,10 @@ def create_If_SubIf(data,vendor, DEL): with tag('single-tagged'): with tag('config'): with tag('vlan-id'):text(data['vlan_id']) - if "l3ipvlan" in data['type']: + if "l3ipvlan" in data['type'] and 'address_ip' in data: with tag('ipv4', xmlns="http://openconfig.net/yang/interfaces/ip"): + if 'mtu' in data: + with tag('mtu'):text(data['mtu']) with tag('addresses'): with tag('address'): with tag('ip'):text(data['address_ip']) 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 c2d18ef172bccaf46b4e323a1fef6ef048232888..157dd0ab89a0eb625d428dd95109faabc399bcf0 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 @@ -64,10 +64,12 @@ def create_NI(parameters,vendor,DEL): elif "L3VRF" in parameters['type']: with tag('config'): with tag('name'):text(parameters['name']) - if vendor == "ADVA": + if "router_id" in parameters: + with tag('router-id'):text(parameters['router_id']) + if vendor is None or vendor == 'ADVA': with tag('type', 'xmlns:oc-ni-types="http://openconfig.net/yang/network-instance-types"'):text('oc-ni-types:',parameters['type']) with tag('route-distinguisher'):text(parameters['route_distinguisher']) - if vendor == "ADVA": + if vendor is None or vendor == 'ADVA': with tag('encapsulation'): with tag('config'): with tag('encapsulation-type', 'xmlns:oc-ni-types="http://openconfig.net/yang/network-instance-types"') :text('oc-ni-types:MPLS') @@ -123,14 +125,29 @@ 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('name'): text(parameters['as']) with tag('global'): with tag('config'): with tag('as') :text(parameters['as']) - if "router-id" in parameters: - with tag('router-id'):text(parameters['router-id']) - if vendor == "ADVA": + if "router_id" in parameters: + with tag('router-id'):text(parameters['router_id']) + 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('afi-safis'): + with tag('afi-safi', 'xmlns:oc-bgp-types="http://openconfig.net/yang/bgp-types"'): + with tag('afi-safi-name'): text('oc-bgp-types:IPV4_UNICAST') + with tag('enabled'): text('true') + with tag('config'): + with tag('neighbor-address'): text(neighbor['ip_address']) + with tag('enabled'): text('true') + with tag('peer-as'): text(parameters['as']) + if vendor is None or 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']) @@ -177,6 +194,9 @@ def associate_If_to_NI(parameters, 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:',parameters['type']) with tag('interfaces'): with tag('interface'): with tag('id'):text(parameters['id']) @@ -315,7 +335,7 @@ def create_table_conns(parameters,DEL): with tag('table-connection','xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" nc:operation="delete"'): with tag('src-protocol','xmlns:oc-pol-types="http://openconfig.net/yang/policy-types"'): text('oc-pol-types:',parameters['src_protocol']) with tag('dst-protocol','xmlns:oc-pol-types="http://openconfig.net/yang/policy-types"'): text('oc-pol-types:',parameters['dst_protocol']) - with tag('address-family', 'xmlns:oc-types="http://openconfig.net/yang/openconfig-types"'):text('oc-types:',parameters['dst_protocol']) + with tag('address-family', 'xmlns:oc-types="http://openconfig.net/yang/openconfig-types"'):text('oc-types:',parameters['address_family']) else: with tag('table-connections'): with tag('table-connection'): @@ -326,6 +346,8 @@ def create_table_conns(parameters,DEL): with tag('src-protocol','xmlns:oc-pol-types="http://openconfig.net/yang/policy-types"'): text('oc-pol-types:',parameters['src_protocol']) with tag('dst-protocol','xmlns:oc-pol-types="http://openconfig.net/yang/policy-types"'): text('oc-pol-types:',parameters['dst_protocol']) with tag('address-family', 'xmlns:oc-types="http://openconfig.net/yang/openconfig-types"'):text('oc-types:',parameters['address_family']) + # for OCNOS: check if needed + #with tag('dst-instance', 'xmlns="http://www.ipinfusion.com/yang/ocnos/ipi-oc-ni-augments"'):text('65000') if len(parameters['default_import_policy']) != 0: with tag('default-import-policy'):text(parameters['default_import_policy']) result = indent( diff --git a/src/device/service/drivers/openconfig/templates/VPN/Routing_policy.py b/src/device/service/drivers/openconfig/templates/VPN/Routing_policy.py index 5cc8cc71de9a952eecc8b3df2d71b6d38c496eb9..69fdd2cc52ec179665b6fc5a766b04b0e6c2a6ae 100644 --- a/src/device/service/drivers/openconfig/templates/VPN/Routing_policy.py +++ b/src/device/service/drivers/openconfig/templates/VPN/Routing_policy.py @@ -133,14 +133,14 @@ data_2 = {'ext_community_member' : '65001:101', 'ext_community_set_name' : 'set_srv_101_a'} print('\nRouting Policy Statement - CREATE\n') -print(rp_statement(data_1, False)) +print(create_rp_statement(data_1, False)) print('\nRouting Policy Statement - DELETE\n') -print(rp_statement(data_1, True)) +print(create_rp_statement(data_1, True)) print('\nRouting Policy Defined Set - CREATE\n') -print(rp_defined_set(data_2, False)) +print(create_rp_def(data_2, False)) print('\nRouting Policy Defined Set - DELETE\n') -print(rp_defined_set(data_2, True)) +print(create_rp_def(data_2, True)) ''' ''' diff --git a/src/device/service/drivers/openconfig/templates/__init__.py b/src/device/service/drivers/openconfig/templates/__init__.py index 0c1a057e7c07bebb0e41e295e8d44082bc3ef236..969ca9f60bd870ca7474f8b06a8b1948a03e84f6 100644 --- a/src/device/service/drivers/openconfig/templates/__init__.py +++ b/src/device/service/drivers/openconfig/templates/__init__.py @@ -121,7 +121,8 @@ def compose_config( # template generation 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) - operation = 'delete' if delete else 'merge' + operation = 'delete' if delete else 'merge' # others + #operation = 'delete' if delete else '' # ipinfusion? return [ '{:s}'.format( diff --git a/src/device/service/drivers/openconfig/templates/interface/subinterface/edit_config.xml b/src/device/service/drivers/openconfig/templates/interface/subinterface/edit_config.xml index e441004006e4cdd445f1d0244a9582b57956af40..2d8d3ee07b3a8df20a4b51be755e18b7aec982de 100644 --- a/src/device/service/drivers/openconfig/templates/interface/subinterface/edit_config.xml +++ b/src/device/service/drivers/openconfig/templates/interface/subinterface/edit_config.xml @@ -1,5 +1,4 @@ - + {{name}} {% if operation is defined and operation != 'delete' %} @@ -31,17 +30,20 @@ {% endif %} {% if address_ip is defined %} - - - - {{address_ip}} - - {{address_ip}} - {{address_prefix}} - - - - + + + {% if mtu is defined %}{{mtu}}{% endif%} + + +
+ {{address_ip}} + + {{address_ip}} + {{address_prefix}} + +
+
+
{% endif %} diff --git a/src/device/service/drivers/openconfig/templates/network_instance/interface/edit_config.xml b/src/device/service/drivers/openconfig/templates/network_instance/interface/edit_config.xml index 855f321b4a69ba1e660487c108a05d0ec4b5d475..e926796d039d54e30f6ba13eb5eb66bcec079c08 100644 --- a/src/device/service/drivers/openconfig/templates/network_instance/interface/edit_config.xml +++ b/src/device/service/drivers/openconfig/templates/network_instance/interface/edit_config.xml @@ -1,6 +1,10 @@ {{name}} + + {{name}} + oc-ni-types:{{type}} + {{id}} diff --git a/src/device/service/drivers/openconfig/templates/network_instance/protocols/edit_config.xml b/src/device/service/drivers/openconfig/templates/network_instance/protocols/edit_config.xml index c9c068e480c0569cfe5f97b78b28fbe03e2595f8..da66d97f053f509a1a595cdb1abc0bd1791ad0bc 100644 --- a/src/device/service/drivers/openconfig/templates/network_instance/protocols/edit_config.xml +++ b/src/device/service/drivers/openconfig/templates/network_instance/protocols/edit_config.xml @@ -9,15 +9,37 @@ oc-pol-types:{{identifier}} {{protocol_name}} + true {% if identifier=='BGP' %} + {{as}} {{as}} {{router_id}} + {% if neighbors is defined %} + + {% for neighbor in neighbors %} + + {{neighbor['ip_address']}} + + + oc-bgp-types:IPV4_UNICAST + true + + + + {{neighbor['ip_address']}} + true + {{as}} + + + {% endfor %} + + {% endif %} {% endif %} {% endif %} diff --git a/src/device/service/drivers/openconfig/templates/network_instance/table_connections/edit_config.xml b/src/device/service/drivers/openconfig/templates/network_instance/table_connections/edit_config.xml index 46bf5e387789c7efc800ad96ed759748273bed34..35c535c6bd3f78e30fc2177ecc722b1115f54fc5 100644 --- a/src/device/service/drivers/openconfig/templates/network_instance/table_connections/edit_config.xml +++ b/src/device/service/drivers/openconfig/templates/network_instance/table_connections/edit_config.xml @@ -11,6 +11,9 @@ oc-pol-types:{{src_protocol}} oc-pol-types:{{dst_protocol}} oc-types:{{address_family}} + {% if False %} + {{as}} + {% endif %} {% if default_import_policy is defined %}{{default_import_policy}}{% endif %} {% endif %} diff --git a/src/device/tests/test_unitary_openconfig_ocnos.py b/src/device/tests/test_unitary_openconfig_ocnos.py new file mode 100644 index 0000000000000000000000000000000000000000..87d951581ad98147f8dd565af616fe034a346693 --- /dev/null +++ b/src/device/tests/test_unitary_openconfig_ocnos.py @@ -0,0 +1,210 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json, logging, os, pytest, time +from typing import Dict, Tuple +os.environ['DEVICE_EMULATED_ONLY'] = 'YES' + +# pylint: disable=wrong-import-position +from device.service.drivers.openconfig.OpenConfigDriver import OpenConfigDriver +#from device.service.driver_api._Driver import ( +# RESOURCE_ENDPOINTS, RESOURCE_INTERFACES, RESOURCE_NETWORK_INSTANCES, RESOURCE_ROUTING_POLICIES, RESOURCE_SERVICES +#) + +logging.basicConfig(level=logging.DEBUG) +#logging.getLogger('ncclient.operations.rpc').setLevel(logging.INFO) +#logging.getLogger('ncclient.transport.parser').setLevel(logging.INFO) + +LOGGER = logging.getLogger(__name__) + + +##### DRIVERS FIXTURE ################################################################################################## + +DEVICES = { + 'CSGW1': {'address': '10.1.1.86', 'port': 830, 'settings': { + 'username': 'ocnos', 'password': 'ocnos', + 'vendor': None, 'force_running': False, 'hostkey_verify': False, 'look_for_keys': False, 'allow_agent': False, + 'commit_per_rule': True, 'device_params': {'name': 'default'}, 'manager_params': {'timeout' : 120} + }}, + 'CSGW2': {'address': '10.1.1.87', 'port': 830, 'settings': { + 'username': 'ocnos', 'password': 'ocnos', + 'vendor': None, 'force_running': False, 'hostkey_verify': False, 'look_for_keys': False, 'allow_agent': False, + 'commit_per_rule': True, 'device_params': {'name': 'default'}, 'manager_params': {'timeout' : 120} + }}, +} + +@pytest.fixture(scope='session') +def drivers() -> Dict[str, OpenConfigDriver]: + _drivers : Dict[str, OpenConfigDriver] = dict() + for device_name, driver_params in DEVICES.items(): + driver = OpenConfigDriver(driver_params['address'], driver_params['port'], **(driver_params['settings'])) + driver.Connect() + _drivers[device_name] = driver + yield _drivers + time.sleep(1) + for _,driver in _drivers.items(): + driver.Disconnect() + + +def network_instance(ni_name, ni_type, ni_router_id=None, ni_route_distinguisher=None) -> Tuple[str, Dict]: + path = '/network_instance[{:s}]'.format(ni_name) + data = {'name': ni_name, 'type': ni_type} + if ni_router_id is not None: data['router_id'] = ni_router_id + if ni_route_distinguisher is not None: data['route_distinguisher'] = ni_route_distinguisher + return path, json.dumps(data) + +def network_instance_add_protocol_bgp(ni_name, ni_type, ni_router_id, ni_bgp_as, neighbors=[]) -> Tuple[str, Dict]: + path = '/network_instance[{:s}]/protocols[BGP]'.format(ni_name) + data = { + 'name': ni_name, 'type': ni_type, 'router_id': ni_router_id, 'identifier': 'BGP', + 'protocol_name': ni_bgp_as, 'as': ni_bgp_as + } + if len(neighbors) > 0: + data['neighbors'] = [ + {'ip_address': neighbor_ip_address, 'remote_as': neighbor_remote_as} + for neighbor_ip_address, neighbor_remote_as in neighbors + ] + return path, json.dumps(data) + +def network_instance_add_protocol_direct(ni_name, ni_type) -> Tuple[str, Dict]: + path = '/network_instance[{:s}]/protocols[DIRECTLY_CONNECTED]'.format(ni_name) + data = { + 'name': ni_name, 'type': ni_type, 'identifier': 'DIRECTLY_CONNECTED', + 'protocol_name': 'DIRECTLY_CONNECTED' + } + return path, json.dumps(data) + +def network_instance_add_protocol_static(ni_name, ni_type) -> Tuple[str, Dict]: + path = '/network_instance[{:s}]/protocols[STATIC]'.format(ni_name) + data = { + 'name': ni_name, 'type': ni_type, 'identifier': 'STATIC', + 'protocol_name': 'STATIC' + } + return path, json.dumps(data) + +#def network_instance_static_route(ni_name, prefix, next_hop, next_hop_index=0) -> Tuple[str, Dict]: +# path = '/network_instance[{:s}]/static_route[{:s}]'.format(ni_name, prefix) +# data = {'name': ni_name, 'prefix': prefix, 'next_hop': next_hop, 'next_hop_index': next_hop_index} +# return path, json.dumps(data) + +def network_instance_add_table_connection( + ni_name, src_protocol, dst_protocol, address_family, default_import_policy, bgp_as=None +) -> Tuple[str, Dict]: + path = '/network_instance[{:s}]/table_connections[{:s}][{:s}][{:s}]'.format( + ni_name, src_protocol, dst_protocol, address_family + ) + data = { + 'name': ni_name, 'src_protocol': src_protocol, 'dst_protocol': dst_protocol, + 'address_family': address_family, 'default_import_policy': default_import_policy, + } + if bgp_as is not None: data['as'] = bgp_as + return path, json.dumps(data) + +def interface( + name, index, description=None, if_type=None, vlan_id=None, mtu=None, ipv4_address_prefix=None, enabled=None +) -> Tuple[str, Dict]: + path = '/interface[{:s}]/subinterface[{:d}]'.format(name, index) + data = {'name': name, 'index': index} + if description is not None: data['description'] = description + if if_type is not None: data['type' ] = if_type + if vlan_id is not None: data['vlan_id' ] = vlan_id + if mtu is not None: data['mtu' ] = mtu + if enabled is not None: data['enabled' ] = enabled + if ipv4_address_prefix is not None: + ipv4_address, ipv4_prefix = ipv4_address_prefix + data['address_ip' ] = ipv4_address + data['address_prefix'] = ipv4_prefix + return path, json.dumps(data) + +def network_instance_interface(ni_name, ni_type, if_name, if_index) -> Tuple[str, Dict]: + path = '/network_instance[{:s}]/interface[{:s}.{:d}]'.format(ni_name, if_name, if_index) + data = {'name': ni_name, 'type': ni_type, 'id': if_name, 'interface': if_name, 'subinterface': if_index} + return path, json.dumps(data) + +def test_configure(drivers : Dict[str, OpenConfigDriver]): + #resources_to_get = [] + #resources_to_get = [RESOURCE_ENDPOINTS] + #resources_to_get = [RESOURCE_INTERFACES] + #resources_to_get = [RESOURCE_NETWORK_INSTANCES] + #resources_to_get = [RESOURCE_ROUTING_POLICIES] + #resources_to_get = [RESOURCE_SERVICES] + #LOGGER.info('resources_to_get = {:s}'.format(str(resources_to_get))) + #results_getconfig = driver.GetConfig(resources_to_get) + #LOGGER.info('results_getconfig = {:s}'.format(str(results_getconfig))) + + csgw1_resources_to_set = [ + network_instance('ecoc24', 'L3VRF', '192.168.150.1', '65001:1'), + network_instance_add_protocol_direct('ecoc24', 'L3VRF'), + network_instance_add_protocol_static('ecoc24', 'L3VRF'), + network_instance_add_protocol_bgp('ecoc24', 'L3VRF', '192.168.150.1', '65001', neighbors=[ + ('192.168.150.2', '65001') + ]), + network_instance_add_table_connection('ecoc24', 'DIRECTLY_CONNECTED', 'BGP', 'IPV4', 'ACCEPT_ROUTE', bgp_as='65001'), + network_instance_add_table_connection('ecoc24', 'STATIC', 'BGP', 'IPV4', 'ACCEPT_ROUTE', bgp_as='65001'), + + interface('ce1', 0, if_type='ethernetCsmacd', mtu=1500), + network_instance_interface('ecoc24', 'L3VRF', 'ce1', 0), + interface('ce1', 0, if_type='ethernetCsmacd', mtu=1500, ipv4_address_prefix=('192.168.10.1', 24), enabled=True), + + interface('xe5', 0, if_type='ethernetCsmacd', mtu=1500), + network_instance_interface('ecoc24', 'L3VRF', 'xe5', 0), + interface('xe5', 0, if_type='ethernetCsmacd', mtu=1500, ipv4_address_prefix=('192.168.150.1', 24), enabled=True), + ] + LOGGER.info('CSGW1 resources_to_set = {:s}'.format(str(csgw1_resources_to_set))) + results_setconfig = drivers['CSGW1'].SetConfig(csgw1_resources_to_set) + LOGGER.info('CSGW1 results_setconfig = {:s}'.format(str(results_setconfig))) + + csgw2_resources_to_set = [ + network_instance('ecoc24', 'L3VRF', '192.168.150.2', '65001:1'), + network_instance_add_protocol_direct('ecoc24', 'L3VRF'), + network_instance_add_protocol_static('ecoc24', 'L3VRF'), + network_instance_add_protocol_bgp('ecoc24', 'L3VRF', '192.168.150.2', '65001', neighbors=[ + ('192.168.150.1', '65001') + ]), + network_instance_add_table_connection('ecoc24', 'DIRECTLY_CONNECTED', 'BGP', 'IPV4', 'ACCEPT_ROUTE', bgp_as='65001'), + network_instance_add_table_connection('ecoc24', 'STATIC', 'BGP', 'IPV4', 'ACCEPT_ROUTE', bgp_as='65001'), + + interface('ce1', 0, if_type='ethernetCsmacd', mtu=1500), + network_instance_interface('ecoc24', 'L3VRF', 'ce1', 0), + interface('ce1', 0, if_type='ethernetCsmacd', mtu=1500, ipv4_address_prefix=('192.168.20.1', 24), enabled=True), + + interface('xe5', 0, if_type='ethernetCsmacd', mtu=1500), + network_instance_interface('ecoc24', 'L3VRF', 'xe5', 0), + interface('xe5', 0, if_type='ethernetCsmacd', mtu=1500, ipv4_address_prefix=('192.168.150.2', 24), enabled=True), + ] + LOGGER.info('CSGW2 resources_to_set = {:s}'.format(str(csgw2_resources_to_set))) + results_setconfig = drivers['CSGW2'].SetConfig(csgw2_resources_to_set) + LOGGER.info('CSGW2 results_setconfig = {:s}'.format(str(results_setconfig))) + + csgw1_resources_to_delete = [ + network_instance_interface('ecoc24', 'L3VRF', 'ce1', 0), + network_instance_interface('ecoc24', 'L3VRF', 'xe5', 0), + #interface('ce1', 0), + #interface('xe5', 0), + network_instance('ecoc24', 'L3VRF'), + ] + LOGGER.info('CSGW1 resources_to_delete = {:s}'.format(str(csgw1_resources_to_delete))) + results_deleteconfig = drivers['CSGW1'].DeleteConfig(csgw1_resources_to_delete) + LOGGER.info('CSGW1 results_deleteconfig = {:s}'.format(str(results_deleteconfig))) + + csgw2_resources_to_delete = [ + network_instance_interface('ecoc24', 'L3VRF', 'ce1', 0), + network_instance_interface('ecoc24', 'L3VRF', 'xe5', 0), + #interface('ce1', 0), + #interface('xe5', 0), + network_instance('ecoc24', 'L3VRF'), + ] + LOGGER.info('CSGW2 resources_to_delete = {:s}'.format(str(csgw2_resources_to_delete))) + results_deleteconfig = drivers['CSGW2'].DeleteConfig(csgw2_resources_to_delete) + LOGGER.info('CSGW2 results_deleteconfig = {:s}'.format(str(results_deleteconfig))) 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 4c6ad47bc210316908ed3e3676abbda6757cf615..88754adae376347851c818c596a4d7d9764a3c3b 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,7 +13,9 @@ # 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 @@ -37,15 +39,20 @@ class BwInfo(_Resource): return bw_allocations def post(self): - bwinfo = request.get_json() - service = bwInfo_2_service(self.client, bwinfo) + 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) stripped_service = copy.deepcopy(service) stripped_service.ClearField('service_endpoint_ids') stripped_service.ClearField('service_constraints') stripped_service.ClearField('service_config') - response = format_grpc_to_json(self.service_client.CreateService(stripped_service)) - response = format_grpc_to_json(self.service_client.UpdateService(service)) + 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 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 59436708cca2fcf7ff0ff65aa4977e2ccfaeda95..111f6f48482d4f2a0e494b0bd2e49eadee25ee60 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,22 +14,41 @@ 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, Constraint, Constraint_SLA_Capacity, + ContextId, Empty, EndPointId, ServiceId, ServiceTypeEnum, Service, ServiceStatusEnum, 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' +DEVICE_SETTINGS_KEY = '/device[{:s}]/settings' +RE_CONFIG_RULE_IF_SUBIF = re.compile(r'^\/interface\[([^\]]+)\]\/subinterface\[([^\]]+)\]$') +MEC_CONSIDERED_FIELDS = ['requestType', 'sessionFilter', 'fixedAllocation', 'allocationDirection', 'fixedBWPriority'] +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) +BGP_NEIGHBOR_IP_A = '192.168.150.1' +BGP_NEIGHBOR_IP_Z = '192.168.150.2' +ROUTER_ID_A = '200.1.1.1' +ROUTER_ID_Z = '200.1.1.2' +ROUTE_DISTINGUISHER = '{:5d}:{:03d}'.format(BGP_AS, 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': @@ -40,12 +59,19 @@ def service_2_bwInfo(service: Service) -> dict: break for config_rule in service.service_config.config_rules: + resource_value_json = json.loads(config_rule.custom.resource_value) + if config_rule.custom.resource_key != '/request': + continue for key in ['allocationDirection', 'fixedBWPriority', 'requestType', 'sourceIp', 'sourcePort', 'dstPort', 'protocol', 'sessionFilter']: - if config_rule.custom.resource_key == key: - if key != 'sessionFilter': - response[key] = config_rule.custom.resource_value - else: - response[key] = json.loads(config_rule.custom.resource_value) + if key not in resource_value_json: + continue + + if key == 'sessionFilter': + response[key] = [resource_value_json[key]] + elif key == 'requestType': + response[key] = str(resource_value_json[key]) + else: + response[key] = resource_value_json[key] unixtime = time.time() response['timeStamp'] = { # Time stamp to indicate when the corresponding information elements are sent @@ -55,47 +81,108 @@ def service_2_bwInfo(service: Service) -> dict: return response -def bwInfo_2_service(client, bwInfo: dict) -> Service: +def bwInfo_2_service(client, bw_info: dict) -> Service: + # add description to allocationDirection code + if 'sessionFilter' in bw_info: + bw_info['sessionFilter'] = bw_info['sessionFilter'][0] # Discard other items in sessionFilter field + service = Service() - 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'] + + service_config_rules = service.service_config.config_rules + + + 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'] devices = client.ListDevices(Empty()).devices + ip_interface_name_dict = {} for device in devices: + device_endpoint_uuids = {ep.name:ep.endpoint_id.endpoint_uuid.uuid for ep in device.device_endpoints} + skip_device = True for cr in device.device_config.config_rules: - 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) - + 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') + short_port_name = match_subif.groups(0)[0] + ip_interface_name_dict[address_ip] = short_port_name + if address_ip not in [a_ip, z_ip]: + continue + port_name = 'PORT-' + short_port_name # `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 in a_ip: + router_id = ROUTER_ID_A + policy_az = POLICY_AZ + policy_za = POLICY_ZA + neighbor_bgp_interface_address_ip = BGP_NEIGHBOR_IP_Z + self_bgp_interface_address_ip = BGP_NEIGHBOR_IP_A + else: + router_id = ROUTER_ID_Z + policy_az = POLICY_ZA + policy_za = POLICY_AZ + neighbor_bgp_interface_address_ip= BGP_NEIGHBOR_IP_A + self_bgp_interface_address_ip = BGP_NEIGHBOR_IP_Z + endpoint_field_updates = { + 'address_ip': (address_ip, True), + 'address_prefix' : (PREFIX_LENGTH, True), + 'sub_interface_index': (0, True), + } + LOGGER.debug(f'BEFORE UPDATE -> device.device_config.config_rules: {service_config_rules}') + update_config_rule_custom(service_config_rules, endpoint_settings_key, endpoint_field_updates) + LOGGER.debug(f'AFTER UPDATE -> device.device_config.config_rules: {service_config_rules}') + skip_device = False + if skip_device: + continue + device_field_updates = { + 'bgp_as':(BGP_AS, True), + 'route_distinguisher': (ROUTE_DISTINGUISHER, True), + 'router_id': (router_id, True), + 'policy_AZ': (policy_az, True), + 'policy_ZA': (policy_za, True), + 'neighbor_bgp_interface_address_ip': (neighbor_bgp_interface_address_ip, True), + 'self_bgp_interface_name': (ip_interface_name_dict[self_bgp_interface_address_ip], True), + 'self_bgp_interface_address_ip': (self_bgp_interface_address_ip, True), + 'bgp_interface_address_prefix': (PREFIX_LENGTH, True) + } + device_settings_key = DEVICE_SETTINGS_KEY.format(device.name) + LOGGER.debug(f'BEFORE UPDATE -> device.device_config.config_rules: {service_config_rules}') + update_config_rule_custom(service_config_rules, device_settings_key, device_field_updates) + LOGGER.debug(f'AFTER UPDATE -> device.device_config.config_rules: {service_config_rules}') + + settings_cr_key = '/settings' + settings_cr_value = {} + update_config_rule_custom(service_config_rules, settings_cr_key, settings_cr_value) + + service.service_status.service_status = ServiceStatusEnum.SERVICESTATUS_PLANNED service.service_type = ServiceTypeEnum.SERVICETYPE_L3NM - if 'appInsId' in bwInfo: - service.service_id.service_uuid.uuid = bwInfo['appInsId'] + if 'appInsId' in bw_info: + service.service_id.service_uuid.uuid = bw_info['appInsId'] service.service_id.context_id.context_uuid.uuid = 'admin' - service.name = bwInfo['appInsId'] + service.name = bw_info['appInsId'] - if 'fixedAllocation' in bwInfo: + if 'fixedAllocation' in bw_info: capacity = Constraint_SLA_Capacity() - capacity.capacity_gbps = float(bwInfo['fixedAllocation']) / 1.e9 + capacity.capacity_gbps = float(bw_info['fixedAllocation']) / 1.e9 constraint = Constraint() constraint.sla_capacity.CopyFrom(capacity) service.service_constraints.append(constraint) diff --git a/src/pathcomp/frontend/service/algorithms/tools/ComposeConfigRules.py b/src/pathcomp/frontend/service/algorithms/tools/ComposeConfigRules.py index 0c98254729afd0b2089c84499a9c739e985b27f5..f92f9b2fff11ab585813ab59e07c463f361413d2 100644 --- a/src/pathcomp/frontend/service/algorithms/tools/ComposeConfigRules.py +++ b/src/pathcomp/frontend/service/algorithms/tools/ComposeConfigRules.py @@ -184,6 +184,10 @@ 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 + + # TODO: check if vlan needs to be 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 4e9ceabc3add1bae949f751f36e2a6f8cb237fa6..7527932877f47a092a35f286defb3744e05db109 100644 --- a/src/service/service/service_handlers/l3nm_openconfig/ConfigRules.py +++ b/src/service/service/service_handlers/l3nm_openconfig/ConfigRules.py @@ -12,24 +12,35 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Dict, List, Tuple +from typing import Any, Dict, List, Optional, 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 get_value(field_name : str, *containers, default=None) -> Optional[Any]: + if len(containers) == 0: raise Exception('No containers specified') + for container in containers: + if field_name not in container: continue + return container[field_name] + return default + 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] + service_settings : TreeNode, device_settings : TreeNode, endpoint_settings : TreeNode, endpoint_acls : List [Tuple] ) -> List[Dict]: if service_settings is None: return [] + if device_settings is None: return [] if endpoint_settings is None: return [] json_settings : Dict = service_settings.value + json_device_settings : Dict = device_settings.value json_endpoint_settings : Dict = endpoint_settings.value - mtu = json_settings.get('mtu', 1450 ) # 1512 + settings = (json_settings, json_endpoint_settings, json_device_settings) + + mtu = get_value('mtu', *settings, default=1450) # 1512 #address_families = json_settings.get('address_families', [] ) # ['IPV4'] - bgp_as = json_settings.get('bgp_as', 65000 ) # 65000 + bgp_as = get_value('bgp_as', *settings, default=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' @@ -76,6 +87,7 @@ def setup_config_rules( 'name': network_instance_name, 'protocol_name': 'BGP', 'identifier': 'BGP', + 'type': 'L3VRF', 'as': bgp_as, 'router_id': router_id, }), @@ -88,7 +100,6 @@ def setup_config_rules( 'protocol_name': 'DIRECTLY_CONNECTED', }), - #Add STATIC protocol to network instance json_config_rule_set( '/network_instance[{:s}]/protocols[STATIC]'.format(network_instance_name), { @@ -114,6 +125,7 @@ def setup_config_rules( json_config_rule_set( '/network_instance[{:s}]/interface[{:s}]'.format(network_instance_name, if_subif_name), { 'name' : network_instance_name, + 'type' : 'L3VRF', 'id' : if_subif_name, 'interface' : if_subif_name, 'subinterface': sub_interface_index, @@ -183,6 +195,7 @@ def setup_config_rules( }), ] + for res_key, res_value in endpoint_acls: json_config_rules.append( {'action': 1, 'acl': res_value} @@ -191,23 +204,27 @@ def setup_config_rules( 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 + service_settings : TreeNode, device_settings : TreeNode, endpoint_settings : TreeNode ) -> List[Dict]: if service_settings is None: return [] + if device_settings is None: return [] if endpoint_settings is None: return [] json_settings : Dict = service_settings.value + json_device_settings : Dict = device_settings.value json_endpoint_settings : Dict = endpoint_settings.value + settings = (json_settings, json_endpoint_settings, json_device_settings) + 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) - #mtu = json_settings.get('mtu', 1450 ) # 1512 + #mtu = get_value('mtu', *settings, default=1450) # 1512 #address_families = json_settings.get('address_families', [] ) # ['IPV4'] - #bgp_as = json_settings.get('bgp_as', 65000 ) # 65000 + #bgp_as = get_value('bgp_as', *settings, default=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' diff --git a/src/service/service/service_handlers/l3nm_openconfig/ConfigRules_test_ocnos.py b/src/service/service/service_handlers/l3nm_openconfig/ConfigRules_test_ocnos.py new file mode 100644 index 0000000000000000000000000000000000000000..5fa1d0b5b6931902b4ac50847c90bf67738032ba --- /dev/null +++ b/src/service/service/service_handlers/l3nm_openconfig/ConfigRules_test_ocnos.py @@ -0,0 +1,337 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (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, 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, device_settings : TreeNode, endpoint_settings : TreeNode, endpoint_acls : List [Tuple] +) -> List[Dict]: + + if service_settings is None: return [] + if device_settings is None: return [] + if endpoint_settings is None: return [] + + json_settings : Dict = service_settings.value + json_device_settings : Dict = device_settings.value + json_endpoint_settings : Dict = endpoint_settings.value + + mtu = json_settings.get('mtu', 1450 ) # 1512 + #address_families = json_settings.get('address_families', [] ) # ['IPV4'] + bgp_as = json_device_settings.get('bgp_as', 65000 ) # 65000 + + router_id = json_device_settings.get('router_id', '0.0.0.0') # '10.95.0.10' + route_distinguisher = json_device_settings.get('route_distinguisher', '65000:101' ) # '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 + + policy_import = json_device_settings.get('policy_AZ', '2' ) # 2 + policy_export = json_device_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 + + self_bgp_if_name = json_device_settings.get('self_bgp_interface_name', '') + self_bgp_address_ip = json_device_settings.get('self_bgp_interface_address_ip', '') + bgp_address_prefix = json_device_settings.get('bgp_interface_address_prefix', '') + bgp_sub_interface_index = json_device_settings.get('self_bgp_sub_interface_index', 0) + neighbor_bgp_if_address_ip= json_device_settings.get('neighbor_bgp_interface_address_ip', '0.0.0.0') # '2.2.2.1' + + # if_subif_name = '{:s}.{:d}'.format(endpoint_name, 0) + if_subif_name = '{:s}'.format(endpoint_name[5:]) + + 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, + #}), + + #Create network instance + json_config_rule_set( + '/network_instance[{:s}]'.format(network_instance_name), { + 'name': network_instance_name, + 'description': network_interface_desc, + 'type': 'L3VRF', + 'route_distinguisher': route_distinguisher, + 'router_id': router_id, + #'address_families': address_families, + }), + + #Add BGP protocol to network instance + json_config_rule_set( + '/network_instance[{:s}]/protocols[BGP]'.format(network_instance_name), { + 'name': network_instance_name, + 'protocol_name': bgp_as, + 'identifier': 'BGP', + 'type': 'L3VRF', + 'as': bgp_as, + 'router_id': router_id, + 'neighbors': [{'ip_address': neighbor_bgp_if_address_ip, 'remote_as': bgp_as}] + }), + + #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 STATIC protocol to network instance + json_config_rule_set( + '/network_instance[{:s}]/protocols[STATIC]'.format(network_instance_name), { + 'name': network_instance_name, + 'identifier': 'STATIC', + 'protocol_name': 'STATIC', + }), + + #Create interface with subinterface (without IP address) + json_config_rule_set( + '/interface[{:s}]/subinterface[{:d}]'.format(if_subif_name, sub_interface_index), { + 'name' : if_subif_name, + 'type' :'ethernetCsmacd', + 'mtu' : mtu, + 'index' : sub_interface_index, + 'description': network_subinterface_desc, + 'vlan_id' : vlan_id, + }), + + #Associate interface to network instance + json_config_rule_set( + '/network_instance[{:s}]/interface[{:s}]'.format(network_instance_name, if_subif_name), { + 'name' : network_instance_name, + 'type' : 'L3VRF', + 'id' : if_subif_name, + 'interface' : if_subif_name, + 'subinterface' : sub_interface_index, + 'address_ip' : address_ip, + 'address_prefix': address_prefix, + }), + + #Create interface with subinterface (with IP address) + json_config_rule_set( + '/interface[{:s}]/subinterface[{:d}]'.format(if_subif_name, sub_interface_index), { + 'name' : if_subif_name, + 'type' :'ethernetCsmacd', + 'mtu' : mtu, + 'index' : sub_interface_index, + 'description' : network_subinterface_desc, + 'vlan_id' : vlan_id, + 'address_ip' : address_ip, + 'address_prefix': address_prefix, + }), + + json_config_rule_set( + '/network_instance[{:s}]/interface[{:s}]'.format(network_instance_name, self_bgp_if_name), { + 'name' : network_instance_name, + 'type' : 'L3VRF', + 'id' : self_bgp_if_name, + 'interface' : self_bgp_if_name, + 'subinterface': bgp_sub_interface_index, + 'address_ip' : self_bgp_address_ip, + 'address_prefix': bgp_address_prefix, + }), + + #Create routing policy + json_config_rule_set( + '/routing_policy/bgp_defined_set[{:s}_rt_import][{:s}]'.format(policy_import,route_distinguisher), { + 'ext_community_set_name': 'set_{:s}'.format(policy_import), + 'ext_community_member' : route_distinguisher, + }), + json_config_rule_set( + # pylint: disable=duplicate-string-formatting-argument + '/routing_policy/policy_definition[{:s}_import]/statement[{:s}]'.format(policy_import, policy_import), { + 'policy_name' : policy_import, + 'statement_name' : 'stm_{:s}'.format(policy_import), # OCNOS: '10', + 'ext_community_set_name': 'set_{:s}'.format(policy_import), + 'policy_result' : 'ACCEPT_ROUTE', + }), + + #Associate routing policy to network instance + json_config_rule_set( + '/network_instance[{:s}]/inter_instance_policies[{:s}]'.format(network_instance_name, policy_import), { + 'name' : network_instance_name, + 'import_policy': policy_import, + }), + + #Create routing policy + json_config_rule_set( + '/routing_policy/bgp_defined_set[{:s}_rt_export][{:s}]'.format(policy_export, route_distinguisher), { + 'ext_community_set_name': 'set_{:s}'.format(policy_export), + 'ext_community_member' : route_distinguisher, + }), + json_config_rule_set( + # pylint: disable=duplicate-string-formatting-argument + '/routing_policy/policy_definition[{:s}_export]/statement[{:s}]'.format(policy_export, policy_export), { + 'policy_name' : policy_export, + 'statement_name' : 'stm_{:s}'.format(policy_export), # OCNOS: '10', + 'ext_community_set_name': 'set_{:s}'.format(policy_export), + 'policy_result' : 'ACCEPT_ROUTE', + }), + + #Associate routing policy to network instance + json_config_rule_set( + '/network_instance[{:s}]/inter_instance_policies[{:s}]'.format(network_instance_name, policy_export),{ + 'name' : network_instance_name, + 'export_policy': policy_export, + }), + + #Create table connections + 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', + 'as' : bgp_as, + }), + + json_config_rule_set( + '/network_instance[{:s}]/table_connections[STATIC][BGP][IPV4]'.format(network_instance_name), { + 'name' : network_instance_name, + 'src_protocol' : 'STATIC', + 'dst_protocol' : 'BGP', + 'address_family' : 'IPV4', + 'default_import_policy': 'ACCEPT_ROUTE', + 'as' : bgp_as, + }), + + ] + + for res_key, res_value in endpoint_acls: + json_config_rules.append( + {'action': 1, 'acl': res_value} + ) + return json_config_rules + +def teardown_config_rules( + service_uuid : str, connection_uuid : str, device_uuid : str, endpoint_uuid : str, endpoint_name : str, + service_settings : TreeNode, device_settings : TreeNode, endpoint_settings : TreeNode +) -> List[Dict]: + + if service_settings is None: return [] + if device_settings is None: return [] + if endpoint_settings is None: return [] + + json_settings : Dict = service_settings.value + json_device_settings : Dict = device_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_instance_name = json_endpoint_settings.get('ni_name', service_short_uuid) #ELAN-AC:1 + #network_interface_desc = '{:s}-NetIf'.format(service_uuid) + # network_subinterface_desc = '{:s}-NetSubIf'.format(service_uuid) + network_subinterface_desc = '' + + mtu = json_settings.get('mtu', 1450 ) # 1512 + #address_families = json_settings.get('address_families', [] ) # ['IPV4'] + #bgp_as = json_device_settings.get('bgp_as', 65000 ) # 65000 + route_distinguisher = json_device_settings.get('route_distinguisher', '0:0' ) # '60001:801' + sub_interface_index = json_endpoint_settings.get('sub_interface_index', 0 ) # 1 + #router_id = json_device_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_device_settings.get('policy_AZ', '2' ) # 2 + policy_export = json_device_settings.get('policy_ZA', '7' ) # 30 + + self_bgp_if_name = json_device_settings.get('self_bgp_interface_name', '') + self_bgp_address_ip = json_device_settings.get('self_bgp_interface_address_ip', '') + bgp_address_prefix = json_device_settings.get('bgp_interface_address_prefix', '') + bgp_sub_interface_index = json_device_settings.get('self_bgp_sub_interface_index', 0) + + # if_subif_name = '{:s}.{:d}'.format(endpoint_name, vlan_id) + if_subif_name = '{:s}'.format(endpoint_name[5:]) + + json_config_rules = [ + #Delete export routing policy + json_config_rule_delete( + # pylint: disable=duplicate-string-formatting-argument + '/routing_policy/policy_definition[{:s}_export]/statement[{:s}]'.format(policy_export, policy_export), { + 'policy_name' : policy_export, + 'statement_name' : 'stm_{:s}'.format(policy_export), # OCNOS: '10', + 'ext_community_set_name': 'set_{:s}'.format(policy_export), + 'policy_result' : 'ACCEPT_ROUTE', + }), + json_config_rule_delete( + '/routing_policy/bgp_defined_set[{:s}_rt_export][{:s}]'.format(policy_export, route_distinguisher), { + 'ext_community_set_name': 'set_{:s}'.format(policy_export), + 'ext_community_member' : route_distinguisher, + }), + + #Delete import routing policy + json_config_rule_delete( + # pylint: disable=duplicate-string-formatting-argument + '/routing_policy/policy_definition[{:s}_import]/statement[{:s}]'.format(policy_import, policy_import), { + 'policy_name' : policy_import, + 'statement_name' : 'stm_{:s}'.format(policy_import), # OCNOS: '10', + 'ext_community_set_name': 'set_{:s}'.format(policy_import), + 'policy_result' : 'ACCEPT_ROUTE', + }), + json_config_rule_delete( + '/routing_policy/bgp_defined_set[{:s}_rt_import][{:s}]'.format(policy_import, route_distinguisher), { + 'ext_community_set_name': 'set_{:s}'.format(policy_import), + 'ext_community_member' : route_distinguisher, + }), + + #Delete interface; automatically deletes: + # - /interface[]/subinterface[] + # json_config_rule_delete('/interface[{:s}]/subinterface[0]'.format(if_subif_name), + # { + # 'name': if_subif_name, + # }), + + #Delete network instance; automatically deletes: + # - /network_instance[]/interface[] + # - /network_instance[]/protocols[] + # - /network_instance[]/inter_instance_policies[] + + #Associate interface to network instance + json_config_rule_set( + '/network_instance[{:s}]/interface[{:s}]'.format('default', if_subif_name), { + 'name' : 'default', + 'id' : if_subif_name, + 'interface' : if_subif_name, + 'subinterface': sub_interface_index, + 'address_ip' : address_ip, + 'address_prefix': address_prefix, + }), + json_config_rule_set( + '/network_instance[{:s}]/interface[{:s}]'.format('default', self_bgp_if_name), { + 'name' : 'default', + 'id' : self_bgp_if_name, + 'interface' : self_bgp_if_name, + 'subinterface': bgp_sub_interface_index, + 'address_ip' : self_bgp_address_ip, + 'address_prefix': bgp_address_prefix, + }), + json_config_rule_delete('/network_instance[{:s}]'.format(network_instance_name), + { + 'name': network_instance_name + }), + ] + return json_config_rules diff --git a/src/service/service/service_handlers/l3nm_openconfig/L3NMOpenConfigServiceHandler.py b/src/service/service/service_handlers/l3nm_openconfig/L3NMOpenConfigServiceHandler.py index d714946d128284d60422779bc6286b64ee0244a7..2c944bfe4772b2f55ff16d1a3c726af3ee4c6e8f 100644 --- a/src/service/service/service_handlers/l3nm_openconfig/L3NMOpenConfigServiceHandler.py +++ b/src/service/service/service_handlers/l3nm_openconfig/L3NMOpenConfigServiceHandler.py @@ -52,6 +52,7 @@ class L3NMOpenConfigServiceHandler(_ServiceHandler): device_uuid, endpoint_uuid = get_device_endpoint_uuids(endpoint) device_obj = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid))) + device_settings = self.__settings_handler.get_device_settings(device_obj) 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) @@ -59,7 +60,7 @@ class L3NMOpenConfigServiceHandler(_ServiceHandler): json_config_rules = setup_config_rules( service_uuid, connection_uuid, device_uuid, endpoint_uuid, endpoint_name, - settings, endpoint_settings, endpoint_acls) + settings, device_settings, endpoint_settings, endpoint_acls) if len(json_config_rules) > 0: del device_obj.device_config.config_rules[:] @@ -90,13 +91,14 @@ class L3NMOpenConfigServiceHandler(_ServiceHandler): device_uuid, endpoint_uuid = get_device_endpoint_uuids(endpoint) device_obj = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid))) + device_settings = self.__settings_handler.get_device_settings(device_obj) endpoint_obj = get_endpoint_matching(device_obj, endpoint_uuid) endpoint_settings = self.__settings_handler.get_endpoint_settings(device_obj, endpoint_obj) endpoint_name = endpoint_obj.name json_config_rules = teardown_config_rules( service_uuid, connection_uuid, device_uuid, endpoint_uuid, endpoint_name, - settings, endpoint_settings) + settings, device_settings, endpoint_settings) if len(json_config_rules) > 0: del device_obj.device_config.config_rules[:] diff --git a/src/webui/service/__init__.py b/src/webui/service/__init__.py index 9b6950aeb0f3fda630db87d5d80ad2bf730fac3d..b864d3549e051b54e888c80547724da14fec5f67 100644 --- a/src/webui/service/__init__.py +++ b/src/webui/service/__init__.py @@ -49,7 +49,10 @@ def json_to_list(json_str : str) -> List[Union[str, Tuple[str, str]]]: if isinstance(data, dict): return [('kv', (key, value)) for key, value in data.items()] elif isinstance(data, list): - return [('item', ', '.join(data))] + if len(data) == 1 and isinstance(data[0], dict): + return [('kv', (key, value)) for key, value in data[0].items()] + else: + return [('item', ', '.join([str(d) for d in data]))] else: return [('item', str(data))]