OPNFLWPLUG-1049 : Device connection hold time to maintain delay between 18/81018/17
authorSomashekhar Javalagi <somashekhar.manohara.javalagi@ericsson.com>
Wed, 20 Mar 2019 04:31:18 +0000 (10:01 +0530)
committerArunprakash D <d.arunprakash@ericsson.com>
Wed, 13 Nov 2019 08:43:29 +0000 (08:43 +0000)
consecutive connections to the controller

Device connection hold time is the least time delay a device has to
mantain between its consecutive connection attempts. If time delay
between the previous connection and the current connection is within
device connection hold time, the device will not be allowed to connect
to the controller. Default value of the device connection hold time is
0 second.

Change-Id: I15d3ca40c11d2ca20ad582bdddafb14dab449e4b
Signed-off-by: Somashekhar Javalagi <somashekhar.manohara.javalagi@ericsson.com>
19 files changed:
openflowplugin-api/src/main/java/org/opendaylight/openflowplugin/api/openflow/configuration/ConfigurationProperty.java
openflowplugin-api/src/main/java/org/opendaylight/openflowplugin/api/openflow/connection/ConnectionManager.java
openflowplugin-api/src/main/java/org/opendaylight/openflowplugin/api/openflow/connection/DeviceConnectionStatusProvider.java [new file with mode: 0644]
openflowplugin-api/src/main/yang/openflow-provider-config.yang
openflowplugin-blueprint-config/src/main/resources/initial/openflowplugin.cfg
openflowplugin-impl/src/main/java/org/opendaylight/openflowplugin/impl/OpenFlowPluginProviderImpl.java
openflowplugin-impl/src/main/java/org/opendaylight/openflowplugin/impl/configuration/ConfigurationServiceFactoryImpl.java
openflowplugin-impl/src/main/java/org/opendaylight/openflowplugin/impl/configuration/OpenFlowProviderConfigImpl.java
openflowplugin-impl/src/main/java/org/opendaylight/openflowplugin/impl/connection/ConnectionContextImpl.java
openflowplugin-impl/src/main/java/org/opendaylight/openflowplugin/impl/connection/ConnectionManagerImpl.java
openflowplugin-impl/src/main/java/org/opendaylight/openflowplugin/impl/connection/HandshakeManagerImpl.java
openflowplugin-impl/src/test/java/org/opendaylight/openflowplugin/impl/OpenFlowPluginProviderImplTest.java
openflowplugin-impl/src/test/java/org/opendaylight/openflowplugin/impl/configuration/ConfigurationServiceFactoryImplTest.java
openflowplugin-impl/src/test/java/org/opendaylight/openflowplugin/impl/configuration/OpenFlowProviderConfigImplTest.java
openflowplugin-impl/src/test/java/org/opendaylight/openflowplugin/impl/connection/ConnectionContextImplTest.java
openflowplugin-impl/src/test/java/org/opendaylight/openflowplugin/impl/connection/ConnectionManagerImplTest.java
openflowplugin-impl/src/test/java/org/opendaylight/openflowplugin/impl/connection/HandshakeManagerImplTest.java
openflowplugin-impl/src/test/java/org/opendaylight/openflowplugin/impl/connection/listener/HandshakeListenerImplTest.java
openflowplugin-impl/src/test/java/org/opendaylight/openflowplugin/impl/connection/listener/SystemNotificationsListenerImplTest.java

index bcc48b4de274bfbe90e60e5c54bea9bc2ec7924d..9084eb4f29801b13f2640f8fb05767af3437b6b4 100644 (file)
@@ -115,7 +115,11 @@ public enum ConfigurationProperty {
     /**
      * Device connection rate limit property type.
      */
-    DEVICE_CONNECTION_RATE_LIMIT_PER_MIN;
+    DEVICE_CONNECTION_RATE_LIMIT_PER_MIN,
+    /**
+     * Device connection hold time property type.
+     */
+    DEVICE_CONNECTION_HOLD_TIME_IN_SECONDS;
 
     private static final Map<String, ConfigurationProperty> KEY_VALUE_MAP;
 
index 9ba367d0d62a3136630ee901f65fac9dfc806143..2e7b3063d3443bafade72fcb2f2dca6487a9cb47 100644 (file)
@@ -17,7 +17,7 @@ import org.opendaylight.openflowplugin.api.openflow.device.handlers.DeviceDiscon
  * It instantiates and registers {@link ConnectionContext}
  * used for handling all communication with device when onSwitchConnected notification is processed.
  */
-public interface ConnectionManager extends SwitchConnectionHandler {
+public interface ConnectionManager extends SwitchConnectionHandler, AutoCloseable {
 
     /**
      * Method registers handler responsible handling operations related to connected device after
diff --git a/openflowplugin-api/src/main/java/org/opendaylight/openflowplugin/api/openflow/connection/DeviceConnectionStatusProvider.java b/openflowplugin-api/src/main/java/org/opendaylight/openflowplugin/api/openflow/connection/DeviceConnectionStatusProvider.java
new file mode 100644 (file)
index 0000000..abbe524
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2019 Ericsson India Global Services Pvt Ltd. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.openflowplugin.api.openflow.connection;
+
+import java.math.BigInteger;
+import java.time.LocalDateTime;
+
+public interface DeviceConnectionStatusProvider extends AutoCloseable {
+
+    /**
+     * Initialize the DeviceConnectionStatusProvider.
+     */
+    void init();
+
+    /**
+     * Get the last connection time of a device.
+     * @param datapathId datapathId of node
+     */
+    LocalDateTime getDeviceLastConnectionTime(BigInteger datapathId);
+
+    /**
+     * Update the last connection time of a device.
+     * @param datapathId datapathId of node
+     * @param time last connected time of datapathId
+     */
+    void addDeviceLastConnectionTime(BigInteger datapathId, LocalDateTime time);
+
+    /**
+      * Clear the last connection time of a device.
+      * @param datapathId datapathId of node
+      */
+    void removeDeviceLastConnectionTime(BigInteger datapathId);
+}
index 21bd712762ad4b4bb9b41221568cfdefdf767429..527a23b36bb1f32ef2de7fc42e31f6c9f0ce3ccb 100644 (file)
@@ -202,5 +202,15 @@ module openflow-provider-config {
             type uint16;
             default 0;
         }
+
+        leaf device-connection-hold-time-in-seconds {
+            description "device connection hold time is the least time delay in seconds a
+            device has to maintain between its consecutive connection attempts. If time delay
+            between the previous connection and the current connection is within device
+            connection hold time, the device will not be allowed to connect to the controller.
+            Default value of the device connection hold time is 0 second.";
+            type uint16;
+            default 0;
+        }
     }
 }
