OPNFLWPLUG-992 : Device connection rate limiter implemenation 57/70157/21
authorSomashekhar Javalagi <somashekhar.manohara.javalagi@ericsson.com>
Wed, 28 Mar 2018 09:37:36 +0000 (15:07 +0530)
committerSomashekhar Javalagi <somashekhar.manohara.javalagi@ericsson.com>
Mon, 23 Apr 2018 15:35:06 +0000 (15:35 +0000)
By default the device connection ratelimitter wll be disabled.

device-connection-rate-limit-per-min is the property available in
openflowplugin.cfg, which can be changed to control the rate at
which number of devices connecting the controller. This will be
useful in reducing load on the controller if there is more number
of switches connected at the time during cluster reboot.

Default value is set to 0, which disables device connection ratelimitter.

Change-Id: I02a53719114e63748ffaa340650927f7b11d6dc1
Signed-off-by: Somashekhar Javalagi <somashekhar.manohara.javalagi@ericsson.com>
13 files changed:
openflowplugin-api/src/main/java/org/opendaylight/openflowplugin/api/openflow/configuration/ConfigurationProperty.java
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/common/DeviceConnectionRateLimiter.java [new file with mode: 0644]
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/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/ConnectionManagerImplTest.java
openflowplugin-impl/src/test/java/org/opendaylight/openflowplugin/impl/connection/HandshakeManagerImplTest.java

index f212c104de0f4389bc57d4321d8da05eb4cfc4c3..bcc48b4de274bfbe90e60e5c54bea9bc2ec7924d 100644 (file)
@@ -111,7 +111,11 @@ public enum ConfigurationProperty {
     /**
      * Enable or disable equal role functionality.
      */
-    ENABLE_EQUAL_ROLE;
+    ENABLE_EQUAL_ROLE,
+    /**
+     * Device connection rate limit property type.
+     */
+    DEVICE_CONNECTION_RATE_LIMIT_PER_MIN;
 
     private static final Map<String, ConfigurationProperty> KEY_VALUE_MAP;
 
index 213d0b356fc33fad8d615379b817330c6d5d0526..21bd712762ad4b4bb9b41221568cfdefdf767429 100644 (file)
@@ -194,5 +194,13 @@ module openflow-provider-config {
             type boolean;
             default "false";
         }
+
+        leaf device-connection-rate-limit-per-min {
+            description "To limit the number of datapath nodes connecting to odl controller
+            instance per minute. Default value 0 indicates that the device connection rate
+            limiter will be disabled.";
+            type uint16;
+            default 0;
+        }
     }
 }
index 1d04928e10bc8ea39e573d8b5f5c3d4cde38a0da..68f7e76d9ed7cb67bd2f8f8fe17526f532987246 100644 (file)
 #
 # use-single-layer-serialization=true
 
+#
+# To limit the number of datapath nodes to be connected to the controller instance
+# per minute. When the default value of zero is set, then the device connection rate
+# limitter will be disabled. If it is set to any value, then only those many
+# number of datapath nodes are allowed to connect to the controller in a minute
+#
+# device-connection-rate-limit-per-min=0
+
 #############################################################################
 #                                                                           #
 #            Forwarding Rule Manager Application Configuration              #
diff --git a/openflowplugin-impl/src/main/java/org/opendaylight/openflowplugin/impl/common/DeviceConnectionRateLimiter.java b/openflowplugin-impl/src/main/java/org/opendaylight/openflowplugin/impl/common/DeviceConnectionRateLimiter.java
new file mode 100644 (file)
index 0000000..5d3862d
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2018 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.impl.common;
+
+import com.google.common.util.concurrent.RateLimiter;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.openflow.provider.config.rev160510.OpenflowProviderConfig;
+
+
+public class DeviceConnectionRateLimiter {
+    private final boolean doRateLimit;
+    private final AtomicReference<RateLimiter> rateLimiter;
+
+    public DeviceConnectionRateLimiter(final OpenflowProviderConfig config) {
+        int deviceConnectionRateLimitPerMin = config.getDeviceConnectionRateLimitPerMin();
+        double rateLimiterSize;
+        if (deviceConnectionRateLimitPerMin == 0) {
+            doRateLimit = false;
+            rateLimiterSize = 1;
+        } else {
+            doRateLimit = true;
+            rateLimiterSize = 1d / (60d / deviceConnectionRateLimitPerMin);
+        }
+        rateLimiter = new AtomicReference<>(RateLimiter.create(rateLimiterSize));
+
+    }
+
+    public boolean tryAquire() {
+        if (doRateLimit) {
+            return rateLimiter.get().tryAcquire(0, TimeUnit.SECONDS);
+        }
+        return true;
+    }
+}
\ No newline at end of file
index 15ef52d6941c7f31b0403a3c70cf8d1b37751d20..a1616dddeee740f20beb2b3dabff7c0e2c432e55 100644 (file)
@@ -94,6 +94,8 @@ public class ConfigurationServiceFactoryImpl implements ConfigurationServiceFact
                             providerConfig.getThreadPoolMaxThreads().getValue().toString())
                     .put(ConfigurationProperty.THREAD_POOL_TIMEOUT.toString(),
                             providerConfig.getThreadPoolTimeout().toString())
