[ONOS-8087] Per device purgeOnDisconnection Flag

Change-Id: I7cb1db12a4d910d70123f116107a898bf9e6d278
(cherry picked from commit 32a9c0b2d92a76e743e9e64eb2f1c6db2da77d12)
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
index a948bfd..12bd9e4 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
@@ -486,10 +486,6 @@
         l2TunnelHandler = new DefaultL2TunnelHandler(this);
         topologyHandler = new TopologyHandler(this);
 
-        compCfgService.preSetProperty("org.onosproject.net.group.impl.GroupManager",
-                                      "purgeOnDisconnection", "true", false);
-        compCfgService.preSetProperty("org.onosproject.net.flow.impl.FlowRuleManager",
-                                      "purgeOnDisconnection", "true", false);
         compCfgService.preSetProperty("org.onosproject.provider.host.impl.HostLocationProvider",
                                       "requestInterceptsEnabled", "false", false);
         compCfgService.preSetProperty("org.onosproject.net.neighbour.impl.NeighbourResolutionManager",
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/config/DeviceConfiguration.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/config/DeviceConfiguration.java
index 123da37..5013e83 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/config/DeviceConfiguration.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/config/DeviceConfiguration.java
@@ -35,6 +35,7 @@
 import org.onosproject.net.HostId;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.config.ConfigException;
+import org.onosproject.net.config.basics.BasicDeviceConfig;
 import org.onosproject.net.config.basics.InterfaceConfig;
 import org.onosproject.net.host.InterfaceIpAddress;
 import org.onosproject.net.intf.Interface;
@@ -109,6 +110,12 @@
         Set<DeviceId> deviceSubjects =
                 srManager.cfgService.getSubjects(DeviceId.class, SegmentRoutingDeviceConfig.class);
         deviceSubjects.forEach(subject -> {
+            //Setting purge on disconnection flag for the device SR has control over.
+            // addConfig returns a config if it exists or creates a new one.
+            log.info("PurgeOnDisconnection set to true for device {}", subject);
+            BasicDeviceConfig basicDeviceConfig = srManager.cfgService.addConfig(subject, BasicDeviceConfig.class);
+            basicDeviceConfig.purgeOnDisconnection(true);
+            srManager.cfgService.applyConfig(subject, BasicDeviceConfig.class, basicDeviceConfig.node());
             SegmentRoutingDeviceConfig config =
                     srManager.cfgService.getConfig(subject, SegmentRoutingDeviceConfig.class);
             SegmentRouterInfo info = new SegmentRouterInfo();
diff --git a/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/IcmpHandlerTest.java b/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/IcmpHandlerTest.java
index cc7c6a8..68472de 100644
--- a/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/IcmpHandlerTest.java
+++ b/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/IcmpHandlerTest.java
@@ -139,6 +139,7 @@
 
         // Apply config
         MockNetworkConfigRegistry mockNetworkConfigRegistry = new MockNetworkConfigRegistry();
+
         mockNetworkConfigRegistry.applyConfig(remoteLeafConfig);
         mockNetworkConfigRegistry.applyConfig(remoteLeafPorts1Config);
         mockNetworkConfigRegistry.applyConfig(remoteLeafPorts2Config);
diff --git a/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/MockNetworkConfigRegistry.java b/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/MockNetworkConfigRegistry.java
index e18483e..12d57ea 100644
--- a/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/MockNetworkConfigRegistry.java
+++ b/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/MockNetworkConfigRegistry.java
@@ -16,10 +16,14 @@
 
 package org.onosproject.segmentrouting;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
+import org.onosproject.net.DeviceId;
 import org.onosproject.net.config.Config;
 import org.onosproject.net.config.NetworkConfigRegistryAdapter;
+import org.onosproject.net.config.basics.BasicDeviceConfig;
 
 import java.util.Objects;
 import java.util.Set;
@@ -44,6 +48,25 @@
     }
 
     @Override
+    public <S, C extends Config<S>> C addConfig(S subject, Class<C> configClass) {
+        Config c = configs.stream()
+                .filter(config -> subject.equals(config.subject()))
+                .filter(config -> configClass.equals(config.getClass()))
+                .findFirst().orElseGet(() -> {
+                    if (configClass.equals(BasicDeviceConfig.class)) {
+                        BasicDeviceConfig deviceConfig = new BasicDeviceConfig();
+                        ObjectMapper mapper = new ObjectMapper();
+                        deviceConfig.init((DeviceId) subject, ((DeviceId) subject).toString(),
+                                          JsonNodeFactory.instance.objectNode(), mapper, config -> {
+                                });
+                        return deviceConfig;
+                    }
+                    return null;
+                });
+        return (C) c;
+    }
+
+    @Override
     public <S, C extends Config<S>> Set<S> getSubjects(Class<S> subject, Class<C> configClass) {
         ImmutableSet.Builder<S> builder = ImmutableSet.builder();
         String cName = configClass.getName();
diff --git a/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/config/DeviceConfigurationTest.java b/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/config/DeviceConfigurationTest.java
index 469def0..1674f6c 100644
--- a/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/config/DeviceConfigurationTest.java
+++ b/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/config/DeviceConfigurationTest.java
@@ -18,6 +18,7 @@
 
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import org.junit.Before;
@@ -29,6 +30,7 @@
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.config.NetworkConfigRegistry;
+import org.onosproject.net.config.basics.BasicDeviceConfig;
 import org.onosproject.net.config.basics.InterfaceConfig;
 import org.onosproject.net.host.InterfaceIpAddress;
 import org.onosproject.net.intf.Interface;
@@ -73,10 +75,12 @@
 
     private DeviceConfiguration devConfig;
 
+    private NetworkConfigRegistry networkConfigService;
+
     @Before
     public void setUp() throws Exception {
         InterfaceService interfaceService;
-        NetworkConfigRegistry networkConfigService;
+        networkConfigService = null;
         NeighbourResolutionService neighbourResolutionService;
         SegmentRoutingManager srManager;
 
@@ -86,6 +90,10 @@
         JsonNode jsonNode = mapper.readTree(jsonStream);
         SegmentRoutingDeviceConfig srDevConfig = new SegmentRoutingDeviceConfig();
         srDevConfig.init(DEV1, CONFIG_KEY, jsonNode, mapper, config -> { });
+        BasicDeviceConfig basicDeviceConfig = new BasicDeviceConfig();
+        basicDeviceConfig.init(DEV1, DEV1.toString(), JsonNodeFactory.instance.objectNode(), mapper, config -> { });
+        BasicDeviceConfig purgeOnDisconnectConfig = basicDeviceConfig.purgeOnDisconnection(true);
+
 
         // Mock interface netcfg
         jsonStream = InterfaceConfig.class.getResourceAsStream("/interface1.json");
@@ -102,6 +110,12 @@
                 .andReturn(Sets.newHashSet(DEV1)).anyTimes();
         expect(networkConfigService.getConfig(DEV1, SegmentRoutingDeviceConfig.class))
                 .andReturn(srDevConfig).anyTimes();
+        expect(networkConfigService.addConfig(DEV1, BasicDeviceConfig.class))
+                .andReturn(basicDeviceConfig).anyTimes();
+        expect(networkConfigService.getConfig(DEV1, BasicDeviceConfig.class))
+                .andReturn(basicDeviceConfig).anyTimes();
+        expect(networkConfigService.applyConfig(DEV1, BasicDeviceConfig.class, purgeOnDisconnectConfig.node()))
+                .andReturn(purgeOnDisconnectConfig).anyTimes();
         expect(networkConfigService.getSubjects(ConnectPoint.class, InterfaceConfig.class))
                 .andReturn(Sets.newHashSet(CP1, CP2)).anyTimes();
         expect(networkConfigService.getConfig(CP1, InterfaceConfig.class)).andReturn(interfaceConfig1).anyTimes();
@@ -157,4 +171,10 @@
         assertTrue(devConfig.inSameSubnet(DEV1, PREFIX2.address()));
         assertFalse(devConfig.inSameSubnet(DEV1, ROUTE1.address()));
     }
+
+    @Test
+    public void getPurgeOnDisconnect() {
+        assertNotNull(networkConfigService.getConfig(DEV1, BasicDeviceConfig.class));
+        assertTrue(networkConfigService.getConfig(DEV1, BasicDeviceConfig.class).purgeOnDisconnection());
+    }
 }
\ No newline at end of file
diff --git a/core/api/src/main/java/org/onosproject/net/config/basics/BasicDeviceConfig.java b/core/api/src/main/java/org/onosproject/net/config/basics/BasicDeviceConfig.java
index 1a7ffd6..47c5375 100644
--- a/core/api/src/main/java/org/onosproject/net/config/basics/BasicDeviceConfig.java
+++ b/core/api/src/main/java/org/onosproject/net/config/basics/BasicDeviceConfig.java
@@ -34,6 +34,7 @@
     private static final String HW_VERSION = "hwVersion";
     private static final String SW_VERSION = "swVersion";
     private static final String SERIAL = "serial";
+    private static final String PURGE_ON_DISCONNECT = "purgeOnDisconnection";
     private static final String DEVICE_KEY_ID = "deviceKeyId";
 
     private static final int DRIVER_MAX_LENGTH = 256;
@@ -54,7 +55,7 @@
                 && hasOnlyFields(ALLOWED, NAME, LOC_TYPE, LATITUDE, LONGITUDE,
                 GRID_Y, GRID_X, UI_TYPE, RACK_ADDRESS, OWNER, TYPE, DRIVER, ROLES,
                 MANUFACTURER, HW_VERSION, SW_VERSION, SERIAL,
-                MANAGEMENT_ADDRESS, PIPECONF, DEVICE_KEY_ID)
+                MANAGEMENT_ADDRESS, PIPECONF, DEVICE_KEY_ID, PURGE_ON_DISCONNECT)
                 && isValidLength(DRIVER, DRIVER_MAX_LENGTH)
                 && isValidLength(MANUFACTURER, MANUFACTURER_MAX_LENGTH)
                 && isValidLength(HW_VERSION, MANUFACTURER_MAX_LENGTH)
@@ -252,6 +253,34 @@
                 deviceKeyId != null ? deviceKeyId.id() : null);
     }
 
