Updating Intent Manager to deal with failures.

Added ids to Flow batch futures.
Adding some basic unit tests for IntentManger
Adding failedIds to the completedOperation in FlowRuleManager

Change-Id: I7645cead193299f70d319d254cd1e82d96909e7b
diff --git a/core/net/src/test/java/org/onlab/onos/net/intent/impl/IntentManagerTest.java b/core/net/src/test/java/org/onlab/onos/net/intent/impl/IntentManagerTest.java
new file mode 100644
index 0000000..b3ebe70
--- /dev/null
+++ b/core/net/src/test/java/org/onlab/onos/net/intent/impl/IntentManagerTest.java
@@ -0,0 +1,348 @@
+package org.onlab.onos.net.intent.impl;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+import org.hamcrest.Description;
+import org.hamcrest.Matchers;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.onos.TestApplicationId;
+import org.onlab.onos.core.ApplicationId;
+import org.onlab.onos.event.impl.TestEventDispatcher;
+import org.onlab.onos.net.NetworkResource;
+import org.onlab.onos.net.flow.FlowRule;
+import org.onlab.onos.net.flow.FlowRuleBatchEntry;
+import org.onlab.onos.net.flow.FlowRuleBatchEntry.FlowRuleOperation;
+import org.onlab.onos.net.flow.FlowRuleBatchOperation;
+import org.onlab.onos.net.intent.Intent;
+import org.onlab.onos.net.intent.IntentCompiler;
+import org.onlab.onos.net.intent.IntentEvent;
+import org.onlab.onos.net.intent.IntentEvent.Type;
+import org.onlab.onos.net.intent.IntentExtensionService;
+import org.onlab.onos.net.intent.IntentId;
+import org.onlab.onos.net.intent.IntentInstaller;
+import org.onlab.onos.net.intent.IntentListener;
+import org.onlab.onos.net.intent.IntentService;
+import org.onlab.onos.net.intent.IntentState;
+import org.onlab.onos.net.intent.IntentTestsMocks;
+import org.onlab.onos.net.resource.LinkResourceAllocations;
+import org.onlab.onos.store.trivial.impl.SimpleIntentBatchQueue;
+import org.onlab.onos.store.trivial.impl.SimpleIntentStore;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasItem;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.onlab.onos.net.intent.IntentState.*;
+import static org.onlab.util.Tools.delay;
+
+/**
+ * Test intent manager and transitions.
+ *
+ * TODO implement the following tests:
+ *  - {submit, withdraw, update, replace} intent
+ *  - {submit, update, recomiling} intent with failed compilation
+ *  - failed reservation
+ *  - push timeout recovery
+ *  - failed items recovery
+ *
+ *  in general, verify intents store, flow store, and work queue
+ */
+public class IntentManagerTest {
+
+    private static final ApplicationId APPID = new TestApplicationId("manager-test");
+
+    private IntentManager manager;
+    private MockFlowRuleService flowRuleService;
+
+    protected IntentService service;
+    protected IntentExtensionService extensionService;
+    protected TestListener listener = new TestListener();
+    protected TestIntentCompiler compiler = new TestIntentCompiler();
+    protected TestIntentInstaller installer = new TestIntentInstaller();
+
+    @Before
+    public void setUp() {
+        manager = new IntentManager();
+        flowRuleService = new MockFlowRuleService();
+        manager.store = new SimpleIntentStore();
+        manager.batchService = new SimpleIntentBatchQueue();
+        manager.eventDispatcher = new TestEventDispatcher();
+        manager.trackerService = new TestIntentTracker();
+        manager.flowRuleService = flowRuleService;
+        service = manager;
+        extensionService = manager;
+
+        manager.activate();
+        service.addListener(listener);
+        extensionService.registerCompiler(MockIntent.class, compiler);
+        extensionService.registerInstaller(MockInstallableIntent.class, installer);
+
+        assertTrue("store should be empty",
+                   Sets.newHashSet(service.getIntents()).isEmpty());
+        assertEquals(0L, flowRuleService.getFlowRuleCount());
+    }
+
+    @After
+    public void tearDown() {
+        // verify that all intents are parked and the batch operation is unblocked
+        Set<IntentState> parked = Sets.newHashSet(INSTALLED, WITHDRAWN, FAILED);
+        for (Intent i : service.getIntents()) {
+            IntentState state = service.getIntentState(i.id());
+            assertTrue("Intent " + i.id() + " is in invalid state " + state,
+                       parked.contains(state));
+        }
+        //the batch has not yet been removed when we receive the last event
+        // FIXME: this doesn't guarantee to avoid the race
+        for (int tries = 0; tries < 10; tries++) {
+            if (manager.batchService.getPendingOperations().isEmpty() &&
+                    manager.batchService.getCurrentOperations().isEmpty()) {
+                break;
+            }
+            delay(10);
+        }
+        assertTrue("There are still pending batch operations.",
+                   manager.batchService.getPendingOperations().isEmpty());
+        assertTrue("There are still outstanding batch operations.",
+                   manager.batchService.getCurrentOperations().isEmpty());
+
+        extensionService.unregisterCompiler(MockIntent.class);
+        extensionService.unregisterInstaller(MockInstallableIntent.class);
+        service.removeListener(listener);
+        manager.deactivate();
+        // TODO null the other refs?
+    }
+
+    @Test
+    public void submitIntent() {
+        flowRuleService.setFuture(true);
+
+        listener.setLatch(1, Type.SUBMITTED);
+        listener.setLatch(1, Type.INSTALLED);
+        Intent intent = new MockIntent(MockIntent.nextId());
+        service.submit(intent);
+        listener.await(Type.SUBMITTED);
+        listener.await(Type.INSTALLED);
+        assertEquals(1L, service.getIntentCount());
+        assertEquals(1L, flowRuleService.getFlowRuleCount());
+    }
+
+    @Test
+    public void withdrawIntent() {
+        flowRuleService.setFuture(true);
+
+        listener.setLatch(1, Type.INSTALLED);
+        Intent intent = new MockIntent(MockIntent.nextId());
+        service.submit(intent);
+        listener.await(Type.INSTALLED);
+        assertEquals(1L, service.getIntentCount());
+        assertEquals(1L, flowRuleService.getFlowRuleCount());
+
+        listener.setLatch(1, Type.WITHDRAWN);
+        service.withdraw(intent);
+        listener.await(Type.WITHDRAWN);
+        assertEquals(1L, service.getIntentCount());
+        assertEquals(0L, flowRuleService.getFlowRuleCount());
+    }
+
+    @Test
+    public void stressSubmitWithdraw() {
+        flowRuleService.setFuture(true);
+
+        int count = 500;
+
+        listener.setLatch(count, Type.INSTALLED);
+        listener.setLatch(count, Type.WITHDRAWN);
+
+        Intent intent = new MockIntent(MockIntent.nextId());
+        for (int i = 0; i < count; i++) {
+            service.submit(intent);
+            service.withdraw(intent);
+        }
+
+        listener.await(Type.INSTALLED);
+        listener.await(Type.WITHDRAWN);
+        assertEquals(1L, service.getIntentCount());
+        assertEquals(0L, flowRuleService.getFlowRuleCount());
+    }
+
+    @Test
+    public void replaceIntent() {
+        flowRuleService.setFuture(true);
+
+        MockIntent intent = new MockIntent(MockIntent.nextId());
+        listener.setLatch(1, Type.INSTALLED);
+        service.submit(intent);
+        listener.await(Type.INSTALLED);
+        assertEquals(1L, service.getIntentCount());
+        assertEquals(1L, manager.flowRuleService.getFlowRuleCount());
+
+        MockIntent intent2 = new MockIntent(MockIntent.nextId());
+        listener.setLatch(1, Type.WITHDRAWN);
+        listener.setLatch(1, Type.SUBMITTED);
+        listener.setLatch(1, Type.INSTALLED);
+        service.replace(intent.id(), intent2);
+        listener.await(Type.WITHDRAWN);
+        listener.await(Type.INSTALLED);
+        assertEquals(2L, service.getIntentCount());
+        assertEquals(1L, manager.flowRuleService.getFlowRuleCount());
+        assertEquals(intent2.number().intValue(),
+                     flowRuleService.flows.iterator().next().priority());
+    }
+
+    private static class TestListener implements IntentListener {
+        final Multimap<IntentEvent.Type, IntentEvent> events = HashMultimap.create();
+        Map<IntentEvent.Type, CountDownLatch> latchMap = Maps.newHashMap();
+
+        @Override
+        public void event(IntentEvent event) {
+            events.put(event.type(), event);
+            if (latchMap.containsKey(event.type())) {
+                latchMap.get(event.type()).countDown();
+            }
+        }
+
+        public int getCounts(IntentEvent.Type type) {
+            return events.get(type).size();
+        }
+
+        public void setLatch(int count, IntentEvent.Type type) {
+            latchMap.put(type, new CountDownLatch(count));
+        }
+
+        public void await(IntentEvent.Type type) {
+            try {
+                latchMap.get(type).await();
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    private static class TestIntentTracker implements ObjectiveTrackerService {
+        private TopologyChangeDelegate delegate;
+        @Override
+        public void setDelegate(TopologyChangeDelegate delegate) {
+            this.delegate = delegate;
+        }
+
+        @Override
+        public void unsetDelegate(TopologyChangeDelegate delegate) {
+            if (delegate.equals(this.delegate)) {
+                this.delegate = null;
+            }
+        }
+
+        @Override
+        public void addTrackedResources(IntentId intentId, Collection<NetworkResource> resources) {
+            //TODO
+        }
+
+        @Override
+        public void removeTrackedResources(IntentId intentId, Collection<NetworkResource> resources) {
+            //TODO
+        }
+    }
+
+    private static class MockIntent extends Intent {
+        private static AtomicLong counter = new AtomicLong(0);
+
+        private final Long number;
+        // Nothing new here
+        public MockIntent(Long number) {
+            super(id(MockIntent.class, number), APPID, null);
+            this.number = number;
+        }
+
+        public Long number() {
+            return number;
+        }
+
+        public static Long nextId() {
+            return counter.getAndIncrement();
+        }
+    }
+
+    private static class MockInstallableIntent extends MockIntent {
+        public MockInstallableIntent(Long number) {
+            super(number);
+        }
+
+        @Override
+        public boolean isInstallable() {
+            return true;
+        }
+    }
+
+    private static class TestIntentCompiler implements IntentCompiler<MockIntent> {
+        @Override
+        public List<Intent> compile(MockIntent intent, List<Intent> installable,
+                                    Set<LinkResourceAllocations> resources) {
+            return Lists.newArrayList(new MockInstallableIntent(intent.number()));
+        }
+    }
+
+    private static class TestIntentInstaller implements IntentInstaller<MockInstallableIntent> {
+        @Override
+        public List<FlowRuleBatchOperation> install(MockInstallableIntent intent) {
+            FlowRule fr = new IntentTestsMocks.MockFlowRule(intent.number().intValue());
+            List<FlowRuleBatchEntry> rules = Lists.newLinkedList();
+            rules.add(new FlowRuleBatchEntry(FlowRuleOperation.ADD, fr));
+            return Lists.newArrayList(new FlowRuleBatchOperation(rules));
+        }
+
+        @Override
+        public List<FlowRuleBatchOperation> uninstall(MockInstallableIntent intent) {
+            FlowRule fr = new IntentTestsMocks.MockFlowRule(intent.number().intValue());
+            List<FlowRuleBatchEntry> rules = Lists.newLinkedList();
+            rules.add(new FlowRuleBatchEntry(FlowRuleOperation.REMOVE, fr));
+            return Lists.newArrayList(new FlowRuleBatchOperation(rules));
+        }
+
+        @Override
+        public List<FlowRuleBatchOperation> replace(MockInstallableIntent oldIntent, MockInstallableIntent newIntent) {
+            FlowRule fr = new IntentTestsMocks.MockFlowRule(oldIntent.number().intValue());
+            FlowRule fr2 = new IntentTestsMocks.MockFlowRule(newIntent.number().intValue());
+            List<FlowRuleBatchEntry> rules = Lists.newLinkedList();
+            rules.add(new FlowRuleBatchEntry(FlowRuleOperation.REMOVE, fr));
+            rules.add(new FlowRuleBatchEntry(FlowRuleOperation.ADD, fr2));
+            return Lists.newArrayList(new FlowRuleBatchOperation(rules));
+        }
+    }
+
+    /**
+     * Hamcrest matcher to check that a conllection of Intents contains an
+     * Intent with the specified Intent Id.
+     */
+    public static class EntryForIntentMatcher extends TypeSafeMatcher<Collection<Intent>> {
+        private final String id;
+
+        public EntryForIntentMatcher(String idValue) {
+            id = idValue;
+        }
+
+        @Override
+        public boolean matchesSafely(Collection<Intent> intents) {
+            return hasItem(Matchers.<Intent>hasProperty("id", equalTo(id))).matches(intents);
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            description.appendText("an intent with id \" ").
+                    appendText(id).
+                    appendText("\"");
+        }
+    }
+}