[OpenFlow] Make OpenFlowMeterProvider configurable

Expose the OpenFlowMeterProvider component to the ComponentConfigService
allowing dynamic reconfiguration of the meter stats poll interval and
the option to force request the meter stats after the meter removal

Change-Id: Ib1512136f147f96761327936551f8f9e2a19b357
diff --git a/providers/openflow/meter/src/main/java/org/onosproject/provider/of/meter/impl/OpenFlowMeterProvider.java b/providers/openflow/meter/src/main/java/org/onosproject/provider/of/meter/impl/OpenFlowMeterProvider.java
index f186ef6..e14a949 100644
--- a/providers/openflow/meter/src/main/java/org/onosproject/provider/of/meter/impl/OpenFlowMeterProvider.java
+++ b/providers/openflow/meter/src/main/java/org/onosproject/provider/of/meter/impl/OpenFlowMeterProvider.java
@@ -23,12 +23,16 @@
 import com.google.common.cache.RemovalNotification;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Maps;
+import org.osgi.service.component.ComponentContext;
 import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Deactivate;
 import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.Modified;
 import org.osgi.service.component.annotations.ReferenceCardinality;
 import org.onlab.util.ItemNotFoundException;
+import org.onlab.util.Tools;
+import org.onosproject.cfg.ComponentConfigService;
 import org.onosproject.core.CoreService;
 import org.onosproject.net.driver.Driver;
 import org.onosproject.net.driver.DriverService;
@@ -72,23 +76,31 @@
 import org.slf4j.Logger;
 
 import java.util.Collection;
+import java.util.Dictionary;
 import java.util.EnumSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.Properties;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.stream.Collectors;
 import java.util.concurrent.ConcurrentHashMap;
 
+import static org.onlab.util.Tools.getIntegerProperty;
 import static org.onosproject.net.DeviceId.deviceId;
 import static org.onosproject.openflow.controller.Dpid.uri;
+import static org.onosproject.provider.of.meter.impl.OsgiPropertyConstants.*;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
  * Provider which uses an OpenFlow controller to handle meters.
  */
-@Component(immediate = true, enabled = true)
+@Component(immediate = true, enabled = true, property = {
+        FORCE_STATS_AFTER_METER_REMOVAL + ":Boolean=" + FORCE_STATS_AFTER_METER_REMOVAL_ENABLED_DEFAULT,
+        METER_STATS_POLL_INTERVAL + ":Integer=" + METER_STATS_POLL_INTERVAL_DEFAULT,
+})
+
 public class OpenFlowMeterProvider extends AbstractProvider implements MeterProvider {
 
     private final Logger log = getLogger(getClass());
@@ -105,16 +117,21 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY)
     protected DriverService driverService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected ComponentConfigService cfgService;
+
     private MeterProviderService providerService;
 
+    private boolean forceStatsAfterMeterRemoval = FORCE_STATS_AFTER_METER_REMOVAL_ENABLED_DEFAULT;
+
+    private int meterStatsPollInterval = METER_STATS_POLL_INTERVAL_DEFAULT;
+
     private static final AtomicLong XID_COUNTER = new AtomicLong(1);
 
-    static final int POLL_INTERVAL = 10;
     static final long TIMEOUT = 30;
 
     private Cache<Long, MeterOperation> pendingOperations;
 
-
     private InternalMeterListener listener = new InternalMeterListener();
     private Map<Dpid, MeterStatsCollector> collectors = new ConcurrentHashMap<>();
 
@@ -134,7 +151,8 @@
     }
 
     @Activate