+    /**
+     * Returns the device purgeOnDisconnection flag for this device.
+     *
+     * @return device purgeOnDisconnection, false if not set.
+     */
+    public boolean purgeOnDisconnection() {
+        return get(PURGE_ON_DISCONNECT, false);
+    }
+
+    /**
+     * Sets the purgeOnDisconnection flag for the device.
+     *
+     * @param purgeOnDisconnection purges flows, groups, meters on disconnection.
+     * @return self
+     */
+    public BasicDeviceConfig purgeOnDisconnection(boolean purgeOnDisconnection) {
+        return (BasicDeviceConfig) setOrClear(PURGE_ON_DISCONNECT, purgeOnDisconnection);
+    }
+
+    /**
+     * Returns if the device purgeOnDisconnection flag for this device has been explicitly configured.
+     *
+     * @return device purgeOnDisconnection explicitly configured, false if not.
+     */
+    public boolean isPurgeOnDisconnectionConfigured() {
+        return hasField(PURGE_ON_DISCONNECT);
+    }
+
     // TODO: device port meta-data to be configured via BasicPortsConfig
     // TODO: device credentials/keys; in a separate config
 
diff --git a/core/net/src/main/java/org/onosproject/net/flow/impl/FlowRuleManager.java b/core/net/src/main/java/org/onosproject/net/flow/impl/FlowRuleManager.java
index d601d8d..dd7f802 100644
--- a/core/net/src/main/java/org/onosproject/net/flow/impl/FlowRuleManager.java
+++ b/core/net/src/main/java/org/onosproject/net/flow/impl/FlowRuleManager.java
@@ -31,6 +31,8 @@
 import org.onosproject.mastership.MastershipService;
 import org.onosproject.net.Device;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.config.NetworkConfigRegistry;
