Commit 3b11d31e authored by Lluis Gifre Renom's avatar Lluis Gifre Renom
Browse files

Context component:

- ConfigRules: improved management and added support for ACL rules
- Constraints: improved management
parent ebbbc134
Loading
Loading
Loading
Loading
+95 −161
Original line number Diff line number Diff line
@@ -12,16 +12,17 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import datetime, logging
import datetime, json, logging
from sqlalchemy import delete
from sqlalchemy.dialects.postgresql import insert
from sqlalchemy.orm import Session
from typing import Dict, List, Optional
from typing import Dict, List, Optional, Set
from common.proto.context_pb2 import ConfigRule
from common.tools.grpc.Tools import grpc_message_to_json_string
from .models.enums.ConfigAction import grpc_to_enum__config_action
from .models.enums.ConfigAction import ORM_ConfigActionEnum, grpc_to_enum__config_action
from .models.ConfigRuleModel import ConfigRuleKindEnum, ConfigRuleModel
from .uuids._Builder import get_uuid_random
from .uuids._Builder import get_uuid_from_string
from .uuids.EndPoint import endpoint_get_uuid

LOGGER = logging.getLogger(__name__)

@@ -31,173 +32,106 @@ def compose_config_rules_data(
) -> List[Dict]:
    dict_config_rules : List[Dict] = list()
    for position,config_rule in enumerate(config_rules):
        configrule_uuid = get_uuid_random()
        str_kind = config_rule.WhichOneof('config_rule')
        kind = ConfigRuleKindEnum._member_map_.get(str_kind.upper()) # pylint: disable=no-member
        dict_config_rule = {
            'configrule_uuid': configrule_uuid,
            'position'  : position,
            'kind'           : ConfigRuleKindEnum._member_map_.get(str_kind.upper()), # pylint: disable=no-member
            'kind'      : kind,
            'action'    : grpc_to_enum__config_action(config_rule.action),
            'data'      : grpc_message_to_json_string(getattr(config_rule, str_kind, {})),
            'created_at': now,
            'updated_at': now,
        }
        if device_uuid  is not None: dict_config_rule['device_uuid' ] = device_uuid
        if service_uuid is not None: dict_config_rule['service_uuid'] = service_uuid
        if slice_uuid   is not None: dict_config_rule['slice_uuid'  ] = slice_uuid

        parent_uuid = None
        if device_uuid is not None:
            dict_config_rule['device_uuid'] = device_uuid
            parent_uuid = device_uuid
        elif service_uuid is not None:
            dict_config_rule['service_uuid'] = service_uuid
            parent_uuid = service_uuid
        elif slice_uuid is not None:
            dict_config_rule['slice_uuid'] = slice_uuid
            parent_uuid = slice_uuid
        else:
            MSG = 'Parent for ConfigRule({:s}) cannot be identified '+\
                  '(device_uuid={:s}, service_uuid={:s}, slice_uuid={:s})'
            str_config_rule = grpc_message_to_json_string(config_rule)
            raise Exception(MSG.format(str_config_rule, str(device_uuid), str(service_uuid), str(slice_uuid)))
        
        configrule_name = None
        if kind == ConfigRuleKindEnum.CUSTOM:
            configrule_name = config_rule.custom.resource_key
        elif kind == ConfigRuleKindEnum.ACL:
            endpoint_uuid = endpoint_get_uuid(config_rule.acl.endpoint_id, allow_random=False)
            rule_set_name = config_rule.acl.rule_set.name
            configrule_name = '{:s}/{:s}'.format(endpoint_uuid, rule_set_name)
        else:
            MSG = 'Name for ConfigRule({:s}) cannot be inferred '+\
                  '(device_uuid={:s}, service_uuid={:s}, slice_uuid={:s})'
            str_config_rule = grpc_message_to_json_string(config_rule)
            raise Exception(MSG.format(str_config_rule, str(device_uuid), str(service_uuid), str(slice_uuid)))

        configrule_uuid = get_uuid_from_string(configrule_name, prefix_for_name=parent_uuid)
        dict_config_rule['configrule_uuid'] = configrule_uuid

        dict_config_rules.append(dict_config_rule)
    return dict_config_rules

