gbp-validate: Tenant and resource level scoping.
Change-Id: Ia25071474c02954516da09b9c3f935b36fcac82b
This commit is contained in:
		@@ -6347,7 +6347,6 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
 | 
			
		||||
                            network_id=mapping.network_id)
 | 
			
		||||
 | 
			
		||||
    def validate_aim_mapping(self, mgr):
 | 
			
		||||
        # Register all AIM resource types used by mapping.
 | 
			
		||||
        mgr.register_aim_resource_class(aim_infra.HostDomainMappingV2)
 | 
			
		||||
        mgr.register_aim_resource_class(aim_resource.ApplicationProfile)
 | 
			
		||||
        mgr.register_aim_resource_class(aim_resource.BridgeDomain)
 | 
			
		||||
@@ -6376,8 +6375,17 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
 | 
			
		||||
        mgr.register_aim_resource_class(aim_resource.InfraAccBundleGroup)
 | 
			
		||||
 | 
			
		||||
        # Copy common Tenant from actual to expected AIM store.
 | 
			
		||||
        mgr.tenant_ids = []
 | 
			
		||||
        project_dict = self.project_details_cache.project_details
 | 
			
		||||
        for project_id in project_dict.keys():
 | 
			
		||||
            tenant_name = project_dict[project_id][0]
 | 
			
		||||
            if tenant_name in mgr.tenants:
 | 
			
		||||
                mgr.tenat_ids.append(project_id)
 | 
			
		||||
 | 
			
		||||
        for tenant in mgr.aim_mgr.find(
 | 
			
		||||
            mgr.actual_aim_ctx, aim_resource.Tenant, name=COMMON_TENANT_NAME):
 | 
			
		||||
            mgr.actual_aim_ctx,
 | 
			
		||||
            aim_resource.Tenant,
 | 
			
		||||
            name=COMMON_TENANT_NAME):
 | 
			
		||||
            mgr.aim_mgr.create(mgr.expected_aim_ctx, tenant)
 | 
			
		||||
 | 
			
		||||
        # Copy AIM resources that are managed via aimctl from actual
 | 
			
		||||
@@ -6395,7 +6403,8 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
 | 
			
		||||
                               aim_resource.L3Outside,
 | 
			
		||||
                               aim_resource.VRF]:
 | 
			
		||||
            for resource in mgr.actual_aim_resources(resource_class):
 | 
			
		||||
                if resource.monitored:
 | 
			
		||||
                if (resource.monitored and
 | 
			
		||||
                    mgr._should_validate_tenant(resource.tenant_name)):
 | 
			
		||||
                    mgr.aim_mgr.create(mgr.expected_aim_ctx, resource)
 | 
			
		||||
 | 
			
		||||
        # Register DB tables to be validated.
 | 
			
		||||
@@ -6412,10 +6421,11 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
 | 
			
		||||
        # manager since this will be needed for both Neutron and GBP
 | 
			
		||||
        # resources.
 | 
			
		||||
        mgr._expected_projects = set()
 | 
			
		||||
 | 
			
		||||
        self._validate_static_resources(mgr)
 | 
			
		||||
        self._validate_address_scopes(mgr)
 | 
			
		||||
        router_dbs, ext_net_routers = self._validate_routers(mgr)
 | 
			
		||||
        self._validate_networks(mgr, router_dbs, ext_net_routers)
 | 
			
		||||
        self._validate_routers(mgr)
 | 
			
		||||
        self._validate_networks(mgr)
 | 
			
		||||
        self._validate_security_groups(mgr)
 | 
			
		||||
        self._validate_ports(mgr)
 | 
			
		||||
        self._validate_subnetpools(mgr)
 | 
			
		||||
@@ -6427,6 +6437,8 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
 | 
			
		||||
    # test execution, where they are called repeatedly.
 | 
			
		||||
 | 
			
		||||
    def _validate_static_resources(self, mgr):
 | 
			
		||||
        if not mgr._should_validate_tenant(COMMON_TENANT_NAME):
 | 
			
		||||
            return
 | 
			
		||||
        self._ensure_common_tenant(mgr.expected_aim_ctx)
 | 
			
		||||
        self._ensure_unrouted_vrf(mgr.expected_aim_ctx)
 | 
			
		||||
        self._ensure_any_filter(mgr.expected_aim_ctx)
 | 
			
		||||
@@ -6434,11 +6446,22 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
 | 
			
		||||
            mgr.expected_aim_ctx)
 | 
			
		||||
 | 
			
		||||
    def _validate_address_scopes(self, mgr):
 | 
			
		||||
        if not mgr._should_validate_neutron_resource("address_scope"):
 | 
			
		||||
            return
 | 
			
		||||
        owned_scopes_by_vrf = defaultdict(list)
 | 
			
		||||
 | 
			
		||||
        scopes_db = None
 | 
			
		||||
        query = BAKERY(lambda s: s.query(
 | 
			
		||||
            as_db.AddressScope))
 | 
			
		||||
        for scope_db in query(mgr.actual_session):
 | 
			
		||||
        if mgr.tenant_scope:
 | 
			
		||||
            query += lambda q: q.filter(
 | 
			
		||||
                as_db.AddressScope.project_id.in_(
 | 
			
		||||
                    sa.bindparam('project_ids', expanding=True)))
 | 
			
		||||
            scopes_db = query(mgr.actual_session).params(
 | 
			
		||||
                    project_ids=list(mgr.tenant_ids))
 | 
			
		||||
        else:
 | 
			
		||||
            scopes_db = query(mgr.actual_session)
 | 
			
		||||
        for scope_db in scopes_db:
 | 
			
		||||
            self._expect_project(mgr, scope_db.project_id)
 | 
			
		||||
            mapping = scope_db.aim_mapping
 | 
			
		||||
            if mapping:
 | 
			
		||||
