Updates for SDN-IP:
* Use the new Leadership Service instead of Distributed Lock to
elect the SDN-IP Leader
* Reimplement the SDN-IP Intent Synchronizer. In the new implementation
the Point-to-Point Peer intents are also synchronized by and pushed
only by the Leader (same as the Multipoint-to-SinglePoint Route intents)
* Minor cleanups
Change-Id: I8e142781211a1d0f2d362875bc28fd05d843cd4b
diff --git a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/IntentSyncTest.java b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/IntentSyncTest.java
index 4b29ba8..3d9cd23 100644
--- a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/IntentSyncTest.java
+++ b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/IntentSyncTest.java
@@ -5,16 +5,23 @@
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.reportMatcher;
import static org.easymock.EasyMock.reset;
import static org.easymock.EasyMock.verify;
+import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import org.apache.commons.collections4.CollectionUtils;
+import org.easymock.IArgumentMatcher;
import org.junit.Before;
import org.junit.Test;
import org.onlab.junit.TestUtils;
@@ -35,10 +42,14 @@
import org.onlab.onos.net.host.HostService;
import org.onlab.onos.net.host.InterfaceIpAddress;
import org.onlab.onos.net.intent.Intent;
+import org.onlab.onos.net.intent.IntentId;
+import org.onlab.onos.net.intent.IntentOperation;
+import org.onlab.onos.net.intent.IntentOperations;
import org.onlab.onos.net.intent.IntentService;
import org.onlab.onos.net.intent.IntentState;
import org.onlab.onos.net.intent.MultiPointToSinglePointIntent;
import org.onlab.onos.net.provider.ProviderId;
+import org.onlab.onos.sdnip.IntentSynchronizer.IntentKey;
import org.onlab.onos.sdnip.config.Interface;
import org.onlab.packet.Ethernet;
import org.onlab.packet.IpAddress;
@@ -97,8 +108,8 @@
intentService = createMock(IntentService.class);
intentSynchronizer = new IntentSynchronizer(APPID, intentService);
- router = new Router(APPID, intentSynchronizer,
- hostService, null, interfaceService);
+ router = new Router(APPID, intentSynchronizer, null, interfaceService,
+ hostService);
}
/**
@@ -263,17 +274,16 @@
// Compose a intent, which is equal to intent5 but the id is different.
MultiPointToSinglePointIntent intent5New =
staticIntentBuilder(intent5, routeEntry5, "00:00:00:00:00:01");
- assertTrue(TestUtils.callMethod(intentSynchronizer,
- "compareMultiPointToSinglePointIntents",
- new Class<?>[] {MultiPointToSinglePointIntent.class,
- MultiPointToSinglePointIntent.class},
- intent5, intent5New).equals(true));
+ assertThat(IntentSynchronizer.IntentKey.equalIntents(
+ intent5, intent5New),
+ is(true));
assertFalse(intent5.equals(intent5New));
MultiPointToSinglePointIntent intent6 = intentBuilder(
routeEntry6.prefix(), "00:00:00:00:00:01", SW1_ETH1);
- // Set up the bgpRoutes and pushedRouteIntents fields in Router class
+ // Set up the bgpRoutes field in Router class and routeIntents fields
+ // in IntentSynchronizer class
InvertedRadixTree<RouteEntry> bgpRoutes =
new ConcurrentInvertedRadixTree<>(
new DefaultByteArrayNodeFactory());
@@ -292,15 +302,14 @@
TestUtils.setField(router, "bgpRoutes", bgpRoutes);
ConcurrentHashMap<Ip4Prefix, MultiPointToSinglePointIntent>
- pushedRouteIntents = new ConcurrentHashMap<>();
- pushedRouteIntents.put(routeEntry1.prefix(), intent1);
- pushedRouteIntents.put(routeEntry3.prefix(), intent3);
- pushedRouteIntents.put(routeEntry4Update.prefix(), intent4Update);
- pushedRouteIntents.put(routeEntry5.prefix(), intent5New);
- pushedRouteIntents.put(routeEntry6.prefix(), intent6);
- pushedRouteIntents.put(routeEntry7.prefix(), intent7);
- TestUtils.setField(intentSynchronizer, "pushedRouteIntents",
- pushedRouteIntents);
+ routeIntents = new ConcurrentHashMap<>();
+ routeIntents.put(routeEntry1.prefix(), intent1);
+ routeIntents.put(routeEntry3.prefix(), intent3);
+ routeIntents.put(routeEntry4Update.prefix(), intent4Update);
+ routeIntents.put(routeEntry5.prefix(), intent5New);
+ routeIntents.put(routeEntry6.prefix(), intent6);
+ routeIntents.put(routeEntry7.prefix(), intent7);
+ TestUtils.setField(intentSynchronizer, "routeIntents", routeIntents);
// Set up expectation
reset(intentService);
@@ -322,18 +331,26 @@
.andReturn(IntentState.WITHDRAWING).anyTimes();
expect(intentService.getIntents()).andReturn(intents).anyTimes();
- intentService.withdraw(intent2);
- intentService.submit(intent3);
- intentService.withdraw(intent4);
- intentService.submit(intent4Update);
- intentService.submit(intent6);
- intentService.submit(intent7);
+ IntentOperations.Builder builder = IntentOperations.builder();
+ builder.addWithdrawOperation(intent2.id());
+ builder.addWithdrawOperation(intent4.id());
+ intentService.execute(eqExceptId(builder.build()));
+
+ builder = IntentOperations.builder();
+ builder.addSubmitOperation(intent3);
+ builder.addSubmitOperation(intent4Update);
+ builder.addSubmitOperation(intent6);
+ builder.addSubmitOperation(intent7);
+ intentService.execute(eqExceptId(builder.build()));
replay(intentService);
// Start the test
intentSynchronizer.leaderChanged(true);
- TestUtils.callMethod(intentSynchronizer, "syncIntents",
+ /*
+ TestUtils.callMethod(intentSynchronizer, "synchronizeIntents",
new Class<?>[] {});
+ */
+ intentSynchronizer.synchronizeIntents();
// Verify
assertEquals(router.getRoutes().size(), 6);
@@ -343,12 +360,12 @@
assertTrue(router.getRoutes().contains(routeEntry5));
assertTrue(router.getRoutes().contains(routeEntry6));
- assertEquals(intentSynchronizer.getPushedRouteIntents().size(), 6);
- assertTrue(intentSynchronizer.getPushedRouteIntents().contains(intent1));
- assertTrue(intentSynchronizer.getPushedRouteIntents().contains(intent3));
- assertTrue(intentSynchronizer.getPushedRouteIntents().contains(intent4Update));
- assertTrue(intentSynchronizer.getPushedRouteIntents().contains(intent5));
- assertTrue(intentSynchronizer.getPushedRouteIntents().contains(intent6));
+ assertEquals(intentSynchronizer.getRouteIntents().size(), 6);
+ assertTrue(intentSynchronizer.getRouteIntents().contains(intent1));
+ assertTrue(intentSynchronizer.getRouteIntents().contains(intent3));
+ assertTrue(intentSynchronizer.getRouteIntents().contains(intent4Update));
+ assertTrue(intentSynchronizer.getRouteIntents().contains(intent5));
+ assertTrue(intentSynchronizer.getRouteIntents().contains(intent6));
verify(intentService);
}
@@ -410,4 +427,129 @@
"ingressPoints", intent.ingressPoints());
return intentNew;
}
+
+ /*
+ * EasyMock matcher that matches {@link IntenOperations} but
+ * ignores the {@link IntentId} when matching.
+ * <p/>
+ * The normal intent equals method tests that the intent IDs are equal,
+ * however in these tests we can't know what the intent IDs will be in
+ * advance, so we can't set up expected intents with the correct IDs. Thus,
+ * the solution is to use an EasyMock matcher that verifies that all the
+ * value properties of the provided intent match the expected values, but
+ * ignores the intent ID when testing equality.
+ */
+ private static final class IdAgnosticIntentOperationsMatcher implements
+ IArgumentMatcher {
+
+ private final IntentOperations intentOperations;
+ private String providedString;
+
+ /**
+ * Constructor taking the expected intent operations to match against.
+ *
+ * @param intentOperations the expected intent operations
+ */
+ public IdAgnosticIntentOperationsMatcher(
+ IntentOperations intentOperations) {
+ this.intentOperations = intentOperations;
+ }
+
+ @Override
+ public void appendTo(StringBuffer strBuffer) {
+ strBuffer.append("IntentOperationsMatcher unable to match: "
+ + providedString);
+ }
+
+ @Override
+ public boolean matches(Object object) {
+ if (!(object instanceof IntentOperations)) {
+ return false;
+ }
+
+ IntentOperations providedIntentOperations =
+ (IntentOperations) object;
+ providedString = providedIntentOperations.toString();
+
+ List<IntentKey> thisSubmitIntents = new LinkedList<>();
+ List<IntentId> thisWithdrawIntentIds = new LinkedList<>();
+ List<IntentKey> thisReplaceIntents = new LinkedList<>();
+ List<IntentKey> thisUpdateIntents = new LinkedList<>();
+ List<IntentKey> providedSubmitIntents = new LinkedList<>();
+ List<IntentId> providedWithdrawIntentIds = new LinkedList<>();
+ List<IntentKey> providedReplaceIntents = new LinkedList<>();
+ List<IntentKey> providedUpdateIntents = new LinkedList<>();
+
+ extractIntents(intentOperations, thisSubmitIntents,
+ thisWithdrawIntentIds, thisReplaceIntents,
+ thisUpdateIntents);
+ extractIntents(providedIntentOperations, providedSubmitIntents,
+ providedWithdrawIntentIds, providedReplaceIntents,
+ providedUpdateIntents);
+
+ return CollectionUtils.isEqualCollection(thisSubmitIntents,
+ providedSubmitIntents) &&
+ CollectionUtils.isEqualCollection(thisWithdrawIntentIds,
+ providedWithdrawIntentIds) &&
+ CollectionUtils.isEqualCollection(thisUpdateIntents,
+ providedUpdateIntents) &&
+ CollectionUtils.isEqualCollection(thisReplaceIntents,
+ providedReplaceIntents);
+ }
+
+ /**
+ * Extracts the intents per operation type. Each intent is encapsulated
+ * in IntentKey so it can be compared by excluding the Intent ID.
+ *
+ * @param intentOperations the container with the intent operations
+ * to extract the intents from
+ * @param submitIntents the SUBMIT intents
+ * @param withdrawIntentIds the WITHDRAW intents IDs
+ * @param replaceIntents the REPLACE intents
+ * @param updateIntents the UPDATE intens
+ */
+ private void extractIntents(IntentOperations intentOperations,
+ List<IntentKey> submitIntents,
+ List<IntentId> withdrawIntentIds,
+ List<IntentKey> replaceIntents,
+ List<IntentKey> updateIntents) {
+ for (IntentOperation oper : intentOperations.operations()) {
+ IntentId intentId;
+ IntentKey intentKey;
+ switch (oper.type()) {
+ case SUBMIT:
+ intentKey = new IntentKey(oper.intent());
+ submitIntents.add(intentKey);
+ break;
+ case WITHDRAW:
+ intentId = oper.intentId();
+ withdrawIntentIds.add(intentId);
+ break;
+ case REPLACE:
+ intentKey = new IntentKey(oper.intent());
+ replaceIntents.add(intentKey);
+ break;
+ case UPDATE:
+ intentKey = new IntentKey(oper.intent());
+ updateIntents.add(intentKey);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Matcher method to set an expected intent to match against (ignoring the
+ * the intent ID).
+ *
+ * @param intent the expected intent
+ * @return something of type IntentOperations
+ */
+ private static IntentOperations eqExceptId(
+ IntentOperations intentOperations) {
+ reportMatcher(new IdAgnosticIntentOperationsMatcher(intentOperations));
+ return intentOperations;
+ }
}