def upsert_config_rules(
    session : Session, config_rules : List[Dict],
    device_uuid : Optional[str] = None, service_uuid : Optional[str] = None, slice_uuid : Optional[str] = None,
) -> List[bool]:
    # TODO: do not delete all rules; just add-remove as needed
) -> bool:
    uuids_to_delete : Set[str] = set()
    uuids_to_upsert : Dict[str, int] = dict()
    rules_to_upsert : List[Dict] = list()
    for config_rule in config_rules:
        if config_rule['action'] == ORM_ConfigActionEnum.SET:
            configrule_uuid = config_rule['configrule_uuid']
            position = uuids_to_upsert.get(configrule_uuid)
            if position is None:
                # if not added, add it
                rules_to_upsert.append(config_rule)
                uuids_to_upsert[config_rule['configrule_uuid']] = len(rules_to_upsert) - 1
            else:
                # if already added, update occurrence
                rules_to_upsert[position] = config_rule
        elif config_rule['action'] == ORM_ConfigActionEnum.DELETE:
            uuids_to_delete.add(config_rule['configrule_uuid'])
        else:
            MSG = 'Action for ConfigRule({:s}) is not supported '+\
                  '(device_uuid={:s}, service_uuid={:s}, slice_uuid={:s})'
            str_config_rule = json.dumps(config_rule)
            raise Exception(MSG.format(str_config_rule, str(device_uuid), str(service_uuid), str(slice_uuid)))

    #LOGGER.warning('uuids_to_delete={:s}'.format(str(uuids_to_delete)))
    #LOGGER.warning('rules_to_upsert={:s}'.format(str(rules_to_upsert)))

    delete_affected = False
    upsert_affected = False

    if len(uuids_to_delete) > 0:
        stmt = delete(ConfigRuleModel)
        if device_uuid  is not None: stmt = stmt.where(ConfigRuleModel.device_uuid  == device_uuid )
        if service_uuid is not None: stmt = stmt.where(ConfigRuleModel.service_uuid == service_uuid)
        if slice_uuid   is not None: stmt = stmt.where(ConfigRuleModel.slice_uuid   == slice_uuid  )
    session.execute(stmt)
        stmt = stmt.where(ConfigRuleModel.configrule_uuid.in_(uuids_to_delete))
        configrule_deletes = session.execute(stmt)#.fetchall()
        delete_affected = int(configrule_deletes.rowcount) > 0

    configrule_updates = []
    if len(config_rules) > 0:
        stmt = insert(ConfigRuleModel).values(config_rules)
        #stmt = stmt.on_conflict_do_update(
        #    index_elements=[ConfigRuleModel.configrule_uuid],
        #    set_=dict(
        #        updated_at = stmt.excluded.updated_at,
        #    )
        #)
    if len(rules_to_upsert) > 0:
        stmt = insert(ConfigRuleModel).values(rules_to_upsert)
        stmt = stmt.on_conflict_do_update(
            index_elements=[ConfigRuleModel.configrule_uuid],
            set_=dict(
                position   = stmt.excluded.position,
                action     = stmt.excluded.action,
                data       = stmt.excluded.data,
                updated_at = stmt.excluded.updated_at,
            )
        )
        stmt = stmt.returning(ConfigRuleModel.created_at, ConfigRuleModel.updated_at)
        configrule_updates = session.execute(stmt).fetchall()
        upsert_affected = any([(updated_at > created_at) for created_at,updated_at in configrule_updates])

    return configrule_updates

