Commit fe7a440c authored by Lluis Gifre Renom's avatar Lluis Gifre Renom
Browse files

Device component:

- Fixed Driver selection logic. Now selection is strict (all filter fields from device should match those from driver, except those the driver explicitly keeps open)
parent 250b1064
Loading
Loading
Loading
Loading
+96 −67
Original line number Diff line number Diff line
@@ -12,82 +12,111 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import logging, operator
import logging
from enum import Enum
from typing import Any, Dict, Iterable, List, Set, Tuple
from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Type
from ._Driver import _Driver
from .Exceptions import (
    UnsatisfiedFilterException, UnsupportedDriverClassException, UnsupportedFilterFieldException,
    UnsupportedFilterFieldValueException)
    AmbiguousFilterException, EmptyFilterFieldException,
    UnsatisfiedFilterException, UnsupportedDriverClassException,
    UnsupportedFilterFieldException, UnsupportedFilterFieldValueException
)
from .FilterFields import FILTER_FIELD_ALLOWED_VALUES, FilterFieldEnum


LOGGER = logging.getLogger(__name__)

class DriverFactory:
    def __init__(self, drivers : List[Tuple[type, List[Dict[FilterFieldEnum, Any]]]]) -> None:
        # Dict{field_name => Dict{field_value => Set{Driver}}}
        self.__indices : Dict[str, Dict[str, Set[_Driver]]] = dict()
SUPPORTED_FILTER_FIELDS = set(FILTER_FIELD_ALLOWED_VALUES.keys())

        for driver_class,filter_field_sets in drivers:
            for filter_fields in filter_field_sets:
                filter_fields = {k.value:v for k,v in filter_fields.items()}
                self.register_driver_class(driver_class, **filter_fields)

    def register_driver_class(self, driver_class, **filter_fields):
        if not issubclass(driver_class, _Driver): raise UnsupportedDriverClassException(str(driver_class))
def sanitize_filter_fields(
    filter_fields : Dict[FilterFieldEnum, Any], driver_name : Optional[str] = None
) -> Dict[FilterFieldEnum, Any]:
    if len(filter_fields) == 0:
        raise EmptyFilterFieldException(
            filter_fields, driver_class_name=driver_name
        )

        driver_name = driver_class.__name__
        supported_filter_fields = set(FILTER_FIELD_ALLOWED_VALUES.keys())
        unsupported_filter_fields = set(filter_fields.keys()).difference(supported_filter_fields)
    unsupported_filter_fields = set(filter_fields.keys()).difference(SUPPORTED_FILTER_FIELDS)
    if len(unsupported_filter_fields) > 0:
            raise UnsupportedFilterFieldException(unsupported_filter_fields, driver_class_name=driver_name)
        raise UnsupportedFilterFieldException(
            unsupported_filter_fields, driver_class_name=driver_name
        )

    sanitized_filter_fields : Dict[FilterFieldEnum, Set[Any]] = dict()
    for field_name, field_values in filter_fields.items():
            field_indice = self.__indices.setdefault(field_name, dict())
        field_enum_values = FILTER_FIELD_ALLOWED_VALUES.get(field_name)
        if not isinstance(field_values, Iterable) or isinstance(field_values, str):
            field_values = [field_values]
        
        sanitized_field_values : Set[Any] = set()
        for field_value in field_values:
            if isinstance(field_value, Enum): field_value = field_value.value
            if field_enum_values is not None and field_value not in field_enum_values:
                raise UnsupportedFilterFieldValueException(
                        field_name, field_value, field_enum_values, driver_class_name=driver_name)
                field_indice_drivers = field_indice.setdefault(field_value, set())
                field_indice_drivers.add(driver_class)
                    field_name, field_value, field_enum_values,
                    driver_class_name=driver_name
                )
            sanitized_field_values.add(field_value)
        
    def get_driver_class(self, **filter_fields) -> _Driver:
        supported_filter_fields = set(FILTER_FIELD_ALLOWED_VALUES.keys())
        unsupported_filter_fields = set(filter_fields.keys()).difference(supported_filter_fields)
        if len(unsupported_filter_fields) > 0: raise UnsupportedFilterFieldException(unsupported_filter_fields)
        if len(sanitized_field_values) == 0: continue # do not add empty filters
        sanitized_filter_fields[field_name] = sanitized_field_values
    
        candidate_driver_classes : Dict[_Driver, int] = None # number of filter hits per driver
        for field_name, field_values in filter_fields.items():
            field_indice = self.__indices.get(field_name)
            if field_indice is None: continue
            field_enum_values = FILTER_FIELD_ALLOWED_VALUES.get(field_name)
            if not isinstance(field_values, Iterable) or isinstance(field_values, str):
                field_values = [field_values]
    return sanitized_filter_fields

            field_candidate_driver_classes = set()
            for field_value in field_values:
                if field_enum_values is not None and field_value not in field_enum_values:
                    raise UnsupportedFilterFieldValueException(field_name, field_value, field_enum_values)
                field_indice_drivers = field_indice.get(field_value)
                if field_indice_drivers is None: continue
                field_candidate_driver_classes = field_candidate_driver_classes.union(field_indice_drivers)

            if candidate_driver_classes is None:
                if len(field_candidate_driver_classes) == 0: continue
                candidate_driver_classes = {k:1 for k in field_candidate_driver_classes}
            else:
                for candidate_driver_class in candidate_driver_classes:
                    if candidate_driver_class not in field_candidate_driver_classes: continue
                    candidate_driver_classes[candidate_driver_class] += 1

        if len(candidate_driver_classes) == 0: raise UnsatisfiedFilterException(filter_fields)
        candidate_driver_classes = sorted(candidate_driver_classes.items(), key=operator.itemgetter(1), reverse=True)

        MSG = '[get_driver_class] candidate_driver_classes={:s}'
        LOGGER.debug(MSG.format(str(candidate_driver_classes)))

        return candidate_driver_classes[0][0]