index 68f7e76d9ed7cb67bd2f8f8fe17526f532987246..31889a49b85dd90df881a0bc0bfd16688081fc99 100644 (file)
 #
 # device-connection-rate-limit-per-min=0
 
+#
+# Device connection hold time is the least time delay in seconds a device has
+# to maintain between its consecutive connection attempts. If time delay between
+# the previous connection and the current connection is within device connection
+# hold time, the device will not be allowed to connect to the controller.
+# Default value of the device connection hold time is 0 second
+#
+# device-connection-hold-time-in-seconds=0
+
 #############################################################################
 #                                                                           #
 #            Forwarding Rule Manager Application Configuration              #
index bb522226ba7f4daafaa2aa1b21272a24f7cb6c99..be1e42f7f8d57204db7d36a8291d8cf28fb67156 100644 (file)
@@ -282,7 +282,7 @@ public class OpenFlowPluginProviderImpl implements
         contextChainHolder.addManager(rpcManager);
         contextChainHolder.addManager(roleManager);
 
-        connectionManager = new ConnectionManagerImpl(config, executorService);
+        connectionManager = new ConnectionManagerImpl(config, executorService, dataBroker);
         connectionManager.setDeviceConnectedHandler(contextChainHolder);
         connectionManager.setDeviceDisconnectedHandler(contextChainHolder);
 
@@ -297,6 +297,7 @@ public class OpenFlowPluginProviderImpl implements
 
     @Override
     @PreDestroy
+    @SuppressWarnings("checkstyle:IllegalCatch")
     public void close() {
         try {
             shutdownSwitchConnections().get(10, TimeUnit.SECONDS);
@@ -313,6 +314,14 @@ public class OpenFlowPluginProviderImpl implements
         gracefulShutdown(hashedWheelTimer);
         unregisterMXBean(MESSAGE_INTELLIGENCE_AGENCY_MX_BEAN_NAME);
         openflowDiagStatusProvider.reportStatus(ServiceState.UNREGISTERED);
+        try {
+            if (connectionManager != null) {
+                connectionManager.close();
+                connectionManager = null;
+            }
+        } catch (Exception e) {
+            LOG.error("Failed to close ConnectionManager", e);
+        }
     }
 
     @SuppressWarnings("checkstyle:IllegalCatch")
index 328e474a8aec3006d4a195090fba701cfd34191a..d7d33f6237bfa888433a02e6334226df4f6d1086 100644 (file)
@@ -86,6 +86,8 @@ public class ConfigurationServiceFactoryImpl implements ConfigurationServiceFact
                             providerConfig.getThreadPoolTimeout().toString())
                     .put(ConfigurationProperty.DEVICE_CONNECTION_RATE_LIMIT_PER_MIN.toString(),
                             providerConfig.getDeviceConnectionRateLimitPerMin().toString())
+                    .put(ConfigurationProperty.DEVICE_CONNECTION_HOLD_TIME_IN_SECONDS.toString(),
+                            providerConfig.getDeviceConnectionHoldTimeInSeconds().toString())
                     .build());
         }
 
index a7406aa058424ee45e04b4c44ada49670bc807a2..e1283c7e5e2d1c3c47b81eabd0eef6aa361bf7ad 100644 (file)
@@ -179,4 +179,10 @@ public class OpenFlowProviderConfigImpl implements OpenflowProviderConfig {
         return service.getProperty(ConfigurationProperty.DEVICE_CONNECTION_RATE_LIMIT_PER_MIN.toString(),
                 Uint16::valueOf);
     }
+
+    @Override
+    public Uint16 getDeviceConnectionHoldTimeInSeconds() {
+        return service.getProperty(ConfigurationProperty.DEVICE_CONNECTION_HOLD_TIME_IN_SECONDS.toString(),
+                Uint16::valueOf);
+    }
 }
index d1d847d1fd79d985ebcd4869e2dca8ac6b67d76a..95f20df881e3f329d12a4d95163347354332af42 100644 (file)
@@ -17,6 +17,7 @@ import org.opendaylight.openflowjava.protocol.api.connection.ConnectionAdapter;
 import org.opendaylight.openflowjava.protocol.api.connection.OutboundQueue;
 import org.opendaylight.openflowjava.protocol.api.connection.OutboundQueueHandlerRegistration;
 import org.opendaylight.openflowplugin.api.openflow.connection.ConnectionContext;
+import org.opendaylight.openflowplugin.api.openflow.connection.DeviceConnectionStatusProvider;
 import org.opendaylight.openflowplugin.api.openflow.connection.HandshakeContext;
 import org.opendaylight.openflowplugin.api.openflow.connection.OutboundQueueProvider;
 import org.opendaylight.openflowplugin.api.openflow.device.DeviceInfo;