#Union_SpecificConfigRule = Union[
#    ConfigRuleCustomModel, ConfigRuleAclModel
#]
#
#def set_config_rule(
#    database : Database, db_config : ConfigModel, position : int, resource_key : str, resource_value : str,
#): # -> Tuple[ConfigRuleModel, bool]:
#
#    str_rule_key_hash = fast_hasher(resource_key)
#    str_config_rule_key = key_to_str([db_config.config_uuid, str_rule_key_hash], separator=':')
#
#    data = {'config_fk': db_config, 'position': position, 'action': ORM_ConfigActionEnum.SET, 'key': resource_key,
#            'value': resource_value}
#    to_add = ConfigRuleModel(**data)
#
#    result = database.create_or_update(to_add)
#    return result
#Tuple_ConfigRuleSpecs = Tuple[Type, str, Dict, ConfigRuleKindEnum]
#
#def parse_config_rule_custom(database : Database, grpc_config_rule) -> Tuple_ConfigRuleSpecs:
#    config_rule_class = ConfigRuleCustomModel
#    str_config_rule_id = grpc_config_rule.custom.resource_key
#    config_rule_data = {
#        'key'  : grpc_config_rule.custom.resource_key,
#        'value': grpc_config_rule.custom.resource_value,
#    }
#    return config_rule_class, str_config_rule_id, config_rule_data, ConfigRuleKindEnum.CUSTOM
#
#def parse_config_rule_acl(database : Database, grpc_config_rule) -> Tuple_ConfigRuleSpecs:
#    config_rule_class = ConfigRuleAclModel
#    grpc_endpoint_id = grpc_config_rule.acl.endpoint_id
#    grpc_rule_set = grpc_config_rule.acl.rule_set
#    device_uuid = grpc_endpoint_id.device_id.device_uuid.uuid
#    endpoint_uuid = grpc_endpoint_id.endpoint_uuid.uuid
#    str_endpoint_key = '/'.join([device_uuid, endpoint_uuid])
#    #str_endpoint_key, db_endpoint = get_endpoint(database, grpc_endpoint_id)
#    str_config_rule_id = ':'.join([str_endpoint_key, grpc_rule_set.name])
#    config_rule_data = {
#        #'endpoint_fk': db_endpoint,
#        'endpoint_id': grpc_message_to_json_string(grpc_endpoint_id),
#        'acl_data': grpc_message_to_json_string(grpc_rule_set),
#    }
#    return config_rule_class, str_config_rule_id, config_rule_data, ConfigRuleKindEnum.ACL
#
#CONFIGRULE_PARSERS = {
#    'custom': parse_config_rule_custom,
#    'acl'   : parse_config_rule_acl,
#}
#
#Union_ConfigRuleModel = Union[
#    ConfigRuleCustomModel, ConfigRuleAclModel,
#]
#
#def set_config_rule(
#    database : Database, db_config : ConfigModel, grpc_config_rule : ConfigRule, position : int
#) -> Tuple[Union_ConfigRuleModel, bool]:
#    grpc_config_rule_kind = str(grpc_config_rule.WhichOneof('config_rule'))
#    parser = CONFIGRULE_PARSERS.get(grpc_config_rule_kind)
#    if parser is None:
#        raise NotImplementedError('ConfigRule of kind {:s} is not implemented: {:s}'.format(
#            grpc_config_rule_kind, grpc_message_to_json_string(grpc_config_rule)))
#
#    # create specific ConfigRule
#    config_rule_class, str_config_rule_id, config_rule_data, config_rule_kind = parser(database, grpc_config_rule)
#    str_config_rule_key_hash = fast_hasher(':'.join([config_rule_kind.value, str_config_rule_id]))
#    str_config_rule_key = key_to_str([db_config.pk, str_config_rule_key_hash], separator=':')
#    result : Tuple[Union_ConfigRuleModel, bool] = update_or_create_object(
#        database, config_rule_class, str_config_rule_key, config_rule_data)
#    db_specific_config_rule, updated = result
#
#    # create generic ConfigRule
#    config_rule_fk_field_name = 'config_rule_{:s}_fk'.format(config_rule_kind.value)
#    config_rule_data = {
#        'config_fk': db_config, 'kind': config_rule_kind, 'position': position,
#        'action': ORM_ConfigActionEnum.SET,
#        config_rule_fk_field_name: db_specific_config_rule
#    }
#    result : Tuple[ConfigRuleModel, bool] = update_or_create_object(
#        database, ConfigRuleModel, str_config_rule_key, config_rule_data)
#    db_config_rule, updated = result
#
#    return db_config_rule, updated
#
#def delete_config_rule(
#    database : Database, db_config : ConfigModel, grpc_config_rule : ConfigRule
#) -> None:
#    grpc_config_rule_kind = str(grpc_config_rule.WhichOneof('config_rule'))
#    parser = CONFIGRULE_PARSERS.get(grpc_config_rule_kind)
#    if parser is None:
#        raise NotImplementedError('ConfigRule of kind {:s} is not implemented: {:s}'.format(
#            grpc_config_rule_kind, grpc_message_to_json_string(grpc_config_rule)))
#
#    # delete generic config rules; self deletes specific config rule
#    _, str_config_rule_id, _, config_rule_kind = parser(database, grpc_config_rule)
#    str_config_rule_key_hash = fast_hasher(':'.join([config_rule_kind.value, str_config_rule_id]))
#    str_config_rule_key = key_to_str([db_config.pk, str_config_rule_key_hash], separator=':')
#    db_config_rule : Optional[ConfigRuleModel] = get_object(
#        database, ConfigRuleModel, str_config_rule_key, raise_if_not_found=False)
#    if db_config_rule is None: return
#    db_config_rule.delete()
#
#def update_config(
#    database : Database, db_parent_pk : str, config_name : str, grpc_config_rules
#) -> List[Tuple[Union[ConfigModel, ConfigRuleModel], bool]]:
#
#    str_config_key = key_to_str([config_name, db_parent_pk], separator=':')
#    result : Tuple[ConfigModel, bool] = get_or_create_object(database, ConfigModel, str_config_key)
#    db_config, created = result
#
#    db_objects = [(db_config, created)]
#
#    for position,grpc_config_rule in enumerate(grpc_config_rules):
#        action = grpc_to_enum__config_action(grpc_config_rule.action)
#
#        if action == ORM_ConfigActionEnum.SET:
#            result : Tuple[ConfigRuleModel, bool] = set_config_rule(
#                database, db_config, grpc_config_rule, position)
#            db_config_rule, updated = result
#            db_objects.append((db_config_rule, updated))
#        elif action == ORM_ConfigActionEnum.DELETE:
#            delete_config_rule(database, db_config, grpc_config_rule)
#        else:
#            msg = 'Unsupported Action({:s}) for ConfigRule({:s})'
#            str_action = str(ConfigActionEnum.Name(action))
#            str_config_rule = grpc_message_to_json_string(grpc_config_rule)
#            raise AttributeError(msg.format(str_action, str_config_rule))
#
#    return db_objects
    return delete_affected or upsert_affected