class DriverFactory:
    def __init__(
        self, drivers : List[Tuple[Type[_Driver], List[Dict[FilterFieldEnum, Any]]]]
    ) -> None:
        self.__drivers : List[Tuple[Type[_Driver], Dict[FilterFieldEnum, Any]]] = list()

        for driver_class,filter_field_sets in drivers:
            #if not issubclass(driver_class, _Driver):
            #    raise UnsupportedDriverClassException(str(driver_class))
            driver_name = driver_class #.__name__

            for filter_fields in filter_field_sets:
                filter_fields = {k.value:v for k,v in filter_fields.items()}
                filter_fields = sanitize_filter_fields(
                    filter_fields, driver_name=driver_name
                )
                self.__drivers.append((driver_class, filter_fields))


    def is_driver_compatible(
        self, driver_filter_fields : Dict[FilterFieldEnum, Any],
        selection_filter_fields  : Dict[FilterFieldEnum, Any]
    ) -> bool:
        # by construction empty driver_filter_fields are not allowed
        # by construction empty selection_filter_fields are not allowed
        for filter_field in SUPPORTED_FILTER_FIELDS:
            driver_values = set(driver_filter_fields.get(filter_field, set()))
            if driver_values is None  : continue # means driver does not restrict
            if len(driver_values) == 0: continue # means driver does not restrict

            selection_values = set(selection_filter_fields.get(filter_field, set()))
            is_field_compatible = selection_values.issubset(driver_values)
            if not is_field_compatible: return False

        return True


    def get_driver_class(self, **selection_filter_fields) -> _Driver:
        sanitized_filter_fields = sanitize_filter_fields(selection_filter_fields)

        compatible_drivers : List[Tuple[Type[_Driver], Dict[FilterFieldEnum, Any]]] = [
            driver_class
            for driver_class,driver_filter_fields in self.__drivers
            if self.is_driver_compatible(driver_filter_fields, sanitized_filter_fields)
        ]

        MSG = '[get_driver_class] compatible_drivers={:s}'
        LOGGER.debug(MSG.format(str(compatible_drivers)))

        num_compatible = len(compatible_drivers)
        if num_compatible == 0: 
            raise UnsatisfiedFilterException(selection_filter_fields)
        if num_compatible > 1:
            raise AmbiguousFilterException(selection_filter_fields, compatible_drivers)
        return compatible_drivers[0]
+15 −0
Original line number Diff line number Diff line
@@ -17,11 +17,26 @@ class UnsatisfiedFilterException(Exception):
        msg = 'No Driver satisfies FilterFields({:s})'
        super().__init__(msg.format(str(filter_fields)))

class AmbiguousFilterException(Exception):
    def __init__(self, filter_fields, compatible_drivers):
        msg = 'Multiple Drivers satisfy FilterFields({:s}): {:s}'
        super().__init__(msg.format(str(filter_fields), str(compatible_drivers)))

class UnsupportedDriverClassException(Exception):
    def __init__(self, driver_class_name):
        msg = 'Class({:s}) is not a subclass of _Driver'
        super().__init__(msg.format(str(driver_class_name)))

class EmptyFilterFieldException(Exception):
    def __init__(self, filter_fields, driver_class_name=None):
        if driver_class_name:
            msg = 'Empty FilterField({:s}) specified by Driver({:s}) is not supported'
            msg = msg.format(str(filter_fields), str(driver_class_name))
        else:
            msg = 'Empty FilterField({:s}) is not supported'
            msg = msg.format(str(filter_fields))
        super().__init__(msg)

class UnsupportedFilterFieldException(Exception):
    def __init__(self, unsupported_filter_fields, driver_class_name=None):
        if driver_class_name:
+51 −85
Original line number Diff line number Diff line
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import os

