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

Several changes:

Common / Context / Device / Service:
- Improvements in unitary testing
- Extended assertion validators tor testing
- Factorized service implementation code to improve testability, reusability and increase code coverage
parent c4453f5d
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -4,4 +4,4 @@ class ServiceException(Exception):
    def __init__(self, code : grpc.StatusCode, details : str) -> None:
        self.code = code
        self.details = details
        super().__init__()
        super().__init__(self.details)
+42 −1
Original line number Diff line number Diff line
@@ -113,6 +113,14 @@ def validate_topology_has_links(message):
    validate_topology(message)
    assert len(message['link']) > 0

def validate_constraint(message):
    assert type(message) is dict
    assert len(message.keys()) == 2
    assert 'constraint_type' in message
    assert type(message['constraint_type']) is str
    assert 'constraint_value' in message
    assert type(message['constraint_value']) is str

def validate_service_id(message):
    assert type(message) is dict
    assert len(message.keys()) == 2
@@ -121,10 +129,43 @@ def validate_service_id(message):
    assert 'cs_id' in message
    validate_uuid(message['cs_id'])

def validate_service_config(message):
    assert type(message) is dict
    assert len(message.keys()) == 1
    assert 'serviceConfig' in message
    assert type(message['serviceConfig']) is str

def validate_service_type(message):
    assert type(message) is str
    assert message in ['UNKNOWN', 'L3NM', 'L2NM', 'TAPI_CONNECTIVITY_SERVICE']

def validate_service_state_enum(message):
    assert type(message) is str
    assert message in ['PLANNED', 'ACTIVE', 'PENDING_REMOVAL']

def validate_service_state(message):
    assert type(message) is dict
    assert len(message.keys()) == 1
    assert 'serviceState' in message
    validate_service_state_enum(message['serviceState'])

def validate_service(message):
    assert type(message) is dict
    assert len(message.keys()) > 1
    assert len(message.keys()) == 6
    assert 'cs_id' in message
    validate_service_id(message['cs_id'])
    assert 'serviceType' in message
    validate_service_type(message['serviceType'])
    assert 'endpointList' in message
    assert type(message['endpointList']) is list
    for endpoint_id in message['endpointList']: validate_endpoint_id(endpoint_id)
    assert 'constraint' in message
    assert type(message['constraint']) is list
    for constraint in message['constraint']: validate_constraint(constraint)
    assert 'serviceState' in message
    validate_service_state(message['serviceState'])
    assert 'serviceConfig' in message
    validate_service_config(message['serviceConfig'])

def validate_service_list(message):
    assert type(message) is dict
+25 −14
Original line number Diff line number Diff line
@@ -7,10 +7,10 @@ from common.database.api.context.Constants import DEFAULT_CONTEXT_ID, DEFAULT_TO
def check_endpoint_id(
    logger : logging.Logger, endpoint_number : int, parent_name : str, endpoint_id : 'EndpointId',
    add_topology_devices_endpoints : Dict[str, Dict[str, Set[str]]],
    default_device_if_empty : Union[str, None] = None,
    predefined_context_id : str = DEFAULT_CONTEXT_ID, acceptable_context_ids : Set[str] = set([DEFAULT_CONTEXT_ID]),
    predefined_topology_id : str = DEFAULT_TOPOLOGY_ID, acceptable_topology_ids : Set[str] = set([DEFAULT_TOPOLOGY_ID])
    ) -> Tuple[str, str, str]:
    predefined_topology_id : str = DEFAULT_TOPOLOGY_ID, acceptable_topology_ids : Set[str] = set([DEFAULT_TOPOLOGY_ID]),
    predefined_device_id : Union[str, None] = None, acceptable_device_ids : Set[str] = set(),
    prevent_same_device_multiple_times : bool = True) -> Tuple[str, str, str]:

    try:
        ep_context_id  = chk_string('endpoint_id[#{}].topoId.contextId.contextUuid.uuid'.format(endpoint_number),
@@ -21,7 +21,7 @@ def check_endpoint_id(
                                    allow_empty=True)
        ep_device_id   = chk_string('endpoint_id[#{}].dev_id.device_id.uuid'.format(endpoint_number),
                                    endpoint_id.dev_id.device_id.uuid,
                                    allow_empty=(default_device_if_empty is not None))
                                    allow_empty=(predefined_device_id is not None))
        ep_port_id     = chk_string('endpoint_id[#{}].port_id.uuid'.format(endpoint_number),
                                    endpoint_id.port_id.uuid,
                                    allow_empty=False)
