Commit 6c8ae3ba authored by Lluis Gifre Renom's avatar Lluis Gifre Renom
Browse files

Service component:

- Upgraded Service Handler Factory (aligned to device driver factory) to prevent selection of partial-fit service handlers resulting in wrong service handler selection
parent 65778c42
Loading
Loading
Loading
Loading
+25 −11
Original line number Diff line number Diff line
@@ -13,17 +13,32 @@
# limitations under the License.

class UnsatisfiedFilterException(Exception):
    def __init__(self, filter_fields):
    def __init__(self, filter_fields) -> None:
        msg = 'No ServiceHandler satisfies FilterFields({:s})'
        super().__init__(msg.format(str(filter_fields)))

class AmbiguousFilterException(Exception):
    def __init__(self, filter_fields, compatible_service_handlers) -> None:
        msg = 'Multiple Service Handlers satisfy FilterFields({:s}): {:s}'
        super().__init__(msg.format(str(filter_fields), str(compatible_service_handlers)))

class UnsupportedServiceHandlerClassException(Exception):
    def __init__(self, service_handler_class_name):
    def __init__(self, service_handler_class_name) -> None:
        msg = 'Class({:s}) is not a subclass of _ServiceHandler'
        super().__init__(msg.format(str(service_handler_class_name)))

