/*
* Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*/
package org.opendaylight.groupbasedpolicy.resolver;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.annotation.concurrent.Immutable;
import org.opendaylight.controller.md.sal.binding.api.DataBroker;
import org.opendaylight.controller.md.sal.binding.api.DataObjectModification;
import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction;
import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
import org.opendaylight.groupbasedpolicy.api.PolicyValidatorRegistry;
import org.opendaylight.groupbasedpolicy.api.ValidationResult;
import org.opendaylight.groupbasedpolicy.api.Validator;
import org.opendaylight.groupbasedpolicy.dto.EgKey;
import org.opendaylight.groupbasedpolicy.dto.IndexedTenant;
import org.opendaylight.groupbasedpolicy.util.DataStoreHelper;
import org.opendaylight.groupbasedpolicy.util.DataTreeChangeHandler;
import org.opendaylight.groupbasedpolicy.util.IidFactory;
import org.opendaylight.groupbasedpolicy.util.InheritanceUtils;
import org.opendaylight.groupbasedpolicy.util.PolicyInfoUtils;
import org.opendaylight.groupbasedpolicy.util.PolicyResolverUtils;
import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.common.rev140421.ActionDefinitionId;
import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.common.rev140421.ClassifierDefinitionId;
import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.common.rev140421.TenantId;
import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.Tenants;
import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.tenants.Tenant;
import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.tenants.tenant.Policy;
import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.tenants.tenant.policy.SubjectFeatureInstances;
import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.tenants.tenant.policy.subject.feature.instances.ActionInstance;
import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.tenants.tenant.policy.subject.feature.instances.ClassifierInstance;
import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.resolved.policy.rev150828.ResolvedPolicies;
import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.resolved.policy.rev150828.ResolvedPoliciesBuilder;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Table;
/**
* The policy resolver is a utility for renderers to help in resolving
* group-based policy into a form that is easier to apply to the actual network.
* For any pair of endpoint groups, there is a set of rules that could apply to
* the endpoints on that group based on the policy configuration. The exact list
* of rules that apply to a given pair of endpoints depends on the conditions
* that are active on the endpoints.
* We need to be able to query against this policy model, enumerate the relevant
* classes of traffic and endpoints, and notify renderers when there are changes
* to policy as it applies to active sets of endpoints and endpoint groups.
* The policy resolver will maintain the necessary state for all tenants in its
* control domain, which is the set of tenants for which policy listeners have
* been registered.
*/
public class PolicyResolver implements PolicyValidatorRegistry, AutoCloseable {
private static final Logger LOG = LoggerFactory.getLogger(PolicyResolver.class);
private final DataBroker dataProvider;
protected final ConcurrentMap<TenantId, IndexedTenant> resolvedTenants;
private PolicyChangeListener tenantChangeListener;
/*
* Store validators for ActionDefinitions from Renderers
*
*/
private SetMultimap<ActionDefinitionId, Validator<ActionInstance>> actionInstanceValidatorsByDefinition =
Multimaps.synchronizedSetMultimap(HashMultimap.<ActionDefinitionId, Validator<ActionInstance>>create());
private SetMultimap<ClassifierDefinitionId, Validator<ClassifierInstance>> classifierInstanceValidatorsByDefinition =
Multimaps
.synchronizedSetMultimap(HashMultimap.<ClassifierDefinitionId, Validator<ClassifierInstance>>create());
public PolicyResolver(DataBroker dataProvider) {
this.dataProvider = dataProvider;
resolvedTenants = new ConcurrentHashMap<>();
tenantChangeListener =
new PolicyChangeListener(dataProvider, new DataTreeIdentifier<>(LogicalDatastoreType.CONFIGURATION,
InstanceIdentifier.builder(Tenants.class).child(Tenant.class).build()));
LOG.debug("Initialized renderer common policy resolver");
}
// *************
// AutoCloseable
// *************
@Override
public void close() {
if (tenantChangeListener != null) {
tenantChangeListener.close();
}
}
// *************************
// PolicyResolutionValidatorRegistrar
// *************************
@Override
public void register(ActionDefinitionId actionDefinitionId, Validator<ActionInstance> validator) {
actionInstanceValidatorsByDefinition.put(actionDefinitionId, validator);
}
@Override
public void unregister(ActionDefinitionId actionDefinitionId, Validator<ActionInstance> validator) {
actionInstanceValidatorsByDefinition.remove(actionDefinitionId, validator);
}
@Override
public void register(ClassifierDefinitionId classifierDefinitionId, Validator<ClassifierInstance> validator) {
classifierInstanceValidatorsByDefinition.put(classifierDefinitionId, validator);
}
@Override
public void unregister(ClassifierDefinitionId classifierDefinitionId, Validator<ClassifierInstance> validator) {
classifierInstanceValidatorsByDefinition.remove(classifierDefinitionId, validator);
}
@VisibleForTesting
void updateTenant(final TenantId tenantId, final Tenant unresolvedTenant) {
if (dataProvider == null) {
LOG.error("Tenant {} will not be resolved because because dataProvider is NULL", tenantId.getValue());
return;
}
if (unresolvedTenant == null) {
LOG.info("Tenant {} not found in CONF; check&delete from OPER", tenantId);
resolvedTenants.remove(tenantId);
ReadWriteTransaction rwTx = dataProvider.newReadWriteTransaction();
DataStoreHelper.removeIfExists(LogicalDatastoreType.OPERATIONAL, IidFactory.tenantIid(tenantId), rwTx);
updateResolvedPolicy(rwTx);
if (DataStoreHelper.submitToDs(rwTx)) {
LOG.debug("Removed resolved tenant {} and wrote resolved policies to Datastore.", tenantId.getValue());
} else {
LOG.error("Failed to remove resolved tenant {} and to write resolved policies to Datastore.",
tenantId.getValue());
}
} else {
LOG.debug("Resolving of tenant inheritance and policy triggered by a change in tenant {}", tenantId);
Tenant resolvedTenant = InheritanceUtils.resolveTenant(unresolvedTenant);
if (isPolicyValid(resolvedTenant.getPolicy())) {
// Update the policy cache and notify listeners
resolvedTenants.put(tenantId, new IndexedTenant(resolvedTenant));
WriteTransaction wTx = dataProvider.newWriteOnlyTransaction();
wTx.put(LogicalDatastoreType.OPERATIONAL, IidFactory.tenantIid(tenantId), resolvedTenant, true);
updateResolvedPolicy(wTx);
if (DataStoreHelper.submitToDs(wTx)) {
LOG.debug("Wrote resolved tenant {} and resolved policies to Datastore.", tenantId.getValue());
} else {
LOG.error("Failed to write resolved tenant {} and resolved policies to Datastore.",
tenantId.getValue());
}
}
}
}
private void updateResolvedPolicy(WriteTransaction wTx) {
Set<IndexedTenant> indexedTenants = getIndexedTenants(resolvedTenants.values());
Table<EgKey, EgKey, org.opendaylight.groupbasedpolicy.dto.Policy> policyMap =
PolicyResolverUtils.resolvePolicy(indexedTenants);
ResolvedPolicies resolvedPolicies =
new ResolvedPoliciesBuilder().setResolvedPolicy(PolicyInfoUtils.buildResolvedPolicy(policyMap, resolvedTenants)).build();
wTx.put(LogicalDatastoreType.OPERATIONAL, InstanceIdentifier.builder(ResolvedPolicies.class).build(),
resolvedPolicies, true);
}
private Set<IndexedTenant> getIndexedTenants(Collection<IndexedTenant> tenantCtxs) {
Set<IndexedTenant> result = new HashSet<>();
for (IndexedTenant tenant : tenantCtxs) {
if (tenant != null) {
result.add(tenant);
}
}
return result;
}
@VisibleForTesting
boolean isPolicyValid(Policy policy) {
if (policy != null && policy.getSubjectFeatureInstances() != null) {
SubjectFeatureInstances subjectFeatureInstances = policy.getSubjectFeatureInstances();
if (actionInstancesAreValid(subjectFeatureInstances.getActionInstance())
&& classifierInstancesAreValid(subjectFeatureInstances.getClassifierInstance())) {
return true;
}
}
return false;
}
/**
* Validation of action instances.
*
* @param actionInstances list of instances to validate
* @return true if instances are valid or if <code>actionInstances</code>
* is <code>null</code>, Otherwise returns false.
*/
private boolean actionInstancesAreValid(List<ActionInstance> actionInstances) {
if (actionInstances == null) {
return true;
}
for (ActionInstance actionInstance : actionInstances) {
Set<Validator<ActionInstance>> actionInstanceValidators =
actionInstanceValidatorsByDefinition.get(actionInstance.getActionDefinitionId());
for (Validator<ActionInstance> actionInstanceValidator : actionInstanceValidators) {
ValidationResult validationResult = actionInstanceValidator.validate(actionInstance);
if (!validationResult.isValid()) {
LOG.error("ActionInstance {} is not valid! {}", actionInstance.getName().getValue(),
validationResult.getMessage());
return false;
}
}
}
return true;
}
/**
* Validation of classifier instances.
*
* @param classifierInstances list of instances to validate
* @return true if instances are valid or if <code>classifierInstances</code>
* is <code>null</code>, Otherwise returns false.
*/
private boolean classifierInstancesAreValid(List<ClassifierInstance> classifierInstances) {
if (classifierInstances == null) {
return true;
}
for (ClassifierInstance classifierInstance : classifierInstances) {
Set<Validator<ClassifierInstance>> classifierInstanceValidators =
classifierInstanceValidatorsByDefinition.get(classifierInstance.getClassifierDefinitionId());
for (Validator<ClassifierInstance> classifierInstanceValidator : classifierInstanceValidators) {
ValidationResult validationResult = classifierInstanceValidator.validate(classifierInstance);
if (!validationResult.isValid()) {
LOG.error("ClassifierInstance {} is not valid! {}", classifierInstance.getName().getValue(),
validationResult.getMessage());
return false;
}
}
}
return true;
}
@Immutable
private class PolicyChangeListener extends DataTreeChangeHandler<Tenant> {
protected PolicyChangeListener(DataBroker dataProvider, DataTreeIdentifier<Tenant> pointOfInterest) {
super(dataProvider);
registerDataTreeChangeListener(pointOfInterest);
}
@Override
protected void onWrite(DataObjectModification<Tenant> rootNode, InstanceIdentifier<Tenant> rootIdentifier) {
Tenant tenantAfter = rootNode.getDataAfter();
updateTenant(tenantAfter.getId(), tenantAfter);
}
@Override
protected void onDelete(DataObjectModification<Tenant> rootNode, InstanceIdentifier<Tenant> rootIdentifier) {
TenantId tenantId = rootIdentifier.firstKeyOf(Tenant.class).getId();
updateTenant(tenantId, null);
}
@Override
protected void onSubtreeModified(DataObjectModification<Tenant> rootNode,
InstanceIdentifier<Tenant> rootIdentifier) {
Tenant tenantAfter = rootNode.getDataAfter();
updateTenant(tenantAfter.getId(), tenantAfter);
}
}
}