@@ -30,33 +30,44 @@ def check_endpoint_id(
        raise ServiceException(grpc.StatusCode.INVALID_ARGUMENT, str(e))

    if len(ep_context_id) == 0:
        # Assumption: if no context is specified for an endpoint_id, use parent context
        # Assumption: if no context is specified for an endpoint_id, use predefined context
        ep_context_id = predefined_context_id
    elif ep_context_id not in acceptable_context_ids:
    elif (len(acceptable_context_ids) > 0) and (ep_context_id not in acceptable_context_ids):
        # Assumption: parent and endpoints should belong to the same context
        msg = ' '.join([
            'Context({}) in {} mismatches acceptable Contexts({}).',
            'Optionally, leave field empty to use predefined Context({}).',
        ])
        msg = msg.format(ep_context_id, parent_name, str(acceptable_context_ids), predefined_context_id)
        msg = msg.format(
            ep_context_id, parent_name, str(acceptable_context_ids), predefined_context_id)
        raise ServiceException(grpc.StatusCode.INVALID_ARGUMENT, msg)

    if len(ep_topology_id) == 0:
        # Assumption: if no topology is specified for an endpoint_id, use parent topology
        # Assumption: if no topology is specified for an endpoint_id, use predefined topology
        ep_topology_id = predefined_topology_id
    elif ep_topology_id not in acceptable_topology_ids:
    elif (len(acceptable_topology_ids) > 0) and (ep_topology_id not in acceptable_topology_ids):
        msg = ' '.join([
            'Topology({}) in {} mismatches acceptable Topologies({}).',
            'Context({})/Topology({}) in {} mismatches acceptable Topologies({}).',
            'Optionally, leave field empty to use predefined Topology({}).',
        ])
        msg = msg.format(ep_topology_id, parent_name, str(acceptable_topology_ids), predefined_topology_id)
        msg = msg.format(
            ep_context_id, ep_topology_id, parent_name, str(acceptable_topology_ids), predefined_topology_id)
        raise ServiceException(grpc.StatusCode.INVALID_ARGUMENT, msg)

    if (len(ep_device_id) == 0) and (default_device_if_empty is not None):
        ep_device_id = default_device_if_empty
    if (predefined_device_id is not None) and (len(ep_device_id) == 0):
        # Assumption: if no device is specified for an endpoint_id, use predefined device, if available
        ep_device_id = predefined_device_id
    elif (len(acceptable_device_ids) > 0) and (ep_device_id not in acceptable_device_ids):
        msg = ' '.join([
            'Context({})/Topology({})/Device({}) in {} mismatches acceptable Devices({}).',
            'Optionally, leave field empty to use predefined Device({}).',
        ])
        msg = msg.format(
            ep_context_id, ep_topology_id, ep_device_id, parent_name, str(acceptable_device_ids), predefined_device_id)
        raise ServiceException(grpc.StatusCode.INVALID_ARGUMENT, msg)

    add_devices = add_topology_devices_endpoints.setdefault(ep_topology_id, dict())
    if ep_device_id in add_devices:
    if prevent_same_device_multiple_times and (ep_device_id in add_devices):
        msg = 'Duplicated Context({})/Topology({})/Device({}) in {}.'
        msg = msg.format(ep_context_id, ep_topology_id, ep_device_id, parent_name)
        raise ServiceException(grpc.StatusCode.INVALID_ARGUMENT, msg)
+2 −1
Original line number Diff line number Diff line
@@ -140,7 +140,8 @@ def test_add_link_wrong_endpoint(context_client : ContextClient):
        context_client.AddLink(Link(**copy_link))
    assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
    msg = ' '.join([
        'Topology(wrong-topo) in Endpoint(#0) of Context(admin)/Topology(admin)/Link(DEV1/EP2 ==> DEV2/EP1)',
        'Context(admin)/Topology(wrong-topo)',
        'in Endpoint(#0) of Context(admin)/Topology(admin)/Link(DEV1/EP2 ==> DEV2/EP1)',
        'mismatches acceptable Topologies({\'admin\'}).',
        'Optionally, leave field empty to use predefined Topology(admin).',
    ])
+5 −31
Original line number Diff line number Diff line
@@ -84,39 +84,14 @@ class DeviceServiceServicerImpl(DeviceServiceServicer):
            LOGGER.debug('ConfigureDevice request: {}'.format(str(request)))

            # ----- Validate request data and pre-conditions -----------------------------------------------------------
            try:
                device_id     = chk_string ('device.device_id.device_id.uuid',
                                            request.device_id.device_id.uuid,
                                            allow_empty=False)
                device_type   = chk_string ('device.device_type',
                                            request.device_type,
                                            allow_empty=True)
                device_config = chk_string ('device.device_config.device_config',
                                            request.device_config.device_config,
                                            allow_empty=True)
                device_opstat = chk_options('device.devOperationalStatus',
                                            request.devOperationalStatus,
                                            operationalstatus_enum_values())
            except Exception as e:
                LOGGER.exception('Invalid arguments:')
                raise ServiceException(grpc.StatusCode.INVALID_ARGUMENT, str(e))

            device_opstat = to_operationalstatus_enum(device_opstat)
            # should not happen because gRPC limits accepted values in enums
            if device_opstat is None:                                           # pragma: no cover
                msg = 'Unsupported OperationalStatus({}).'                      # pragma: no cover
                msg = msg.format(request.devOperationalStatus)                  # pragma: no cover
                raise ServiceException(grpc.StatusCode.INVALID_ARGUMENT, msg)   # pragma: no cover
            device_id, device_type, device_config, device_opstat, db_endpoints_ports = \
                check_device_request('UpdateDevice', request, self.database, LOGGER)

            # ----- Implement changes in the database ------------------------------------------------------------------
            db_context = self.database.context(DEFAULT_CONTEXT_ID).create()
            db_topology = db_context.topology(DEFAULT_TOPOLOGY_ID).create()

            if not db_topology.devices.contains(device_id):
                msg = 'Device({}) does not exist in the database.'
                msg = msg.format(device_id)
                raise ServiceException(grpc.StatusCode.NOT_FOUND, msg)

            db_device = db_topology.device(device_id)

            db_device_attributes = db_device.attributes.get(attributes=['device_type'])
            # should not happen, device creation through Database API ensures all fields are always present
            if len(db_device_attributes) == 0:                                                  # pragma: no cover
@@ -136,7 +111,7 @@ class DeviceServiceServicerImpl(DeviceServiceServicer):
                msg = msg.format(device_id, db_device_type, device_type)
                raise ServiceException(grpc.StatusCode.INVALID_ARGUMENT, msg)

            if len(request.endpointList) > 0:
            if len(db_endpoints_ports) > 0:
                msg = 'Endpoints belonging to Device({}) cannot be modified.'
                msg = msg.format(device_id)
                raise ServiceException(grpc.StatusCode.INVALID_ARGUMENT, msg)
@@ -157,7 +132,6 @@ class DeviceServiceServicerImpl(DeviceServiceServicer):
                msg = msg.format(device_id)
                raise ServiceException(grpc.StatusCode.ABORTED, msg)

            # ----- Implement changes in the database ------------------------------------------------------------------
            db_device.update(update_attributes=update_attributes)

            # ----- Compose reply --------------------------------------------------------------------------------------
Loading