Add more MdsalApiManager test methods, key them
[genius.git] / mdsalutil / mdsalutil-api / src / test / java / org / opendaylight / genius / mdsalutil / interfaces / testutils / TestIMdsalApiManager.java
1 /*
2  * Copyright (c) 2016, 2017 Red Hat, 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.genius.mdsalutil.interfaces.testutils;
9
10 import static com.google.common.truth.Truth.assertThat;
11 import static org.junit.Assert.assertTrue;
12 import static org.junit.Assert.fail;
13 import static org.opendaylight.mdsal.binding.testutils.AssertDataObjects.assertEqualBeans;
14 import static org.opendaylight.yangtools.testutils.mockito.MoreAnswers.realOrException;
15
16 import ch.vorburger.xtendbeans.XtendBeanGenerator;
17 import com.google.common.collect.ComparisonChain;
18 import com.google.common.collect.ImmutableList;
19 import com.google.common.collect.Iterables;
20 import com.google.common.collect.Lists;
21 import com.google.common.util.concurrent.CheckedFuture;
22 import com.google.common.util.concurrent.Futures;
23 import java.math.BigInteger;
24 import java.util.Collection;
25 import java.util.HashMap;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Objects;
29 import org.junit.ComparisonFailure;
30 import org.mockito.Mockito;
31 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
32 import org.opendaylight.genius.infra.Datastore.Configuration;
33 import org.opendaylight.genius.infra.TypedReadWriteTransaction;
34 import org.opendaylight.genius.infra.TypedWriteTransaction;
35 import org.opendaylight.genius.mdsalutil.FlowEntity;
36 import org.opendaylight.genius.mdsalutil.GroupEntity;
37 import org.opendaylight.genius.mdsalutil.interfaces.IMdsalApiManager;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.Flow;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowKey;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.group.buckets.Bucket;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.groups.Group;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45 /**
46  * Fake IMdsalApiManager useful for tests.
47  *
48  * <p>Read e.g.
49  * http://googletesting.blogspot.ch/2013/07/testing-on-toilet-know-your-test-doubles.html
50  * and http://martinfowler.com/articles/mocksArentStubs.html for more background.
51  *
52  * <p>This class is abstract just to save reading lines and typing keystrokes to
53  * manually implement a bunch of methods we're not yet interested in.  Create instances
54  * of it using it's static {@link #newInstance()} method.
55  *
56  * @author Michael Vorburger
57  * @author Faseela K
58  */
59 public abstract class TestIMdsalApiManager implements IMdsalApiManager {
60
61     private static final Logger LOG = LoggerFactory.getLogger(TestIMdsalApiManager.class);
62
63     private Map<InternalFlowKey, FlowEntity> flows;
64     private Map<InternalGroupKey, Group> groups;
65     private Map<InternalBucketKey, Bucket> buckets;
66
67     public static TestIMdsalApiManager newInstance() {
68         TestIMdsalApiManager instance = Mockito.mock(TestIMdsalApiManager.class, realOrException());
69         instance.init();
70         return instance;
71     }
72
73     private void init() {
74         this.flows = new HashMap<>();
75         this.groups = new HashMap<>();
76         this.buckets = new HashMap<>();
77     }
78
79     /**
80      * Get list of installed flows.
81      * Prefer the {@link #assertFlows(Iterable)} instead of using this and checking yourself.
82      * @return immutable copy of list of flows
83      */
84     public synchronized List<FlowEntity> getFlows() {
85         return ImmutableList.copyOf(retrieveFlows());
86     }
87
88     public synchronized void assertFlows(Iterable<FlowEntity> expectedFlows) {
89         checkNonEmptyFlows(expectedFlows);
90         Collection<FlowEntity> nonNullFlows = retrieveFlows();
91         if (!Iterables.isEmpty(expectedFlows)) {
92             assertTrue("No Flows created (bean wiring may be broken?)", !nonNullFlows.isEmpty());
93         }
94         // TODO Support Iterable <-> List directly within XtendBeanGenerator
95         List<FlowEntity> expectedFlowsAsNewArrayList = Lists.newArrayList(expectedFlows);
96         assertEqualBeans(expectedFlowsAsNewArrayList, nonNullFlows);
97     }
98
99
100     private synchronized void checkNonEmptyFlows(Iterable<FlowEntity> expectedFlows) {
101         if (!Iterables.isEmpty(expectedFlows)) {
102             assertTrue("No Flows created (bean wiring may be broken?)", !retrieveFlows().isEmpty());
103         }
104     }
105
106     // ComparisonException doesn’t allow us to keep the cause (which we don’t care about anyway)
107     @SuppressWarnings("checkstyle:AvoidHidingCauseException")
108     public synchronized void assertFlowsInAnyOrder(Iterable<FlowEntity> expectedFlows) {
109         checkNonEmptyFlows(expectedFlows);
110         // TODO Support Iterable <-> List directly within XtendBeanGenerator
111
112         List<FlowEntity> sortedFlows = sortFlows(retrieveFlows());
113         Map<InternalFlowKey, FlowEntity> keyedExpectedFlows = new HashMap<>();
114         for (FlowEntity expectedFlow : expectedFlows) {
115             keyedExpectedFlows.put(
116                 new InternalFlowKey(expectedFlow.getDpnId(), expectedFlow.getFlowId(), expectedFlow.getTableId()),
117                 expectedFlow);
118         }
119         List<FlowEntity> sortedExpectedFlows = sortFlows(keyedExpectedFlows.values());
120
121         // FYI: This containsExactlyElementsIn() assumes that FlowEntity, and everything in it,
122         // has correctly working equals() implementations.  assertEqualBeans() does not assume
123         // that, and would work even without equals, because it only uses property reflection.
124         // Normally this will lead to the same result, but if one day it doesn't (because of
125         // a bug in an equals() implementation somewhere), then it's worth to keep this diff
126         // in mind.
127
128         // FTR: This use of G Truth and then catch AssertionError and using assertEqualBeans iff NOK
129         // (thus discarding the message from G Truth) is a bit of a hack, but it works well...
130         // If you're tempted to improve this, please remember that correctly re-implementing
131         // containsExactlyElementsIn (or Hamcrest's similar containsInAnyOrder) isn't a 1 line
132         // trivia... e.g. a.containsAll(b) && b.containsAll(a) isn't sufficient, because it
133         // won't work for duplicates (which we frequently have here); and ordering before is
134         // not viable because FlowEntity is not Comparable, and Comparator based on hashCode
135         // is not a good idea (different instances can have same hashCode), and e.g. on
136         // System#identityHashCode even less so.
137         try {
138             assertThat(sortedFlows).containsExactlyElementsIn(sortedExpectedFlows);
139         } catch (AssertionError e) {
140             // We LOG the AssertionError just for clarity why containsExactlyElementsIn() failed
141             LOG.warn("assert containsExactlyElementsIn() failed", e);
142             // We LOG the expected and actual flow in case of a failed assertion
143             // because, even though that is typically just a HUGE String that's
144             // hard to read (the diff printed subsequently by assertEqualBeans
145             // is, much, more readable), there are cases when looking more closely
146             // at the full toString() output of the flows is still useful, so:
147             // TIP: Use e.g. 'wdiff -n expected.txt actual.txt | colordiff' to compare these!
148             LOG.warn("assert failed [order ignored!]; expected flows ({}): {}", sortedExpectedFlows.size(),
149                 sortedExpectedFlows);
150             LOG.warn("assert failed [order ignored!]; actual flows   ({}): {}", sortedFlows.size(), sortedFlows);
151             for (int i = 0; i < sortedExpectedFlows.size() && i < sortedFlows.size(); i++) {
152                 if (!sortedExpectedFlows.get(i).equals(sortedFlows.get(i))) {
153                     LOG.warn("First mismatch at index {};", i);
154                     LOG.warn("               expected {}", sortedExpectedFlows.get(i));
155                     LOG.warn("                    got {}", sortedFlows.get(i));
156                     break;
157                 }
158             }
159             // The point of now also doing assertEqualBeans() is just that its output,
160             // in case of a comparison failure, is *A LOT* more clearly readable
161             // than what G Truth (or Hamcrest) can do based on toString.
162             assertEqualBeans(sortedExpectedFlows, sortedFlows);
163             if (sortedExpectedFlows.toString().equals(sortedFlows.toString())
164                     && !sortedExpectedFlows.equals(sortedFlows)) {
165                 fail("Suspected toString, missing getter, equals (hashCode) bug in FlowEntity related class!!! :-(");
166             }
167             throw new ComparisonFailure(
168                     "assertEqualBeans() MUST fail - given that the assertThat.containsExactlyElementsIn() just failed!"
169                     // Beware, we're using XtendBeanGenerator instead of XtendYangBeanGenerator like in
170                     // AssertDataObjects, but for FlowEntity it's the same... it only makes a difference for DataObjects
171                     + " What is missing in: " + new XtendBeanGenerator().getExpression(sortedFlows),
172                     sortedExpectedFlows.toString(), sortedFlows.toString());
173             // If this ^^^ occurs, then there is probably a bug in ch.vorburger.xtendbeans
174         }
175     }
176
177     private List<FlowEntity> sortFlows(Iterable<FlowEntity> flowsToSort) {
178         List<FlowEntity> sortedFlows = Lists.newArrayList(flowsToSort);
179         sortedFlows.sort((flow1, flow2) -> ComparisonChain.start()
180                 .compare(flow1.getTableId(), flow2.getTableId())
181                 .compare(flow1.getPriority(), flow2.getPriority())
182                 .compare(flow1.getFlowId(), flow2.getFlowId())
183                 .result());
184         return sortedFlows;
185     }
186
187     private void storeFlow(FlowEntity flowEntity) {
188         flows.put(new InternalFlowKey(flowEntity.getDpnId(), flowEntity.getFlowId(), flowEntity.getTableId()),
189             flowEntity);
190     }
191
192     private Collection<FlowEntity> retrieveFlows() {
193         return flows.values();
194     }
195
196     private void deleteFlow(BigInteger dpId, String flowId, short tableId) {
197         flows.remove(new InternalFlowKey(dpId, flowId, tableId));
198     }
199
200     private void storeGroup(BigInteger dpnId, Group group) {
201         groups.put(new InternalGroupKey(dpnId, group.key().getGroupId().getValue()), group);
202     }
203
204     private Collection<Group> retrieveGroups() {
205         return groups.values();
206     }
207
208     private void deleteGroup(BigInteger dpnId, long groupId) {
209         groups.remove(new InternalGroupKey(dpnId, groupId));
210     }
211
212     private void storeBucket(BigInteger dpnId, long groupId, Bucket bucket) {
213         buckets.put(new InternalBucketKey(dpnId, groupId, bucket.getBucketId().getValue()), bucket);
214     }
215
216     private Collection<Bucket> retrieveBuckets() {
217         return buckets.values();
218     }
219
220     private void deleteBucket(BigInteger dpnId, long groupId, long bucketId) {
221         buckets.remove(new InternalBucketKey(dpnId, groupId, bucketId));
222     }
223
224     @Override
225     public void addFlow(TypedWriteTransaction<Configuration> tx, FlowEntity flowEntity) {
226         storeFlow(flowEntity);
227     }
228
229     @Override
230     public void addFlow(TypedWriteTransaction<Configuration> tx, BigInteger dpId, Flow flow) {
231         throw new UnsupportedOperationException("addFlow(..., BigInteger, Flow) isn't supported yet");
232     }
233
234     @Override
235     public void removeFlow(TypedReadWriteTransaction<Configuration> tx, BigInteger dpId, Flow flow) {
236         removeFlow(tx, dpId, flow.key(), flow.getTableId());
237     }
238
239     @Override
240     public void removeFlow(TypedReadWriteTransaction<Configuration> tx, FlowEntity flowEntity) {
241         deleteFlow(flowEntity.getDpnId(), flowEntity.getFlowId(), flowEntity.getTableId());
242     }
243
244     @Override
245     public void removeFlow(TypedReadWriteTransaction<Configuration> tx, BigInteger dpId, FlowKey flowKey,
246             short tableId) {
247         deleteFlow(dpId, flowKey.getId().getValue(), tableId);
248     }
249
250     @Override
251     public void removeFlow(TypedReadWriteTransaction<Configuration> tx, BigInteger dpId, String flowId,
252             short tableId) {
253         deleteFlow(dpId, flowId, tableId);
254     }
255
256     @Override
257     public synchronized CheckedFuture<Void, TransactionCommitFailedException> removeFlow(BigInteger dpnId,
258             FlowEntity flowEntity) {
259         deleteFlow(dpnId, flowEntity.getFlowId(), flowEntity.getTableId());
260         return Futures.immediateCheckedFuture(null);
261     }
262
263     @Override
264     public void addGroup(TypedWriteTransaction<Configuration> tx, GroupEntity groupEntity) {
265         storeGroup(groupEntity.getDpnId(), groupEntity.getGroupBuilder().build());
266     }
267
268     @Override
269     public void addGroup(TypedWriteTransaction<Configuration> tx, BigInteger dpId, Group group) {
270         storeGroup(dpId, group);
271     }
272
273     @Override
274     public void removeGroup(TypedReadWriteTransaction<Configuration> tx, BigInteger dpId, Group group) {
275         deleteGroup(dpId, group.getGroupId().getValue());
276     }
277
278     @Override
279     public void removeGroup(TypedReadWriteTransaction<Configuration> tx, BigInteger dpId, long groupId) {
280         deleteGroup(dpId, groupId);
281     }
282
283     @Override
284     public void addBucket(TypedReadWriteTransaction<Configuration> tx, BigInteger dpId, long groupId,
285             Bucket bucket) {
286         storeBucket(dpId, groupId, bucket);
287     }
288
289     @Override
290     public void removeBucket(TypedReadWriteTransaction<Configuration> tx, BigInteger dpId, long groupId,
291             long bucketId) {
292         deleteBucket(dpId, groupId, bucketId);
293     }
294
295     @Override
296     public synchronized CheckedFuture<Void, TransactionCommitFailedException> installFlow(FlowEntity flowEntity) {
297         storeFlow(flowEntity);
298         return Futures.immediateCheckedFuture(null);
299     }
300
301     @Override
302     public synchronized CheckedFuture<Void, TransactionCommitFailedException> installFlow(BigInteger dpId,
303             FlowEntity flowEntity) {
304         // TODO should dpId be considered here? how? Copy clone FlowEntity and change its dpId?
305         return installFlow(flowEntity);
306     }
307
308     @Override
309     public synchronized void batchedAddFlow(BigInteger dpId, FlowEntity flowEntity) {
310         storeFlow(flowEntity);
311     }
312
313     @Override
314     public synchronized void batchedRemoveFlow(BigInteger dpId, FlowEntity flowEntity) {
315         deleteFlow(dpId, flowEntity.getFlowId(), flowEntity.getTableId());
316     }
317
318     @Override
319     public void syncInstallGroup(BigInteger dpId, Group group, long delayTime) {
320         storeGroup(dpId, group);
321     }
322
323     @Override
324     public void syncInstallGroup(BigInteger dpId, Group group) {
325         storeGroup(dpId, group);
326     }
327
328     @Override
329     public void syncRemoveGroup(BigInteger dpId, Group groupEntity) {
330         deleteGroup(dpId, groupEntity.getGroupId().getValue());
331     }
332
333     private final class InternalFlowKey {
334         private final BigInteger dpnId;
335         private final String flowId;
336         private final short tableId;
337
338         private InternalFlowKey(BigInteger dpnId, String flowId, short tableId) {
339             this.dpnId = dpnId;
340             this.flowId = flowId;
341             this.tableId = tableId;
342         }
343
344         @Override
345         public boolean equals(Object obj) {
346             if (this == obj) {
347                 return true;
348             }
349             if (obj == null || getClass() != obj.getClass()) {
350                 return false;
351             }
352             InternalFlowKey that = (InternalFlowKey) obj;
353             return tableId == that.tableId && Objects.equals(dpnId, that.dpnId) && Objects.equals(flowId, that.flowId);
354         }
355
356         @Override
357         public int hashCode() {
358             return Objects.hash(dpnId, flowId, tableId);
359         }
360     }
361
362     private final class InternalGroupKey {
363         private final BigInteger dpnId;
364         private final long groupId;
365
366         private InternalGroupKey(BigInteger dpnId, long groupId) {
367             this.dpnId = dpnId;
368             this.groupId = groupId;
369         }
370
371         @Override
372         public boolean equals(Object obj) {
373             if (this == obj) {
374                 return true;
375             }
376             if (obj == null || getClass() != obj.getClass()) {
377                 return false;
378             }
379             InternalGroupKey that = (InternalGroupKey) obj;
380             return groupId == that.groupId && Objects.equals(dpnId, that.dpnId);
381         }
382
383         @Override
384         public int hashCode() {
385             return Objects.hash(dpnId, groupId);
386         }
387     }
388
389     private final class InternalBucketKey {
390         private final BigInteger dpnId;
391         private final long groupId;
392         private final long bucketId;
393
394         private InternalBucketKey(BigInteger dpnId, long groupId, long bucketId) {
395             this.dpnId = dpnId;
396             this.groupId = groupId;
397             this.bucketId = bucketId;
398         }
399
400         @Override
401         public boolean equals(Object obj) {
402             if (this == obj) {
403                 return true;
404             }
405             if (obj == null || getClass() != obj.getClass()) {
406                 return false;
407             }
408             InternalBucketKey that = (InternalBucketKey) obj;
409             return groupId == that.groupId && bucketId == that.bucketId && Objects.equals(dpnId, that.dpnId);
410         }
411
412         @Override
413         public int hashCode() {
414             return Objects.hash(dpnId, groupId, bucketId);
415         }
416     }
417 }