[SDFAB-500][SDFAB-499] Implement user defined index mode for the meter service

- Introduce a boolean to control the meter service modes
- User defined mode does not provide any coordination to the apps
- Only one mode can be active at time
- In addition some sanity checks are peformed by the meter service
- Update existing unit tests and add new ones to test the new behaviors
- Initial clean up of the meters subsystems

Change-Id: I61500b794f27e94abd11637c84bce0dbb2e073f3
diff --git a/core/store/dist/src/test/java/org/onosproject/store/meter/impl/DistributedMeterStoreTest.java b/core/store/dist/src/test/java/org/onosproject/store/meter/impl/DistributedMeterStoreTest.java
index 0527755..37ff7b4 100644
--- a/core/store/dist/src/test/java/org/onosproject/store/meter/impl/DistributedMeterStoreTest.java
+++ b/core/store/dist/src/test/java/org/onosproject/store/meter/impl/DistributedMeterStoreTest.java
@@ -21,10 +21,8 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.onlab.junit.TestUtils;
-import org.onlab.packet.IpAddress;
 import org.onlab.util.KryoNamespace;
 import org.onosproject.TestApplicationId;
-import org.onosproject.cluster.NodeId;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.behaviour.MeterQuery;
 import org.onosproject.net.driver.Behaviour;
@@ -37,11 +35,16 @@
 import org.onosproject.net.meter.DefaultMeter;
 import org.onosproject.net.meter.DefaultMeterFeatures;
 import org.onosproject.net.meter.Meter;
+import org.onosproject.net.meter.MeterCellId;
 import org.onosproject.net.meter.MeterFeatures;
 import org.onosproject.net.meter.MeterFeaturesKey;
 import org.onosproject.net.meter.MeterId;
 import org.onosproject.net.meter.MeterKey;
+import org.onosproject.net.meter.MeterScope;
 import org.onosproject.net.meter.MeterState;
+import org.onosproject.net.meter.MeterTableKey;
+import org.onosproject.net.pi.model.PiMeterId;
+import org.onosproject.net.pi.runtime.PiMeterCellId;
 import org.onosproject.store.service.Serializer;
 import org.onosproject.store.service.TestStorageService;
 