+import org.onosproject.net.config.basics.BasicDeviceConfig;
 import org.onosproject.net.device.DeviceEvent;
 import org.onosproject.net.device.DeviceListener;
 import org.onosproject.net.device.DeviceService;
@@ -174,6 +176,9 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY)
     protected ClusterService clusterService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected NetworkConfigRegistry netCfgService;
+
     @Activate
     public void activate(ComponentContext context) {
         store.setDelegate(delegate);
@@ -828,7 +833,14 @@
                 case DEVICE_AVAILABILITY_CHANGED:
                     DeviceId deviceId = event.subject().id();
                     if (!deviceService.isAvailable(deviceId)) {
-                        if (purgeOnDisconnection) {
+                        BasicDeviceConfig cfg = netCfgService.getConfig(deviceId, BasicDeviceConfig.class);
+                        //if purgeOnDisconnection is set for the device or it's a global configuration
+                        // lets remove the flows. Priority is given to the per device flag
+                        boolean purge = cfg != null && cfg.isPurgeOnDisconnectionConfigured() ?
+                                cfg.purgeOnDisconnection() : purgeOnDisconnection;
+                        if (purge) {
+                            log.info("PurgeOnDisconnection is requested for device {}, " +
+                                             "removing flows", deviceId);
                             store.purgeFlowRule(deviceId);
                         }
                     }
diff --git a/core/net/src/main/java/org/onosproject/net/group/impl/GroupManager.java b/core/net/src/main/java/org/onosproject/net/group/impl/GroupManager.java
index 47c8f85..e633a96 100644
--- a/core/net/src/main/java/org/onosproject/net/group/impl/GroupManager.java
+++ b/core/net/src/main/java/org/onosproject/net/group/impl/GroupManager.java
@@ -21,6 +21,8 @@
 import org.onosproject.core.ApplicationId;
 import org.onosproject.mastership.MastershipService;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.config.NetworkConfigRegistry;
+import org.onosproject.net.config.basics.BasicDeviceConfig;
 import org.onosproject.net.device.DeviceEvent;
 import org.onosproject.net.device.DeviceListener;
 import org.onosproject.net.device.DeviceService;
@@ -114,6 +116,9 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY)
     protected MastershipService mastershipService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected NetworkConfigRegistry netCfgService;
+
     /** Frequency (in seconds) for polling groups via fallback provider. */
     private int fallbackGroupPollFrequency = GM_POLL_FREQUENCY_DEFAULT;
 
@@ -169,11 +174,11 @@
         flag = Tools.isPropertyEnabled(properties, GM_PURGE_ON_DISCONNECTION);
         if (flag == null) {
             log.info("PurgeOnDisconnection is not configured, " +
-                    "using current value of {}", purgeOnDisconnection);
+                             "using current value of {}", purgeOnDisconnection);
         } else {
             purgeOnDisconnection = flag;
             log.info("Configured. PurgeOnDisconnection is {}",
-                    purgeOnDisconnection ? "enabled" : "disabled");
+                     purgeOnDisconnection ? "enabled" : "disabled");
         }
         String s = get(properties, GM_POLL_FREQUENCY);
         try {
@@ -451,8 +456,14 @@
                         log.debug("Device {} became unavailable for {}; clearing initial audit status",
                                 deviceId, event.type());
                         store.deviceInitialAuditCompleted(deviceId, false);
-
-                        if (purgeOnDisconnection) {
+                        BasicDeviceConfig cfg = netCfgService.getConfig(deviceId, BasicDeviceConfig.class);
+                        //if purgeOnDisconnection is set for the device or it's a global configuration
+                        // lets remove the groups.
+                        boolean purge = cfg != null && cfg.isPurgeOnDisconnectionConfigured() ?
+                                cfg.purgeOnDisconnection() : purgeOnDisconnection;
+                        if (purge) {
+                            log.info("PurgeOnDisconnection is requested for device {}, " +
+                                             "removing groups", deviceId);
                             store.purgeGroupEntry(deviceId);
                         }
                     }
diff --git a/core/net/src/main/java/org/onosproject/net/meter/impl/MeterManager.java b/core/net/src/main/java/org/onosproject/net/meter/impl/MeterManager.java
index cdc3244..03f4889 100644
--- a/core/net/src/main/java/org/onosproject/net/meter/impl/MeterManager.java
+++ b/core/net/src/main/java/org/onosproject/net/meter/impl/MeterManager.java
@@ -20,6 +20,8 @@
 import org.onosproject.cfg.ComponentConfigService;
 import org.onosproject.mastership.MastershipService;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.config.NetworkConfigRegistry;
+import org.onosproject.net.config.basics.BasicDeviceConfig;
 import org.onosproject.net.device.DeviceEvent;
 import org.onosproject.net.device.DeviceListener;
 import org.onosproject.net.device.DeviceService;
@@ -114,6 +116,9 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY)
     protected MastershipService mastershipService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected NetworkConfigRegistry netCfgService;
