Commit 8a35e040 authored by Pedro Duarte's avatar Pedro Duarte
Browse files

add initial restconf service handler implementation

parent 06fa8f05
Loading
Loading
Loading
Loading
+316 −0
Original line number Diff line number Diff line
# Copyright 2022-2024 ETSI 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 logging

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


LOGGER = logging.getLogger(__name__)

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, device_settings : TreeNode, endpoint_settings : TreeNode, endpoint_acls : List [Tuple]
) -> List[Dict]:

    LOGGER.info(f"service_settings : {service_settings}")
    LOGGER.info(f"endpoint_settings : {endpoint_settings}")

    if service_settings  is None: return []
    if device_settings   is None: 
        device_settings = TreeNode('settings')
        device_settings.value = {}
    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)

    mtu                       = get_value('mtu', *settings, default=1450)   # 1512
    #address_families         = json_settings.get('address_families',             []       )  # ['IPV4']
    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'
    sub_interface_index       = json_endpoint_settings.get('sub_interface_index', 0        )  # 1
    vlan_id                   = json_endpoint_settings.get('vlan_id',             1        )  # 400
    address_ip                = json_endpoint_settings.get('address_ip',          '0.0.0.0')  # '2.2.2.1'
    address_prefix            = json_endpoint_settings.get('address_prefix',      24       )  # 30

    policy_import             = json_endpoint_settings.get('policy_AZ',            '2'     )  # 2
    policy_export             = json_endpoint_settings.get('policy_ZA',            '7'     )  # 30
    #network_interface_desc    = '{:s}-NetIf'.format(service_uuid)
    network_interface_desc    = json_endpoint_settings.get('ni_description','')
    #network_subinterface_desc = '{:s}-NetSubIf'.format(service_uuid)
    network_subinterface_desc = json_endpoint_settings.get('subif_description','')
    #service_short_uuid       = service_uuid.split('-')[-1]
    #network_instance_name    = '{:s}-NetInst'.format(service_short_uuid)
    network_instance_name     = json_endpoint_settings.get('ni_name',          service_uuid.split('-')[-1])  #ELAN-AC:1

    mtu                 = int(mtu)
    bgp_as              = int(bgp_as)
    sub_interface_index = int(sub_interface_index)
    vlan_id             = int(vlan_id)
    address_prefix      = int(address_prefix)


    if_subif_name       = '{:s}.{:d}'.format(endpoint_name, vlan_id)

    json_config_rules = [
        # Configure Interface (not used)
        #json_config_rule_set(
        #    '/interface[{:s}]'.format(endpoint_name), {
        #        'name': endpoint_name, 
        #        'description': network_interface_desc, 
        #        'mtu': mtu,
        #}),

        #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', 
                'identifier': 'BGP', 
                'type': 'L3VRF',
                'as': bgp_as,
                'router_id': router_id, 
        }),

        #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
        json_config_rule_set(
            '/interface[{:s}]/subinterface[{:d}]'.format(if_subif_name, sub_interface_index), {
                'name'          : if_subif_name,
                'type'          :'l3ipvlan',
                'mtu'           : mtu,
                'index'         : sub_interface_index,
                'description'   : network_subinterface_desc, 
                'vlan_id'       : vlan_id,
                'address_ip'    : address_ip, 
                'address_prefix': address_prefix,
        }),

        #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,
        }), 

        # TODO Validate need
        #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),
        #         '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,
        # }),

        # TODO Validate need
        #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),
        #         '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',
        # }),

        # 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',
        # }),

    ]

    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

    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                       = get_value('mtu', *settings, default=1450)   # 1512
    #address_families    = json_settings.get('address_families',             []       )  # ['IPV4']
    #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'
    vlan_id             = json_endpoint_settings.get('vlan_id',             1        )  # 400
    #address_ip          = json_endpoint_settings.get('address_ip',          '0.0.0.0')  # '2.2.2.1'
    #address_prefix      = json_endpoint_settings.get('address_prefix',      24       )  # 30
    policy_import       = json_endpoint_settings.get('policy_AZ',            '2'      )  # 2
    policy_export       = json_endpoint_settings.get('policy_ZA',            '7'      )  # 30

    if_subif_name             = '{:s}.{:d}'.format(endpoint_name, vlan_id)

    json_config_rules = [
        #Delete table connections
        # json_config_rule_delete(
        #     '/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', 
        # }),

        
        # json_config_rule_delete(
        #     '/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',
        # }),


        # TODO Validate need
        #Delete export routing policy 
        # json_config_rule_delete(
        #     '/routing_policy/policy_definition[{:s}_export]'.format(network_instance_name), {
        #         'policy_name': '{:s}_export'.format(network_instance_name),
        # }),
        # 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),
        # }),

        # #Delete import routing policy 

        # json_config_rule_delete(
        #     '/routing_policy/policy_definition[{:s}_import]'.format(network_instance_name), {
        #         'policy_name': '{:s}_import'.format(network_instance_name),
        # }),
        # 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),
        # }),

        #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[]
        json_config_rule_delete('/network_instance[{:s}]'.format(network_instance_name),
        {
            'name': network_instance_name
        }),
    ]
    return json_config_rules
