Commit 4881a30f authored by Lluis Gifre Renom's avatar Lluis Gifre Renom
Browse files

Service component - L3NM gNMI OpenConfig Service Handler:

- ConfigRuleComposer: Added ignore of management interfaces
- ConfigRuleComposer: Corrected endpoint attribute names
- ConfigRuleComposer: Added alias mappings from device/endpoint name to their UUID to prevent split-brain conditions
- L3NMGnmiOpenConfigServiceHandler: implemented correct path data loading
parent d41a3bc7
Loading
Loading
Loading
Loading
+50 −18
Original line number Diff line number Diff line
@@ -12,16 +12,19 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import json, netaddr, re
import json, logging, netaddr, re
from typing import Dict, List, Optional, Set, Tuple
from common.DeviceTypes import DeviceTypeEnum
from common.proto.context_pb2 import ConfigActionEnum, Device, EndPoint, Service
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__)

NETWORK_INSTANCE = 'teraflowsdn'

RE_IF = re.compile(r'^\/interface\[([^\]]+)\]\/subinterface\[([^\]]+)\]$')
RE_IF    = re.compile(r'^\/interface\[([^\]]+)\]$')
RE_SUBIF = re.compile(r'^\/interface\[([^\]]+)\]\/subinterface\[([^\]]+)\]$')
RE_SR    = re.compile(r'^\/network_instance\[([^\]]+)\]\/protocols\[STATIC\]/route\[([^\:]+)\:([^\]]+)\]$')