@@ -47,14 +48,17 @@ public class ConnectionContextImpl implements ConnectionContext {
     private HandshakeContext handshakeContext;
     private DeviceInfo deviceInfo;
     private final List<PortStatusMessage> portStatusMessages = new ArrayList<>();
+    private final DeviceConnectionStatusProvider deviceConnectionStatusProvider;
 
     /**
      * Constructor.
      *
      * @param connectionAdapter - connection adapter
      */
-    public ConnectionContextImpl(final ConnectionAdapter connectionAdapter) {
+    public ConnectionContextImpl(final ConnectionAdapter connectionAdapter,
+                                 final DeviceConnectionStatusProvider deviceConnectionStatusProvider) {
         this.connectionAdapter = connectionAdapter;
+        this.deviceConnectionStatusProvider = deviceConnectionStatusProvider;
     }
 
     @Override
index 3507dd1eddea324b27a6c44df089c5d9a1e50fe0..bd65f52be2e84129658fefeaa431891a87da8994 100644 (file)
@@ -7,13 +7,28 @@
  */
 package org.opendaylight.openflowplugin.impl.connection;
 
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import java.math.BigInteger;
 import java.net.InetAddress;
+import java.time.LocalDateTime;
+import java.util.Collection;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutorService;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.mdsal.binding.api.ClusteredDataTreeChangeListener;
+import org.opendaylight.mdsal.binding.api.DataBroker;
+import org.opendaylight.mdsal.binding.api.DataObjectModification;
+import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
+import org.opendaylight.mdsal.binding.api.DataTreeModification;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
 import org.opendaylight.openflowjava.protocol.api.connection.ConnectionAdapter;
 import org.opendaylight.openflowjava.protocol.api.connection.ConnectionReadyListener;
 import org.opendaylight.openflowplugin.api.OFConstants;
 import org.opendaylight.openflowplugin.api.openflow.connection.ConnectionContext;
 import org.opendaylight.openflowplugin.api.openflow.connection.ConnectionManager;
+import org.opendaylight.openflowplugin.api.openflow.connection.DeviceConnectionStatusProvider;
 import org.opendaylight.openflowplugin.api.openflow.connection.HandshakeContext;
 import org.opendaylight.openflowplugin.api.openflow.device.handlers.DeviceConnectedHandler;
 import org.opendaylight.openflowplugin.api.openflow.device.handlers.DeviceDisconnectedHandler;
@@ -24,9 +39,13 @@ import org.opendaylight.openflowplugin.impl.connection.listener.ConnectionReadyL
 import org.opendaylight.openflowplugin.impl.connection.listener.HandshakeListenerImpl;
 import org.opendaylight.openflowplugin.impl.connection.listener.OpenflowProtocolListenerInitialImpl;
 import org.opendaylight.openflowplugin.impl.connection.listener.SystemNotificationsListenerImpl;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.OpenflowProtocolListener;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.system.rev130927.SystemNotificationsListener;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.openflow.provider.config.rev160510.OpenflowProviderConfig;
+import org.opendaylight.yangtools.concepts.ListenerRegistration;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -34,22 +53,31 @@ public class ConnectionManagerImpl implements ConnectionManager {
 
     private static final Logger LOG = LoggerFactory.getLogger(ConnectionManagerImpl.class);
     private static final boolean BITMAP_NEGOTIATION_ENABLED = true;
-    private DeviceConnectedHandler deviceConnectedHandler;
     private final OpenflowProviderConfig config;
     private final ExecutorService executorService;
     private final DeviceConnectionRateLimiter deviceConnectionRateLimiter;
+    private final DataBroker dataBroker;
+    private final int deviceConnectionHoldTime;
+    private DeviceConnectedHandler deviceConnectedHandler;
     private DeviceDisconnectedHandler deviceDisconnectedHandler;
+    private DeviceConnectionStatusProvider deviceConnectionStatusProvider;
 
-    public ConnectionManagerImpl(final OpenflowProviderConfig config, final ExecutorService executorService) {
+    public ConnectionManagerImpl(final OpenflowProviderConfig config, final ExecutorService executorService,
+                                 final DataBroker dataBroker) {
         this.config = config;
         this.executorService = executorService;
         this.deviceConnectionRateLimiter = new DeviceConnectionRateLimiter(config);
+        this.dataBroker = dataBroker;
+        this.deviceConnectionHoldTime = config.getDeviceConnectionHoldTimeInSeconds().toJava();
+        deviceConnectionStatusProvider = new DeviceConnectionStatusProviderImpl();
+        deviceConnectionStatusProvider.init();
     }
 
     @Override
     public void onSwitchConnected(final ConnectionAdapter connectionAdapter) {
         LOG.trace("prepare connection context");
-        final ConnectionContext connectionContext = new ConnectionContextImpl(connectionAdapter);
+        final ConnectionContext connectionContext = new ConnectionContextImpl(connectionAdapter,
+                deviceConnectionStatusProvider);
         connectionContext.setDeviceDisconnectedHandler(this.deviceDisconnectedHandler);
 
         HandshakeListener handshakeListener = new HandshakeListenerImpl(connectionContext, deviceConnectedHandler);
@@ -81,7 +109,7 @@ public class ConnectionManagerImpl implements ConnectionManager {
         HandshakeManagerImpl handshakeManager = new HandshakeManagerImpl(connectionAdapter,
                 OFConstants.VERSION_ORDER.get(0),
                 OFConstants.VERSION_ORDER, new ErrorHandlerSimpleImpl(), handshakeListener, BITMAP_NEGOTIATION_ENABLED,
-                deviceConnectionRateLimiter);
+                deviceConnectionRateLimiter, deviceConnectionHoldTime, deviceConnectionStatusProvider);
 
         return handshakeManager;
     }
@@ -101,4 +129,92 @@ public class ConnectionManagerImpl implements ConnectionManager {
     public void setDeviceDisconnectedHandler(final DeviceDisconnectedHandler deviceDisconnectedHandler) {
         this.deviceDisconnectedHandler = deviceDisconnectedHandler;
     }
+
+    @VisibleForTesting
+    DeviceConnectionStatusProvider getDeviceConnectionStatusProvider() {
+        return deviceConnectionStatusProvider;
+    }
+
+    @Override
+    public void close() throws Exception {
+        if (deviceConnectionStatusProvider != null) {
+            deviceConnectionStatusProvider.close();
+            deviceConnectionStatusProvider = null;
+        }
+    }
+
+    class DeviceConnectionStatusProviderImpl implements DeviceConnectionStatusProvider,
+            ClusteredDataTreeChangeListener<Node> {
+        private final Map<BigInteger, LocalDateTime> deviceConnectionMap = new ConcurrentHashMap<>();
+
+        private ListenerRegistration<DeviceConnectionStatusProviderImpl> listenerRegistration;
+
+        @Override
+        @SuppressWarnings({"checkstyle:IllegalCatch"})
+        public void init() {
+            DataTreeIdentifier<Node> treeId = DataTreeIdentifier.create(LogicalDatastoreType.OPERATIONAL,
+                    getWildCardPath());
+            try {
+                listenerRegistration = dataBroker.registerDataTreeChangeListener(treeId, this);
+            } catch (Exception e) {
+                LOG.error("DeviceConnectionStatusProvider listener registration failed", e);
+            }
+        }
+
+        @Override
+        public LocalDateTime getDeviceLastConnectionTime(BigInteger nodeId) {
+            return deviceConnectionMap.get(nodeId);
+        }
+
+        @Override
+        public void addDeviceLastConnectionTime(BigInteger nodeId, LocalDateTime time) {
+            deviceConnectionMap.put(nodeId, time);
+        }
+
+        @Override
+        public void removeDeviceLastConnectionTime(BigInteger nodeId) {
+            deviceConnectionMap.remove(nodeId);
+        }
+
+        @Override
+        public void onDataTreeChanged(@NonNull Collection<DataTreeModification<Node>> changes) {
+            Preconditions.checkNotNull(changes, "Changes must not be null!");
+            for (DataTreeModification<Node> change : changes) {
+                final DataObjectModification<Node> mod = change.getRootNode();
+                switch (mod.getModificationType()) {
+                    case DELETE:
+                        break;
+                    case SUBTREE_MODIFIED:
+                        break;
+                    case WRITE:
+                        processNodeModification(change);
+                        break;
+                    default:
+                        throw new IllegalArgumentException("Unhandled modification type " + mod.getModificationType());
+                }
+            }
+        }
+
+        private InstanceIdentifier<Node> getWildCardPath() {
+            return InstanceIdentifier.create(Nodes.class).child(Node.class);
+        }
+
+        private void processNodeModification(DataTreeModification<Node> change) {
+            final InstanceIdentifier<Node> key = change.getRootPath().getRootIdentifier();
+            final InstanceIdentifier<Node> nodeIdent = key.firstIdentifierOf(Node.class);
+            String[] nodeIdentity = nodeIdent.firstKeyOf(Node.class).getId().getValue().split(":");
+            String nodeId = nodeIdentity[1];
+            LOG.info("Clearing the device connection timer for the device {}", nodeId);
+            removeDeviceLastConnectionTime(new BigInteger(nodeId));
+        }
+
+        @Override
+        public void close() {
+            if (listenerRegistration != null) {
+                listenerRegistration.close();
+                listenerRegistration = null;
+            }
+        }
+    }
+
 }
index 0708eb14e918343900041c3fefbc6d9033b0a819..36fe52daf8aaf5da8ebeed579494e7d31fce3569 100644 (file)
@@ -14,10 +14,13 @@ import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.MoreExecutors;
 import com.google.common.util.concurrent.SettableFuture;
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import java.math.BigInteger;
+import java.time.LocalDateTime;
 import java.util.List;
 import java.util.Objects;
 import org.opendaylight.openflowjava.protocol.api.connection.ConnectionAdapter;
 import org.opendaylight.openflowplugin.api.OFConstants;
+import org.opendaylight.openflowplugin.api.openflow.connection.DeviceConnectionStatusProvider;
 import org.opendaylight.openflowplugin.api.openflow.md.core.ErrorHandler;
 import org.opendaylight.openflowplugin.api.openflow.md.core.HandshakeListener;
 import org.opendaylight.openflowplugin.api.openflow.md.core.HandshakeManager;
@@ -59,6 +62,8 @@ public class HandshakeManagerImpl implements HandshakeManager {
     private boolean useVersionBitmap; // not final just for unit test
 
     private final DeviceConnectionRateLimiter deviceConnectionRateLimiter;
+    private final int deviceConnectionHoldTime;
+    private final DeviceConnectionStatusProvider deviceConnectionStatusProvider;
 
     /**
      * Constructor.
@@ -69,12 +74,16 @@ public class HandshakeManagerImpl implements HandshakeManager {
      * @param errorHandler      the ErrorHandler
      * @param handshakeListener the HandshakeListener
      * @param useVersionBitmap  should use negotiation bit map
+     * @param deviceConnectionRateLimiter  device connection rate limiter utility
+     * @param deviceConnectionHoldTime  deivce connection hold time in seconds
+     * @param deviceConnectionStatusProvider  utility for maintaining device connection states
      */
     public HandshakeManagerImpl(final ConnectionAdapter connectionAdapter, final Short highestVersion,
-                                final List<Short> versionOrder,
-                                final ErrorHandler errorHandler, final HandshakeListener handshakeListener,
-                                final boolean useVersionBitmap,
-                                final DeviceConnectionRateLimiter deviceConnectionRateLimiter) {
+                                final List<Short> versionOrder, final ErrorHandler errorHandler,
+                                final HandshakeListener handshakeListener, final boolean useVersionBitmap,
+                                final DeviceConnectionRateLimiter deviceConnectionRateLimiter,
+                                final int deviceConnectionHoldTime,
+                                final DeviceConnectionStatusProvider deviceConnectionStatusProvider) {
         this.highestVersion = highestVersion;
         this.versionOrder = versionOrder;
         this.connectionAdapter = connectionAdapter;
@@ -82,6 +91,8 @@ public class HandshakeManagerImpl implements HandshakeManager {
         this.handshakeListener = handshakeListener;
         this.useVersionBitmap = useVersionBitmap;
         this.deviceConnectionRateLimiter = deviceConnectionRateLimiter;
+        this.deviceConnectionHoldTime = deviceConnectionHoldTime;
+        this.deviceConnectionStatusProvider = deviceConnectionStatusProvider;
     }
 
     @Override
@@ -390,10 +401,9 @@ public class HandshakeManagerImpl implements HandshakeManager {
                             GetFeaturesOutput featureOutput = rpcFeatures.getResult();
 
                             final Uint64 dpId = featureOutput.getDatapathId();
-                            connectionAdapter.setDatapathId(dpId == null ? null : dpId.toJava());
-                            if (!deviceConnectionRateLimiter.tryAquire()) {
-                                LOG.warn("Openflowplugin hit the device connection rate limit threshold. Denying"
-                                        + " the connection from device {}", featureOutput.getDatapathId());
+                            BigInteger datapathId = dpId == null ? null : dpId.toJava();
+                            connectionAdapter.setDatapathId(datapathId);
+                            if (datapathId == null || !isAllowedToConnect(datapathId)) {
                                 connectionAdapter.disconnect();
                                 return;
                             }
@@ -429,6 +439,30 @@ public class HandshakeManagerImpl implements HandshakeManager {
         LOG.debug("future features [{}] hooked ..", xid);
     }
 
+    public boolean isAllowedToConnect(BigInteger nodeId) {
+        // The device isn't allowed for connection till device connection hold time is over
+        if (deviceConnectionHoldTime > 0) {
+            LocalDateTime lastConnectionTime = deviceConnectionStatusProvider.getDeviceLastConnectionTime(nodeId);
+            if (lastConnectionTime == null) {
+                LOG.debug("Initial connection attempt by device {} to the controller node. Allowing to connect after {}"
+                        + "seconds", nodeId, deviceConnectionHoldTime);
+                deviceConnectionStatusProvider.addDeviceLastConnectionTime(nodeId, LocalDateTime.now());
+                return false;
+            } else if (LocalDateTime.now().isBefore(lastConnectionTime.plusSeconds(deviceConnectionHoldTime))) {
+                LOG.trace("Device trying to connect before the connection delay {} seconds, disconnecting the device "
+                                + "{}", deviceConnectionHoldTime, nodeId);
+                return false;
+            }
+        }
+
+        if (!deviceConnectionRateLimiter.tryAquire()) {
+            LOG.debug("Permit not acquired for device {}, disconnecting the device.", nodeId);
+            connectionAdapter.disconnect();
+            return false;
+        }
+        return true;
+    }
+
     /**
      * Method for unit testing, only.
      * This method is not thread safe and can only safely be used from a test.
index abf719b2454ad8984debaef8af93430571675c3d..934fb1516bdfe2fedbde1ecc94cbfa9725e46b1b 100644 (file)
@@ -18,6 +18,7 @@ import com.google.common.util.concurrent.Futures;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Matchers;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
 import org.opendaylight.infrautils.ready.SystemReadyMonitor;
@@ -89,6 +90,7 @@ public class OpenFlowPluginProviderImplTest {
     private static final long BASIC_TIMER_DELAY = 1L;
     private static final boolean USE_SINGLE_LAYER_SERIALIZATION = false;
     private static final Uint16 DEVICE_CONNECTION_RATE_LIMIT_PER_MIN = Uint16.ZERO;
+    private static final Uint16 DEVICE_CONNECTION_HOLD_TIME_IN_SECONDS = Uint16.valueOf(60);
 
     @Before
     public void setUp() {
@@ -109,6 +111,9 @@ public class OpenFlowPluginProviderImplTest {
                 .thenReturn(THREAD_POOL_TIMEOUT);
         when(configurationService.getProperty(eq(ConfigurationProperty.DEVICE_CONNECTION_RATE_LIMIT_PER_MIN.toString()),
                 any())).thenReturn(DEVICE_CONNECTION_RATE_LIMIT_PER_MIN);
+        when(configurationService.getProperty(
+                Matchers.eq(ConfigurationProperty.DEVICE_CONNECTION_HOLD_TIME_IN_SECONDS.toString()),
+                Matchers.any())).thenReturn(DEVICE_CONNECTION_HOLD_TIME_IN_SECONDS);
     }
 
     @Test
index 926345c7fb36a833701b63e3125bf595f0723adc..a2992bbc6e5d47f9affb8b683c0643450f21aa8f 100644 (file)
@@ -34,7 +34,7 @@ import org.opendaylight.yangtools.yang.common.Uint32;
 
 @RunWith(MockitoJUnitRunner.class)
 public class ConfigurationServiceFactoryImplTest {
-    private static final int CONFIG_PROP_COUNT = 23;
+    private static final int CONFIG_PROP_COUNT = 24;
     private static final boolean IS_STATISTICS_POLLING_ON = true;
     private static final int BARRIER_COUNT_LIMIT = 2000;
     private static final long BARRIER_INTERVAL_TIMEOUT_LIMIT = 3000;
@@ -53,6 +53,7 @@ public class ConfigurationServiceFactoryImplTest {
     private static final int THREAD_POOL_MAX_THREADS = 1000;
     private static final Uint32 THREAD_POOL_TIMEOUT = Uint32.valueOf(60);
     private static final Uint16 DEVICE_CONNECTION_RATE_LIMIT_PER_MIN = Uint16.ZERO;
+    private static final Uint16 DEVICE_CONNECTION_HOLD_TIME_IN_SECONDS = Uint16.valueOf(60);
 
     @Mock
     private OpenflowProviderConfig config;
@@ -88,6 +89,7 @@ public class ConfigurationServiceFactoryImplTest {
         when(config.getThreadPoolMaxThreads()).thenReturn(new NonZeroUint16Type(THREAD_POOL_MAX_THREADS));
         when(config.getThreadPoolTimeout()).thenReturn(THREAD_POOL_TIMEOUT);
         when(config.getDeviceConnectionRateLimitPerMin()).thenReturn(DEVICE_CONNECTION_RATE_LIMIT_PER_MIN);
+        when(config.getDeviceConnectionHoldTimeInSeconds()).thenReturn(DEVICE_CONNECTION_HOLD_TIME_IN_SECONDS);
 
         final Map<String, String> properties = new Hashtable<>();
         properties.put(ConfigurationProperty.IS_STATISTICS_POLLING_ON.toString(),
index 31cb23327de877be87e51639597fa8e331340ae4..b8b00fa420a4efc80fa0807d264d1bace3168e64 100644 (file)
@@ -43,6 +43,7 @@ public class OpenFlowProviderConfigImplTest {
     private static final int THREAD_POOL_MAX_THREADS = 1000;
     private static final Uint32 THREAD_POOL_TIMEOUT = Uint32.valueOf(60);
     private static final Uint16 DEVICE_CONNECTION_RATE_LIMIT_PER_MIN = Uint16.ZERO;
+    private static final Uint16 DEVICE_CONNECTION_HOLD_TIME_IN_SECONDS = Uint16.valueOf(60);
 
     @Mock
     private ConfigurationService configurationService;
@@ -84,6 +85,9 @@ public class OpenFlowProviderConfigImplTest {
                 .thenReturn(THREAD_POOL_TIMEOUT);
         when(configurationService.getProperty(eq(ConfigurationProperty.DEVICE_CONNECTION_RATE_LIMIT_PER_MIN.toString()),
                 any())).thenReturn(DEVICE_CONNECTION_RATE_LIMIT_PER_MIN);
+        when(configurationService.getProperty(
+                eq(ConfigurationProperty.DEVICE_CONNECTION_HOLD_TIME_IN_SECONDS.toString()),
+                any())).thenReturn(DEVICE_CONNECTION_HOLD_TIME_IN_SECONDS);
         openflowProviderConfig = new OpenFlowProviderConfigImpl(configurationService);
     }
 
@@ -178,4 +182,10 @@ public class OpenFlowProviderConfigImplTest {
         assertEquals(DEVICE_CONNECTION_RATE_LIMIT_PER_MIN, openflowProviderConfig.getDeviceConnectionRateLimitPerMin());
     }
 
+    @Test
+    public void getDeviceConnectionHoldTimeInSeconds() {
+        assertEquals(DEVICE_CONNECTION_HOLD_TIME_IN_SECONDS,
+                openflowProviderConfig.getDeviceConnectionHoldTimeInSeconds());
+    }
+
 }
\ No newline at end of file
index 9903d206f3ec6a6c5af11d84cb1bb7b7d4c45469..15323dbcd96d97a90660964a64d2ed1133e26b1c 100644 (file)
@@ -19,6 +19,7 @@ import org.mockito.runners.MockitoJUnitRunner;
 import org.opendaylight.openflowjava.protocol.api.connection.ConnectionAdapter;
 import org.opendaylight.openflowjava.protocol.api.connection.OutboundQueueHandlerRegistration;
 import org.opendaylight.openflowplugin.api.openflow.connection.ConnectionContext;
+import org.opendaylight.openflowplugin.api.openflow.connection.DeviceConnectionStatusProvider;
 import org.opendaylight.openflowplugin.api.openflow.connection.HandshakeContext;
 import org.opendaylight.openflowplugin.api.openflow.connection.OutboundQueueProvider;
 import org.opendaylight.openflowplugin.api.openflow.device.handlers.DeviceDisconnectedHandler;
@@ -40,6 +41,8 @@ public class ConnectionContextImplTest {
     private DeviceDisconnectedHandler deviceDisconnectedHandler;
     @Mock
     private OutboundQueueProvider outboundQueueProvider;
+    @Mock
+    private DeviceConnectionStatusProvider deviceConnectionStatusProvider;
 
     private ConnectionContextImpl connectionContext;
 
@@ -49,7 +52,7 @@ public class ConnectionContextImplTest {
                 .thenReturn(InetSocketAddress.createUnresolved("ofp-ut.example.org", 4242));
         Mockito.when(connetionAdapter.isAlive()).thenReturn(true);
 
-        connectionContext = new ConnectionContextImpl(connetionAdapter);
+        connectionContext = new ConnectionContextImpl(connetionAdapter, deviceConnectionStatusProvider);
         connectionContext.setHandshakeContext(handshakeContext);
         connectionContext.setNodeId(new NodeId("ut-node:123"));
         connectionContext.setOutboundQueueHandleRegistration(outboundQueueRegistration);
index 7e95063ab838606d0899d066b97a7f3e1a985cc5..8e0c3778fad6ac26137aabbed3f12879f94b1ed5 100644 (file)
@@ -12,6 +12,7 @@ import static org.mockito.ArgumentMatchers.any;
 import com.google.common.util.concurrent.SettableFuture;
 import java.math.BigInteger;
 import java.net.InetSocketAddress;
+import java.time.LocalDateTime;
 import java.util.concurrent.SynchronousQueue;
 import java.util.concurrent.TimeUnit;
 import org.junit.After;
@@ -24,6 +25,7 @@ import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.runners.MockitoJUnitRunner;
+import org.opendaylight.mdsal.binding.api.DataBroker;
 import org.opendaylight.openflowjava.protocol.api.connection.ConnectionAdapter;
 import org.opendaylight.openflowjava.protocol.api.connection.ConnectionReadyListener;
 import org.opendaylight.openflowplugin.api.OFConstants;
@@ -61,9 +63,12 @@ public class ConnectionManagerImplTest {
     private ArgumentCaptor<ConnectionReadyListener> connectionReadyListenerAC;
     @Captor
     private ArgumentCaptor<OpenflowProtocolListener> ofpListenerAC;
+    @Mock
+    DataBroker dataBroker;
 
     private static final long ECHO_REPLY_TIMEOUT = 500;
     private static final int DEVICE_CONNECTION_RATE_LIMIT_PER_MIN = 0;
+    private static final int DEVICE_CONNECTION_HOLD_TIME_IN_SECONDS = 60;
 
     @Before
     public void setUp() {
@@ -74,7 +79,8 @@ public class ConnectionManagerImplTest {
         connectionManagerImpl = new ConnectionManagerImpl(new OpenflowProviderConfigBuilder()
                 .setEchoReplyTimeout(new NonZeroUint32Type(ECHO_REPLY_TIMEOUT))
                 .setDeviceConnectionRateLimitPerMin(DEVICE_CONNECTION_RATE_LIMIT_PER_MIN)
-                .build(), threadPool);
+                .setDeviceConnectionHoldTimeInSeconds(DEVICE_CONNECTION_HOLD_TIME_IN_SECONDS)
+                .build(), threadPool, dataBroker);
 
         connectionManagerImpl.setDeviceConnectedHandler(deviceConnectedHandler);
         final InetSocketAddress deviceAddress = InetSocketAddress.createUnresolved("yahoo", 42);
@@ -126,6 +132,10 @@ public class ConnectionManagerImplTest {
         final RpcResult<HelloOutput> helloResponse = RpcResultBuilder.success((HelloOutput) null).build();
         voidResponseFx.set(helloResponse);
 
+        //set dpn last connected time to be before dpn hold time seconds from now
+        connectionManagerImpl.getDeviceConnectionStatusProvider().addDeviceLastConnectionTime(BigInteger.TEN,
+                LocalDateTime.now().minusSeconds(DEVICE_CONNECTION_HOLD_TIME_IN_SECONDS));
+
         // send hello reply
         final HelloMessage hello = new HelloMessageBuilder().setVersion(OFConstants.OFP_VERSION_1_3).setXid(1L).build();
         ofpListenerAC.getValue().onHelloMessage(hello);
@@ -173,6 +183,9 @@ public class ConnectionManagerImplTest {
                 SettableFuture.create();
         Mockito.when(connection.getFeatures(any(GetFeaturesInput.class))).thenReturn(featureResponseFx);
 
+        //set dpn last connected time to be before dpn hold time seconds from now
+        connectionManagerImpl.getDeviceConnectionStatusProvider().addDeviceLastConnectionTime(BigInteger.TEN,
+                LocalDateTime.now().minusSeconds(DEVICE_CONNECTION_HOLD_TIME_IN_SECONDS));
 
         // fire handshake - send hello reply
         final HelloMessage hello = new HelloMessageBuilder().setVersion(OFConstants.OFP_VERSION_1_3).setXid(1L).build();
index 87426ad0b75a7daec89778423dacad2e45b56225..4b1251cb7ca7ba9f218c737aeb0fab500dce13b8 100644 (file)
@@ -12,6 +12,8 @@ import static org.mockito.ArgumentMatchers.anyShort;
 
 import com.google.common.collect.Lists;
 import com.google.common.util.concurrent.Futures;
+import java.math.BigInteger;
+import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.List;
 import org.junit.After;
@@ -25,6 +27,7 @@ import org.mockito.Mockito;
 import org.mockito.runners.MockitoJUnitRunner;
 import org.opendaylight.openflowjava.protocol.api.connection.ConnectionAdapter;
 import org.opendaylight.openflowplugin.api.OFConstants;
+import org.opendaylight.openflowplugin.api.openflow.connection.DeviceConnectionStatusProvider;
 import org.opendaylight.openflowplugin.api.openflow.md.core.ErrorHandler;
 import org.opendaylight.openflowplugin.api.openflow.md.core.HandshakeListener;
 import org.opendaylight.openflowplugin.impl.common.DeviceConnectionRateLimiter;
@@ -50,6 +53,8 @@ import org.slf4j.LoggerFactory;
 public class HandshakeManagerImplTest {
 
     private static final Logger LOG = LoggerFactory.getLogger(HandshakeManagerImplTest.class);
+    private static final int DEVICE_CONNECTION_RATE_LIMIT_PER_MIN = 0;
+    private static final int DEVICE_CONNECTION_HOLD_TIME_IN_SECONDS = 60;
 
     private HandshakeManagerImpl handshakeManager;
     @Mock
@@ -58,6 +63,8 @@ public class HandshakeManagerImplTest {
     private ErrorHandler errorHandler;
     @Mock
     private HandshakeListener handshakeListener;
+    @Mock
+    private DeviceConnectionStatusProvider deviceConnectionStatusProvider;
 
     private DeviceConnectionRateLimiter deviceConnectionRateLimiter;
 
@@ -67,8 +74,6 @@ public class HandshakeManagerImplTest {
 
     private int expectedErrors = 0;
 
-    private static final int DEVICE_CONNECTION_RATE_LIMIT_PER_MIN = 0;
-
     /**
      * invoked before every test method.
      */
@@ -77,12 +82,16 @@ public class HandshakeManagerImplTest {
         deviceConnectionRateLimiter = new DeviceConnectionRateLimiter(new OpenflowProviderConfigBuilder()
                 .setDeviceConnectionRateLimitPerMin(DEVICE_CONNECTION_RATE_LIMIT_PER_MIN).build());
         handshakeManager = new HandshakeManagerImpl(adapter, OFConstants.OFP_VERSION_1_3, OFConstants.VERSION_ORDER,
-                errorHandler, handshakeListener, false, deviceConnectionRateLimiter);
+                errorHandler, handshakeListener, false, deviceConnectionRateLimiter,
+                DEVICE_CONNECTION_HOLD_TIME_IN_SECONDS, deviceConnectionStatusProvider);
 
-        resultFeatures = RpcResultBuilder.success(new GetFeaturesOutputBuilder().build()).build();
+        resultFeatures = RpcResultBuilder.success(new GetFeaturesOutputBuilder().setDatapathId(BigInteger.ONE).build())
+                .build();
 
         Mockito.when(adapter.hello(any(HelloInput.class)))
                 .thenReturn(Futures.immediateFuture(RpcResultBuilder.success((HelloOutput) null).build()));
+        Mockito.when(deviceConnectionStatusProvider.getDeviceLastConnectionTime(BigInteger.ONE))
+                .thenReturn(LocalDateTime.now().minusSeconds(DEVICE_CONNECTION_HOLD_TIME_IN_SECONDS));
     }
 
     /**
index 2982270b0f811b9a44fc41cc2eb5bec60950c053..d70ba44312972e0bda5f3686cf56f1f2137f091b 100644 (file)
@@ -25,6 +25,7 @@ import org.mockito.runners.MockitoJUnitRunner;
 import org.opendaylight.openflowjava.protocol.api.connection.ConnectionAdapter;
 import org.opendaylight.openflowplugin.api.OFConstants;
 import org.opendaylight.openflowplugin.api.openflow.connection.ConnectionContext;
+import org.opendaylight.openflowplugin.api.openflow.connection.DeviceConnectionStatusProvider;
 import org.opendaylight.openflowplugin.api.openflow.connection.HandshakeContext;
 import org.opendaylight.openflowplugin.api.openflow.device.handlers.DeviceConnectedHandler;
 import org.opendaylight.openflowplugin.impl.connection.ConnectionContextImpl;
@@ -49,6 +50,8 @@ public class HandshakeListenerImplTest {
     private ConnectionAdapter connectionAdapter;
     @Mock
     private HandshakeContext handshakeContext;
+    @Mock
+    private DeviceConnectionStatusProvider deviceConnectionStatusProvider;
     @Captor
     private ArgumentCaptor<NodeId> nodeIdCaptor;
 
@@ -59,7 +62,8 @@ public class HandshakeListenerImplTest {
     public void setUp() {
         Mockito.when(connectionAdapter.barrier(ArgumentMatchers.any()))
                 .thenReturn(RpcResultBuilder.success(new BarrierOutputBuilder().build()).buildFuture());
-        connectionContextSpy = Mockito.spy(new ConnectionContextImpl(connectionAdapter));
+        connectionContextSpy = Mockito.spy(new ConnectionContextImpl(connectionAdapter,
+                deviceConnectionStatusProvider));
         Mockito.when(connectionContextSpy.getConnectionAdapter()).thenReturn(connectionAdapter);
         Mockito.when(features.getDatapathId()).thenReturn(Uint64.valueOf(10));
         handshakeListener = new HandshakeListenerImpl(connectionContextSpy, deviceConnectedHandler);
index 2015859a110efde3d0afd7931d2ecad6dff3eb06..ab43fce0f075db11cb4991ece460b5c5a0247018 100644 (file)
@@ -25,6 +25,7 @@ import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnitRunner;
 import org.opendaylight.openflowjava.protocol.api.connection.ConnectionAdapter;
 import org.opendaylight.openflowplugin.api.openflow.connection.ConnectionContext;
+import org.opendaylight.openflowplugin.api.openflow.connection.DeviceConnectionStatusProvider;
 import org.opendaylight.openflowplugin.impl.connection.ConnectionContextImpl;
 import org.opendaylight.openflowplugin.impl.util.ThreadPoolLoggingExecutor;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
@@ -53,6 +54,8 @@ public class SystemNotificationsListenerImplTest {
     private ConnectionAdapter connectionAdapter;
     @Mock
     private FeaturesReply features;
+    @Mock
+    private DeviceConnectionStatusProvider deviceConnectionStatusProvider;
 
     private ConnectionContext connectionContext;
     private ConnectionContextImpl connectionContextGolem;
@@ -66,7 +69,7 @@ public class SystemNotificationsListenerImplTest {
 
     @Before
     public void setUp() {
-        connectionContextGolem = new ConnectionContextImpl(connectionAdapter);
+        connectionContextGolem = new ConnectionContextImpl(connectionAdapter, deviceConnectionStatusProvider);
         connectionContextGolem.changeStateToWorking();
         connectionContextGolem.setNodeId(NODE_ID);
         connectionContextGolem.setFeatures(features);