+
     /** Number of worker threads. */
     private int numThreads = MM_NUM_THREADS_DEFAULT;
 
@@ -189,11 +194,11 @@
         flag = Tools.isPropertyEnabled(properties, MM_PURGE_ON_DISCONNECTION);
         if (flag == null) {
             log.info("PurgeOnDisconnection is not configured," +
-                    "using current value of {}", purgeOnDisconnection);
+                             "using current value of {}", purgeOnDisconnection);
         } else {
             purgeOnDisconnection = flag;
             log.info("Configured. PurgeOnDisconnection is {}",
-                    purgeOnDisconnection ? "enabled" : "disabled");
+                     purgeOnDisconnection ? "enabled" : "disabled");
         }
 
         String s = get(properties, MM_FALLBACK_METER_POLL_FREQUENCY);
@@ -442,7 +447,14 @@
                 case DEVICE_AVAILABILITY_CHANGED:
                     DeviceId deviceId = event.subject().id();
                     if (!deviceService.isAvailable(deviceId)) {
-                        if (purgeOnDisconnection) {
+                        BasicDeviceConfig cfg = netCfgService.getConfig(deviceId, BasicDeviceConfig.class);
+                        //if purgeOnDisconnection is set for the device or it's a global configuration
+                        // lets remove the meters.
+                        boolean purge = cfg != null && cfg.isPurgeOnDisconnectionConfigured() ?
+                                cfg.purgeOnDisconnection() : purgeOnDisconnection;
+                        if (purge) {
+                            log.info("PurgeOnDisconnection is requested for device {}, " +
+                                             "removing meters", deviceId);
                             store.purgeMeter(deviceId);
                         }
                     }