[ONOS-6248] VPLS refactoring

Change-Id: I8ffb2199ca108ad8dfe271681068636fc4af2a40
diff --git a/apps/vpls/src/test/java/org/onosproject/vpls/VplsOperationManagerTest.java b/apps/vpls/src/test/java/org/onosproject/vpls/VplsOperationManagerTest.java
new file mode 100644
index 0000000..36ffda9
--- /dev/null
+++ b/apps/vpls/src/test/java/org/onosproject/vpls/VplsOperationManagerTest.java
@@ -0,0 +1,543 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.vpls;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.cluster.ClusterServiceAdapter;
+import org.onosproject.cluster.Leader;
+import org.onosproject.cluster.Leadership;
+import org.onosproject.cluster.LeadershipEvent;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.EncapsulationType;
+import org.onosproject.net.Host;
+import org.onosproject.net.host.HostServiceAdapter;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentData;
+import org.onosproject.net.intent.IntentEvent;
+import org.onosproject.net.intent.IntentState;
+import org.onosproject.store.service.WallClockTimestamp;
+import org.onosproject.vpls.api.VplsData;
+import org.onosproject.vpls.api.VplsOperation;
+
+import java.util.ArrayDeque;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.Set;
+
+import static org.junit.Assert.*;
+import static org.onlab.junit.TestTools.assertAfter;
+import static org.onlab.junit.TestTools.delay;
+
+/**
+ * Tests for {@link VplsOperationManager}.
+ */
+public class VplsOperationManagerTest extends VplsTest {
+
+    VplsOperationManager vplsOperationManager;
+    private static final int OPERATION_DELAY = 1000;
+    private static final int OPERATION_DURATION = 1500;
+
+    @Before
+    public void setup() {
+        if (idGenerator == null) {
+            idGenerator = new TestIdGenerator();
+        }
+        Intent.unbindIdGenerator(idGenerator);
+        Intent.bindIdGenerator(idGenerator);
+        vplsOperationManager = new VplsOperationManager();
+        vplsOperationManager.coreService = new TestCoreService();
+        vplsOperationManager.intentService = new TestIntentService();
+        vplsOperationManager.leadershipService = new TestLeadershipService();
+        vplsOperationManager.clusterService = new ClusterServiceAdapter();
+        vplsOperationManager.hostService = new TestHostService();
+        vplsOperationManager.vplsStore = new TestVplsStore();
+        vplsOperationManager.isLeader = true;
+        vplsOperationManager.activate();
+    }
+
+    @After
+    public void tearDown() {
+        vplsOperationManager.deactivate();
+    }
+
+    /**
+     * Sends leadership event to the manager and checks if the manager is
+     * leader or not.
+     */
+    @Test
+    public void testLeadershipEvent() {
+        vplsOperationManager.isLeader = false;
+        vplsOperationManager.localNodeId = NODE_ID_1;
+
+        // leader changed to self
+        Leader leader = new Leader(NODE_ID_1, 0, 0);
+        Leadership leadership = new Leadership(APP_NAME, leader, ImmutableList.of());
+        LeadershipEvent event = new LeadershipEvent(LeadershipEvent.Type.LEADER_CHANGED, leadership);
+        ((TestLeadershipService) vplsOperationManager.leadershipService).sendEvent(event);
+        assertTrue(vplsOperationManager.isLeader);
+
+        // leader changed to other
+        leader = new Leader(NODE_ID_2, 0, 0);
+        leadership = new Leadership(APP_NAME, leader, ImmutableList.of());
+        event = new LeadershipEvent(LeadershipEvent.Type.LEADER_CHANGED, leadership);
+        ((TestLeadershipService) vplsOperationManager.leadershipService).sendEvent(event);
+        assertFalse(vplsOperationManager.isLeader);
+    }
+
+    /**
+     * Submits an ADD operation to the operation manager; check if the VPLS
+     * store changed after a period.
+     */
+    @Test
+    public void testSubmitAddOperation() {
+        VplsData vplsData = VplsData.of(VPLS1);
+        vplsData.addInterfaces(ImmutableSet.of(V100H1, V100H2));
+
+        VplsOperation vplsOperation = VplsOperation.of(vplsData,
+                                                       VplsOperation.Operation.ADD);
+
+        vplsOperationManager.submit(vplsOperation);
+        assertAfter(OPERATION_DELAY, OPERATION_DURATION, () -> {
+            Collection<VplsData> vplss = vplsOperationManager.vplsStore.getAllVpls();
+            assertEquals(1, vplss.size());
+            VplsData result = vplss.iterator().next();
+
+            assertEquals(vplsData, result);
+            assertEquals(VplsData.VplsState.ADDED, result.state());
+
+            Set<Intent> intentsInstalled =
+                    Sets.newHashSet(vplsOperationManager.intentService.getIntents());
+            assertEquals(4, intentsInstalled.size());
+        });
+    }
+
+    /**
+     * Submits an ADD operation to the operation manager; check the VPLS state
+     * from store if Intent install failed.
+     */
+    @Test
+    public void testSubmitAddOperationFail() {
+        vplsOperationManager.intentService = new AlwaysFailureIntentService();
+        VplsData vplsData = VplsData.of(VPLS1);
+        vplsData.addInterfaces(ImmutableSet.of(V100H1, V100H2));
+
+        VplsOperation vplsOperation = VplsOperation.of(vplsData,
+                                                       VplsOperation.Operation.ADD);
+        vplsOperationManager.submit(vplsOperation);
+        assertAfter(OPERATION_DELAY, OPERATION_DURATION, () -> {
+            Collection<VplsData> vplss = vplsOperationManager.vplsStore.getAllVpls();
+            assertEquals(1, vplss.size());
+            VplsData result = vplss.iterator().next();
+
+            assertEquals(vplsData, result);
+            assertEquals(VplsData.VplsState.FAILED, result.state());
+        });
+    }
+
+    /**
+     * Submits an REMOVE operation to the operation manager; check if the VPLS
+     * store changed after a period.
+     */
+    @Test
+    public void testSubmitRemoveOperation() {
+        VplsData vplsData = VplsData.of(VPLS1);
+        vplsData.addInterfaces(ImmutableSet.of(V100H1, V100H2));
+        vplsData.state(VplsData.VplsState.REMOVING);
+
+        VplsOperation vplsOperation = VplsOperation.of(vplsData,
+                                                       VplsOperation.Operation.REMOVE);
+
+        vplsOperationManager.submit(vplsOperation);
+
+        assertAfter(OPERATION_DELAY, OPERATION_DURATION, () -> {
+            Collection<VplsData> vplss = vplsOperationManager.vplsStore.getAllVpls();
+            assertEquals(0, vplss.size());
+        });
+    }
+
+    /**
+     * Submits an UPDATE operation with VPLS interface update to the operation manager; check if the VPLS
+     * store changed after a period.
+     */
+    @Test
+    public void testSubmitUpdateOperation() {
+        VplsData vplsData = VplsData.of(VPLS1);
+        vplsData.addInterfaces(ImmutableSet.of(V100H1));
+        vplsData.state(VplsData.VplsState.ADDED);
+        vplsOperationManager.vplsStore.addVpls(vplsData);
+
+        vplsData = VplsData.of(VPLS1, EncapsulationType.VLAN);
+        vplsData.addInterfaces(ImmutableSet.of(V100H1, V100H2));
+        vplsData.state(VplsData.VplsState.UPDATING);
+
+        VplsOperation vplsOperation = VplsOperation.of(vplsData,
+                                                       VplsOperation.Operation.UPDATE);
+
+        vplsOperationManager.submit(vplsOperation);
+
+        assertAfter(OPERATION_DELAY, OPERATION_DURATION, () -> {
+            Collection<VplsData> vplss = vplsOperationManager.vplsStore.getAllVpls();
+            VplsData result = vplss.iterator().next();
+            VplsData expected = VplsData.of(VPLS1, EncapsulationType.VLAN);
+            expected.addInterfaces(ImmutableSet.of(V100H1, V100H2));
+            expected.state(VplsData.VplsState.ADDED);
+
+            assertEquals(1, vplss.size());
+            assertEquals(expected, result);
+
+            Set<Intent> intentsInstalled =
+                    Sets.newHashSet(vplsOperationManager.intentService.getIntents());
+            assertEquals(4, intentsInstalled.size());
+        });
+    }
+
+    /**
+     * Submits an UPDATE operation with VPLS host update to the operation manager; check if the VPLS
+     * store changed after a period.
+     */
+    @Test
+    public void testSubmitUpdateHostOperation() {
+        vplsOperationManager.hostService = new EmptyHostService();
+        VplsData vplsData = VplsData.of(VPLS1);
+        vplsData.addInterfaces(ImmutableSet.of(V100H1, V100H2));
+
+        VplsOperation vplsOperation = VplsOperation.of(vplsData,
+                                                       VplsOperation.Operation.ADD);
+        vplsOperationManager.submit(vplsOperation);
+        delay(1000);
+        vplsOperationManager.hostService = new TestHostService();
+
+        vplsData = VplsData.of(VPLS1);
+        vplsData.addInterfaces(ImmutableSet.of(V100H1, V100H2));
+        vplsData.state(VplsData.VplsState.UPDATING);
+
+        vplsOperation = VplsOperation.of(vplsData,
+                                                       VplsOperation.Operation.UPDATE);
+
+        vplsOperationManager.submit(vplsOperation);
+
+        assertAfter(OPERATION_DELAY, OPERATION_DURATION, () -> {
+            Collection<VplsData> vplss = vplsOperationManager.vplsStore.getAllVpls();
+            VplsData result = vplss.iterator().next();
+            VplsData expected = VplsData.of(VPLS1);
+            expected.addInterfaces(ImmutableSet.of(V100H1, V100H2));
+            expected.state(VplsData.VplsState.ADDED);
+            assertEquals(1, vplss.size());
+            assertEquals(expected, result);
+
+            assertEquals(4, vplsOperationManager.intentService.getIntentCount());
+        });
+    }
+
+    /**
+     * Submits same operation twice to the manager; the manager should ignore
+     * duplicated operation.
+     */
+    @Test
+    public void testDuplicateOperationInQueue() {
+        VplsData vplsData = VplsData.of(VPLS1);
+        vplsData.addInterfaces(ImmutableSet.of(V100H1, V100H2));
+
+        VplsOperation vplsOperation = VplsOperation.of(vplsData,
+                                                       VplsOperation.Operation.ADD);
+
+        vplsOperationManager.submit(vplsOperation);
+        vplsOperationManager.submit(vplsOperation);
+        Deque<VplsOperation> opQueue = vplsOperationManager.pendingVplsOperations.get(VPLS1);
+        assertEquals(1, opQueue.size());
+
+        // Clear operation queue before scheduler process it
+        opQueue.clear();
+    }
+
+    /**
+     * Submits REMOVE operation after submits ADD operation; there should be no
+     * pending or running operation in the manager.
+     */
+    @Test
+    public void testDoNothingOperation() {
+        VplsData vplsData = VplsData.of(VPLS1);
+        vplsData.addInterfaces(ImmutableSet.of(V100H1, V100H2));
+
+        VplsOperation vplsOperation = VplsOperation.of(vplsData,
+                                                       VplsOperation.Operation.ADD);
+        vplsOperationManager.submit(vplsOperation);
+        vplsOperation = VplsOperation.of(vplsData,
+                                         VplsOperation.Operation.REMOVE);
+        vplsOperationManager.submit(vplsOperation);
+        assertAfter(OPERATION_DELAY, OPERATION_DURATION, () -> {
+            assertEquals(0, vplsOperationManager.pendingVplsOperations.size());
+
+            // Should not have any running operation
+            assertEquals(0, vplsOperationManager.runningOperations.size());
+        });
+    }
+
+    /**
+     * Optimize operations which don't need to be optimized.
+     */
+    @Test
+    public void testOptimizeOperationsNoOptimize() {
+        // empty queue
+        Deque<VplsOperation> operations = new ArrayDeque<>();
+        VplsOperation vplsOperation =
+                VplsOperationManager.getOptimizedVplsOperation(operations);
+        assertNull(vplsOperation);
+
+        // one operation
+        VplsData vplsData = VplsData.of(VPLS1);
+        vplsOperation = VplsOperation.of(vplsData, VplsOperation.Operation.ADD);
+        operations.add(vplsOperation);
+        VplsOperation result =
+                VplsOperationManager.getOptimizedVplsOperation(operations);
+        assertEquals(vplsOperation, result);
+
+    }
+
+    /**
+     * Optimize operations with first is ADD operation and last is also ADD
+     * operation.
+     */
+    @Test
+    public void testOptimizeOperationsAToA() {
+        Deque<VplsOperation> operations = new ArrayDeque<>();
+        VplsData vplsData = VplsData.of(VPLS1);
+        vplsData.addInterfaces(ImmutableSet.of(V100H1));
+        VplsOperation vplsOperation = VplsOperation.of(vplsData,
+                                                       VplsOperation.Operation.ADD);
+        operations.add(vplsOperation);
+        vplsData = VplsData.of(VPLS1, EncapsulationType.VLAN);
+        vplsData.addInterfaces(ImmutableSet.of(V100H1, V100H2));
+        vplsOperation = VplsOperation.of(vplsData,
+                                         VplsOperation.Operation.ADD);
+        operations.add(vplsOperation);
+        vplsOperation = VplsOperationManager.getOptimizedVplsOperation(operations);
+        assertEquals(VplsOperation.of(vplsData, VplsOperation.Operation.ADD), vplsOperation);
+    }
+
+    /**
+     * Optimize operations with first is ADD operation and last is REMOVE
+     * operation.
+     */
+    @Test
+    public void testOptimizeOperationsAToR() {
+        Deque<VplsOperation> operations = new ArrayDeque<>();
+        VplsData vplsData = VplsData.of(VPLS1);
+        vplsData.addInterfaces(ImmutableSet.of(V100H1));
+        VplsOperation vplsOperation = VplsOperation.of(vplsData,
+                                                       VplsOperation.Operation.ADD);
+        operations.add(vplsOperation);
+        vplsOperation = VplsOperation.of(vplsData,
+                                         VplsOperation.Operation.REMOVE);
+        operations.add(vplsOperation);
+        vplsOperation = VplsOperationManager.getOptimizedVplsOperation(operations);
+        assertNull(vplsOperation);
+    }
+
+    /**
+     * Optimize operations with first is ADD operation and last is UPDATE
+     * operation.
+     */
+    @Test
+    public void testOptimizeOperationsAToU() {
+        Deque<VplsOperation> operations = new ArrayDeque<>();
+        VplsData vplsData = VplsData.of(VPLS1);
+        vplsData.addInterfaces(ImmutableSet.of(V100H1));
+        VplsOperation vplsOperation = VplsOperation.of(vplsData,
+                                                       VplsOperation.Operation.ADD);
+        operations.add(vplsOperation);
+        vplsData = VplsData.of(VPLS1, EncapsulationType.VLAN);
+        vplsData.addInterfaces(ImmutableSet.of(V100H1, V100H2));
+        vplsOperation = VplsOperation.of(vplsData,
+                                         VplsOperation.Operation.UPDATE);
+        operations.add(vplsOperation);
+        vplsOperation = VplsOperationManager.getOptimizedVplsOperation(operations);
+        assertEquals(VplsOperation.of(vplsData, VplsOperation.Operation.ADD), vplsOperation);
+    }
+
+    /**
+     * Optimize operations with first is REMOVE operation and last is ADD
+     * operation.
+     */
+    @Test
+    public void testOptimizeOperationsRToA() {
+        Deque<VplsOperation> operations = new ArrayDeque<>();
+        VplsData vplsData = VplsData.of(VPLS1);
+        vplsData.addInterfaces(ImmutableSet.of(V100H1));
+        VplsOperation vplsOperation = VplsOperation.of(vplsData,
+                                                       VplsOperation.Operation.REMOVE);
+        operations.add(vplsOperation);
+        vplsData = VplsData.of(VPLS1, EncapsulationType.VLAN);
+        vplsData.addInterfaces(ImmutableSet.of(V100H1, V100H2));
+        vplsOperation = VplsOperation.of(vplsData,
+                                         VplsOperation.Operation.ADD);
+        operations.add(vplsOperation);
+        vplsOperation = VplsOperationManager.getOptimizedVplsOperation(operations);
+        assertEquals(VplsOperation.of(vplsData, VplsOperation.Operation.UPDATE), vplsOperation);
+    }
+
+    /**
+     * Optimize operations with first is REMOVE operation and last is also
+     * REMOVE operation.
+     */
+    @Test
+    public void testOptimizeOperationsRToR() {
+        Deque<VplsOperation> operations = new ArrayDeque<>();
+        VplsData vplsData = VplsData.of(VPLS1);
+        vplsData.addInterfaces(ImmutableSet.of(V100H1));
+        VplsOperation vplsOperation = VplsOperation.of(vplsData,
+                                                       VplsOperation.Operation.REMOVE);
+        operations.add(vplsOperation);
+        vplsData = VplsData.of(VPLS1, EncapsulationType.VLAN);
+        vplsData.addInterfaces(ImmutableSet.of(V100H1, V100H2));
+        vplsOperation = VplsOperation.of(vplsData,
+                                         VplsOperation.Operation.REMOVE);
+        operations.add(vplsOperation);
+        vplsOperation = VplsOperationManager.getOptimizedVplsOperation(operations);
+        vplsData = VplsData.of(VPLS1);
+        vplsData.addInterfaces(ImmutableSet.of(V100H1));
+        assertEquals(VplsOperation.of(vplsData, VplsOperation.Operation.REMOVE), vplsOperation);
+    }
+
+    /**
+     * Optimize operations with first is REMOVE operation and last is UPDATE
+     * operation.
+     */
+    @Test
+    public void testOptimizeOperationsRToU() {
+        Deque<VplsOperation> operations = new ArrayDeque<>();
+        VplsData vplsData = VplsData.of(VPLS1);
+        vplsData.addInterfaces(ImmutableSet.of(V100H1));
+        VplsOperation vplsOperation = VplsOperation.of(vplsData,
+                                                       VplsOperation.Operation.REMOVE);
+        operations.add(vplsOperation);
+        vplsData = VplsData.of(VPLS1, EncapsulationType.VLAN);
+        vplsData.addInterfaces(ImmutableSet.of(V100H1, V100H2));
+        vplsOperation = VplsOperation.of(vplsData,
+                                         VplsOperation.Operation.UPDATE);
+        operations.add(vplsOperation);
+        vplsOperation = VplsOperationManager.getOptimizedVplsOperation(operations);
+        assertEquals(VplsOperation.of(vplsData, VplsOperation.Operation.UPDATE), vplsOperation);
+    }
+
+    /**
+     * Optimize operations with first is UPDATE operation and last is ADD
+     * operation.
+     */
+    @Test
+    public void testOptimizeOperationsUToA() {
+        Deque<VplsOperation> operations = new ArrayDeque<>();
+        VplsData vplsData = VplsData.of(VPLS1);
+        vplsData.addInterfaces(ImmutableSet.of(V100H1));
+        VplsOperation vplsOperation = VplsOperation.of(vplsData,
+                                                       VplsOperation.Operation.UPDATE);
+        operations.add(vplsOperation);
+        vplsData = VplsData.of(VPLS1, EncapsulationType.VLAN);
+        vplsData.addInterfaces(ImmutableSet.of(V100H1, V100H2));
+        vplsOperation = VplsOperation.of(vplsData,
+                                         VplsOperation.Operation.ADD);
+        operations.add(vplsOperation);
+        vplsOperation = VplsOperationManager.getOptimizedVplsOperation(operations);
+        assertEquals(VplsOperation.of(vplsData, VplsOperation.Operation.UPDATE), vplsOperation);
+    }
+
+    /**
+     * Optimize operations with first is UPDATE operation and last is REMOVE
+     * operation.
+     */
+    @Test
+    public void testOptimizeOperationsUToR() {
+        Deque<VplsOperation> operations = new ArrayDeque<>();
+        VplsData vplsData = VplsData.of(VPLS1);
+        vplsData.addInterfaces(ImmutableSet.of(V100H1));
+        VplsOperation vplsOperation = VplsOperation.of(vplsData,
+                                                       VplsOperation.Operation.UPDATE);
+        operations.add(vplsOperation);
+        vplsData = VplsData.of(VPLS1, EncapsulationType.VLAN);
+        vplsData.addInterfaces(ImmutableSet.of(V100H1, V100H2));
+        vplsOperation = VplsOperation.of(vplsData,
+                                         VplsOperation.Operation.REMOVE);
+        operations.add(vplsOperation);
+        vplsOperation = VplsOperationManager.getOptimizedVplsOperation(operations);
+        assertEquals(VplsOperation.of(vplsData, VplsOperation.Operation.REMOVE), vplsOperation);
+    }
+
+    /**
+     * Optimize operations with first is UPDATE operation and last is also
+     * UPDATE operation.
+     */
+    @Test
+    public void testOptimizeOperationsUToU() {
+        Deque<VplsOperation> operations = new ArrayDeque<>();
+        VplsData vplsData = VplsData.of(VPLS1);
+        vplsData.addInterfaces(ImmutableSet.of(V100H1));
+        VplsOperation vplsOperation = VplsOperation.of(vplsData,
+                                                       VplsOperation.Operation.UPDATE);
+        operations.add(vplsOperation);
+        vplsData = VplsData.of(VPLS1, EncapsulationType.VLAN);
+        vplsData.addInterfaces(ImmutableSet.of(V100H1, V100H2));
+        vplsOperation = VplsOperation.of(vplsData,
+                                         VplsOperation.Operation.UPDATE);
+        operations.add(vplsOperation);
+        vplsOperation = VplsOperationManager.getOptimizedVplsOperation(operations);
+        assertEquals(VplsOperation.of(vplsData, VplsOperation.Operation.UPDATE), vplsOperation);
+    }
+
+    /**
+     * Test Intent service which always fail when submit or withdraw Intents.
+     */
+    class AlwaysFailureIntentService extends TestIntentService {
+        @Override
+        public void submit(Intent intent) {
+            intents.add(new IntentData(intent, IntentState.FAILED, new WallClockTimestamp()));
+            if (listener != null) {
+                IntentEvent event = IntentEvent.getEvent(IntentState.FAILED, intent).get();
+                listener.event(event);
+            }
+        }
+
+        @Override
+        public void withdraw(Intent intent) {
+            intents.forEach(intentData -> {
+                if (intentData.intent().key().equals(intent.key())) {
+                    intentData.setState(IntentState.FAILED);
+
+                    if (listener != null) {
+                        IntentEvent event = IntentEvent.getEvent(IntentState.FAILED, intent).get();
+                        listener.event(event);
+                    }
+                }
+            });
+        }
+    }
+
+    /**
+     * Test host service without any hosts.
+     */
+    class EmptyHostService extends HostServiceAdapter {
+        @Override
+        public Set<Host> getConnectedHosts(ConnectPoint connectPoint) {
+            return ImmutableSet.of();
+        }
+    }
+
+}