+                    .put(ConfigurationProperty.DEVICE_CONNECTION_RATE_LIMIT_PER_MIN.toString(),
+                            providerConfig.getDeviceConnectionRateLimitPerMin().toString())
                     .build());
 
             LOG.info("Loading configuration from '{}' configuration file", OFConstants.CONFIG_FILE_ID);
index 81d6cf25d523b44cfe6fbf162c0d4701a5e94da0..1adf381f778f5b5bd0b057552c35446f1cc2b662 100644 (file)
@@ -179,4 +179,10 @@ public class OpenFlowProviderConfigImpl implements OpenflowProviderConfig {
         return OpenflowProviderConfig.class;
     }
 
+    @Override
+    public Integer getDeviceConnectionRateLimitPerMin() {
+        return service.getProperty(ConfigurationProperty.DEVICE_CONNECTION_RATE_LIMIT_PER_MIN.toString(),
+                Integer::valueOf);
+    }
+
 }
index 9f35df5ed1bd0f6fb8c866a53331c0504b410ca1..61d2424a9c2c8d4f5257a3904ce1a7ec33494b68 100644 (file)
@@ -20,6 +20,7 @@ import org.opendaylight.openflowplugin.api.openflow.device.handlers.DeviceConnec
 import org.opendaylight.openflowplugin.api.openflow.device.handlers.DeviceDisconnectedHandler;
 import org.opendaylight.openflowplugin.api.openflow.md.core.HandshakeListener;
 import org.opendaylight.openflowplugin.api.openflow.md.core.HandshakeManager;
+import org.opendaylight.openflowplugin.impl.common.DeviceConnectionRateLimiter;
 import org.opendaylight.openflowplugin.impl.connection.listener.ConnectionReadyListenerImpl;
 import org.opendaylight.openflowplugin.impl.connection.listener.HandshakeListenerImpl;
 import org.opendaylight.openflowplugin.impl.connection.listener.OpenflowProtocolListenerInitialImpl;
@@ -37,11 +38,13 @@ public class ConnectionManagerImpl implements ConnectionManager {
     private DeviceConnectedHandler deviceConnectedHandler;
     private final OpenflowProviderConfig config;
     private final ExecutorService executorService;
+    private final DeviceConnectionRateLimiter deviceConnectionRateLimiter;
     private DeviceDisconnectedHandler deviceDisconnectedHandler;
 
     public ConnectionManagerImpl(final OpenflowProviderConfig config, final ExecutorService executorService) {
         this.config = config;
         this.executorService = executorService;
+        this.deviceConnectionRateLimiter = new DeviceConnectionRateLimiter(config);
     }
 
     @Override
@@ -78,7 +81,8 @@ public class ConnectionManagerImpl implements ConnectionManager {
                                                     final HandshakeListener handshakeListener) {
         HandshakeManagerImpl handshakeManager = new HandshakeManagerImpl(connectionAdapter,
                 OFConstants.VERSION_ORDER.get(0),
-                OFConstants.VERSION_ORDER, new ErrorHandlerSimpleImpl(), handshakeListener, BITMAP_NEGOTIATION_ENABLED);
+                OFConstants.VERSION_ORDER, new ErrorHandlerSimpleImpl(), handshakeListener, BITMAP_NEGOTIATION_ENABLED,
+                deviceConnectionRateLimiter);
 
         return handshakeManager;
     }
index 5d32cf64670a024ad3388e79ed7d13408bae1876..a4dbfc649e37ea89f8b0759b2bea277674f22683 100644 (file)
@@ -24,6 +24,7 @@ import org.opendaylight.openflowplugin.api.OFConstants;
 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;
+import org.opendaylight.openflowplugin.impl.common.DeviceConnectionRateLimiter;
 import org.opendaylight.openflowplugin.impl.util.MessageFactory;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.GetFeaturesInputBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.GetFeaturesOutput;
