bf4387f6ddc90ecba3b29425f5f458a100ed9307
[groupbasedpolicy.git] / groupbasedpolicy / src / main / java / org / opendaylight / groupbasedpolicy / resolver / PolicyResolver.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.groupbasedpolicy.resolver;
9
10 import java.util.Collection;
11 import java.util.HashSet;
12 import java.util.List;
13 import java.util.Set;
14 import java.util.concurrent.ConcurrentHashMap;
15 import java.util.concurrent.ConcurrentMap;
16 import java.util.concurrent.atomic.AtomicReference;
17
18 import javax.annotation.concurrent.Immutable;
19
20 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
21 import org.opendaylight.controller.md.sal.binding.api.DataChangeListener;
22 import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
23 import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction;
24 import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
25 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
26 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
27 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
28 import org.opendaylight.groupbasedpolicy.api.PolicyValidatorRegistrar;
29 import org.opendaylight.groupbasedpolicy.api.ValidationResult;
30 import org.opendaylight.groupbasedpolicy.api.Validator;
31 import org.opendaylight.groupbasedpolicy.dto.EgKey;
32 import org.opendaylight.groupbasedpolicy.dto.IndexedTenant;
33 import org.opendaylight.groupbasedpolicy.dto.Policy;
34 import org.opendaylight.groupbasedpolicy.util.DataStoreHelper;
35 import org.opendaylight.groupbasedpolicy.util.InheritanceUtils;
36 import org.opendaylight.groupbasedpolicy.util.PolicyInfoUtils;
37 import org.opendaylight.groupbasedpolicy.util.PolicyResolverUtils;
38 import org.opendaylight.groupbasedpolicy.util.TenantUtils;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.common.rev140421.ActionDefinitionId;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.common.rev140421.ClassifierDefinitionId;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.common.rev140421.TenantId;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.tenants.Tenant;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.tenants.tenant.SubjectFeatureInstances;
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.tenants.tenant.subject.feature.instances.ActionInstance;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.tenants.tenant.subject.feature.instances.ClassifierInstance;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.resolved.policy.rev150828.ResolvedPolicies;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.resolved.policy.rev150828.ResolvedPoliciesBuilder;
48 import org.opendaylight.yangtools.concepts.ListenerRegistration;
49 import org.opendaylight.yangtools.yang.binding.DataObject;
50 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
53
54 import com.google.common.base.Optional;
55 import com.google.common.collect.HashMultimap;
56 import com.google.common.collect.Multimaps;
57 import com.google.common.collect.SetMultimap;
58 import com.google.common.collect.Table;
59 import com.google.common.util.concurrent.FutureCallback;
60 import com.google.common.util.concurrent.Futures;
61 import com.google.common.util.concurrent.ListenableFuture;
62
63 /**
64  * The policy resolver is a utility for renderers to help in resolving
65  * group-based policy into a form that is easier to apply to the actual network.
66  *
67  * For any pair of endpoint groups, there is a set of rules that could apply to
68  * the endpoints on that group based on the policy configuration. The exact list
69  * of rules that apply to a given pair of endpoints depends on the conditions
70  * that are active on the endpoints.
71  *
72  * We need to be able to query against this policy model, enumerate the relevant
73  * classes of traffic and endpoints, and notify renderers when there are changes
74  * to policy as it applies to active sets of endpoints and endpoint groups.
75  *
76  * The policy resolver will maintain the necessary state for all tenants in its
77  * control domain, which is the set of tenants for which policy listeners have
78  * been registered.
79  *
80  */
81 public class PolicyResolver implements PolicyValidatorRegistrar, AutoCloseable {
82
83     private static final Logger LOG = LoggerFactory.getLogger(PolicyResolver.class);
84
85     private final DataBroker dataProvider;
86
87     protected ConcurrentMap<TenantId, TenantContext> resolvedTenants;
88
89     /*
90      * Store validators for ActionDefinitions from Renderers
91      *
92      */
93     protected SetMultimap<ActionDefinitionId, Validator<ActionInstance>> actionInstanceValidatorsByDefinition = Multimaps.synchronizedSetMultimap(HashMultimap.<ActionDefinitionId, Validator<ActionInstance>>create());
94     protected SetMultimap<ClassifierDefinitionId, Validator<ClassifierInstance>> classifierInstanceValidatorsByDefinition = Multimaps.synchronizedSetMultimap(HashMultimap.<ClassifierDefinitionId, Validator<ClassifierInstance>>create());
95
96     public PolicyResolver(DataBroker dataProvider) {
97         this.dataProvider = dataProvider;
98         resolvedTenants = new ConcurrentHashMap<>();
99         LOG.debug("Initialized renderer common policy resolver");
100     }
101
102     // *************
103     // AutoCloseable
104     // *************
105     @Override
106     public void close() throws Exception {
107         for (TenantContext ctx : resolvedTenants.values()) {
108             if (ctx.registration != null) {
109                 ctx.registration.close();
110             }
111         }
112     }
113
114     // *************************
115     // PolicyResolutionValidatorRegistrar
116     // *************************
117
118     @Override
119     public void register(ActionDefinitionId actionDefinitionId,
120             Validator<ActionInstance> validator) {
121         actionInstanceValidatorsByDefinition.put(actionDefinitionId, validator);
122     }
123
124     @Override
125     public void unregister(ActionDefinitionId actionDefinitionId,
126             Validator<ActionInstance> validator) {
127         actionInstanceValidatorsByDefinition.remove(actionDefinitionId, validator);
128     }
129
130     @Override
131     public void register(ClassifierDefinitionId classifierDefinitionId,
132             Validator<ClassifierInstance> validator) {
133         classifierInstanceValidatorsByDefinition.put(classifierDefinitionId, validator);
134     }
135
136     @Override
137     public void unregister(ClassifierDefinitionId classifierDefinitionId,
138             Validator<ClassifierInstance> validator) {
139         classifierInstanceValidatorsByDefinition.remove(classifierDefinitionId, validator);
140     }
141
142     /**
143      * Subscribe the resolver to updates related to a particular tenant Make
144      * sure that this can't be called concurrently with subscribe
145      *
146      * @param tenantId the tenant ID to subscribe to
147      */
148     protected void subscribeTenant(TenantId tenantId) {
149         if (!resolvedTenants.containsKey(tenantId)) {
150             updateTenant(tenantId);
151         }
152     }
153
154     /**
155      * Unsubscribe the resolver from updates related to a particular tenant Make
156      * sure that this can't be called concurrently with subscribe
157      *
158      * @param tenantId the tenant ID to subscribe to
159      */
160     protected void unsubscribeTenant(TenantId tenantId) {
161         TenantContext context = resolvedTenants.get(tenantId);
162         if (context != null) {
163             resolvedTenants.remove(tenantId);
164             context.registration.close();
165         }
166     }
167
168     private void updateTenant(final TenantId tenantId) {
169         if (dataProvider == null) {
170             return;
171         }
172
173         TenantContext context = resolvedTenants.get(tenantId);
174         if (context == null) {
175             ListenerRegistration<DataChangeListener> registration = null;
176             registration = dataProvider
177                     .registerDataChangeListener(LogicalDatastoreType.CONFIGURATION,
178                             TenantUtils.tenantIid(tenantId),
179                             new PolicyChangeListener(tenantId),
180                             DataChangeScope.SUBTREE);
181             LOG.debug("Data change listener for tenant {} in CONF DS is registered.", tenantId.getValue());
182
183             context = new TenantContext(registration);
184             TenantContext oldContext
185                     = resolvedTenants.putIfAbsent(tenantId, context);
186             if (oldContext != null) {
187                 // already registered in a different thread; just use the other
188                 // context
189                 registration.close();
190                 context = oldContext;
191             } else {
192                 LOG.info("Added tenant {} to policy scope", tenantId);
193             }
194         }
195
196         // Resolve the new tenant and update atomically
197         final AtomicReference<IndexedTenant> tenantRef = context.tenant;
198         final IndexedTenant ot = tenantRef.get();
199         ReadOnlyTransaction transaction
200                 = dataProvider.newReadOnlyTransaction();
201         final InstanceIdentifier<Tenant> tiid = TenantUtils.tenantIid(tenantId);
202         ListenableFuture<Optional<Tenant>> unresolved;
203
204         unresolved = transaction.read(LogicalDatastoreType.CONFIGURATION, tiid);
205
206         Futures.addCallback(unresolved, new FutureCallback<Optional<Tenant>>() {
207             @Override
208             public void onSuccess(Optional<Tenant> result) {
209                 if (!result.isPresent()) {
210                     LOG.info("Tenant {} not found in CONF; check&delete from OPER", tenantId);
211                     deleteOperTenantIfExists(tiid, tenantId);
212                     return;
213                 }
214                 LOG.debug("Resolving of tenant inheritance and policy triggered by a change in tenant {}", tenantId);
215                 Tenant t = InheritanceUtils.resolveTenant(result.get());
216                 SubjectFeatureInstances subjectFeatureInstances = t.getSubjectFeatureInstances();
217                 if (subjectFeatureInstances != null) {
218                     // TODO log and remove invalid action instances
219                     if (actionInstancesAreValid(subjectFeatureInstances.getActionInstance())
220                             && classifierInstancesAreValid(subjectFeatureInstances.getClassifierInstance())) {
221                     IndexedTenant it = new IndexedTenant(t);
222                     if (!tenantRef.compareAndSet(ot, it)) {
223                         // concurrent update of tenant policy. Retry
224                         updateTenant(tenantId);
225                     } else {
226                         // Update the policy cache and notify listeners
227                         WriteTransaction wt = dataProvider.newWriteOnlyTransaction();
228                         wt.put(LogicalDatastoreType.OPERATIONAL, tiid, t, true);
229                         wt.submit();
230                         updatePolicy();
231                     }
232                 }
233             }
234         }
235
236             @Override
237             public void onFailure(Throwable t) {
238                 LOG.error("Count not get tenant {}", tenantId, t);
239             }
240         });
241     }
242
243     private void deleteOperTenantIfExists(final InstanceIdentifier<Tenant> tiid, final TenantId tenantId) {
244         final ReadWriteTransaction rwTx = dataProvider.newReadWriteTransaction();
245
246         ListenableFuture<Optional<Tenant>> readFuture = rwTx.read(LogicalDatastoreType.OPERATIONAL, tiid);
247         Futures.addCallback(readFuture, new FutureCallback<Optional<Tenant>>() {
248             @Override
249             public void onSuccess(Optional<Tenant> result) {
250                 if(result.isPresent()){
251                     TenantContext tenantContext = resolvedTenants.get(tenantId);
252                     tenantContext.tenant.set(null);
253                     rwTx.delete(LogicalDatastoreType.OPERATIONAL, tiid);
254                     rwTx.submit();
255                     updatePolicy();
256                 }
257             }
258
259             @Override
260             public void onFailure(Throwable t) {
261                 LOG.error("Failed to read operational datastore: {}", t);
262                 rwTx.cancel();
263             }
264         });
265     }
266
267     protected void updatePolicy() {
268         try {
269             Set<IndexedTenant> indexedTenants = getIndexedTenants(resolvedTenants.values());
270             Table<EgKey, EgKey, Policy> policyMap = PolicyResolverUtils.resolvePolicy(indexedTenants);
271             updatePolicyInDataStore(policyMap);
272         } catch (Exception e) {
273             LOG.error("Failed to update policy", e);
274         }
275     }
276
277     private void updatePolicyInDataStore(Table<EgKey, EgKey, Policy> policyMap) {
278         if (dataProvider == null) {
279             LOG.error("Couldn't Write Resolved Tenants Policy Info to Datastore because dataProvider is NULL");
280             return;
281         }
282         ResolvedPolicies resolvedPolicies = new ResolvedPoliciesBuilder().setResolvedPolicy(
283                 PolicyInfoUtils.buildResolvedPolicy(policyMap)).build();
284
285         WriteTransaction t = dataProvider.newWriteOnlyTransaction();
286         t.put(LogicalDatastoreType.OPERATIONAL, InstanceIdentifier.builder(ResolvedPolicies.class).build(),
287                 resolvedPolicies, true);
288         if (DataStoreHelper.submitToDs(t)) {
289             LOG.debug("Wrote resolved policies to Datastore");
290         } else {
291             LOG.error("Failed to write resolved policies to Datastore.");
292         }
293     }
294
295     private Set<IndexedTenant> getIndexedTenants(Collection<TenantContext> tenantCtxs) {
296         Set<IndexedTenant> result = new HashSet<>();
297         for (TenantContext tenant : tenantCtxs) {
298             IndexedTenant t = tenant.tenant.get();
299             if (t != null) {
300                 result.add(t);
301             }
302         }
303         return result;
304     }
305
306     /**
307      * Validation of action instances.
308      *
309      * @param actionInstances list of instances to validate
310      * @return true if instances are valid or if <code>actionInstances</code>
311      * is <code>null</code>, Otherwise returns false.
312      *
313      */
314     private boolean actionInstancesAreValid(List<ActionInstance> actionInstances) {
315         if (actionInstances == null) {
316             return true;
317         }
318         for (ActionInstance actionInstance : actionInstances) {
319             Set<Validator<ActionInstance>> actionInstanceValidators = actionInstanceValidatorsByDefinition.get(actionInstance.getActionDefinitionId());
320             for (Validator<ActionInstance> actionInstanceValidator : actionInstanceValidators) {
321                 ValidationResult validationResult = actionInstanceValidator.validate(actionInstance);
322                 if (!validationResult.isValid()) {
323                     LOG.error("ActionInstance {} is not valid!", actionInstance.getName());
324                     return false;
325                 }
326             }
327         }
328         return true;
329     }
330
331     /**
332      * Validation of classifier instances.
333      *
334      * @param classifierInstances list of instances to validate
335      * @return true if instances are valid or if <code>classifierInstances</code>
336      * is <code>null</code>, Otherwise returns false.
337      *
338      */
339     private boolean classifierInstancesAreValid(List<ClassifierInstance> classifierInstances) {
340         if (classifierInstances == null) {
341             return true;
342         }
343         for (ClassifierInstance classifierInstance : classifierInstances) {
344             Set<Validator<ClassifierInstance>> classifierInstanceValidators = classifierInstanceValidatorsByDefinition.get(classifierInstance.getClassifierDefinitionId());
345             for (Validator<ClassifierInstance> classifierInstanceValidator : classifierInstanceValidators) {
346                 ValidationResult validationResult = classifierInstanceValidator.validate(classifierInstance);
347                 if (!validationResult.isValid()) {
348                     LOG.error("ClassifierInstance {} is not valid!", classifierInstance.getName());
349                     return false;
350                 }
351             }
352         }
353         return true;
354     }
355
356     protected static class TenantContext {
357
358         ListenerRegistration<DataChangeListener> registration;
359
360         AtomicReference<IndexedTenant> tenant = new AtomicReference<>();
361
362         public TenantContext(ListenerRegistration<DataChangeListener> registration) {
363             super();
364             this.registration = registration;
365         }
366     }
367
368     @Immutable
369     private class PolicyChangeListener implements DataChangeListener {
370
371         final TenantId tenantId;
372
373         public PolicyChangeListener(TenantId tenantId) {
374             super();
375             this.tenantId = tenantId;
376         }
377
378         @Override
379         public void onDataChanged(AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> arg0) {
380             updateTenant(tenantId);
381         }
382
383     }
384
385 }