+166 −0
Original line number Diff line number Diff line
# Copyright 2022-2024 ETSI 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
from typing import Any, List, Optional, Tuple, Union
from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method
from common.proto.context_pb2 import ConfigRule, DeviceId, Service
from common.tools.object_factory.Device import json_device_id
from common.type_checkers.Checkers import chk_type
from service.service.service_handler_api.Tools import get_device_endpoint_uuids, get_endpoint_matching
from service.service.service_handler_api._ServiceHandler import _ServiceHandler
from service.service.service_handler_api.SettingsHandler import SettingsHandler
from service.service.task_scheduler.TaskExecutor import TaskExecutor
from .ConfigRules import setup_config_rules, teardown_config_rules

LOGGER = logging.getLogger(__name__)

METRICS_POOL = MetricsPool('Service', 'Handler', labels={'handler': 'l3nm_restconf'})

class L3NMRestconfServiceHandler(_ServiceHandler):
    def __init__(   # pylint: disable=super-init-not-called
        self, service : Service, task_executor : TaskExecutor, **settings
    ) -> None:
        self.__service = service
        self.__task_executor = task_executor
        self.__settings_handler = SettingsHandler(service.service_config, **settings)

    @metered_subclass_method(METRICS_POOL)
    def SetEndpoint(
        self, endpoints : List[Tuple[str, str, Optional[str]]], connection_uuid : Optional[str] = None
    ) -> List[Union[bool, Exception]]:
        chk_type('endpoints', endpoints, list)
        if len(endpoints) == 0: return []

        service_uuid = self.__service.service_id.service_uuid.uuid
        settings = self.__settings_handler.get('/settings')

        results = []
        for endpoint in endpoints:
            try:
                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)
                endpoint_name = endpoint_obj.name

                json_config_rules = setup_config_rules(
                    service_uuid, connection_uuid, device_uuid, endpoint_uuid, endpoint_name,
                    settings, device_settings, endpoint_settings, endpoint_acls)
                
                LOGGER.info(f"json_config_rules : {json_config_rules}")

                if len(json_config_rules) > 0:
                    del device_obj.device_config.config_rules[:]
                    for json_config_rule in json_config_rules:
                        device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule))
                    self.__task_executor.configure_device(device_obj)

                results.append(True)
            except Exception as e: # pylint: disable=broad-except
                LOGGER.exception('Unable to SetEndpoint({:s})'.format(str(endpoint)))
                results.append(e)

        return results

    @metered_subclass_method(METRICS_POOL)
    def DeleteEndpoint(
        self, endpoints : List[Tuple[str, str, Optional[str]]], connection_uuid : Optional[str] = None
    ) -> List[Union[bool, Exception]]:
        chk_type('endpoints', endpoints, list)
        if len(endpoints) == 0: return []

        service_uuid = self.__service.service_id.service_uuid.uuid
        settings = self.__settings_handler.get('/settings')

        results = []
        for endpoint in endpoints:
            try:
                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, device_settings, endpoint_settings)

                if len(json_config_rules) > 0:
                    del device_obj.device_config.config_rules[:]
                    for json_config_rule in json_config_rules:
                        device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule))
                    self.__task_executor.configure_device(device_obj)

                results.append(True)
            except Exception as e: # pylint: disable=broad-except
                LOGGER.exception('Unable to DeleteEndpoint({:s})'.format(str(endpoint)))
                results.append(e)

        return results

    @metered_subclass_method(METRICS_POOL)
    def SetConstraint(self, constraints : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
        chk_type('constraints', constraints, list)
        if len(constraints) == 0: return []

        msg = '[SetConstraint] Method not implemented. Constraints({:s}) are being ignored.'
        LOGGER.warning(msg.format(str(constraints)))
        return [True for _ in range(len(constraints))]

    @metered_subclass_method(METRICS_POOL)
    def DeleteConstraint(self, constraints : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
        chk_type('constraints', constraints, list)
        if len(constraints) == 0: return []

        msg = '[DeleteConstraint] Method not implemented. Constraints({:s}) are being ignored.'
        LOGGER.warning(msg.format(str(constraints)))
        return [True for _ in range(len(constraints))]

    @metered_subclass_method(METRICS_POOL)
    def SetConfig(self, resources : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
        chk_type('resources', resources, list)
        if len(resources) == 0: return []

        results = []
        for resource in resources:
            try:
                resource_value = json.loads(resource[1])
                self.__settings_handler.set(resource[0], resource_value)
                results.append(True)
            except Exception as e: # pylint: disable=broad-except
                LOGGER.exception('Unable to SetConfig({:s})'.format(str(resource)))
                results.append(e)

        return results

    @metered_subclass_method(METRICS_POOL)
    def DeleteConfig(self, resources : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
        chk_type('resources', resources, list)
        if len(resources) == 0: return []

        results = []
        for resource in resources:
            try:
                self.__settings_handler.delete(resource[0])
            except Exception as e: # pylint: disable=broad-except
                LOGGER.exception('Unable to DeleteConfig({:s})'.format(str(resource)))
                results.append(e)

        return results
+14 −0
Original line number Diff line number Diff line
# Copyright 2022-2024 ETSI 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.