X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=mdsalutil%2Fmdsalutil-api%2Fsrc%2Ftest%2Fjava%2Forg%2Fopendaylight%2Fgenius%2Fmdsalutil%2Finterfaces%2Ftestutils%2FTestIMdsalApiManager.java;h=ef571cccecfce724a00ca4d887af67cf43e35f37;hb=f767edd5a1c52116a669e9e93d57932e3c441f26;hp=6d90cfb439fbd7c88fbf24c7116df803749869e0;hpb=3f9e5582f4746f2e9518cab6dd667da12216c80c;p=genius.git diff --git a/mdsalutil/mdsalutil-api/src/test/java/org/opendaylight/genius/mdsalutil/interfaces/testutils/TestIMdsalApiManager.java b/mdsalutil/mdsalutil-api/src/test/java/org/opendaylight/genius/mdsalutil/interfaces/testutils/TestIMdsalApiManager.java index 6d90cfb43..ef571ccce 100644 --- a/mdsalutil/mdsalutil-api/src/test/java/org/opendaylight/genius/mdsalutil/interfaces/testutils/TestIMdsalApiManager.java +++ b/mdsalutil/mdsalutil-api/src/test/java/org/opendaylight/genius/mdsalutil/interfaces/testutils/TestIMdsalApiManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Red Hat, Inc. and others. All rights reserved. + * Copyright (c) 2016, 2017 Red Hat, 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, @@ -9,9 +9,12 @@ package org.opendaylight.genius.mdsalutil.interfaces.testutils; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.opendaylight.mdsal.binding.testutils.AssertDataObjects.assertEqualBeans; import static org.opendaylight.yangtools.testutils.mockito.MoreAnswers.realOrException; +import ch.vorburger.xtendbeans.XtendBeanGenerator; +import com.google.common.collect.ComparisonChain; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; @@ -19,11 +22,26 @@ import com.google.common.util.concurrent.CheckedFuture; import com.google.common.util.concurrent.Futures; import java.math.BigInteger; import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Objects; +import org.junit.ComparisonFailure; import org.mockito.Mockito; import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException; +import org.opendaylight.genius.infra.Datastore.Configuration; +import org.opendaylight.genius.infra.TypedReadWriteTransaction; +import org.opendaylight.genius.infra.TypedWriteTransaction; import org.opendaylight.genius.mdsalutil.FlowEntity; +import org.opendaylight.genius.mdsalutil.GroupEntity; import org.opendaylight.genius.mdsalutil.interfaces.IMdsalApiManager; +import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.Flow; +import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.group.buckets.Bucket; +import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.groups.Group; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Fake IMdsalApiManager useful for tests. @@ -37,13 +55,22 @@ import org.opendaylight.genius.mdsalutil.interfaces.IMdsalApiManager; * of it using it's static {@link #newInstance()} method. * * @author Michael Vorburger + * @author Faseela K */ public abstract class TestIMdsalApiManager implements IMdsalApiManager { - private List flows; + private static final Logger LOG = LoggerFactory.getLogger(TestIMdsalApiManager.class); + + private Map flows; + private Map groups; + private Map buckets; public static TestIMdsalApiManager newInstance() { - return Mockito.mock(TestIMdsalApiManager.class, realOrException()); + TestIMdsalApiManager instance = Mockito.mock(TestIMdsalApiManager.class, realOrException()); + instance.flows = new HashMap<>(); + instance.groups = new HashMap<>(); + instance.buckets = new HashMap<>(); + return instance; } /** @@ -51,39 +78,42 @@ public abstract class TestIMdsalApiManager implements IMdsalApiManager { * Prefer the {@link #assertFlows(Iterable)} instead of using this and checking yourself. * @return immutable copy of list of flows */ - public synchronized List getFlows() { - return ImmutableList.copyOf(getOrNewFlows()); - } - - private synchronized List getOrNewFlows() { - if (flows == null) { - flows = new ArrayList<>(); - } - return flows; + public List getFlows() { + return retrieveFlows(); } - public synchronized void assertFlows(Iterable expectedFlows) { + public void assertFlows(Iterable expectedFlows) { checkNonEmptyFlows(expectedFlows); - List nonNullFlows = getOrNewFlows(); + Collection nonNullFlows = retrieveFlows(); if (!Iterables.isEmpty(expectedFlows)) { assertTrue("No Flows created (bean wiring may be broken?)", !nonNullFlows.isEmpty()); } // TODO Support Iterable <-> List directly within XtendBeanGenerator List expectedFlowsAsNewArrayList = Lists.newArrayList(expectedFlows); - assertEqualBeans(expectedFlowsAsNewArrayList, nonNullFlows); + assertEqualBeans(expectedFlowsAsNewArrayList, new ArrayList<>(nonNullFlows)); } - private synchronized void checkNonEmptyFlows(Iterable expectedFlows) { + private void checkNonEmptyFlows(Iterable expectedFlows) { if (!Iterables.isEmpty(expectedFlows)) { - assertTrue("No Flows created (bean wiring may be broken?)", !getOrNewFlows().isEmpty()); + assertTrue("No Flows created (bean wiring may be broken?)", !retrieveFlows().isEmpty()); } } - public synchronized void assertFlowsInAnyOrder(Iterable expectedFlows) { + // ComparisonException doesn’t allow us to keep the cause (which we don’t care about anyway) + @SuppressWarnings("checkstyle:AvoidHidingCauseException") + public void assertFlowsInAnyOrder(Iterable expectedFlows) { checkNonEmptyFlows(expectedFlows); // TODO Support Iterable <-> List directly within XtendBeanGenerator - List expectedFlowsAsNewArrayList = Lists.newArrayList(expectedFlows); + + List sortedFlows = sortFlows(retrieveFlows()); + Map keyedExpectedFlows = new HashMap<>(); + for (FlowEntity expectedFlow : expectedFlows) { + keyedExpectedFlows.put( + new InternalFlowKey(expectedFlow.getDpnId(), expectedFlow.getFlowId(), expectedFlow.getTableId()), + expectedFlow); + } + List sortedExpectedFlows = sortFlows(keyedExpectedFlows.values()); // FYI: This containsExactlyElementsIn() assumes that FlowEntity, and everything in it, // has correctly working equals() implementations. assertEqualBeans() does not assume @@ -102,25 +132,243 @@ public abstract class TestIMdsalApiManager implements IMdsalApiManager { // is not a good idea (different instances can have same hashCode), and e.g. on // System#identityHashCode even less so. try { - assertThat(flows).containsExactlyElementsIn(expectedFlowsAsNewArrayList); + assertThat(sortedFlows).containsExactlyElementsIn(sortedExpectedFlows); } catch (AssertionError e) { - // The point of this is basically just that our assertEqualBeans output, + // We LOG the AssertionError just for clarity why containsExactlyElementsIn() failed + LOG.warn("assert containsExactlyElementsIn() failed", e); + // We LOG the expected and actual flow in case of a failed assertion + // because, even though that is typically just a HUGE String that's + // hard to read (the diff printed subsequently by assertEqualBeans + // is, much, more readable), there are cases when looking more closely + // at the full toString() output of the flows is still useful, so: + // TIP: Use e.g. 'wdiff -n expected.txt actual.txt | colordiff' to compare these! + LOG.warn("assert failed [order ignored!]; expected flows ({}): {}", sortedExpectedFlows.size(), + sortedExpectedFlows); + LOG.warn("assert failed [order ignored!]; actual flows ({}): {}", sortedFlows.size(), sortedFlows); + for (int i = 0; i < sortedExpectedFlows.size() && i < sortedFlows.size(); i++) { + if (!sortedExpectedFlows.get(i).equals(sortedFlows.get(i))) { + LOG.warn("First mismatch at index {};", i); + LOG.warn(" expected {}", sortedExpectedFlows.get(i)); + LOG.warn(" got {}", sortedFlows.get(i)); + break; + } + } + // The point of now also doing assertEqualBeans() is just that its output, // in case of a comparison failure, is *A LOT* more clearly readable // than what G Truth (or Hamcrest) can do based on toString. - assertEqualBeans(expectedFlowsAsNewArrayList, flows); + assertEqualBeans(sortedExpectedFlows, sortedFlows); + if (sortedExpectedFlows.toString().equals(sortedFlows.toString()) + && !sortedExpectedFlows.equals(sortedFlows)) { + fail("Suspected toString, missing getter, equals (hashCode) bug in FlowEntity related class!!! :-("); + } + throw new ComparisonFailure( + "assertEqualBeans() MUST fail - given that the assertThat.containsExactlyElementsIn() just failed!" + // Beware, we're using XtendBeanGenerator instead of XtendYangBeanGenerator like in + // AssertDataObjects, but for FlowEntity it's the same... it only makes a difference for DataObjects + + " What is missing in: " + new XtendBeanGenerator().getExpression(sortedFlows), + sortedExpectedFlows.toString(), sortedFlows.toString()); + // If this ^^^ occurs, then there is probably a bug in ch.vorburger.xtendbeans } } + private List sortFlows(Iterable flowsToSort) { + List sortedFlows = Lists.newArrayList(flowsToSort); + sortedFlows.sort((flow1, flow2) -> ComparisonChain.start() + .compare(flow1.getTableId(), flow2.getTableId()) + .compare(flow1.getPriority(), flow2.getPriority()) + .compare(flow1.getFlowId(), flow2.getFlowId()) + .result()); + return sortedFlows; + } + + private synchronized void storeFlow(FlowEntity flowEntity) { + flows.put(new InternalFlowKey(flowEntity.getDpnId(), flowEntity.getFlowId(), flowEntity.getTableId()), + flowEntity); + } + + private synchronized List retrieveFlows() { + return ImmutableList.copyOf(flows.values()); + } + + private synchronized void deleteFlow(BigInteger dpId, String flowId, short tableId) { + flows.remove(new InternalFlowKey(dpId, flowId, tableId)); + } + + private synchronized void storeGroup(BigInteger dpnId, Group group) { + groups.put(new InternalGroupKey(dpnId, group.key().getGroupId().getValue()), group); + } + + private synchronized void deleteGroup(BigInteger dpnId, long groupId) { + groups.remove(new InternalGroupKey(dpnId, groupId)); + } + + private synchronized void storeBucket(BigInteger dpnId, long groupId, Bucket bucket) { + buckets.put(new InternalBucketKey(dpnId, groupId, bucket.getBucketId().getValue()), bucket); + } + + private synchronized void deleteBucket(BigInteger dpnId, long groupId, long bucketId) { + buckets.remove(new InternalBucketKey(dpnId, groupId, bucketId)); + } + @Override - public synchronized void installFlow(FlowEntity flowEntity) { - getOrNewFlows().add(flowEntity); + public void addFlow(TypedWriteTransaction tx, FlowEntity flowEntity) { + storeFlow(flowEntity); } @Override - public synchronized CheckedFuture removeFlow(BigInteger dpnId, - FlowEntity flowEntity) { - getOrNewFlows().remove(flowEntity); + public void addFlow(TypedWriteTransaction tx, BigInteger dpId, Flow flow) { + throw new UnsupportedOperationException("addFlow(..., BigInteger, Flow) isn't supported yet"); + } + + @Override + public void removeFlow(TypedReadWriteTransaction tx, BigInteger dpId, Flow flow) { + removeFlow(tx, dpId, flow.key(), flow.getTableId()); + } + + @Override + public void removeFlow(TypedReadWriteTransaction tx, FlowEntity flowEntity) { + deleteFlow(flowEntity.getDpnId(), flowEntity.getFlowId(), flowEntity.getTableId()); + } + + @Override + public void removeFlow(TypedReadWriteTransaction tx, BigInteger dpId, FlowKey flowKey, + short tableId) { + deleteFlow(dpId, flowKey.getId().getValue(), tableId); + } + + @Override + public void removeFlow(TypedReadWriteTransaction tx, BigInteger dpId, String flowId, + short tableId) { + deleteFlow(dpId, flowId, tableId); + } + + @Override + public void addGroup(TypedWriteTransaction tx, GroupEntity groupEntity) { + storeGroup(groupEntity.getDpnId(), groupEntity.getGroupBuilder().build()); + } + + @Override + public void addGroup(TypedWriteTransaction tx, BigInteger dpId, Group group) { + storeGroup(dpId, group); + } + + @Override + public void removeGroup(TypedReadWriteTransaction tx, BigInteger dpId, Group group) { + deleteGroup(dpId, group.getGroupId().getValue()); + } + + @Override + public void removeGroup(TypedReadWriteTransaction tx, BigInteger dpId, long groupId) { + deleteGroup(dpId, groupId); + } + + @Override + public void addBucket(TypedReadWriteTransaction tx, BigInteger dpId, long groupId, + Bucket bucket) { + storeBucket(dpId, groupId, bucket); + } + + @Override + public void removeBucket(TypedReadWriteTransaction tx, BigInteger dpId, long groupId, + long bucketId) { + deleteBucket(dpId, groupId, bucketId); + } + + @Override + public CheckedFuture installFlow(FlowEntity flowEntity) { + storeFlow(flowEntity); return Futures.immediateCheckedFuture(null); } + @Override + public CheckedFuture installFlow(BigInteger dpId, + FlowEntity flowEntity) { + // TODO should dpId be considered here? how? Copy clone FlowEntity and change its dpId? + return installFlow(flowEntity); + } + + private static final class InternalFlowKey { + private final BigInteger dpnId; + private final String flowId; + private final short tableId; + + private InternalFlowKey(BigInteger dpnId, String flowId, short tableId) { + this.dpnId = dpnId; + this.flowId = flowId; + this.tableId = tableId; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + InternalFlowKey that = (InternalFlowKey) obj; + return tableId == that.tableId && Objects.equals(dpnId, that.dpnId) && Objects.equals(flowId, that.flowId); + } + + @Override + public int hashCode() { + return Objects.hash(dpnId, flowId, tableId); + } + } + + private static final class InternalGroupKey { + private final BigInteger dpnId; + private final long groupId; + + private InternalGroupKey(BigInteger dpnId, long groupId) { + this.dpnId = dpnId; + this.groupId = groupId; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + InternalGroupKey that = (InternalGroupKey) obj; + return groupId == that.groupId && Objects.equals(dpnId, that.dpnId); + } + + @Override + public int hashCode() { + return Objects.hash(dpnId, groupId); + } + } + + private static final class InternalBucketKey { + private final BigInteger dpnId; + private final long groupId; + private final long bucketId; + + private InternalBucketKey(BigInteger dpnId, long groupId, long bucketId) { + this.dpnId = dpnId; + this.groupId = groupId; + this.bucketId = bucketId; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + InternalBucketKey that = (InternalBucketKey) obj; + return groupId == that.groupId && bucketId == that.bucketId && Objects.equals(dpnId, that.dpnId); + } + + @Override + public int hashCode() { + return Objects.hash(dpnId, groupId, bucketId); + } + } }