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