from common.DeviceTypes import DeviceTypeEnum
from common.proto.context_pb2 import DeviceDriverEnum
from device.Config import LOAD_ALL_DEVICE_DRIVERS
@@ -23,56 +23,15 @@ DRIVERS = []
from .emulated.EmulatedDriver import EmulatedDriver # pylint: disable=wrong-import-position
DRIVERS.append(
    (EmulatedDriver, [
        # TODO: multi-filter is not working
        {
            FilterFieldEnum.DEVICE_TYPE: [
                DeviceTypeEnum.EMULATED_DATACENTER,
                DeviceTypeEnum.EMULATED_MICROWAVE_RADIO_SYSTEM,
                DeviceTypeEnum.EMULATED_OPEN_LINE_SYSTEM,
                DeviceTypeEnum.EMULATED_OPTICAL_ROADM,
                DeviceTypeEnum.EMULATED_OPTICAL_TRANSPONDER,
                DeviceTypeEnum.EMULATED_P4_SWITCH,
                DeviceTypeEnum.EMULATED_PACKET_ROUTER,
                DeviceTypeEnum.EMULATED_PACKET_SWITCH,

                #DeviceTypeEnum.DATACENTER,
                #DeviceTypeEnum.MICROWAVE_RADIO_SYSTEM,
                #DeviceTypeEnum.OPEN_LINE_SYSTEM,
                #DeviceTypeEnum.OPTICAL_ROADM,
                #DeviceTypeEnum.OPTICAL_TRANSPONDER,
                #DeviceTypeEnum.P4_SWITCH,
                DeviceTypeEnum.PACKET_ROUTER,
                DeviceTypeEnum.PACKET_POP,
                #DeviceTypeEnum.PACKET_SWITCH,
            ],
            FilterFieldEnum.DEVICE_TYPE: [], # any device type
            FilterFieldEnum.DRIVER: [
                DeviceDriverEnum.DEVICEDRIVER_UNDEFINED,
            ],
        },
        #{
        #    # Emulated devices, all drivers => use Emulated
        #    FilterFieldEnum.DEVICE_TYPE: [
        #        DeviceTypeEnum.EMULATED_DATACENTER,
        #        DeviceTypeEnum.EMULATED_MICROWAVE_RADIO_SYSTEM,
        #        DeviceTypeEnum.EMULATED_OPEN_LINE_SYSTEM,
        #        DeviceTypeEnum.EMULATED_OPTICAL_ROADM,
        #        DeviceTypeEnum.EMULATED_OPTICAL_TRANSPONDER,
        #        DeviceTypeEnum.EMULATED_P4_SWITCH,
        #        DeviceTypeEnum.EMULATED_PACKET_ROUTER,
        #        DeviceTypeEnum.EMULATED_PACKET_SWITCH,
        #    ],
        #    FilterFieldEnum.DRIVER: [
        #        DeviceDriverEnum.DEVICEDRIVER_UNDEFINED,
        #        DeviceDriverEnum.DEVICEDRIVER_OPENCONFIG,
        #        DeviceDriverEnum.DEVICEDRIVER_TRANSPORT_API,
        #        DeviceDriverEnum.DEVICEDRIVER_P4,
        #        DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY,
        #        DeviceDriverEnum.DEVICEDRIVER_ONF_TR_532,
        #        DeviceDriverEnum.DEVICEDRIVER_GNMI_OPENCONFIG,
        #    ],
        #}
        }
    ]))

if LOAD_ALL_DEVICE_DRIVERS:
    from .ietf_l2vpn.IetfL2VpnDriver import IetfL2VpnDriver # pylint: disable=wrong-import-position
    DRIVERS.append(
        (IetfL2VpnDriver, [
@@ -82,7 +41,7 @@ DRIVERS.append(
            }
        ]))


if LOAD_ALL_DEVICE_DRIVERS:
    from .ietf_l3vpn.IetfL3VpnDriver import IetfL3VpnDriver # pylint: disable=wrong-import-position
    DRIVERS.append(
        (IetfL3VpnDriver, [
@@ -92,15 +51,20 @@ DRIVERS.append(
            }
        ]))

if LOAD_ALL_DEVICE_DRIVERS:
    from .ietf_actn.IetfActnDriver import IetfActnDriver # pylint: disable=wrong-import-position
    DRIVERS.append(
        (IetfActnDriver, [
            {
            FilterFieldEnum.DEVICE_TYPE: DeviceTypeEnum.OPEN_LINE_SYSTEM,
                FilterFieldEnum.DEVICE_TYPE: [
                    DeviceTypeEnum.OPEN_LINE_SYSTEM,
                    DeviceTypeEnum.NCE,
                ],
                FilterFieldEnum.DRIVER: DeviceDriverEnum.DEVICEDRIVER_IETF_ACTN,
            }
        ]))

if LOAD_ALL_DEVICE_DRIVERS:
    from .ietf_slice.IetfSliceDriver import IetfSliceDriver # pylint: disable=wrong-import-position
    DRIVERS.append(
        (IetfSliceDriver, [
@@ -110,6 +74,7 @@ DRIVERS.append(
            }
        ]))

if LOAD_ALL_DEVICE_DRIVERS:
    from .nce.NCEDriver import NCEDriver # pylint: disable=wrong-import-position
    DRIVERS.append(
        (NCEDriver, [
@@ -200,6 +165,7 @@ if LOAD_ALL_DEVICE_DRIVERS:
                FilterFieldEnum.DRIVER     : DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY,
            }
        ]))

if LOAD_ALL_DEVICE_DRIVERS:
    from .ryu.RyuDriver import RyuDriver
    DRIVERS.append(