class EmptyFilterFieldException(Exception):
    def __init__(self, filter_fields, service_handler_class_name=None) -> None:
        if service_handler_class_name:
            msg = 'Empty FilterField({:s}) specified by ServiceHandler({:s}) is not supported'
            msg = msg.format(str(filter_fields), str(service_handler_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, service_handler_class_name=None):
    def __init__(self, unsupported_filter_fields, service_handler_class_name=None) -> None:
        if service_handler_class_name:
            msg = 'FilterFields({:s}) specified by ServiceHandler({:s}) are not supported'
            msg = msg.format(str(unsupported_filter_fields), str(service_handler_class_name))
@@ -34,8 +49,8 @@ class UnsupportedFilterFieldException(Exception):

class UnsupportedFilterFieldValueException(Exception):
    def __init__(
        self, filter_field_name, filter_field_value, allowed_filter_field_values, service_handler_class_name=None):

        self, filter_field_name, filter_field_value, allowed_filter_field_values, service_handler_class_name=None
    ) -> None:
        if service_handler_class_name:
            msg = 'FilterField({:s}={:s}) specified by ServiceHandler({:s}) is not supported. Allowed values are {:s}'
            msg = msg.format(
@@ -47,20 +62,19 @@ class UnsupportedFilterFieldValueException(Exception):
        super().__init__(msg)

#class UnsupportedResourceKeyException(Exception):
#    def __init__(self, resource_key):
#    def __init__(self, resource_key) -> None:
#        msg = 'ResourceKey({:s}) not supported'
#        msg = msg.format(str(resource_key))
#        super().__init__(msg)
#

#class ConfigFieldNotFoundException(Exception):
#    def __init__(self, config_field_name):
#    def __init__(self, config_field_name) -> None:
#        msg = 'ConfigField({:s}) not specified in resource'
#        msg = msg.format(str(config_field_name))
#        super().__init__(msg)
#

#class ConfigFieldsNotSupportedException(Exception):
#    def __init__(self, config_fields):
#    def __init__(self, config_fields) -> None:
#        msg = 'ConfigFields({:s}) not supported in resource'
#        msg = msg.format(str(config_fields))
#        super().__init__(msg)
#
 No newline at end of file
+16 −46
Original line number Diff line number Diff line
@@ -13,56 +13,26 @@
# limitations under the License.

from enum import Enum
from common.proto.context_pb2 import DeviceDriverEnum, ServiceTypeEnum
from typing import Any, Dict, Optional
from common.proto.context_pb2 import Device, DeviceDriverEnum, Service, ServiceTypeEnum

class FilterFieldEnum(Enum):
    SERVICE_TYPE  = 'service_type'
    DEVICE_DRIVER = 'device_driver'

SERVICE_TYPE_VALUES = {
    ServiceTypeEnum.SERVICETYPE_UNKNOWN,
    ServiceTypeEnum.SERVICETYPE_L3NM,
    ServiceTypeEnum.SERVICETYPE_L2NM,
    ServiceTypeEnum.SERVICETYPE_L1NM,
    ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE,
    ServiceTypeEnum.SERVICETYPE_TE,
    ServiceTypeEnum.SERVICETYPE_E2E,
    ServiceTypeEnum.SERVICETYPE_OPTICAL_CONNECTIVITY,
    ServiceTypeEnum.SERVICETYPE_QKD,
    ServiceTypeEnum.SERVICETYPE_INT,
    ServiceTypeEnum.SERVICETYPE_ACL,
    ServiceTypeEnum.SERVICETYPE_IP_LINK,
    ServiceTypeEnum.SERVICETYPE_IPOWDM,
    ServiceTypeEnum.SERVICETYPE_TAPI_LSP,
}

DEVICE_DRIVER_VALUES = {
    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_XR,
    DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN,
    DeviceDriverEnum.DEVICEDRIVER_GNMI_OPENCONFIG,
    DeviceDriverEnum.DEVICEDRIVER_OPTICAL_TFS,
    DeviceDriverEnum.DEVICEDRIVER_IETF_ACTN,
    DeviceDriverEnum.DEVICEDRIVER_OC,
    DeviceDriverEnum.DEVICEDRIVER_QKD,
    DeviceDriverEnum.DEVICEDRIVER_IETF_L3VPN,
    DeviceDriverEnum.DEVICEDRIVER_IETF_SLICE,
    DeviceDriverEnum.DEVICEDRIVER_NCE,
    DeviceDriverEnum.DEVICEDRIVER_SMARTNIC,
    DeviceDriverEnum.DEVICEDRIVER_MORPHEUS,
    DeviceDriverEnum.DEVICEDRIVER_RYU,
    DeviceDriverEnum.DEVICEDRIVER_GNMI_NOKIA_SRLINUX,
    DeviceDriverEnum.DEVICEDRIVER_OPENROADM,
    DeviceDriverEnum.DEVICEDRIVER_RESTCONF_OPENCONFIG,
# Map allowed filter fields to allowed values per Filter field.
# If no restriction (free text) None is specified
FILTER_FIELD_ALLOWED_VALUES = {
    FilterFieldEnum.SERVICE_TYPE.value  : set(ServiceTypeEnum.values()),
    FilterFieldEnum.DEVICE_DRIVER.value : set(DeviceDriverEnum.values()),
}

# Map allowed filter fields to allowed values per Filter field. If no restriction (free text) None is specified
FILTER_FIELD_ALLOWED_VALUES = {
    FilterFieldEnum.SERVICE_TYPE.value  : SERVICE_TYPE_VALUES,
    FilterFieldEnum.DEVICE_DRIVER.value : DEVICE_DRIVER_VALUES,
def get_service_handler_filter_fields(
    service : Optional[Service], device : Optional[Device]
) -> Dict[FilterFieldEnum, Any]:
    if service is None: return {}
    if device is None: return {}
    return {
        FilterFieldEnum.SERVICE_TYPE  : service.service_type,
        FilterFieldEnum.DEVICE_DRIVER : [driver for driver in device.device_drivers],
    }
+101 −72
Original line number Diff line number Diff line
@@ -12,93 +12,122 @@
# 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 TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Set, Tuple
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Set, Tuple, Type
from common.proto.context_pb2 import Device, DeviceDriverEnum, Service
from common.tools.grpc.Tools import grpc_message_to_json_string
from .Exceptions import (
    UnsatisfiedFilterException, UnsupportedServiceHandlerClassException, UnsupportedFilterFieldException,
    UnsupportedFilterFieldValueException)
    AmbiguousFilterException, EmptyFilterFieldException,
    UnsatisfiedFilterException, UnsupportedServiceHandlerClassException,
    UnsupportedFilterFieldException, UnsupportedFilterFieldValueException
)
from .FilterFields import FILTER_FIELD_ALLOWED_VALUES, FilterFieldEnum

if TYPE_CHECKING:
    from service.service.service_handler_api._ServiceHandler import _ServiceHandler
    from ._ServiceHandler import _ServiceHandler


LOGGER = logging.getLogger(__name__)

class ServiceHandlerFactory:
    def __init__(self, service_handlers : List[Tuple[type, List[Dict[FilterFieldEnum, Any]]]]) -> None:
        # Dict{field_name => Dict{field_value => Set{ServiceHandler}}}
        self.__indices : Dict[str, Dict[str, Set['_ServiceHandler']]] = {}
SUPPORTED_FILTER_FIELDS = set(FILTER_FIELD_ALLOWED_VALUES.keys())

        for service_handler_class,filter_field_sets in service_handlers:
            for filter_fields in filter_field_sets:
                filter_fields = {k.value:v for k,v in filter_fields.items()}
                self.register_service_handler_class(service_handler_class, **filter_fields)

    def register_service_handler_class(self, service_handler_class, **filter_fields):
        from service.service.service_handler_api._ServiceHandler import _ServiceHandler
def check_is_class_valid(service_handler_class : Type['_ServiceHandler']) -> None:
    from ._ServiceHandler import _ServiceHandler
    if not issubclass(service_handler_class, _ServiceHandler):
        raise UnsupportedServiceHandlerClassException(str(service_handler_class))

        service_handler_name = service_handler_class.__name__
        supported_filter_fields = set(FILTER_FIELD_ALLOWED_VALUES.keys())
        unsupported_filter_fields = set(filter_fields.keys()).difference(supported_filter_fields)
def sanitize_filter_fields(
    filter_fields : Dict[FilterFieldEnum, Any], service_handler_name : Optional[str] = None
) -> Dict[FilterFieldEnum, Any]:
    if len(filter_fields) == 0:
        raise EmptyFilterFieldException(
            filter_fields, service_handler_class_name=service_handler_name
        )

    unsupported_filter_fields = set(filter_fields.keys()).difference(SUPPORTED_FILTER_FIELDS)
    if len(unsupported_filter_fields) > 0:
        raise UnsupportedFilterFieldException(
                unsupported_filter_fields, service_handler_class_name=service_handler_name)
            unsupported_filter_fields, service_handler_class_name=service_handler_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, service_handler_class_name=service_handler_name)
                field_indice_service_handlers = field_indice.setdefault(field_value, set())
                field_indice_service_handlers.add(service_handler_class)
                    field_name, field_value, field_enum_values,
                    service_handler_class_name=service_handler_name
                )
            sanitized_field_values.add(field_value)
        
    def get_service_handler_class(self, **filter_fields) -> '_ServiceHandler':
        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_service_handler_classes : Dict['_ServiceHandler', int] = None # num. filter hits per service_handler
        for field_name, field_values in filter_fields.items():
            field_indice = self.__indices.get(field_name)
            if field_indice is None: continue
            if not isinstance(field_values, Iterable) or isinstance(field_values, str):
                field_values = [field_values]
            if len(field_values) == 0:
                # do not allow empty fields; might cause wrong selection
                raise UnsatisfiedFilterException(filter_fields)
    return sanitized_filter_fields

            field_enum_values = FILTER_FIELD_ALLOWED_VALUES.get(field_name)

            field_candidate_service_handler_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_service_handlers = field_indice.get(field_value)
                if field_indice_service_handlers is None: continue
                field_candidate_service_handler_classes = field_candidate_service_handler_classes.union(
                    field_indice_service_handlers)

            if candidate_service_handler_classes is None:
                candidate_service_handler_classes = {k:1 for k in field_candidate_service_handler_classes}
            else:
                for candidate_service_handler_class in candidate_service_handler_classes:
                    if candidate_service_handler_class not in field_candidate_service_handler_classes: continue
                    candidate_service_handler_classes[candidate_service_handler_class] += 1

        if len(candidate_service_handler_classes) == 0: raise UnsatisfiedFilterException(filter_fields)
        candidate_service_handler_classes = sorted(
            candidate_service_handler_classes.items(), key=operator.itemgetter(1), reverse=True)
        return candidate_service_handler_classes[0][0]