-    public void activate() {
+    public void activate(ComponentContext context) {
+        cfgService.registerProperties(getClass());
         providerService = providerRegistry.register(this);
 
         pendingOperations = CacheBuilder.newBuilder()
@@ -151,11 +169,16 @@
         controller.addEventListener(listener);
         controller.addListener(listener);
 
-        controller.getSwitches().forEach((sw -> createStatsCollection(sw)));
+        modified(context);
+
+        if (collectors.isEmpty()) {
+            controller.getSwitches().forEach((sw -> createStatsCollection(sw)));
+        }
     }
 
     @Deactivate
     public void deactivate() {
+        cfgService.unregisterProperties(getClass(), false);
         providerRegistry.unregister(this);
         collectors.values().forEach(MeterStatsCollector::stop);
         collectors.clear();
@@ -164,6 +187,41 @@
         providerService = null;
     }
 
+    @Modified
+    public void modified(ComponentContext context) {
+        Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties();
+
+        // update FORCE_STATS_AFTER_METER_REMOVAL if needed
+        Boolean forceStatsAfterMeterRemovalEnabled =
+                Tools.isPropertyEnabled(properties, FORCE_STATS_AFTER_METER_REMOVAL);
+        if (forceStatsAfterMeterRemovalEnabled == null) {
+            log.info("forceStatsAfterMeterRemoval is not configured, " +
+                    "using current value of {}", forceStatsAfterMeterRemoval);
+        } else {
+            forceStatsAfterMeterRemoval = forceStatsAfterMeterRemovalEnabled;
+            log.info("Configured. forceStatsAfterMeterRemoval is {}",
+                    forceStatsAfterMeterRemovalEnabled ? "enabled" : "disabled");
+        }
+
+        // update METER_STATS_POLL_INTERVAL if needed
+        Integer newMeterPollInterval = getIntegerProperty(properties, METER_STATS_POLL_INTERVAL);
+        if (newMeterPollInterval != null && newMeterPollInterval > 0
+                && newMeterPollInterval != meterStatsPollInterval) {
+            meterStatsPollInterval = newMeterPollInterval;
+            // restart meter stats collectors, old instances will be automatically purged before creation
+            // in the call to createStatsCollection
+            controller.getSwitches().forEach((sw -> {
+                createStatsCollection(sw);
+            }));
+            log.info("Configured. meterStatsPollInterval to {}", newMeterPollInterval);
+        } else if (newMeterPollInterval != null && newMeterPollInterval <= 0) {
+            log.warn("meterStatsPollInterval must be greater than 0");
+            // Reset property with the old value
+            cfgService.setProperty(getClass().getName(), METER_STATS_POLL_INTERVAL,
+                    Integer.toString(meterStatsPollInterval));
+        }
+    }
+
     @Override
     public void performMeterOperation(DeviceId deviceId, MeterOperations meterOps) {
         Dpid dpid = Dpid.dpid(deviceId.uri());
@@ -193,7 +251,7 @@
 
         performOperation(sw, meterOp);
 
-        if (meterOp.type().equals(MeterOperation.Type.REMOVE)) {
+        if (forceStatsAfterMeterRemoval && meterOp.type().equals(MeterOperation.Type.REMOVE)) {
             forceMeterStats(deviceId);
         }
 
@@ -242,7 +300,7 @@
 
     private void createStatsCollection(OpenFlowSwitch sw) {
         if (sw != null && isMeterSupported(sw)) {
-            MeterStatsCollector msc = new MeterStatsCollector(sw, POLL_INTERVAL);
+            MeterStatsCollector msc = new MeterStatsCollector(sw, meterStatsPollInterval);
             stopCollectorIfNeeded(collectors.put(new Dpid(sw.getId()), msc));
             msc.start();
         }
diff --git a/providers/openflow/meter/src/main/java/org/onosproject/provider/of/meter/impl/OsgiPropertyConstants.java b/providers/openflow/meter/src/main/java/org/onosproject/provider/of/meter/impl/OsgiPropertyConstants.java
new file mode 100644
index 0000000..da8f9eb
--- /dev/null
+++ b/providers/openflow/meter/src/main/java/org/onosproject/provider/of/meter/impl/OsgiPropertyConstants.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2021-present Open Networking Foundation
+ *
+ * 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.provider.of.meter.impl;
+
+/**
+ * Name/Value constants for properties.
+ */
+
+public final class OsgiPropertyConstants {
+
+    private OsgiPropertyConstants() {
+    }
+    // Meter stats are used to reconcile the controller internal MeterStore with the information available
+    // from the switch. This property defines the poll interval for the meter stats request.
+    public static final String METER_STATS_POLL_INTERVAL = "meterStatsPollInterval";
+    public static final int METER_STATS_POLL_INTERVAL_DEFAULT = 10;
+
+    // Defines if the controller should force request the meter stats after a meter has been removed to
+    // sync the MeterStore as soon as possible with the switch meters while avoid waiting for the next
+    // meter stats poll cycle.
+    public static final String FORCE_STATS_AFTER_METER_REMOVAL = "forceStatsAfterMeterRemoval";
+    public static final boolean FORCE_STATS_AFTER_METER_REMOVAL_ENABLED_DEFAULT = true;
+
+
+
+}