@@ -61,13 +64,6 @@
  * Meter store tests.
  */
 public class DistributedMeterStoreTest {
-
-    // Test node id
-    private static final NodeId NID_LOCAL = new NodeId("local");
-
-    // Test ip address
-    private static final IpAddress LOCALHOST = IpAddress.valueOf("127.0.0.1");
-
     // Store under testing
     private DistributedMeterStore meterStore;
 
@@ -82,6 +78,10 @@
     private MeterId mid2 = MeterId.meterId(2);
     private MeterId mid3 = MeterId.meterId(3);
     private MeterId mid10 = MeterId.meterId(10);
+    private MeterCellId cid4 = PiMeterCellId.ofIndirect(
+            PiMeterId.of("foo"), 4);
+    private MeterCellId invalidCid = PiMeterCellId.ofIndirect(
+            PiMeterId.of("foo"), 11);
 
     // Bands used during the tests
     private Band b1 = DefaultBand.builder()
@@ -114,6 +114,14 @@
             .withBands(Collections.singletonList(b1))
             .build();
 
+    private Meter m4 = DefaultMeter.builder()
+            .forDevice(did3)
+            .fromApp(APP_ID)
+            .withCellId(cid4)
+            .withUnit(Meter.Unit.KB_PER_SEC)
+            .withBands(Collections.singletonList(b1))
+            .build();
+
     // Meter features used during the tests
     private MeterFeatures mef1 = DefaultMeterFeatures.builder().forDevice(did1)
             .withMaxMeters(3L)
@@ -133,6 +141,17 @@
             .withMaxBands((byte) 0)
             .withMaxColors((byte) 0)
             .build();
+    private MeterFeatures mef3 = DefaultMeterFeatures.builder().forDevice(did3)
+            .withStartIndex(0L)
+            .withEndIndex(10L)
+            .withScope(MeterScope.of("foo"))
+            .withBandTypes(new HashSet<>())
+            .withUnits(new HashSet<>())
+            .hasStats(false)
+            .hasBurst(false)
+            .withMaxBands((byte) 0)
+            .withMaxColors((byte) 0)
+            .build();
 
     @Before
     public void setup() {
@@ -158,11 +177,13 @@
         meterStore.deactivate();
     }
 
-    private void initMeterStore() {
+    private void initMeterStore(boolean enableUserDefinedIndex) {
+        meterStore.userDefinedIndexMode(enableUserDefinedIndex);
         // Let's store feature for device 1
         meterStore.storeMeterFeatures(mef1);
         // Let's store feature for device 2
         meterStore.storeMeterFeatures(mef2);
+        meterStore.storeMeterFeatures(mef3);
     }
 
     /**
@@ -201,7 +222,7 @@
     @Test
     public void testAllocateId() {
         // Init the store
-        initMeterStore();
+        initMeterStore(false);
         // Allocate a meter id and verify is equal to mid1
         assertThat(mid1, is(meterStore.allocateMeterId(did1)));
         // Allocate a meter id and verify is equal to mid2
@@ -214,7 +235,7 @@
     @Test
     public void testFreeId() {
         // Init the store
-        initMeterStore();
+        initMeterStore(false);
         // Allocate a meter id and verify is equal to mid1
         assertThat(mid1, is(meterStore.allocateMeterId(did1)));
         // Free the above id
@@ -233,7 +254,7 @@
     @Test
     public void testReuseId() {
         // Init the store
-        initMeterStore();
+        initMeterStore(false);
         // Reserve id 1
         MeterId meterIdOne = meterStore.allocateMeterId(did2);
         // Free the above id
@@ -291,7 +312,7 @@
     @Test
     public void testQueryMeters() {
         // Init the store
-        initMeterStore();
+        initMeterStore(false);
         // Let's test queryMeters
         assertThat(mid1, is(meterStore.allocateMeterId(did3)));
         // Let's test queryMeters error
@@ -304,7 +325,7 @@
     @Test
     public void testMaxMeterError() {
         // Init the store
-        initMeterStore();
+        initMeterStore(false);
         // Reserve id 1
         assertThat(mid1, is(meterStore.allocateMeterId(did1)));
         // Reserve id 2
@@ -319,7 +340,7 @@
     @Test
     public void testStoreMeter() {
         // Init the store
-        initMeterStore();
+        initMeterStore(false);
         // Simulate the allocation of an id
         MeterId idOne = meterStore.allocateMeterId(did1);
         // Verify the allocation
@@ -350,7 +371,7 @@
     @Test
     public void testDeleteMeter() {
         // Init the store
-        initMeterStore();
+        initMeterStore(false);
         // Simulate the allocation of an id
         MeterId idOne = meterStore.allocateMeterId(did1);
         // Verify the allocation
@@ -396,7 +417,7 @@
     @Test
     public void testNoDeleteMeter() {
         // Init the store
-        initMeterStore();
+        initMeterStore(false);
         // Simulate the allocation of an id
         MeterId idOne = meterStore.allocateMeterId(did1);
         // Create the key
@@ -440,7 +461,7 @@
     @Test
     public void testPurgeMeterDeviceAndApp() {
         // Init the store
-        initMeterStore();
+        initMeterStore(false);
         // add the meters
         ((DefaultMeter) m1).setState(MeterState.PENDING_ADD);
         ((DefaultMeter) m2).setState(MeterState.PENDING_ADD);
@@ -465,7 +486,7 @@
     @Test
     public void testGetMetersImmutability() {
         // Init the store
-        initMeterStore();
+        initMeterStore(false);
 
         // Simulate the allocation of an id
         MeterId idOne = meterStore.allocateMeterId(did1);
@@ -513,8 +534,144 @@
         metersDevice = meterStore.getAllMeters(did1);
         assertThat(2, is(meters.size()));
         assertThat(2, is(metersDevice.size()));
+    }
 
+    /**
+     * Test invalid allocation of a cell id.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidCellId() {
+        initMeterStore(true);
+        // MF defines an end index equals to 10
+        Meter meterBad = DefaultMeter.builder()
+                .forDevice(did3)
+                .fromApp(APP_ID)
+                .withCellId(invalidCid)
+                .withUnit(Meter.Unit.KB_PER_SEC)
+                .withBands(Collections.singletonList(b1))
+                .build();
+        ((DefaultMeter) meterBad).setState(MeterState.PENDING_ADD);
+        meterStore.storeMeter(meterBad);
+    }
 
+    /**
+     * Test enabling user defined index mode.
+     */
+    @Test
+    public void testEnableUserDefinedIndex() {
+        initMeterStore(false);
+        assertTrue(meterStore.userDefinedIndexMode(true));
+    }
+
+    /**
+     * Test invalid enabling user defined index mode.
+     */
+    @Test
+    public void testInvalidEnableUserDefinedIndex() {
+        testStoreMeter();
+        assertFalse(meterStore.userDefinedIndexMode(true));
+    }
+
+    /**
+     * Test disabling user defined index mode.
+     */
+    @Test
+    public void testDisableUserDefinedIndex() {
+        initMeterStore(true);
+        assertFalse(meterStore.userDefinedIndexMode(false));
+    }
+
+    /**
+     * Test store meter in user defined index mode.
+     */
+    @Test
+    public void testStoreMeterInUserDefinedIndexMode() {
+        initMeterStore(true);
+        // Let's create a meter
+        Meter meterOne = DefaultMeter.builder()
+                .forDevice(did3)
+                .fromApp(APP_ID)
+                .withCellId(cid4)
+                .withUnit(Meter.Unit.KB_PER_SEC)
+                .withBands(Collections.singletonList(b1))
+                .build();
+        // Set the state
+        ((DefaultMeter) meterOne).setState(MeterState.PENDING_ADD);
+        // Store the meter
+        meterStore.storeMeter(meterOne);
+        // Let's create meter key
+        MeterKey meterKey = MeterKey.key(did3, cid4);
+        // Verify the store
+        assertThat(1, is(meterStore.getAllMeters().size()));
+        assertThat(1, is(meterStore.getAllMeters(did3).size()));
+        assertThat(m4, is(meterStore.getMeter(meterKey)));
+    }
+
+    /**
+     * Test invalid disabling user defined index mode.
+     */
+    @Test
+    public void testInvalidDisableUserDefinedIndex() {
+        testStoreMeterInUserDefinedIndexMode();
+        assertTrue(meterStore.userDefinedIndexMode(false));
+    }
+
+    /**
+     * Test allocation of meter ids in user defined index mode.
+     */
+    @Test
+    public void testAllocateIdInUserDefinedIndexMode() {
+        initMeterStore(true);
+        assertNull(meterStore.allocateMeterId(did1));
+    }
+
+    /**
+     * Test free of meter ids in user defined index mode.
+     */
+    @Test
+    public void testFreeIdInUserMode() {
+        initMeterStore(true);
+        // Free the id and expect not being available
+        meterStore.freeMeterId(did1, mid1);
+        MeterTableKey globalKey = MeterTableKey.key(did1, MeterScope.globalScope());
+        assertNotNull(meterStore.availableMeterIds.get(globalKey));
+        assertTrue(meterStore.availableMeterIds.get(globalKey).isEmpty());
+    }
+
+    /**
+     * Test delete meter in user defined index mode.
+     */
+    @Test
+    public void testDeleteMeterInUserDefinedIndexMode() {
+        initMeterStore(true);
+        Meter meterOne = DefaultMeter.builder()
+                .forDevice(did3)
+                .fromApp(APP_ID)
+                .withCellId(cid4)
+                .withUnit(Meter.Unit.KB_PER_SEC)
+                .withBands(Collections.singletonList(b1))
+                .build();
+        ((DefaultMeter) meterOne).setState(MeterState.PENDING_ADD);
+        meterStore.storeMeter(meterOne);
+
+        ((DefaultMeter) meterOne).setState(MeterState.PENDING_REMOVE);
+        MeterKey meterKey = MeterKey.key(did3, cid4);
+        meterStore.deleteMeter(meterOne);
+        CompletableFuture<Void> future = CompletableFuture.runAsync(
+                () -> meterStore.purgeMeter(meterOne)
+        );
+
+        try {
+            future.get();
+        } catch (InterruptedException | ExecutionException e) {
+            e.printStackTrace();
+        }
+        assertThat(0, is(meterStore.getAllMeters().size()));
+        assertThat(0, is(meterStore.getAllMeters(did3).size()));
+        assertNull(meterStore.getMeter(meterKey));
+        MeterTableKey globalKey = MeterTableKey.key(did1, MeterScope.globalScope());
+        assertNotNull(meterStore.availableMeterIds.get(globalKey));
+        assertTrue(meterStore.availableMeterIds.get(globalKey).isEmpty());
     }
 
     // Test class for driver service.