class ServiceHandlerFactory:
    def __init__(
        self, service_handlers : List[Tuple[Type['_ServiceHandler'], List[Dict[FilterFieldEnum, Any]]]]
    ) -> None:
        self.__service_handlers : List[Tuple[Type['_ServiceHandler'], Dict[FilterFieldEnum, Any]]] = list()

        for service_handler_class,filter_field_sets in service_handlers:
            check_is_class_valid(service_handler_class)
            service_handler_name = service_handler_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, service_handler_name=service_handler_name
                )
                self.__service_handlers.append((service_handler_class, filter_fields))


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

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

        return True


    def get_service_handler_class(self, **selection_filter_fields) -> '_ServiceHandler':
        sanitized_filter_fields = sanitize_filter_fields(selection_filter_fields)

        compatible_service_handlers : List[Tuple[Type[_ServiceHandler], Dict[FilterFieldEnum, Any]]] = [
            service_handler_class
            for service_handler_class,service_handler_filter_fields in self.__service_handlers
            if self.is_service_handler_compatible(service_handler_filter_fields, sanitized_filter_fields)
        ]

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

        num_compatible = len(compatible_service_handlers)
        if num_compatible == 0: 
            raise UnsatisfiedFilterException(selection_filter_fields)
        if num_compatible > 1:
            raise AmbiguousFilterException(selection_filter_fields, compatible_service_handlers)
        return compatible_service_handlers[0]

def get_common_device_drivers(drivers_per_device : List[Set[int]]) -> Set[int]:
    common_device_drivers = None