@@ -6464,12 +6487,22 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
 | 
			
		||||
                        mgr.expected_aim_ctx, vrf, scopes)
 | 
			
		||||
 | 
			
		||||
    def _validate_routers(self, mgr):
 | 
			
		||||
        if not mgr._should_validate_neutron_resource("router"):
 | 
			
		||||
            return
 | 
			
		||||
        router_dbs = {}
 | 
			
		||||
        ext_net_routers = defaultdict(list)
 | 
			
		||||
 | 
			
		||||
        query = BAKERY(lambda s: s.query(
 | 
			
		||||
            l3_db.Router))
 | 
			
		||||
        for router_db in query(mgr.actual_session):
 | 
			
		||||
        if mgr.tenant_scope:
 | 
			
		||||
            query += lambda q: q.filter(
 | 
			
		||||
                l3_db.Router.project_id.in_(
 | 
			
		||||
                    sa.bindparam('project_ids', expanding=True)))
 | 
			
		||||
            rtr_dbs = query(mgr.actual_session).params(
 | 
			
		||||
                project_ids=list(mgr.tenant_ids))
 | 
			
		||||
        else:
 | 
			
		||||
            rtr_dbs = query(mgr.actual_session)
 | 
			
		||||
        for router_db in rtr_dbs:
 | 
			
		||||
            self._expect_project(mgr, router_db.project_id)
 | 
			
		||||
            router_dbs[router_db.id] = router_db
 | 
			
		||||
            if router_db.gw_port_id:
 | 
			
		||||