@@ -57,6 +58,8 @@ public class HandshakeManagerImpl implements HandshakeManager {
 
     private boolean useVersionBitmap; // not final just for unit test
 
+    private final DeviceConnectionRateLimiter deviceConnectionRateLimiter;
+
     /**
      * Constructor.
      *
@@ -68,13 +71,15 @@ public class HandshakeManagerImpl implements HandshakeManager {
      * @param useVersionBitmap  should use negotiation bit map
      */
     public HandshakeManagerImpl(ConnectionAdapter connectionAdapter, Short highestVersion, List<Short> versionOrder,
-            ErrorHandler errorHandler, HandshakeListener handshakeListener, boolean useVersionBitmap) {
+                                ErrorHandler errorHandler, HandshakeListener handshakeListener,
+                                boolean useVersionBitmap, DeviceConnectionRateLimiter deviceConnectionRateLimiter) {
         this.highestVersion = highestVersion;
         this.versionOrder = versionOrder;
         this.connectionAdapter = connectionAdapter;
         this.errorHandler = errorHandler;
         this.handshakeListener = handshakeListener;
         this.useVersionBitmap = useVersionBitmap;
+        this.deviceConnectionRateLimiter = deviceConnectionRateLimiter;
     }
 
     @Override
@@ -385,6 +390,12 @@ public class HandshakeManagerImpl implements HandshakeManager {
                         LOG.trace("features are back");
                         if (rpcFeatures.isSuccessful()) {
                             GetFeaturesOutput featureOutput = rpcFeatures.getResult();
+                            if (!deviceConnectionRateLimiter.tryAquire()) {
+                                LOG.warn("Openflowplugin hit the device connection rate limit threshold. Denying"
+                                        + " the connection from device {}", featureOutput.getDatapathId());
+                                connectionAdapter.disconnect();
+                                return;
+                            }
 
                             LOG.debug("obtained features: datapathId={}", featureOutput.getDatapathId());
                             LOG.debug("obtained features: auxiliaryId={}", featureOutput.getAuxiliaryId());
index f75bc45f4123c1ddac8c624d0889b6ab66d581f5..c7f8e4052e31d01f0a7ce044032d5474a572a4f1 100644 (file)
@@ -85,6 +85,7 @@ public class OpenFlowPluginProviderImplTest {
     private static final long THREAD_POOL_TIMEOUT = 60;
     private static final long BASIC_TIMER_DELAY = 1L;
     private static final boolean USE_SINGLE_LAYER_SERIALIZATION = false;
+    private static final int DEVICE_CONNECTION_RATE_LIMIT_PER_MIN = 0;
 
     @Before
     public void setUp() throws Exception {
@@ -109,6 +110,8 @@ public class OpenFlowPluginProviderImplTest {
                 .thenReturn(GLOBAL_NOTIFICATION_QUOTA);
         when(configurationService.getProperty(eq(ConfigurationProperty.BASIC_TIMER_DELAY.toString()), any()))
                 .thenReturn(BASIC_TIMER_DELAY);
+        when(configurationService.getProperty(eq(ConfigurationProperty.DEVICE_CONNECTION_RATE_LIMIT_PER_MIN.toString()),
+                any())).thenReturn(DEVICE_CONNECTION_RATE_LIMIT_PER_MIN);
     }
 
     @Test
index a18da729a05c87dd82f15ed9dd4c3a6818d72733..e5010013aead1de46e8febbe91bac6d2c7560676 100644 (file)
@@ -37,7 +37,7 @@ import org.osgi.service.cm.ConfigurationAdmin;
 
 @RunWith(MockitoJUnitRunner.class)
 public class ConfigurationServiceFactoryImplTest {
-    private static final int CONFIG_PROP_COUNT = 22;
+    private static final int CONFIG_PROP_COUNT = 23;
     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;
@@ -55,6 +55,7 @@ public class ConfigurationServiceFactoryImplTest {
     private static final int THREAD_POOL_MIN_THREADS_UPDATE = 4;
     private static final int THREAD_POOL_MAX_THREADS = 1000;
     private static final long THREAD_POOL_TIMEOUT = 60;
+    private static final int DEVICE_CONNECTION_RATE_LIMIT_PER_MIN = 0;
 
     @Mock
     private OpenflowProviderConfig config;
@@ -101,6 +102,7 @@ public class ConfigurationServiceFactoryImplTest {
         when(config.getThreadPoolMinThreads()).thenReturn(THREAD_POOL_MIN_THREADS);
         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);
 
         final Dictionary<String, Object> properties = new Hashtable<>();
         properties.put(ConfigurationProperty.IS_STATISTICS_POLLING_ON.toString(), IS_STATISTICS_POLLING_ON);
index 46f0da45b825f2ca99310b9d68a9e2394786dbb7..55af2a5f47556bab288e19f8f22ff63684709836 100644 (file)
@@ -40,6 +40,7 @@ public class OpenFlowProviderConfigImplTest {
     private static final Integer THREAD_POOL_MIN_THREADS = 3;
     private static final Integer THREAD_POOL_MAX_THREADS = 1000;
     private static final Long THREAD_POOL_TIMEOUT = 60L;
+    private static final Integer DEVICE_CONNECTION_RATE_LIMIT_PER_MIN = 0;
 
     @Mock
     private ConfigurationService configurationService;
@@ -79,6 +80,8 @@ public class OpenFlowProviderConfigImplTest {
                 .thenReturn(THREAD_POOL_MAX_THREADS);
         when(configurationService.getProperty(eq(ConfigurationProperty.THREAD_POOL_TIMEOUT.toString()), any()))
                 .thenReturn(THREAD_POOL_TIMEOUT);
+        when(configurationService.getProperty(eq(ConfigurationProperty.DEVICE_CONNECTION_RATE_LIMIT_PER_MIN.toString()),
+                any())).thenReturn(DEVICE_CONNECTION_RATE_LIMIT_PER_MIN);
         openflowProviderConfig = new OpenFlowProviderConfigImpl(configurationService);
     }
 
@@ -168,4 +171,9 @@ public class OpenFlowProviderConfigImplTest {
         assertEquals(USE_SINGLE_LAYER_SERIALIZATION, openflowProviderConfig.isUseSingleLayerSerialization());
     }
 
+    @Test
+    public void getDeviceConnectionRateLimitPerMin() throws Exception {
+        assertEquals(DEVICE_CONNECTION_RATE_LIMIT_PER_MIN, openflowProviderConfig.getDeviceConnectionRateLimitPerMin());
+    }
+
 }
\ No newline at end of file
index 1dfd70ea5dfeda35f03a3ffbadb2a6d28e53d62a..cec8c64a361dbb93e8683e8abb80125a19b4c61b 100644 (file)
@@ -61,6 +61,7 @@ public class ConnectionManagerImplTest {
     private ArgumentCaptor<OpenflowProtocolListener> ofpListenerAC;
 
     private static final long ECHO_REPLY_TIMEOUT = 500;
+    private static final int DEVICE_CONNECTION_RATE_LIMIT_PER_MIN = 0;
 
     @Before
     public void setUp() {
@@ -70,6 +71,7 @@ public class ConnectionManagerImplTest {
 
         connectionManagerImpl = new ConnectionManagerImpl(new OpenflowProviderConfigBuilder()
                 .setEchoReplyTimeout(new NonZeroUint32Type(ECHO_REPLY_TIMEOUT))
+                .setDeviceConnectionRateLimitPerMin(DEVICE_CONNECTION_RATE_LIMIT_PER_MIN)
                 .build(), threadPool);
 
         connectionManagerImpl.setDeviceConnectedHandler(deviceConnectedHandler);
index 83a4c5df04ccaf16618a83a710e65f2cf01f1655..650a7b5160b22b67c8cd9a303a1de5ee4a5301dc 100644 (file)
@@ -25,6 +25,7 @@ import org.opendaylight.openflowjava.protocol.api.connection.ConnectionAdapter;
 import org.opendaylight.openflowplugin.api.OFConstants;
 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;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.common.types.rev130731.HelloElementType;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.GetFeaturesInput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.GetFeaturesOutput;
@@ -33,6 +34,7 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731
 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.HelloMessageBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.hello.Elements;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.hello.ElementsBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.openflow.provider.config.rev160510.OpenflowProviderConfigBuilder;
 import org.opendaylight.yangtools.yang.common.RpcResult;
 import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
 import org.slf4j.Logger;
@@ -54,19 +56,25 @@ public class HandshakeManagerImplTest {
     @Mock
     private HandshakeListener handshakeListener;
 
+    private DeviceConnectionRateLimiter deviceConnectionRateLimiter;
+
     private RpcResult<GetFeaturesOutput> resultFeatures;
 
     private final long helloXid = 42L;
 
     private int expectedErrors = 0;
 
+    private static final int DEVICE_CONNECTION_RATE_LIMIT_PER_MIN = 0;
+
     /**
      * invoked before every test method.
      */
     @Before
     public void setUp() {
+        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);
+                errorHandler, handshakeListener, false, deviceConnectionRateLimiter);
 
         resultFeatures = RpcResultBuilder.success(new GetFeaturesOutputBuilder().build()).build();