+4 −3
Original line number Diff line number Diff line
@@ -47,14 +47,14 @@ def compose_constraints_data(
def upsert_constraints(
    session : Session, constraints : List[Dict],
    service_uuid : Optional[str] = None, slice_uuid : Optional[str] = None
) -> List[bool]:
) -> bool:
    # TODO: do not delete all constraints; just add-remove as needed
    stmt = delete(ConstraintModel)
    if service_uuid is not None: stmt = stmt.where(ConstraintModel.service_uuid == service_uuid)
    if slice_uuid   is not None: stmt = stmt.where(ConstraintModel.slice_uuid   == slice_uuid  )
    session.execute(stmt)

    constraint_updates = []
    changed = False
    if len(constraints) > 0:
        stmt = insert(ConstraintModel).values(constraints)
        #stmt = stmt.on_conflict_do_update(
@@ -65,8 +65,9 @@ def upsert_constraints(
        #)
        stmt = stmt.returning(ConstraintModel.created_at, ConstraintModel.updated_at)
        constraint_updates = session.execute(stmt).fetchall()
        changed = any([(updated_at > created_at) for created_at,updated_at in constraint_updates])

    return constraint_updates
    return changed


#    def set_constraint(self, db_constraints: ConstraintsModel, grpc_constraint: Constraint, position: int
+5 −4
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ from sqlalchemy_cockroachdb import run_transaction
from typing import Dict, List, Optional, Set, Tuple
from common.method_wrappers.ServiceExceptions import InvalidArgumentException, NotFoundException
from common.proto.context_pb2 import Device, DeviceId
from common.tools.grpc.Tools import grpc_message_to_json_string
from common.tools.object_factory.Device import json_device_id
from .models.DeviceModel import DeviceModel
from .models.EndPointModel import EndPointModel
@@ -136,6 +137,7 @@ def device_set(db_engine : Engine, request : Device) -> Tuple[Dict, bool]:
        created_at,updated_at = session.execute(stmt).fetchone()
        updated = updated_at > created_at

        updated_endpoints = False
        if len(endpoints_data) > 0:
            stmt = insert(EndPointModel).values(endpoints_data)
            stmt = stmt.on_conflict_do_update(
@@ -149,17 +151,16 @@ def device_set(db_engine : Engine, request : Device) -> Tuple[Dict, bool]:
            )
            stmt = stmt.returning(EndPointModel.created_at, EndPointModel.updated_at)
            endpoint_updates = session.execute(stmt).fetchall()
            updated = updated or any([(updated_at > created_at) for created_at,updated_at in endpoint_updates])
            updated_endpoints = any([(updated_at > created_at) for created_at,updated_at in endpoint_updates])

        if len(related_topologies) > 0:
            session.execute(insert(TopologyDeviceModel).values(related_topologies).on_conflict_do_nothing(
                index_elements=[TopologyDeviceModel.topology_uuid, TopologyDeviceModel.device_uuid]
            ))

        configrule_updates = upsert_config_rules(session, config_rules, device_uuid=device_uuid)
        updated = updated or any([(updated_at > created_at) for created_at,updated_at in configrule_updates])
        changed_config_rules = upsert_config_rules(session, config_rules, device_uuid=device_uuid)

        return updated
        return updated or updated_endpoints or changed_config_rules

    updated = run_transaction(sessionmaker(bind=db_engine), callback)
    return json_device_id(device_uuid),updated
+4 −6
Original line number Diff line number Diff line
@@ -118,6 +118,7 @@ def service_set(db_engine : Engine, request : Service) -> Tuple[Dict, bool]:
        created_at,updated_at = session.execute(stmt).fetchone()
        updated = updated_at > created_at

        # TODO: check if endpoints are changed
        if len(service_endpoints_data) > 0:
            stmt = insert(ServiceEndPointModel).values(service_endpoints_data)
            stmt = stmt.on_conflict_do_nothing(
@@ -125,13 +126,10 @@ def service_set(db_engine : Engine, request : Service) -> Tuple[Dict, bool]:
            )
            session.execute(stmt)

        constraint_updates = upsert_constraints(session, constraints, service_uuid=service_uuid)
        updated = updated or any([(updated_at > created_at) for created_at,updated_at in constraint_updates])
        changed_constraints = upsert_constraints(session, constraints, service_uuid=service_uuid)
        changed_config_rules = upsert_config_rules(session, config_rules, service_uuid=service_uuid)

        configrule_updates = upsert_config_rules(session, config_rules, service_uuid=service_uuid)
        updated = updated or any([(updated_at > created_at) for created_at,updated_at in configrule_updates])

        return updated
        return updated or changed_constraints or changed_config_rules

    updated = run_transaction(sessionmaker(bind=db_engine), callback)
    return json_service_id(service_uuid, json_context_id(context_uuid)),updated
+6 −6
Original line number Diff line number Diff line
@@ -136,6 +136,7 @@ def slice_set(db_engine : Engine, request : Slice) -> Tuple[Dict, bool]:
        created_at,updated_at = session.execute(stmt).fetchone()
        updated = updated_at > created_at

        # TODO: check if endpoints are changed
        if len(slice_endpoints_data) > 0:
            stmt = insert(SliceEndPointModel).values(slice_endpoints_data)
            stmt = stmt.on_conflict_do_nothing(
@@ -143,6 +144,7 @@ def slice_set(db_engine : Engine, request : Slice) -> Tuple[Dict, bool]:
            )
            session.execute(stmt)

        # TODO: check if services are changed
        if len(slice_services_data) > 0:
            stmt = insert(SliceServiceModel).values(slice_services_data)
            stmt = stmt.on_conflict_do_nothing(
@@ -150,6 +152,7 @@ def slice_set(db_engine : Engine, request : Slice) -> Tuple[Dict, bool]:
            )
            session.execute(stmt)

        # TODO: check if subslices are changed
        if len(slice_subslices_data) > 0:
            stmt = insert(SliceSubSliceModel).values(slice_subslices_data)
            stmt = stmt.on_conflict_do_nothing(
@@ -157,13 +160,10 @@ def slice_set(db_engine : Engine, request : Slice) -> Tuple[Dict, bool]:
            )
            session.execute(stmt)

        constraint_updates = upsert_constraints(session, constraints, slice_uuid=slice_uuid)
        updated = updated or any([(updated_at > created_at) for created_at,updated_at in constraint_updates])
        changed_constraints = upsert_constraints(session, constraints, slice_uuid=slice_uuid)
        changed_config_rules = upsert_config_rules(session, config_rules, slice_uuid=slice_uuid)

        configrule_updates = upsert_config_rules(session, config_rules, slice_uuid=slice_uuid)
        updated = updated or any([(updated_at > created_at) for created_at,updated_at in configrule_updates])

        return updated
        return updated or changed_constraints or changed_config_rules

    updated = run_transaction(sessionmaker(bind=db_engine), callback)
    return json_slice_id(slice_uuid, json_context_id(context_uuid)),updated