@@ -6497,13 +6530,38 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
 | 
			
		||||
 | 
			
		||||
        return router_dbs, ext_net_routers
 | 
			
		||||
 | 
			
		||||
    def _validate_networks(self, mgr, router_dbs, ext_net_routers):
 | 
			
		||||
    def _get_routers(self, mgr):
 | 
			
		||||
        router_dbs = {}
 | 
			
		||||
        ext_net_routers = defaultdict(list)
 | 
			
		||||
 | 
			
		||||
        query = BAKERY(lambda s: s.query(
 | 
			
		||||
            l3_db.Router))
 | 
			
		||||
        for router_db in query(mgr.actual_session):
 | 
			
		||||
            router_dbs[router_db.id] = router_db
 | 
			
		||||
            if router_db.gw_port_id:
 | 
			
		||||
                ext_net_routers[router_db.gw_port.network_id].append(
 | 
			
		||||
                    router_db.id)
 | 
			
		||||
        return router_dbs, ext_net_routers
 | 
			
		||||
 | 
			
		||||
    def _validate_networks(self, mgr):
 | 
			
		||||
        if not mgr._should_validate_neutron_resource("network"):
 | 
			
		||||
            return
 | 
			
		||||
        router_dbs, ext_net_routers = self._get_routers(mgr)
 | 
			
		||||
        net_dbs = {}
 | 
			
		||||
        query = BAKERY(lambda s: s.query(
 | 
			
		||||
            models_v2.Network))
 | 
			
		||||
        query += lambda q: q.options(
 | 
			
		||||
            orm.joinedload('segments'))
 | 
			
		||||
        net_dbs = {net_db.id: net_db for net_db in query(mgr.actual_session)}
 | 
			
		||||
 | 
			
		||||
        if mgr.tenant_scope:
 | 
			
		||||
            query += lambda q: q.filter(
 | 
			
		||||
                models_v2.Network.project_id.in_(
 | 
			
		||||
                    sa.bindparam('project_ids', expanding=True)))
 | 
			
		||||
            net_db_dicts = query(mgr.actual_session).params(
 | 
			
		||||
                project_ids=list(mgr.tenant_ids))
 | 
			
		||||
            net_dbs = {net_db.id: net_db for net_db in net_db_dicts}
 | 
			
		||||
        else:
 | 
			
		||||
            net_dbs = {net_db.id: net_db
 | 
			
		||||
                for net_db in query(mgr.actual_session)}
 | 
			
		||||
        router_ext_prov, router_ext_cons = self._get_router_ext_contracts(mgr)
 | 
			
		||||
        routed_nets = self._get_router_interface_info(mgr)
 | 
			
		||||
        network_vrfs, router_vrfs = self._determine_vrfs(
 | 
			
		||||
@@ -7025,6 +7083,8 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
 | 
			
		||||
        return bd, epg, vrf
 | 
			
		||||
 | 
			
		||||
    def _validate_security_groups(self, mgr):
 | 
			
		||||
        if not mgr._should_validate_neutron_resource("security_group"):
 | 
			
		||||
            return
 | 
			
		||||
        sg_ips = defaultdict(set)
 | 
			
		||||
 | 
			
		||||
        query = BAKERY(lambda s: s.query(
 | 
			
		||||
@@ -7043,14 +7103,23 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
 | 
			
		||||
        # lazy='subquery' to lazy='dynamic'. If there is any way to
 | 
			
		||||
        # override this dynamic loading with eager loading for a
 | 
			
		||||
        # specific query, we may want to do so.
 | 
			
		||||
        sg_dbs = None
 | 
			
		||||
        query = BAKERY(lambda s: s.query(
 | 
			
		||||
            sg_models.SecurityGroup))
 | 
			
		||||
        for sg_db in query(mgr.actual_session):
 | 
			
		||||
        if mgr.tenant_scope:
 | 
			
		||||
            query += lambda q: q.filter(
 | 
			
		||||
                sg_models.SecurityGroup.tenant_id.in_(
 | 
			
		||||
                    sa.bindparam('project_ids', expanding=True)))
 | 
			
		||||
            sg_dbs = query(mgr.actual_session).params(
 | 
			
		||||
                    project_ids=list(mgr.tenant_ids))
 | 
			
		||||
        else:
 | 
			
		||||
            sg_dbs = query(mgr.actual_session)
 | 
			
		||||
        for sg_db in sg_dbs:
 | 
			
		||||
            # Ignore anonymous SGs, which seem to be a Neutron bug.
 | 
			
		||||
            if sg_db.tenant_id:
 | 
			
		||||
                self._expect_project(mgr, sg_db.project_id)
 | 
			
		||||
                tenant_name = self.name_mapper.project(
 | 
			
		||||
                    mgr.expected_session, sg_db.tenant_id)
 | 
			
		||||
                self._expect_project(mgr, sg_db.project_id)
 | 
			
		||||
                sg = aim_resource.SecurityGroup(
 | 
			
		||||
                    tenant_name=tenant_name, name=sg_db.id,
 | 
			
		||||
                    display_name=aim_utils.sanitize_display_name(sg_db.name))
 | 
			
		||||
@@ -7091,10 +7160,16 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
 | 
			
		||||
                    mgr.expect_aim_resource(sg_rule)
 | 
			
		||||
 | 
			
		||||
    def _validate_ports(self, mgr):
 | 
			
		||||
        if not mgr._should_validate_neutron_resource("port"):
 | 
			
		||||
            return
 | 
			
		||||
        query = BAKERY(lambda s: s.query(
 | 
			
		||||
            models_v2.Port.project_id))
 | 
			
		||||
        query += lambda q: q.distinct()
 | 
			
		||||
        for project_id, in query(mgr.actual_session):
 | 
			
		||||
            tenant_name = self.name_mapper.project(
 | 
			
		||||
                mgr.expected_session, project_id)
 | 
			
		||||
            if not mgr._should_validate_tenant(tenant_name):
 | 
			
		||||
                continue
 | 
			
		||||
            self._expect_project(mgr, project_id)
 | 
			
		||||
        query = BAKERY(lambda s: s.query(
 | 
			
		||||
            models_v2.Port))
 | 
			
		||||
@@ -7124,17 +7199,29 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
 | 
			
		||||
                    mgr.expect_aim_resource(resource)
 | 
			
		||||
 | 
			
		||||
    def _validate_subnetpools(self, mgr):
 | 
			
		||||
        if not mgr._should_validate_neutron_resource("subnet_pool"):
 | 
			
		||||
            return
 | 
			
		||||
        query = BAKERY(lambda s: s.query(
 | 
			
		||||
            models_v2.SubnetPool.project_id))
 | 
			
		||||
        query += lambda q: q.distinct()
 | 
			
		||||
        for project_id, in query(mgr.actual_session):
 | 
			
		||||
            tenant_name = self.name_mapper.project(
 | 
			
		||||
                mgr.expected_session, project_id)
 | 
			
		||||
            if not mgr._should_validate_tenant(tenant_name):
 | 
			
		||||
                continue
 | 
			
		||||
            self._expect_project(mgr, project_id)
 | 
			
		||||
 | 
			
		||||
    def _validate_floatingips(self, mgr):
 | 
			
		||||
        if not mgr._should_validate_neutron_resource("floatingip"):
 | 
			
		||||
            return
 | 
			
		||||
        query = BAKERY(lambda s: s.query(
 | 
			
		||||
            l3_db.FloatingIP.project_id))
 | 
			
		||||
        query += lambda q: q.distinct()
 | 
			
		||||
        for project_id, in query(mgr.actual_session):
 | 
			
		||||
            tenant_name = self.name_mapper.project(
 | 
			
		||||
                mgr.expected_session, project_id)
 | 
			
		||||
            if not mgr._should_validate_tenant(tenant_name):
 | 
			
		||||
                continue
 | 
			
		||||
            self._expect_project(mgr, project_id)
 | 
			
		||||
 | 
			
		||||
    def _validate_port_bindings(self, mgr):
 | 
			
		||||
 
 | 
			
		||||
@@ -151,9 +151,9 @@ class AIMMappingDriver(nrd.CommonNeutronBase, aim_rpc.AIMMappingRPCMixin):
 | 
			
		||||
    def start_rpc_listeners(self):
 | 
			
		||||
        return []
 | 
			
		||||
 | 
			
		||||
    def validate_state(self, repair):
 | 
			
		||||
    def validate_state(self, repair, resources, tenants):
 | 
			
		||||
        mgr = aim_validation.ValidationManager()
 | 
			
		||||
        return mgr.validate(repair)
 | 
			
		||||
        return mgr.validate(repair, resources, tenants)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def aim_mech_driver(self):
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,7 @@
 | 
			
		||||
from contextlib import contextmanager
 | 
			
		||||
import copy
 | 
			
		||||
 | 
			
		||||
from aim.aim_lib.db import model as aim_lib_model
 | 
			
		||||
from aim import aim_store
 | 
			
		||||
from aim.api import resource as aim_resource
 | 
			
		||||
from aim import context as aim_context
 | 
			
		||||
@@ -24,11 +25,14 @@ from neutron_lib.plugins import directory
 | 
			
		||||
from oslo_log import log
 | 
			
		||||
 | 
			
		||||
from gbpservice.neutron.db import api as db_api
 | 
			
		||||
from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import db
 | 
			
		||||
from gbpservice.neutron.services.grouppolicy import (
 | 
			
		||||
    group_policy_driver_api as api)
 | 
			
		||||
 | 
			
		||||
LOG = log.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
COMMON_TENANT_NAME = 'common'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InternalValidationError(Exception):
 | 
			
		||||
    pass
 | 
			
		||||
@@ -54,11 +58,65 @@ class ValidationManager(object):
 | 
			
		||||
            if driver:
 | 
			
		||||
                self.sfcd = driver.obj
 | 
			
		||||
 | 
			
		||||
    def validate(self, repair=False):
 | 
			
		||||
    def validate(self, repair=False, resources=None, tenants=None):
 | 
			
		||||
        self.output("Validating deployment, repair: %s" % repair)
 | 
			
		||||
 | 
			
		||||
        self.result = api.VALIDATION_PASSED
 | 
			
		||||
        self.repair = repair
 | 
			
		||||
        self.neutron_resources = resources if resources else []
 | 
			
		||||
        self.tenants = tenants if tenants else []
 | 
			
		||||
        self.resource_scope = True if self.neutron_resources else False
 | 
			
		||||
        self.tenant_scope = True if self.tenants else False
 | 
			
		||||
 | 
			
		||||
        self.neutron_to_aim_mapping = {
 | 
			
		||||
            "router": [
 | 
			
		||||
                aim_resource.Contract,
 | 
			
		||||
                aim_resource.ContractSubject,
 | 
			
		||||
                aim_resource.Tenant,
 | 
			
		||||
                aim_resource.ApplicationProfile],
 | 
			
		||||
            "security_group": [
 | 
			
		||||
                aim_resource.SecurityGroup,
 | 
			
		||||
                aim_resource.SecurityGroupRule,
 | 
			
		||||
                aim_resource.SecurityGroupSubject,
 | 
			
		||||
                aim_resource.Tenant,
 | 
			
		||||
                aim_resource.ApplicationProfile],
 | 
			
		||||
            "network": [
 | 
			
		||||
                aim_resource.BridgeDomain,
 | 
			
		||||
                aim_resource.ApplicationProfile,
 | 
			
		||||
                aim_resource.EndpointGroup,
 | 
			
		||||
                aim_resource.ExternalNetwork,
 | 
			
		||||
                aim_resource.ExternalSubnet,
 | 
			
		||||
                db.NetworkMapping,
 | 
			
		||||
                aim_resource.L3Outside,
 | 
			
		||||
                aim_resource.Subnet,
 | 
			
		||||
                aim_resource.VRF,
 | 
			
		||||
                aim_resource.Tenant,
 | 
			
		||||
                aim_resource.ApplicationProfile,
 | 
			
		||||
                aim_lib_model.CloneL3Out],
 | 
			
		||||
            "port": [
 | 
			
		||||
                aim_resource.InfraAccBundleGroup,
 | 
			
		||||
                aim_resource.SpanVsourceGroup,
 | 
			
		||||
                aim_resource.SpanVdestGroup,
 | 
			
		||||
                aim_resource.SpanVsource,
 | 
			
		||||
                aim_resource.SpanVdest,
 | 
			
		||||
                aim_resource.SpanVepgSummary,
 | 
			
		||||
                aim_resource.SpanSpanlbl,
 | 
			
		||||
                aim_resource.Tenant,
 | 
			
		||||
                aim_resource.ApplicationProfile],
 | 
			
		||||
            "subnetpool": [
 | 
			
		||||
                aim_resource.Tenant,
 | 
			
		||||
                aim_resource.ApplicationProfile],
 | 
			
		||||
            "floatingip": [
 | 
			
		||||
                aim_resource.Tenant,
 | 
			
		||||
                aim_resource.ApplicationProfile],
 | 
			
		||||
            "address_scope": [
 | 
			
		||||
                aim_resource.VRF,
 | 
			
		||||
                db.AddressScopeMapping,
 | 
			
		||||
                aim_resource.Tenant,
 | 
			
		||||
                aim_resource.ApplicationProfile],
 | 
			
		||||
                                   }
 | 
			
		||||
        self.aim_resources = set()
 | 
			
		||||
        for resource in self.neutron_resources:
 | 
			
		||||
            self.aim_resources.update(self.neutron_to_aim_mapping[resource])
 | 
			
		||||
 | 
			
		||||
        # REVISIT: Validate configuration.
 | 
			
		||||
 | 
			
		||||
@@ -147,7 +205,8 @@ class ValidationManager(object):
 | 
			
		||||
            self._actual_aim_resources[resource_class] = {
 | 
			
		||||
                tuple(resource.identity): resource
 | 
			
		||||
                for resource in self.aim_mgr.find(
 | 
			
		||||
                        self.actual_aim_ctx, resource_class)}
 | 
			
		||||
                    self.actual_aim_ctx, resource_class)
 | 
			
		||||
                if self._should_register_resource(resource)}
 | 
			
		||||
 | 
			
		||||
    def expect_aim_resource(self, resource, replace=False, remove=False):
 | 
			
		||||
        expected_resources = self._expected_aim_resources[resource.__class__]
 | 
			
		||||
@@ -189,6 +248,8 @@ class ValidationManager(object):
 | 
			
		||||
        return list(self._actual_aim_resources[resource_class].values())
 | 
			
		||||
 | 
			
		||||
    def register_db_instance_class(self, instance_class, primary_keys):
 | 
			
		||||
        if self.aim_resources and instance_class not in self.aim_resources:
 | 
			
		||||
            return
 | 
			
		||||
        self._expected_db_instances.setdefault(instance_class, {})
 | 
			
		||||
        self._db_instance_primary_keys[instance_class] = primary_keys
 | 
			
		||||
 | 
			
		||||
@@ -240,9 +301,14 @@ class ValidationManager(object):
 | 
			
		||||
        for resource_class in self._expected_aim_resources.keys():
 | 
			
		||||
            self._validate_aim_resource_class(resource_class)
 | 
			
		||||
 | 
			
		||||
    def _should_validate_neutron_resource(self, resource):
 | 
			
		||||
        if self.neutron_resources:
 | 
			
		||||
            return True if resource in self.neutron_resources else False
 | 
			
		||||
        else:
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
    def _validate_aim_resource_class(self, resource_class):
 | 
			
		||||
        expected_resources = self._expected_aim_resources[resource_class]
 | 
			
		||||
 | 
			
		||||
        for actual_resource in self.actual_aim_resources(resource_class):
 | 
			
		||||
            key = tuple(actual_resource.identity)
 | 
			
		||||
            expected_resource = expected_resources.pop(key, None)
 | 
			
		||||
@@ -252,11 +318,57 @@ class ValidationManager(object):
 | 
			
		||||
        for expected_resource in expected_resources.values():
 | 
			
		||||
            self._handle_missing_aim_resource(expected_resource)
 | 
			
		||||
 | 
			
		||||
    def _should_validate_unexpected_resource(self, resource):
 | 
			
		||||
        resource_class = resource.__class__
 | 
			
		||||
        if self.tenant_scope:
 | 
			
		||||
            if resource_class == aim_resource.Tenant:
 | 
			
		||||
                if (resource.name != COMMON_TENANT_NAME and
 | 
			
		||||
                    resource.name in self.tenants):
 | 
			
		||||
                    return True
 | 
			
		||||
                else:
 | 
			
		||||
                    return False
 | 
			
		||||
            else:
 | 
			
		||||
                if not hasattr(resource, 'tenant_name'):
 | 
			
		||||
                    return True
 | 
			
		||||
                return True if resource.tenant_name in self.tenants else False
 | 
			
		||||
        else:
 | 
			
		||||
            if self.resource_scope:
 | 
			
		||||
                return False
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def _should_validate_unexpected_db_instance(self, instance):
 | 
			
		||||
        instance_class = instance.__class__
 | 
			
		||||
        if self.tenant_scope:
 | 
			
		||||
            instance_tenant = None
 | 
			
		||||
            if instance_class == aim_lib_model.CloneL3Out:
 | 
			
		||||
                instance_tenant = instance.tenant_name
 | 
			
		||||
            elif instance_class == db.AddressScopeMapping:
 | 
			
		||||
                instance_tenant = instance.vrf_tenant_name
 | 
			
		||||
            elif instance_class == db.NetworkMapping:
 | 
			
		||||
                if hasattr(instance, 'l3out_tenant_name'):
 | 
			
		||||
                    instance_tenant = instance.l3out_tenant_name
 | 
			
		||||
                else:
 | 
			
		||||
                    instance_tenant = instance.epg_tenant_name
 | 
			
		||||
            if instance_tenant:
 | 
			
		||||
                return True if instance_tenant in self.tenants else False
 | 
			
		||||
            else:
 | 
			
		||||
                return True
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def _validate_actual_aim_resource(self, actual_resource,
 | 
			
		||||
                                      expected_resource):
 | 
			
		||||
        if not expected_resource:
 | 
			
		||||
            # Some infra resources do not have the monitored
 | 
			
		||||
            # attribute, but are treated as if they are monitored.
 | 
			
		||||
            # During resource scoped run of validation routine
 | 
			
		||||
            # it will report tenant and ap of some other resource
 | 
			
		||||
            # as unexpected, although they may be expected for other
 | 
			
		||||
            # resource. We can only know for sure from  unscoped validationn
 | 
			
		||||
            # or from tenant scoped validation of reported tenant/ap.
 | 
			
		||||
            # Therefore ignore them during recource scoped run.
 | 
			
		||||
            if (not self._should_validate_unexpected_resource(
 | 
			
		||||
                expected_resource)):
 | 
			
		||||
                return
 | 
			
		||||
            if not getattr(actual_resource, 'monitored', True):
 | 
			
		||||
                self._handle_unexpected_aim_resource(actual_resource)
 | 
			
		||||
        else:
 | 
			
		||||
@@ -268,6 +380,29 @@ class ValidationManager(object):
 | 
			
		||||
                self._handle_incorrect_aim_resource(
 | 
			
		||||
                    expected_resource, actual_resource)
 | 
			
		||||
 | 
			
		||||
    def _should_validate_tenant(self, tenant):
 | 
			
		||||
        if tenant == COMMON_TENANT_NAME:
 | 
			
		||||
            return True
 | 
			
		||||
        if self.tenants:
 | 
			
		||||
            return True if tenant in self.tenants else False
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def _should_register_resource(self, resource):
 | 
			
		||||
        resource_class = resource.__class__
 | 
			
		||||
        if (resource_class == aim_resource.Tenant and
 | 
			
		||||
            self.tenants and resource.name != COMMON_TENANT_NAME and
 | 
			
		||||
            resource.name not in self.tenants):
 | 
			
		||||
            return False
 | 
			
		||||
        if (not hasattr(resource, 'tenant_name') or
 | 
			
		||||
            resource.tenant_name == COMMON_TENANT_NAME):
 | 
			
		||||
            return True
 | 
			
		||||
        if (self.aim_resources and
 | 
			
		||||
            resource_class not in self.aim_resources):
 | 
			
		||||
            return False
 | 
			
		||||
        if self.tenants:
 | 
			
		||||
            return True if resource.tenant_name in self.tenants else False
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def _handle_unexpected_aim_resource(self, actual_resource):
 | 
			
		||||
        if self.should_repair(
 | 
			
		||||
                "unexpected %(type)s: %(actual)r" %
 | 
			
		||||
@@ -316,6 +451,9 @@ class ValidationManager(object):
 | 
			
		||||
        key = tuple([getattr(actual_instance, k) for k in primary_keys])
 | 
			
		||||
        expected_instance = expected_instances.pop(key, None)
 | 
			
		||||
        if not expected_instance:
 | 
			
		||||
            if (not self._should_validate_unexpected_db_instance(
 | 
			
		||||
                actual_instance)):
 | 
			
		||||
                return
 | 
			
		||||
            self._handle_unexpected_db_instance(actual_instance)
 | 
			
		||||
        else:
 | 
			
		||||
            if not self._is_db_instance_correct(
 | 
			
		||||
 
 | 
			
		||||
@@ -1400,7 +1400,7 @@ class PolicyDriver(object):
 | 
			
		||||
        """
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def validate_state(self, repair):
 | 
			
		||||
    def validate_state(self, repair, resources, tenants):
 | 
			
		||||
        """Validate persistent state managed by the driver.
 | 
			
		||||
 | 
			
		||||
        :param repair: Repair invalid state if True.
 | 
			
		||||
 
 | 
			
		||||
@@ -71,8 +71,9 @@ class GroupPolicyPlugin(group_policy_mapping_db.GroupPolicyMappingDbPlugin):
 | 
			
		||||
    def start_rpc_listeners(self):
 | 
			
		||||
        return self.policy_driver_manager.start_rpc_listeners()
 | 
			
		||||
 | 
			
		||||
    def validate_state(self, repair):
 | 
			
		||||
        return self.policy_driver_manager.validate_state(repair)
 | 
			
		||||
    def validate_state(self, repair, resources, tenants):
 | 
			
		||||
        return self.policy_driver_manager.validate_state(repair,
 | 
			
		||||
            resources, tenants)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def servicechain_plugin(self):
 | 
			
		||||
 
 | 
			
		||||
@@ -495,10 +495,10 @@ class PolicyDriverManager(stevedore.named.NamedExtensionManager):
 | 
			
		||||
    def start_rpc_listeners(self):
 | 
			
		||||
        return self._call_on_drivers("start_rpc_listeners")
 | 
			
		||||
 | 
			
		||||
    def validate_state(self, repair):
 | 
			
		||||
    def validate_state(self, repair, resources, tenants):
 | 
			
		||||
        result = api.VALIDATION_PASSED
 | 
			
		||||
        for driver in self.ordered_policy_drivers:
 | 
			
		||||
            this_result = driver.obj.validate_state(repair)
 | 
			
		||||
            this_result = driver.obj.validate_state(repair, resources, tenants)
 | 
			
		||||
            if this_result not in api.VALIDATION_RESULT_PRECEDENCE:
 | 
			
		||||
                LOG.error("Policy driver %(name)s validate_state returned "
 | 
			
		||||
                          "unrecognized result: %(result)s",
 | 
			
		||||
 
 | 
			
		||||
@@ -45,6 +45,11 @@ class AimValidationTestMixin(object):
 | 
			
		||||
        # Validate should pass.
 | 
			
		||||
        self.assertEqual(api.VALIDATION_PASSED, self.av_mgr.validate())
 | 
			
		||||
 | 
			
		||||
    def _validate_scoped(self, resources=None, tenants=None):
 | 
			
		||||
        # Validate should pass.
 | 
			
		||||
        self.assertEqual(api.VALIDATION_PASSED,
 | 
			
		||||
            self.av_mgr.validate(False, resources, tenants))
 | 
			
		||||
 | 
			
		||||
    def _validate_repair_validate(self):
 | 
			
		||||
        # Validate should fail.
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
@@ -57,6 +62,27 @@ class AimValidationTestMixin(object):
 | 
			
		||||
        # Validate should pass.
 | 
			
		||||
        self.assertEqual(api.VALIDATION_PASSED, self.av_mgr.validate())
 | 
			
		||||
 | 
			
		||||
    def _validate_repair_validate_scoped(self, resources, tenants):
 | 
			
		||||
        # Validate should fail.
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            api.VALIDATION_FAILED_REPAIRABLE,
 | 
			
		||||
            self.av_mgr.validate(False, resources, tenants))
 | 
			
		||||
 | 
			
		||||
        # Repair.
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            api.VALIDATION_REPAIRED,
 | 
			
		||||
            self.av_mgr.validate(True, resources, tenants))
 | 
			
		||||
 | 
			
		||||
        # Validate should pass.
 | 
			
		||||
        self.assertEqual(api.VALIDATION_PASSED,
 | 
			
		||||
            self.av_mgr.validate(False, resources, tenants))
 | 
			
		||||
 | 
			
		||||
    def _validate_repairable_scoped(self, resources, tenants):
 | 
			
		||||
        # Validate should fail.
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            api.VALIDATION_FAILED_REPAIRABLE,
 | 
			
		||||
            self.av_mgr.validate(False, resources, tenants))
 | 
			
		||||
 | 
			
		||||
    def _validate_unrepairable(self):
 | 
			
		||||
        # Repair should fail.
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
@@ -757,6 +783,7 @@ class TestNeutronMapping(AimValidationTestCase):
 | 
			
		||||
        (self.db_session.query(aim_lib_model.CloneL3Out).
 | 
			
		||||
         filter_by(tenant_name=tenant_name, name=l3out_name).
 | 
			
		||||
         delete())
 | 
			
		||||
        self._validate_repairable_scoped(["network"], None)
 | 
			
		||||
        self._validate_repair_validate()
 | 
			
		||||
 | 
			
		||||
        # Corrupt the CloneL3Out record and test.
 | 
			
		||||
@@ -1068,6 +1095,116 @@ class TestNeutronMapping(AimValidationTestCase):
 | 
			
		||||
        self.aim_mgr.delete(self.aim_ctx, source_groups[0])
 | 
			
		||||
        self._validate_repair_validate()
 | 
			
		||||
 | 
			
		||||
    def test_network_scope(self):
 | 
			
		||||
        kwargs = {'apic:extra_provided_contracts': ['ep1', 'ep2'],
 | 
			
		||||
                  'apic:extra_consumed_contracts': ['ec1', 'ec2'],
 | 
			
		||||
                  'apic:epg_contract_masters': [{'app_profile_name': 'ap1',
 | 
			
		||||
                                                 'name': 'ec3'},
 | 
			
		||||
                                                {'app_profile_name': 'ap2',
 | 
			
		||||
                                                 'name': 'ec4'}]}
 | 
			
		||||
        net_resp = self._make_network(
 | 
			
		||||
            self.fmt, 'net1', True, arg_list=tuple(kwargs.keys()), **kwargs)
 | 
			
		||||
        net = net_resp['network']
 | 
			
		||||
        net_id = net['id']
 | 
			
		||||
        self._validate()
 | 
			
		||||
        self._validate_scoped(["router"], None)
 | 
			
		||||
        self._validate_scoped(["security_group"], None)
 | 
			
		||||
 | 
			
		||||
        # Test AIM resources.
 | 
			
		||||
        bd_dn = net['apic:distinguished_names']['BridgeDomain']
 | 
			
		||||
        epg_dn = net['apic:distinguished_names']['EndpointGroup']
 | 
			
		||||
 | 
			
		||||
        # Delete the network's mapping record and test.
 | 
			
		||||
        (self.db_session.query(db.NetworkMapping).
 | 
			
		||||
         filter_by(network_id=net_id).
 | 
			
		||||
         delete())
 | 
			
		||||
 | 
			
		||||
        # delete BridgeDomain.
 | 
			
		||||
        bd = aim_resource.BridgeDomain.from_dn(bd_dn)
 | 
			
		||||
        self.aim_mgr.delete(self.aim_ctx, bd)
 | 
			
		||||
 | 
			
		||||
        # delete EndpointGroup.
 | 
			
		||||
        epg = aim_resource.EndpointGroup.from_dn(epg_dn)
 | 
			
		||||
        self.aim_mgr.delete(self.aim_ctx, epg)
 | 
			
		||||
 | 
			
		||||
        # self._validate_scoped(["router"], None)
 | 
			
		||||
        self._validate_repair_validate_scoped(["network"], None)
 | 
			
		||||
 | 
			
		||||
    def test_tenant_scope(self):
 | 
			
		||||
        # setting scope to security group but
 | 
			
		||||
        # should validate common tenant resources
 | 
			
		||||
        tenant = aim_resource.Tenant(name='common')
 | 
			
		||||
        self.aim_mgr.delete(self.aim_ctx, tenant)
 | 
			
		||||
        self._validate_repair_validate_scoped(["security_group"], None)
 | 
			
		||||
 | 
			
		||||
        net_resp1 = self._make_network(
 | 
			
		||||
            self.fmt, 'net1', True, tenant_id='tenant_1')
 | 
			
		||||
        net1 = net_resp1['network']
 | 
			
		||||
        bd_dn1 = net1['apic:distinguished_names']['BridgeDomain']
 | 
			
		||||
        epg_dn1 = net1['apic:distinguished_names']['EndpointGroup']
 | 
			
		||||
 | 
			
		||||
        bd1 = aim_resource.BridgeDomain.from_dn(bd_dn1)
 | 
			
		||||
        self.aim_mgr.delete(self.aim_ctx, bd1)
 | 
			
		||||
 | 
			
		||||
        # delete EndpointGroup.
 | 
			
		||||
        epg1 = aim_resource.EndpointGroup.from_dn(epg_dn1)
 | 
			
		||||
        self.aim_mgr.delete(self.aim_ctx, epg1)
 | 
			
		||||
 | 
			
		||||
        net_resp2 = self._make_network(
 | 
			
		||||
            self.fmt, 'net2', True, tenant_id='tenant_2')
 | 
			
		||||
        net2 = net_resp2['network']
 | 
			
		||||
        bd_dn2 = net2['apic:distinguished_names']['BridgeDomain']
 | 
			
		||||
        epg_dn2 = net2['apic:distinguished_names']['EndpointGroup']
 | 
			
		||||
 | 
			
		||||
        bd2 = aim_resource.BridgeDomain.from_dn(bd_dn2)
 | 
			
		||||
        self.aim_mgr.delete(self.aim_ctx, bd2)
 | 
			
		||||
 | 
			
		||||
        # delete EndpointGroup.
 | 
			
		||||
        epg2 = aim_resource.EndpointGroup.from_dn(epg_dn2)
 | 
			
		||||
        self.aim_mgr.delete(self.aim_ctx, epg2)
 | 
			
		||||
        self._validate_repair_validate_scoped(None, ['prj_tenant_1'])
 | 
			
		||||
        self._validate_repair_validate_scoped(None, ['prj_tenant_2'])
 | 
			
		||||
 | 
			
		||||
    def test_security_group_scope(self):
 | 
			
		||||
        sg = self._make_security_group(
 | 
			
		||||
            self.fmt, 'sg1', 'security group 1')['security_group']
 | 
			
		||||
        rule1 = self._build_security_group_rule(
 | 
			
		||||
            sg['id'], 'ingress', 'tcp', '22', '23')
 | 
			
		||||
        rules = {'security_group_rules': [rule1['security_group_rule']]}
 | 
			
		||||
        sg_rule = self._make_security_group_rule(
 | 
			
		||||
            self.fmt, rules)['security_group_rules'][0]
 | 
			
		||||
 | 
			
		||||
        # Test the AIM SecurityGroup.
 | 
			
		||||
        tenant_name = self.driver.aim_mech_driver.name_mapper.project(
 | 
			
		||||
            None, sg['project_id'])
 | 
			
		||||
        sg_name = sg['id']
 | 
			
		||||
        aim_sg = aim_resource.SecurityGroup(
 | 
			
		||||
            name=sg_name, tenant_name=tenant_name)
 | 
			
		||||
        self._test_aim_resource(aim_sg)
 | 
			
		||||
        self.aim_mgr.delete(self.aim_ctx, aim_sg)
 | 
			
		||||
 | 
			
		||||
        # Test the AIM SecurityGroupSubject.
 | 
			
		||||
        aim_subject = aim_resource.SecurityGroupSubject(
 | 
			
		||||
            name='default', security_group_name=sg_name,
 | 
			
		||||
            tenant_name=tenant_name)
 | 
			
		||||
        self._test_aim_resource(aim_subject)
 | 
			
		||||
        self.aim_mgr.delete(self.aim_ctx, aim_subject)
 | 
			
		||||
 | 
			
		||||
        # Test the AIM SecurityGroupRule.
 | 
			
		||||
        aim_rule = aim_resource.SecurityGroupRule(
 | 
			
		||||
            name=sg_rule['id'],
 | 
			
		||||
            security_group_subject_name='default',
 | 
			
		||||
            security_group_name=sg_name,
 | 
			
		||||
            tenant_name=tenant_name)
 | 
			
		||||
        self._test_aim_resource(aim_rule)
 | 
			
		||||
        self.aim_mgr.delete(self.aim_ctx, aim_rule)
 | 
			
		||||
 | 
			
		||||
        aim_tenant = aim_resource.Tenant(name=tenant_name)
 | 
			
		||||
        self._test_aim_resource(aim_tenant)
 | 
			
		||||
        self.aim_mgr.delete(self.aim_ctx, aim_tenant)
 | 
			
		||||
 | 
			
		||||
        self._validate_repair_validate_scoped(None, [tenant_name])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestGbpMapping(AimValidationTestCase):
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,9 @@ from gbpservice.neutron.services.grouppolicy import (
 | 
			
		||||
# the CLI options must be registered before the GBP service plugin and
 | 
			
		||||
# the configured policy drivers can be loaded.
 | 
			
		||||
cli_opts = [
 | 
			
		||||
    cfg.BoolOpt('repair', default=False, help='Enable repair of invalid state.')
 | 
			
		||||
    cfg.BoolOpt('repair', default=False, help='Enable repair of invalid state.'),
 | 
			
		||||
    cfg.ListOpt('resources', default=[], help='List of resources to be reconciled'),
 | 
			
		||||
    cfg.ListOpt('tenants', default=[], help='List of tenants to be reconciled')
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -52,7 +54,7 @@ def main():
 | 
			
		||||
    if not gbp_plugin:
 | 
			
		||||
        sys.exit("GBP service plugin not configured.")
 | 
			
		||||
 | 
			
		||||
    result = gbp_plugin.validate_state(cfg.CONF.repair)
 | 
			
		||||
    result = gbp_plugin.validate_state(cfg.CONF.repair, cfg.CONF.resources, cfg.CONF.tenants)
 | 
			
		||||
    if result in [api.VALIDATION_FAILED_REPAIRABLE,
 | 
			
		||||
                  api.VALIDATION_FAILED_UNREPAIRABLE,
 | 
			
		||||
                  api.VALIDATION_FAILED_WITH_EXCEPTION]:
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user