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)
|
network_id=mapping.network_id)
|
||||||
|
|
||||||
def validate_aim_mapping(self, mgr):
|
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_infra.HostDomainMappingV2)
|
||||||
mgr.register_aim_resource_class(aim_resource.ApplicationProfile)
|
mgr.register_aim_resource_class(aim_resource.ApplicationProfile)
|
||||||
mgr.register_aim_resource_class(aim_resource.BridgeDomain)
|
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)
|
mgr.register_aim_resource_class(aim_resource.InfraAccBundleGroup)
|
||||||
|
|
||||||
# Copy common Tenant from actual to expected AIM store.
|
# 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(
|
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)
|
mgr.aim_mgr.create(mgr.expected_aim_ctx, tenant)
|
||||||
|
|
||||||
# Copy AIM resources that are managed via aimctl from actual
|
# Copy AIM resources that are managed via aimctl from actual
|
||||||
@@ -6395,7 +6403,8 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
|
|||||||
aim_resource.L3Outside,
|
aim_resource.L3Outside,
|
||||||
aim_resource.VRF]:
|
aim_resource.VRF]:
|
||||||
for resource in mgr.actual_aim_resources(resource_class):
|
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)
|
mgr.aim_mgr.create(mgr.expected_aim_ctx, resource)
|
||||||
|
|
||||||
# Register DB tables to be validated.
|
# 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
|
# manager since this will be needed for both Neutron and GBP
|
||||||
# resources.
|
# resources.
|
||||||
mgr._expected_projects = set()
|
mgr._expected_projects = set()
|
||||||
|
|
||||||
self._validate_static_resources(mgr)
|
self._validate_static_resources(mgr)
|
||||||
self._validate_address_scopes(mgr)
|
self._validate_address_scopes(mgr)
|
||||||
router_dbs, ext_net_routers = self._validate_routers(mgr)
|
self._validate_routers(mgr)
|
||||||
self._validate_networks(mgr, router_dbs, ext_net_routers)
|
self._validate_networks(mgr)
|
||||||
self._validate_security_groups(mgr)
|
self._validate_security_groups(mgr)
|
||||||
self._validate_ports(mgr)
|
self._validate_ports(mgr)
|
||||||
self._validate_subnetpools(mgr)
|
self._validate_subnetpools(mgr)
|
||||||
@@ -6427,6 +6437,8 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
|
|||||||
# test execution, where they are called repeatedly.
|
# test execution, where they are called repeatedly.
|
||||||
|
|
||||||
def _validate_static_resources(self, mgr):
|
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_common_tenant(mgr.expected_aim_ctx)
|
||||||
self._ensure_unrouted_vrf(mgr.expected_aim_ctx)
|
self._ensure_unrouted_vrf(mgr.expected_aim_ctx)
|
||||||
self._ensure_any_filter(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)
|
mgr.expected_aim_ctx)
|
||||||
|
|
||||||
def _validate_address_scopes(self, mgr):
|
def _validate_address_scopes(self, mgr):
|
||||||
|
if not mgr._should_validate_neutron_resource("address_scope"):
|
||||||
|
return
|
||||||
owned_scopes_by_vrf = defaultdict(list)
|
owned_scopes_by_vrf = defaultdict(list)
|
||||||
|
|
||||||
|
scopes_db = None
|
||||||
query = BAKERY(lambda s: s.query(
|
query = BAKERY(lambda s: s.query(
|
||||||
as_db.AddressScope))
|
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)
|
self._expect_project(mgr, scope_db.project_id)
|
||||||
mapping = scope_db.aim_mapping
|
mapping = scope_db.aim_mapping
|
||||||
if mapping:
|
if mapping:
|
||||||
@@ -6464,12 +6487,22 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
|
|||||||
mgr.expected_aim_ctx, vrf, scopes)
|
mgr.expected_aim_ctx, vrf, scopes)
|
||||||
|
|
||||||
def _validate_routers(self, mgr):
|
def _validate_routers(self, mgr):
|
||||||
|
if not mgr._should_validate_neutron_resource("router"):
|
||||||
|
return
|
||||||
router_dbs = {}
|
router_dbs = {}
|
||||||
ext_net_routers = defaultdict(list)
|
ext_net_routers = defaultdict(list)
|
||||||
|
|
||||||
query = BAKERY(lambda s: s.query(
|
query = BAKERY(lambda s: s.query(
|
||||||
l3_db.Router))
|
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)
|
self._expect_project(mgr, router_db.project_id)
|
||||||
router_dbs[router_db.id] = router_db
|
router_dbs[router_db.id] = router_db
|
||||||
if router_db.gw_port_id:
|
if router_db.gw_port_id:
|
||||||
@@ -6497,13 +6530,38 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
|
|||||||
|
|
||||||
return router_dbs, ext_net_routers
|
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(
|
query = BAKERY(lambda s: s.query(
|
||||||
models_v2.Network))
|
models_v2.Network))
|
||||||
query += lambda q: q.options(
|
query += lambda q: q.options(
|
||||||
orm.joinedload('segments'))
|
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)
|
router_ext_prov, router_ext_cons = self._get_router_ext_contracts(mgr)
|
||||||
routed_nets = self._get_router_interface_info(mgr)
|
routed_nets = self._get_router_interface_info(mgr)
|
||||||
network_vrfs, router_vrfs = self._determine_vrfs(
|
network_vrfs, router_vrfs = self._determine_vrfs(
|
||||||
@@ -7025,6 +7083,8 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
|
|||||||
return bd, epg, vrf
|
return bd, epg, vrf
|
||||||
|
|
||||||
def _validate_security_groups(self, mgr):
|
def _validate_security_groups(self, mgr):
|
||||||
|
if not mgr._should_validate_neutron_resource("security_group"):
|
||||||
|
return
|
||||||
sg_ips = defaultdict(set)
|
sg_ips = defaultdict(set)
|
||||||
|
|
||||||
query = BAKERY(lambda s: s.query(
|
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
|
# lazy='subquery' to lazy='dynamic'. If there is any way to
|
||||||
# override this dynamic loading with eager loading for a
|
# override this dynamic loading with eager loading for a
|
||||||
# specific query, we may want to do so.
|
# specific query, we may want to do so.
|
||||||
|
sg_dbs = None
|
||||||
query = BAKERY(lambda s: s.query(
|
query = BAKERY(lambda s: s.query(
|
||||||
sg_models.SecurityGroup))
|
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.
|
# Ignore anonymous SGs, which seem to be a Neutron bug.
|
||||||
if sg_db.tenant_id:
|
if sg_db.tenant_id:
|
||||||
self._expect_project(mgr, sg_db.project_id)
|
|
||||||
tenant_name = self.name_mapper.project(
|
tenant_name = self.name_mapper.project(
|
||||||
mgr.expected_session, sg_db.tenant_id)
|
mgr.expected_session, sg_db.tenant_id)
|
||||||
|
self._expect_project(mgr, sg_db.project_id)
|
||||||
sg = aim_resource.SecurityGroup(
|
sg = aim_resource.SecurityGroup(
|
||||||
tenant_name=tenant_name, name=sg_db.id,
|
tenant_name=tenant_name, name=sg_db.id,
|
||||||
display_name=aim_utils.sanitize_display_name(sg_db.name))
|
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)
|
mgr.expect_aim_resource(sg_rule)
|
||||||
|
|
||||||
def _validate_ports(self, mgr):
|
def _validate_ports(self, mgr):
|
||||||
|
if not mgr._should_validate_neutron_resource("port"):
|
||||||
|
return
|
||||||
query = BAKERY(lambda s: s.query(
|
query = BAKERY(lambda s: s.query(
|
||||||
models_v2.Port.project_id))
|
models_v2.Port.project_id))
|
||||||
query += lambda q: q.distinct()
|
query += lambda q: q.distinct()
|
||||||
for project_id, in query(mgr.actual_session):
|
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)
|
self._expect_project(mgr, project_id)
|
||||||
query = BAKERY(lambda s: s.query(
|
query = BAKERY(lambda s: s.query(
|
||||||
models_v2.Port))
|
models_v2.Port))
|
||||||
@@ -7124,17 +7199,29 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
|
|||||||
mgr.expect_aim_resource(resource)
|
mgr.expect_aim_resource(resource)
|
||||||
|
|
||||||
def _validate_subnetpools(self, mgr):
|
def _validate_subnetpools(self, mgr):
|
||||||
|
if not mgr._should_validate_neutron_resource("subnet_pool"):
|
||||||
|
return
|
||||||
query = BAKERY(lambda s: s.query(
|
query = BAKERY(lambda s: s.query(
|
||||||
models_v2.SubnetPool.project_id))
|
models_v2.SubnetPool.project_id))
|
||||||
query += lambda q: q.distinct()
|
query += lambda q: q.distinct()
|
||||||
for project_id, in query(mgr.actual_session):
|
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)
|
self._expect_project(mgr, project_id)
|
||||||
|
|
||||||
def _validate_floatingips(self, mgr):
|
def _validate_floatingips(self, mgr):
|
||||||
|
if not mgr._should_validate_neutron_resource("floatingip"):
|
||||||
|
return
|
||||||
query = BAKERY(lambda s: s.query(
|
query = BAKERY(lambda s: s.query(
|
||||||
l3_db.FloatingIP.project_id))
|
l3_db.FloatingIP.project_id))
|
||||||
query += lambda q: q.distinct()
|
query += lambda q: q.distinct()
|
||||||
for project_id, in query(mgr.actual_session):
|
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)
|
self._expect_project(mgr, project_id)
|
||||||
|
|
||||||
def _validate_port_bindings(self, mgr):
|
def _validate_port_bindings(self, mgr):
|
||||||
|
|||||||
@@ -151,9 +151,9 @@ class AIMMappingDriver(nrd.CommonNeutronBase, aim_rpc.AIMMappingRPCMixin):
|
|||||||
def start_rpc_listeners(self):
|
def start_rpc_listeners(self):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def validate_state(self, repair):
|
def validate_state(self, repair, resources, tenants):
|
||||||
mgr = aim_validation.ValidationManager()
|
mgr = aim_validation.ValidationManager()
|
||||||
return mgr.validate(repair)
|
return mgr.validate(repair, resources, tenants)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def aim_mech_driver(self):
|
def aim_mech_driver(self):
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
|
from aim.aim_lib.db import model as aim_lib_model
|
||||||
from aim import aim_store
|
from aim import aim_store
|
||||||
from aim.api import resource as aim_resource
|
from aim.api import resource as aim_resource
|
||||||
from aim import context as aim_context
|
from aim import context as aim_context
|
||||||
@@ -24,11 +25,14 @@ from neutron_lib.plugins import directory
|
|||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
from gbpservice.neutron.db import api as db_api
|
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 (
|
from gbpservice.neutron.services.grouppolicy import (
|
||||||
group_policy_driver_api as api)
|
group_policy_driver_api as api)
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
COMMON_TENANT_NAME = 'common'
|
||||||
|
|
||||||
|
|
||||||
class InternalValidationError(Exception):
|
class InternalValidationError(Exception):
|
||||||
pass
|
pass
|
||||||
@@ -54,11 +58,65 @@ class ValidationManager(object):
|
|||||||
if driver:
|
if driver:
|
||||||
self.sfcd = driver.obj
|
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.output("Validating deployment, repair: %s" % repair)
|
||||||
|
|
||||||
self.result = api.VALIDATION_PASSED
|
self.result = api.VALIDATION_PASSED
|
||||||
self.repair = repair
|
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.
|
# REVISIT: Validate configuration.
|
||||||
|
|
||||||
@@ -147,7 +205,8 @@ class ValidationManager(object):
|
|||||||
self._actual_aim_resources[resource_class] = {
|
self._actual_aim_resources[resource_class] = {
|
||||||
tuple(resource.identity): resource
|
tuple(resource.identity): resource
|
||||||
for resource in self.aim_mgr.find(
|
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):
|
def expect_aim_resource(self, resource, replace=False, remove=False):
|
||||||
expected_resources = self._expected_aim_resources[resource.__class__]
|
expected_resources = self._expected_aim_resources[resource.__class__]
|
||||||
@@ -189,6 +248,8 @@ class ValidationManager(object):
|
|||||||
return list(self._actual_aim_resources[resource_class].values())
|
return list(self._actual_aim_resources[resource_class].values())
|
||||||
|
|
||||||
def register_db_instance_class(self, instance_class, primary_keys):
|
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._expected_db_instances.setdefault(instance_class, {})
|
||||||
self._db_instance_primary_keys[instance_class] = primary_keys
|
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():
|
for resource_class in self._expected_aim_resources.keys():
|
||||||
self._validate_aim_resource_class(resource_class)
|
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):
|
def _validate_aim_resource_class(self, resource_class):
|
||||||
expected_resources = self._expected_aim_resources[resource_class]
|
expected_resources = self._expected_aim_resources[resource_class]
|
||||||
|
|
||||||
for actual_resource in self.actual_aim_resources(resource_class):
|
for actual_resource in self.actual_aim_resources(resource_class):
|
||||||
key = tuple(actual_resource.identity)
|
key = tuple(actual_resource.identity)
|
||||||
expected_resource = expected_resources.pop(key, None)
|
expected_resource = expected_resources.pop(key, None)
|
||||||
@@ -252,11 +318,57 @@ class ValidationManager(object):
|
|||||||
for expected_resource in expected_resources.values():
|
for expected_resource in expected_resources.values():
|
||||||
self._handle_missing_aim_resource(expected_resource)
|
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,
|
def _validate_actual_aim_resource(self, actual_resource,
|
||||||
expected_resource):
|
expected_resource):
|
||||||
if not expected_resource:
|
if not expected_resource:
|
||||||
# Some infra resources do not have the monitored
|
# Some infra resources do not have the monitored
|
||||||
# attribute, but are treated as if they are 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):
|
if not getattr(actual_resource, 'monitored', True):
|
||||||
self._handle_unexpected_aim_resource(actual_resource)
|
self._handle_unexpected_aim_resource(actual_resource)
|
||||||
else:
|
else:
|
||||||
@@ -268,6 +380,29 @@ class ValidationManager(object):
|
|||||||
self._handle_incorrect_aim_resource(
|
self._handle_incorrect_aim_resource(
|
||||||
expected_resource, actual_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):
|
def _handle_unexpected_aim_resource(self, actual_resource):
|
||||||
if self.should_repair(
|
if self.should_repair(
|
||||||
"unexpected %(type)s: %(actual)r" %
|
"unexpected %(type)s: %(actual)r" %
|
||||||
@@ -316,6 +451,9 @@ class ValidationManager(object):
|
|||||||
key = tuple([getattr(actual_instance, k) for k in primary_keys])
|
key = tuple([getattr(actual_instance, k) for k in primary_keys])
|
||||||
expected_instance = expected_instances.pop(key, None)
|
expected_instance = expected_instances.pop(key, None)
|
||||||
if not expected_instance:
|
if not expected_instance:
|
||||||
|
if (not self._should_validate_unexpected_db_instance(
|
||||||
|
actual_instance)):
|
||||||
|
return
|
||||||
self._handle_unexpected_db_instance(actual_instance)
|
self._handle_unexpected_db_instance(actual_instance)
|
||||||
else:
|
else:
|
||||||
if not self._is_db_instance_correct(
|
if not self._is_db_instance_correct(
|
||||||
|
|||||||
@@ -1400,7 +1400,7 @@ class PolicyDriver(object):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def validate_state(self, repair):
|
def validate_state(self, repair, resources, tenants):
|
||||||
"""Validate persistent state managed by the driver.
|
"""Validate persistent state managed by the driver.
|
||||||
|
|
||||||
:param repair: Repair invalid state if True.
|
:param repair: Repair invalid state if True.
|
||||||
|
|||||||
@@ -71,8 +71,9 @@ class GroupPolicyPlugin(group_policy_mapping_db.GroupPolicyMappingDbPlugin):
|
|||||||
def start_rpc_listeners(self):
|
def start_rpc_listeners(self):
|
||||||
return self.policy_driver_manager.start_rpc_listeners()
|
return self.policy_driver_manager.start_rpc_listeners()
|
||||||
|
|
||||||
def validate_state(self, repair):
|
def validate_state(self, repair, resources, tenants):
|
||||||
return self.policy_driver_manager.validate_state(repair)
|
return self.policy_driver_manager.validate_state(repair,
|
||||||
|
resources, tenants)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def servicechain_plugin(self):
|
def servicechain_plugin(self):
|
||||||
|
|||||||
@@ -495,10 +495,10 @@ class PolicyDriverManager(stevedore.named.NamedExtensionManager):
|
|||||||
def start_rpc_listeners(self):
|
def start_rpc_listeners(self):
|
||||||
return self._call_on_drivers("start_rpc_listeners")
|
return self._call_on_drivers("start_rpc_listeners")
|
||||||
|
|
||||||
def validate_state(self, repair):
|
def validate_state(self, repair, resources, tenants):
|
||||||
result = api.VALIDATION_PASSED
|
result = api.VALIDATION_PASSED
|
||||||
for driver in self.ordered_policy_drivers:
|
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:
|
if this_result not in api.VALIDATION_RESULT_PRECEDENCE:
|
||||||
LOG.error("Policy driver %(name)s validate_state returned "
|
LOG.error("Policy driver %(name)s validate_state returned "
|
||||||
"unrecognized result: %(result)s",
|
"unrecognized result: %(result)s",
|
||||||
|
|||||||
@@ -45,6 +45,11 @@ class AimValidationTestMixin(object):
|
|||||||
# Validate should pass.
|
# Validate should pass.
|
||||||
self.assertEqual(api.VALIDATION_PASSED, self.av_mgr.validate())
|
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):
|
def _validate_repair_validate(self):
|
||||||
# Validate should fail.
|
# Validate should fail.
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
@@ -57,6 +62,27 @@ class AimValidationTestMixin(object):
|
|||||||
# Validate should pass.
|
# Validate should pass.
|
||||||
self.assertEqual(api.VALIDATION_PASSED, self.av_mgr.validate())
|
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):
|
def _validate_unrepairable(self):
|
||||||
# Repair should fail.
|
# Repair should fail.
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
@@ -757,6 +783,7 @@ class TestNeutronMapping(AimValidationTestCase):
|
|||||||
(self.db_session.query(aim_lib_model.CloneL3Out).
|
(self.db_session.query(aim_lib_model.CloneL3Out).
|
||||||
filter_by(tenant_name=tenant_name, name=l3out_name).
|
filter_by(tenant_name=tenant_name, name=l3out_name).
|
||||||
delete())
|
delete())
|
||||||
|
self._validate_repairable_scoped(["network"], None)
|
||||||
self._validate_repair_validate()
|
self._validate_repair_validate()
|
||||||
|
|
||||||
# Corrupt the CloneL3Out record and test.
|
# Corrupt the CloneL3Out record and test.
|
||||||
@@ -1068,6 +1095,116 @@ class TestNeutronMapping(AimValidationTestCase):
|
|||||||
self.aim_mgr.delete(self.aim_ctx, source_groups[0])
|
self.aim_mgr.delete(self.aim_ctx, source_groups[0])
|
||||||
self._validate_repair_validate()
|
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):
|
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 CLI options must be registered before the GBP service plugin and
|
||||||
# the configured policy drivers can be loaded.
|
# the configured policy drivers can be loaded.
|
||||||
cli_opts = [
|
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:
|
if not gbp_plugin:
|
||||||
sys.exit("GBP service plugin not configured.")
|
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,
|
if result in [api.VALIDATION_FAILED_REPAIRABLE,
|
||||||
api.VALIDATION_FAILED_UNREPAIRABLE,
|
api.VALIDATION_FAILED_UNREPAIRABLE,
|
||||||
api.VALIDATION_FAILED_WITH_EXCEPTION]:
|
api.VALIDATION_FAILED_WITH_EXCEPTION]:
|
||||||
|
|||||||
Reference in New Issue
Block a user