def _interface(
@@ -82,9 +85,9 @@ class EndpointComposer:
            self.objekt = endpoint_obj
        if settings is None: return
        json_settings : Dict = settings.value
        self.ipv4_address = json_settings['ipv4_address']
        self.ipv4_prefix_len = json_settings['ipv4_prefix_len']
        self.sub_interface_index = json_settings['sub_interface_index']
        self.ipv4_address = json_settings['address_ip']
        self.ipv4_prefix_len = json_settings['address_prefix']
        self.sub_interface_index = json_settings.get('index', 0)

    def get_config_rules(self, network_instance_name : str, delete : bool = False) -> List[Dict]:
        if self.ipv4_address is None: return []
@@ -102,20 +105,25 @@ class EndpointComposer:

    def dump(self) -> Dict:
        return {
            'sub_interface_index' : self.sub_interface_index,
            'ipv4_address'        : self.ipv4_address,
            'ipv4_prefix_len'     : self.ipv4_prefix_len,
            'index'         : self.sub_interface_index,
            'address_ip'    : self.ipv4_address,
            'address_prefix': self.ipv4_prefix_len,
        }

class DeviceComposer:
    def __init__(self, device_uuid : str) -> None:
        self.uuid = device_uuid
        self.objekt : Optional[Device] = None
        self.endpoints : Dict[str, EndpointComposer] = dict()
        self.aliases : Dict[str, str] = dict() # endpoint_name => endpoint_uuid
        self.endpoints : Dict[str, EndpointComposer] = dict() # endpoint_uuid => EndpointComposer
        self.connected : Set[str] = set()
        self.static_routes : Dict[str, Dict[int, str]] = dict() # {prefix => {metric => next_hop}}

    def set_endpoint_alias(self, endpoint_name : str, endpoint_uuid : str) -> None:
        self.aliases[endpoint_name] = endpoint_uuid

    def get_endpoint(self, endpoint_uuid : str) -> EndpointComposer:
        endpoint_uuid = self.aliases.get(endpoint_uuid, endpoint_uuid)
        if endpoint_uuid not in self.endpoints:
            self.endpoints[endpoint_uuid] = EndpointComposer(endpoint_uuid)
        return self.endpoints[endpoint_uuid]
@@ -123,17 +131,36 @@ class DeviceComposer:
    def configure(self, device_obj : Device, settings : Optional[TreeNode]) -> None:
        self.objekt = device_obj
        for endpoint_obj in device_obj.device_endpoints:
            endpoint_uuid = endpoint_obj.endpoint_id.endpoint_uuid.uuid
            self.set_endpoint_alias(endpoint_obj.name, endpoint_uuid)
            self.get_endpoint(endpoint_obj.name).configure(endpoint_obj, None)

        # Find management interfaces
        mgmt_ifaces = set()
        for config_rule in device_obj.device_config.config_rules:
            if config_rule.action != ConfigActionEnum.CONFIGACTION_SET: continue
            if config_rule.WhichOneof('config_rule') != 'custom': continue
            config_rule_custom = config_rule.custom

            match = RE_IF.match(config_rule_custom.resource_key)
            if match is None: continue
            if_name = match.groups()[0]
            resource_value = json.loads(config_rule_custom.resource_value)
            management = resource_value.get('management', False)
            if management: mgmt_ifaces.add(if_name)

        # Find data plane interfaces
        for config_rule in device_obj.device_config.config_rules:
            if config_rule.action != ConfigActionEnum.CONFIGACTION_SET: continue
            if config_rule.WhichOneof('config_rule') != 'custom': continue
            config_rule_custom = config_rule.custom

            match = RE_SUBIF.match(config_rule_custom.resource_key)
            if match is not None:
                if_name, subif_index = match.groups()
                if if_name in mgmt_ifaces: continue
                resource_value = json.loads(config_rule_custom.resource_value)
                if 'address_ip' not in resource_value: continue
                if 'address_prefix' not in resource_value: continue
                ipv4_network    = str(resource_value['address_ip'])
                ipv4_prefix_len = int(resource_value['address_prefix'])
                endpoint = self.get_endpoint(if_name)
@@ -197,19 +224,24 @@ class DeviceComposer:
class ConfigRuleComposer:
    def __init__(self) -> None:
        self.objekt : Optional[Service] = None
        self.devices : Dict[str, DeviceComposer] = dict()
        self.aliases : Dict[str, str] = dict() # device_name => device_uuid
        self.devices : Dict[str, DeviceComposer] = dict() # device_uuid => DeviceComposer

    def configure(self, service_obj : Service, settings : Optional[TreeNode]) -> None:
        self.objekt = service_obj
        if settings is None: return
        #json_settings : Dict = settings.value
        # For future use
    def set_device_alias(self, device_name : str, device_uuid : str) -> None:
        self.aliases[device_name] = device_uuid

    def get_device(self, device_uuid : str) -> DeviceComposer:
        device_uuid = self.aliases.get(device_uuid, device_uuid)
        if device_uuid not in self.devices:
            self.devices[device_uuid] = DeviceComposer(device_uuid)
        return self.devices[device_uuid]

    def configure(self, service_obj : Service, settings : Optional[TreeNode]) -> None:
        self.objekt = service_obj
        if settings is None: return
        #json_settings : Dict = settings.value
        # For future use

    def get_config_rules(
        self, network_instance_name : str = NETWORK_INSTANCE, delete : bool = False
    ) -> Dict[str, List[Dict]]:
+12 −3
Original line number Diff line number Diff line
@@ -15,13 +15,15 @@
import json, logging
from typing import Any, Dict, 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.proto.context_pb2 import ConfigRule, ConnectionId, DeviceId, Service
from common.tools.object_factory.Connection import json_connection_id
from common.tools.object_factory.Device import json_device_id
from common.type_checkers.Checkers import chk_type
from service.service.service_handler_api._ServiceHandler import _ServiceHandler
from service.service.service_handler_api.SettingsHandler import SettingsHandler
from service.service.service_handler_api.Tools import get_device_endpoint_uuids, get_endpoint_matching
from service.service.task_scheduler.TaskExecutor import TaskExecutor
from service.service.tools.EndpointIdFormatters import endpointids_to_raw
from .ConfigRuleComposer import ConfigRuleComposer
from .StaticRouteGenerator import StaticRouteGenerator

@@ -51,17 +53,20 @@ class L3NMGnmiOpenConfigServiceHandler(_ServiceHandler):

            device_obj = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid)))
            device_settings = self.__settings_handler.get_device_settings(device_obj)
            self.__config_rule_composer.set_device_alias(device_obj.name, device_uuid)
            _device = self.__config_rule_composer.get_device(device_obj.name)
            _device.configure(device_obj, device_settings)

            endpoint_obj = get_endpoint_matching(device_obj, endpoint_uuid)
            endpoint_settings = self.__settings_handler.get_endpoint_settings(device_obj, endpoint_obj)
            _device.set_endpoint_alias(endpoint_obj.name, endpoint_uuid)
            _endpoint = _device.get_endpoint(endpoint_obj.name)
            _endpoint.configure(endpoint_obj, endpoint_settings)

            self.__endpoint_map[(device_uuid, endpoint_uuid)] = (device_obj.name, endpoint_obj.name)

        self.__static_route_generator.compose(endpoints)
        LOGGER.debug('config_rule_composer = {:s}'.format(json.dumps(self.__config_rule_composer.dump())))

    def _do_configurations(
        self, config_rules_per_device : Dict[str, List[Dict]], endpoints : List[Tuple[str, str, Optional[str]]],
@@ -99,7 +104,9 @@ class L3NMGnmiOpenConfigServiceHandler(_ServiceHandler):
        chk_type('endpoints', endpoints, list)
        if len(endpoints) == 0: return []
        #service_uuid = self.__service.service_id.service_uuid.uuid
        self._compose_config_rules(endpoints)
        connection = self.__task_executor.get_connection(ConnectionId(**json_connection_id(connection_uuid)))
        connection_endpoint_ids = endpointids_to_raw(connection.path_hops_endpoint_ids)
        self._compose_config_rules(connection_endpoint_ids)
        #network_instance_name = service_uuid.split('-')[0]
        #config_rules_per_device = self.__config_rule_composer.get_config_rules(network_instance_name, delete=False)
        config_rules_per_device = self.__config_rule_composer.get_config_rules(delete=False)
@@ -115,7 +122,9 @@ class L3NMGnmiOpenConfigServiceHandler(_ServiceHandler):
        chk_type('endpoints', endpoints, list)
        if len(endpoints) == 0: return []
        #service_uuid = self.__service.service_id.service_uuid.uuid
        self._compose_config_rules(endpoints)
        connection = self.__task_executor.get_connection(ConnectionId(**json_connection_id(connection_uuid)))
        connection_endpoint_ids = endpointids_to_raw(connection.path_hops_endpoint_ids)
        self._compose_config_rules(connection_endpoint_ids)
        #network_instance_name = service_uuid.split('-')[0]
        #config_rules_per_device = self.__config_rule_composer.get_config_rules(network_instance_name, delete=True)
        config_rules_per_device = self.__config_rule_composer.get_config_rules(delete=True)