From 9b0fbcc63e1abfad4c9f4f4991588854f2a329cb Mon Sep 17 00:00:00 2001 From: Paul Nicolucci Date: Wed, 9 Apr 2025 21:27:00 -0400 Subject: [PATCH 1/4] Ensure MaxOpenConnectionsHandler is sharable and not recreated for each connection --- .../netty/internal/tcp/TCPChannelInitializerImpl.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/dev/io.openliberty.netty.internal.impl/src/io/openliberty/netty/internal/tcp/TCPChannelInitializerImpl.java b/dev/io.openliberty.netty.internal.impl/src/io/openliberty/netty/internal/tcp/TCPChannelInitializerImpl.java index 87f185fe7c95..cd0a847b4135 100644 --- a/dev/io.openliberty.netty.internal.impl/src/io/openliberty/netty/internal/tcp/TCPChannelInitializerImpl.java +++ b/dev/io.openliberty.netty.internal.impl/src/io/openliberty/netty/internal/tcp/TCPChannelInitializerImpl.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2021, 2023 IBM Corporation and others. + * Copyright (c) 2021, 2025 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at @@ -34,13 +34,13 @@ public class TCPChannelInitializerImpl extends ChannelInitializerWrapper { TCPMessageConstants.TCP_BUNDLE, TCPChannelInitializerImpl.class.getName()); TCPConfigurationImpl config; - NettyFrameworkImpl bundle; - + MaxOpenConnectionsHandler maxHandler; public TCPChannelInitializerImpl(BootstrapConfiguration config, NettyFrameworkImpl bundle) { this.bundle = bundle; this.config = (TCPConfigurationImpl) config; + maxHandler = new MaxOpenConnectionsHandler(this.config.getMaxOpenConnections()); } @Override @@ -59,7 +59,6 @@ protected void initChannel(Channel channel) throws Exception { if (config.getInactivityTimeout() > 0) { channel.pipeline().addLast(NettyConstants.INACTIVITY_TIMEOUT_HANDLER_NAME, new InactivityTimeoutHandler(0, 0, config.getInactivityTimeout(), TimeUnit.MILLISECONDS)); } - MaxOpenConnectionsHandler maxHandler = new MaxOpenConnectionsHandler(config.getMaxOpenConnections()); if (config.getAccessLists() != null) { AccessListHandler includeHandler = new AccessListHandler(config.getAccessLists()); channel.pipeline().addLast(NettyConstants.ACCESSLIST_HANDLER_NAME, includeHandler); From e0bab417b7f03f2a3645afd03306ad031bf8514b Mon Sep 17 00:00:00 2001 From: Paul Nicolucci Date: Mon, 14 Apr 2025 11:37:48 -0400 Subject: [PATCH 2/4] Update MaxOpenConnectionsHandler to ensure a channelName is available for the warning message --- .../internal/tcp/MaxOpenConnectionsHandler.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/dev/io.openliberty.netty.internal.impl/src/io/openliberty/netty/internal/tcp/MaxOpenConnectionsHandler.java b/dev/io.openliberty.netty.internal.impl/src/io/openliberty/netty/internal/tcp/MaxOpenConnectionsHandler.java index 29807c548f72..7dd13c549275 100644 --- a/dev/io.openliberty.netty.internal.impl/src/io/openliberty/netty/internal/tcp/MaxOpenConnectionsHandler.java +++ b/dev/io.openliberty.netty.internal.impl/src/io/openliberty/netty/internal/tcp/MaxOpenConnectionsHandler.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2021, 2022 IBM Corporation and others. + * Copyright (c) 2021, 2025 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at @@ -14,6 +14,7 @@ import com.ibm.websphere.ras.Tr; import com.ibm.websphere.ras.TraceComponent; +import io.netty.channel.Channel; import io.netty.channel.ChannelHandler.Sharable; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; @@ -47,6 +48,15 @@ public void channelActive(ChannelHandlerContext ctx) throws Exception { long currentTime = System.currentTimeMillis(); if (currentTime > (lastConnExceededTime + 600000L)) { String channelName = ctx.channel().attr(ConfigConstants.NAME_KEY).get(); + + // If the channelName is null check the parent for a name. + if (channelName == null) { + Channel parentChannel = ctx.channel().parent(); + if (parentChannel != null) { + channelName = parentChannel.attr(ConfigConstants.NAME_KEY).get(); + } + } + Tr.warning(tc, TCPMessageConstants.MAX_CONNS_EXCEEDED, channelName, maxConnections); lastConnExceededTime = currentTime; } From 8c454a3bcb5d298333810b59326ac4f22cab588e Mon Sep 17 00:00:00 2001 From: Paul Nicolucci Date: Mon, 14 Apr 2025 11:44:18 -0400 Subject: [PATCH 3/4] Ensure ExternalName is set before a validation error can occur --- .../internal/tcp/TCPConfigurationImpl.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/dev/io.openliberty.netty.internal.impl/src/io/openliberty/netty/internal/tcp/TCPConfigurationImpl.java b/dev/io.openliberty.netty.internal.impl/src/io/openliberty/netty/internal/tcp/TCPConfigurationImpl.java index 7d7effbcf3e1..3979f266fa8e 100644 --- a/dev/io.openliberty.netty.internal.impl/src/io/openliberty/netty/internal/tcp/TCPConfigurationImpl.java +++ b/dev/io.openliberty.netty.internal.impl/src/io/openliberty/netty/internal/tcp/TCPConfigurationImpl.java @@ -118,6 +118,10 @@ public TCPConfigurationImpl(Map options, boolean inbound) throws this.channelProperties = options; if (this.channelProperties != null) { + // Set the name of the channel before setting the values of the other properties. This ensures it is available + // if a validation error occurs. + this.externalName = (String) this.channelProperties.get(ConfigConstants.EXTERNAL_NAME); + // read in values now, to save time reading them in each time // they are requested setValues(); @@ -162,7 +166,10 @@ public AddressAndHostNameAccessLists getAccessLists() { //@formatter:off private boolean skipKey(String key) { - return key.equals("id") || + // We set externalName in the constructor. If we don't skip it while processing the rest of the properties the below error occurs: + // CWWKO0212W: TCP Channel defaultHttpEndpoint has been constructed with an incorrect configuration property. Property Name: ExternalName Value: defaultHttpEndpoint + return key.equals(ConfigConstants.EXTERNAL_NAME) || + key.equals("id") || key.equals("type") || key.startsWith("service.") || key.startsWith("component.") || @@ -201,13 +208,7 @@ private void setValues() throws NettyException { value = entry.getValue(); if (skipKey(key)) { - // skip osgi standard properties - continue; - } - - // add the name for the channel - if (key.equalsIgnoreCase(ConfigConstants.EXTERNAL_NAME)) { - this.externalName = (String) value; + // skip osgi standard properties and ExternalName continue; } From aebf1161881c144356fe99d4fcb5876c68ed5199 Mon Sep 17 00:00:00 2001 From: Paul Nicolucci Date: Wed, 9 Apr 2025 21:21:27 -0400 Subject: [PATCH 4/4] Split max open connection tests into test methods, add additional tests --- .../transport/http_fat/FATSuite.java | 2 +- .../http_fat/MaxOpenConnectionsTest.java | 146 ----- .../http_fat/MaxOpenConnectionsTests.java | 506 ++++++++++++++++++ 3 files changed, 507 insertions(+), 147 deletions(-) delete mode 100644 dev/io.openliberty.transport.http_fat/fat/src/io/openliberty/transport/http_fat/MaxOpenConnectionsTest.java create mode 100644 dev/io.openliberty.transport.http_fat/fat/src/io/openliberty/transport/http_fat/MaxOpenConnectionsTests.java diff --git a/dev/io.openliberty.transport.http_fat/fat/src/io/openliberty/transport/http_fat/FATSuite.java b/dev/io.openliberty.transport.http_fat/fat/src/io/openliberty/transport/http_fat/FATSuite.java index 061aff113e2b..51618f03b552 100644 --- a/dev/io.openliberty.transport.http_fat/fat/src/io/openliberty/transport/http_fat/FATSuite.java +++ b/dev/io.openliberty.transport.http_fat/fat/src/io/openliberty/transport/http_fat/FATSuite.java @@ -23,7 +23,7 @@ @RunWith(Suite.class) @SuiteClasses({ AccessListsTests.class, - MaxOpenConnectionsTest.class, + MaxOpenConnectionsTests.class, PortOpenRetriesTests.class, SoLingerTests.class }) diff --git a/dev/io.openliberty.transport.http_fat/fat/src/io/openliberty/transport/http_fat/MaxOpenConnectionsTest.java b/dev/io.openliberty.transport.http_fat/fat/src/io/openliberty/transport/http_fat/MaxOpenConnectionsTest.java deleted file mode 100644 index b3717cda4a01..000000000000 --- a/dev/io.openliberty.transport.http_fat/fat/src/io/openliberty/transport/http_fat/MaxOpenConnectionsTest.java +++ /dev/null @@ -1,146 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2025 IBM Corporation and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - *******************************************************************************/ -package io.openliberty.transport.http_fat; - -import static org.junit.Assert.assertNotNull; - -import java.net.Socket; -import java.net.URL; -import java.util.logging.Logger; - -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; - -import com.ibm.websphere.simplicity.config.HttpEndpoint; -import com.ibm.websphere.simplicity.config.ServerConfiguration; - -import componenttest.annotation.Server; -import componenttest.custom.junit.runner.FATRunner; -import componenttest.custom.junit.runner.Mode; -import componenttest.custom.junit.runner.Mode.TestMode; -import componenttest.topology.impl.LibertyServer; -import componenttest.topology.utils.HttpUtils; - -/** - * Test to ensure that the tcpOptions maxOpenConnections works. - */ -@RunWith(FATRunner.class) -@Mode(TestMode.FULL) -public class MaxOpenConnectionsTest { - - static final Logger LOG = Logger.getLogger(MaxOpenConnectionsTest.class.getName()); - - @Server("MaxOpenConnections") - public static LibertyServer server; - - @BeforeClass - public static void setup() throws Exception { - // Start the server and use the class name so we can find logs easily. - server.startServer(MaxOpenConnectionsTest.class.getSimpleName() + ".log"); - - } - - @AfterClass - public static void tearDown() throws Exception { - // Stop the server - if (server != null && server.isStarted()) { - // CWWKO0222W: TCP Channel defaultHttpEndpoint has exceeded the maximum number of open connections 2. - server.stopServer("CWWKO0222W"); - } - - } - - /** - * The test will first check the default value of maxOpenConnections by searching the trace file. - * - * Next the server will set maxOpenConnections to 2 and validate the value by searching the trace file. - * - * Three connections are then created. Since the number of connections is 1 greater than maxOpenConnections - * the following exception should be found in the logs: - * CWWKO0222W: TCP Channel defaultHttpEndpoint has exceeded the maximum number of open connections 2. - * - * The below configuration will be used to set maxOpenConnections to 2: - * - * - */ - @Test - public void testMaxOpenConnections() throws Exception { - URL url = HttpUtils.createURL(server, "/"); - - // Validate that maxOpenConnections default is 128000. - assertNotNull("The default value of maxOpenConnections was not 128000!", server.waitForStringInTrace("maxOpenConnections: 128000")); - - // Set maxOpenConnections to 2. - server.saveServerConfiguration(); - - ServerConfiguration configuration = server.getServerConfiguration(); - LOG.info("Server configuration that was saved: " + configuration); - - HttpEndpoint httpEndpoint = configuration.getHttpEndpoints().getById("defaultHttpEndpoint"); - httpEndpoint.getTcpOptions().setMaxOpenConnections(new Integer(2)); - - server.setMarkToEndOfLog(); - server.setTraceMarkToEndOfDefaultTrace(); - server.updateServerConfiguration(configuration); - server.waitForConfigUpdateInLogUsingMark(null); - - // Validate that maxOpenConnections is set to 2. - assertNotNull("The configured value of maxOpenConnections was not 2!", server.waitForStringInTraceUsingMark("maxOpenConnections: 2")); - - // Ensure the TCP Channel has started. - // CWWKO0219I: TCP Channel defaultHttpEndpoint has been started and is now listening for requests on host * (IPv4) port 8010. - assertNotNull("The TCP Channel was not started!", server.waitForStringInLogUsingMark("CWWKO0219I")); - - // Create three Socket connections. - Socket socket1 = null; - Socket socket2 = null; - Socket socket3 = null; - try { - LOG.info("Creating the first connection."); - socket1 = new Socket(url.getHost(), url.getPort()); - socket1.setKeepAlive(true); - - LOG.info("Creating the second connection."); - socket2 = new Socket(url.getHost(), url.getPort()); - socket2.setKeepAlive(true); - - LOG.info("Creating the third connection."); - socket3 = new Socket(url.getHost(), url.getPort()); - socket3.setKeepAlive(true); - } catch (Exception e) { - LOG.info("Exception occurred when creating Sockets: " + e.toString()); - } finally { - // We should get an exception for the third connection since the server has maxOpenConnections set to 2. - // Wait for the message in the logs before closing the sockets to ensure they are not closed prematurely. - assertNotNull("The CWWKO0222W message was not found in the logs!", server.waitForStringInLogUsingMark("CWWKO0222W")); - - if (socket1 != null) { - LOG.info("Closing the first connection."); - socket1.close(); - } - if (socket2 != null) { - LOG.info("Closing the second connection."); - socket2.close(); - } - if (socket3 != null) { - LOG.info("Closing the third connection."); - socket3.close(); - } - - // Restore the server to the default state. - server.setMarkToEndOfLog(); - server.restoreServerConfiguration(); - server.waitForConfigUpdateInLogUsingMark(null); - } - } - -} diff --git a/dev/io.openliberty.transport.http_fat/fat/src/io/openliberty/transport/http_fat/MaxOpenConnectionsTests.java b/dev/io.openliberty.transport.http_fat/fat/src/io/openliberty/transport/http_fat/MaxOpenConnectionsTests.java new file mode 100644 index 000000000000..fb97c6dfdf09 --- /dev/null +++ b/dev/io.openliberty.transport.http_fat/fat/src/io/openliberty/transport/http_fat/MaxOpenConnectionsTests.java @@ -0,0 +1,506 @@ +/******************************************************************************* + * Copyright (c) 2025 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package io.openliberty.transport.http_fat; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.net.Socket; +import java.net.URL; +import java.util.List; +import java.util.logging.Logger; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import com.ibm.websphere.simplicity.RemoteFile; +import com.ibm.websphere.simplicity.config.HttpEndpoint; +import com.ibm.websphere.simplicity.config.ServerConfiguration; + +import componenttest.annotation.AllowedFFDC; +import componenttest.annotation.Server; +import componenttest.custom.junit.runner.FATRunner; +import componenttest.custom.junit.runner.Mode; +import componenttest.custom.junit.runner.Mode.TestMode; +import componenttest.topology.impl.LibertyServer; +import componenttest.topology.utils.HttpUtils; + +/** + * Tests to ensure that the tcpOptions maxOpenConnections works. + */ +@RunWith(FATRunner.class) +public class MaxOpenConnectionsTests { + + private static final String CLASS_NAME = MaxOpenConnectionsTests.class.getName(); + private static final Logger LOG = Logger.getLogger(CLASS_NAME); + private static boolean runningNetty = false; + + @Server("MaxOpenConnections") + public static LibertyServer server; + + @BeforeClass + public static void setup() throws Exception { + // Start the server and use the class name so we can find logs easily. + server.startServer(MaxOpenConnectionsTests.class.getSimpleName() + ".log"); + + // Go through logs and check if Netty is being used + // Wait for the TCP Channel to finish loading and get the TCP Channel started message. + // CWWKO0219I: TCP Channel defaultHttpEndpoint has been started and is now listening for requests on host * (IPv6) port 8010. + String tcpChannelMessage = server.waitForStringInLog("CWWKO0219I: TCP Channel defaultHttpEndpoint"); + LOG.info("Endpoint: " + tcpChannelMessage); + + runningNetty = tcpChannelMessage.contains("io.openliberty.netty.internal.tcp.TCPUtils"); + LOG.info("Netty? " + runningNetty); + } + + @AfterClass + public static void tearDown() throws Exception { + // Stop the server + if (server != null && server.isStarted()) { + // CWWKO0222W: TCP Channel defaultHttpEndpoint has exceeded the maximum number of open connections 2. + // CWWKO0029E: An exception was generated when initializing chain CHAIN-defaultHttpEndpoint because of exception com.ibm.wsspi.channelfw.exception.ChannelException: A TCP Channel has been constructed with incorrect configuration property value, Channel Name: defaultHttpEndpoint name: maxOpenConnections value: -1 minimum Value: 1 maximum Value: 1280000 + // CWWKO0211E: TCP Channel defaultHttpEndpoint has been constructed with an incorrect configuration property value. Name: maxOpenConnections Value: -1 Valid Range: Minimum 1, Maximum 1280000 + if (runningNetty) { + server.stopServer("CWWKO0222W", "CWWKO0211E"); + } else { + server.stopServer("CWWKO0222W", "CWWKO0029E", "CWWKO0211E"); + } + } + } + + /** + * Save the server configuration before each test, this should be the default server + * configuration. + * + * @throws Exception + */ + @Before + public void beforeTest() throws Exception { + server.saveServerConfiguration(); + } + + /** + * Restore the server configuration to the default state after each test. + * + * @throws Exception + */ + @After + public void afterTest() throws Exception { + // Restore the servers to their default state. + server.setMarkToEndOfLog(); + server.setTraceMarkToEndOfDefaultTrace(); + server.restoreServerConfiguration(); + server.waitForConfigUpdateInLogUsingMark(null); + } + + /** + * The test will check the default value of maxOpenConnections by searching the trace file. + * + * The default value is 128000. + * + * @throws Exception + */ + @Test + public void testMaxOpenConnections_default() throws Exception { + // Validate that maxOpenConnections default is 128000. + assertNotNull("The default value of maxOpenConnections was not 128000!", server.waitForStringInTrace("maxOpenConnections: 128000")); + } + + /** + * The test will set maxOpenConnections to a value of 2 and validate in the trace file that + * the correct value is being used. + * + * The below configuration will be used to set maxOpenConnections to 2: + * + * + * @throws Exception + */ + @Test + @Mode(TestMode.FULL) + public void testMaxOpenConnections_not_default() throws Exception { + ServerConfiguration configuration = server.getServerConfiguration(); + LOG.info("Server configuration that the test started with: " + configuration); + + HttpEndpoint httpEndpoint = configuration.getHttpEndpoints().getById("defaultHttpEndpoint"); + httpEndpoint.getTcpOptions().setMaxOpenConnections(2); + + server.setMarkToEndOfLog(); + server.setTraceMarkToEndOfDefaultTrace(); + server.updateServerConfiguration(configuration); + server.waitForConfigUpdateInLogUsingMark(null); + + // Validate that maxOpenConnections is set to 2. + assertNotNull("The configured value of maxOpenConnections was not 2!", server.waitForStringInTraceUsingMark("maxOpenConnections: 2")); + } + + /** + * The test will set maxOpenConnections to a value of -1 and validate that is an incorrect configuration. + * + * The below configuration will be used to set maxOpenConnections to -1: + * + * + * ExpectedFFDC: + * Exception = com.ibm.wsspi.channelfw.exception.ChannelException + * Source = com.ibm.ws.channelfw.internal.ChannelFrameworkImpl.initChainInternal + * probeid = 2206 + * Stack Dump = com.ibm.wsspi.channelfw.exception.ChannelException: A TCP Channel has been constructed with incorrect configuration property value, Channel Name: + * defaultHttpEndpoint name: maxOpenConnections value: -1 minimum Value: 1 maximum Value: 1280000 + * + * ExpectedFFDC: + * Exception = com.ibm.wsspi.channelfw.exception.ChannelException + * Source = com.ibm.ws.tcpchannel.internal.TCPChannelConfiguration + * probeid = 102 + * Stack Dump = com.ibm.wsspi.channelfw.exception.ChannelException: A TCP Channel has been constructed with incorrect configuration property value, Channel Name: + * defaultHttpEndpoint name: maxOpenConnections value: -1 minimum Value: 1 maximum Value: 1280000 + * + * @throws Exception + */ + @Test + @Mode(TestMode.FULL) + @AllowedFFDC({ "com.ibm.wsspi.channelfw.exception.ChannelException", "io.openliberty.netty.internal.exception.NettyException" }) + public void testMaxOpenConnections_invalid() throws Exception { + String expectedFFDC; + + if (!runningNetty) { + expectedFFDC = "com.ibm.wsspi.channelfw.exception.ChannelException"; + } else { + expectedFFDC = "io.openliberty.netty.internal.exception.NettyException"; + } + + ServerConfiguration configuration = server.getServerConfiguration(); + LOG.info("Server configuration that the test started with: " + configuration); + + HttpEndpoint httpEndpoint = configuration.getHttpEndpoints().getById("defaultHttpEndpoint"); + httpEndpoint.getTcpOptions().setMaxOpenConnections(-1); + + server.setMarkToEndOfLog(); + server.setTraceMarkToEndOfDefaultTrace(); + server.updateServerConfiguration(configuration); + server.waitForConfigUpdateInLogUsingMark(null); + + // Validate error messages due to an invalid configuration. + // When not running Netty there are two unique message, with Netty the same message is output twice. + if (!runningNetty) { + assertNotNull("CWWKO0211E was not found and should have been!", server.waitForStringInLogUsingMark("CWWKO0211E")); + assertNotNull("CWWKO0029E was not found and should have been!", server.waitForStringInLogUsingMark("CWWKO0029E")); + } else { + assertTrue("CWWKO0211E was not found twice and should have been!", server.waitForMultipleStringsInLogUsingMark(2, "CWWKO0211E") == 2); + } + + assertTrue("There were not two FFDCs created!", server.waitForMultipleStringsInLogUsingMark(2, "FFDC1015I") == 2); + + List ffdcFileNames = server.listFFDCFiles(server.getServerName()); + + // There should be at least 2 FFDCs depending on test ordering there could be more. + assertTrue("There was not exactly two FFDCs generated!", ffdcFileNames.size() >= 2); + + // Get the latest two FFDCs. + RemoteFile ffdcFile1 = server.getFFDCLogFile(ffdcFileNames.get(ffdcFileNames.size() - 1)); + RemoteFile ffdcFile2 = server.getFFDCLogFile(ffdcFileNames.get(ffdcFileNames.size() - 2)); + + List lines = server.findStringsInFileInLibertyServerRoot(expectedFFDC, "logs/ffdc/" + ffdcFile1.getName()); + assertTrue("The expected FFDC: " + expectedFFDC + " was not found!", lines.size() > 0); + + lines = server.findStringsInFileInLibertyServerRoot(expectedFFDC, "logs/ffdc/" + ffdcFile2.getName()); + assertTrue("The expected FFDC: " + expectedFFDC + " was not found!", lines.size() > 0); + } + + /** + * The test will set maxOpenConnections to a value of -1 and validate that is an incorrect configuration. + * + * The test will also update the name of the defaultHttpEndpoint to updatedHttpEndpoint and validate the + * correct name is used in the error messages. + * + * The below configuration will be used to set maxOpenConnections to -1: + * + * + * ExpectedFFDC: + * Exception = com.ibm.wsspi.channelfw.exception.ChannelException + * Source = com.ibm.ws.channelfw.internal.ChannelFrameworkImpl.initChainInternal + * probeid = 2206 + * Stack Dump = com.ibm.wsspi.channelfw.exception.ChannelException: A TCP Channel has been constructed with incorrect configuration property value, Channel Name: + * defaultHttpEndpoint name: maxOpenConnections value: -1 minimum Value: 1 maximum Value: 1280000 + * + * ExpectedFFDC: + * Exception = com.ibm.wsspi.channelfw.exception.ChannelException + * Source = com.ibm.ws.tcpchannel.internal.TCPChannelConfiguration + * probeid = 102 + * Stack Dump = com.ibm.wsspi.channelfw.exception.ChannelException: A TCP Channel has been constructed with incorrect configuration property value, Channel Name: + * defaultHttpEndpoint name: maxOpenConnections value: -1 minimum Value: 1 maximum Value: 1280000 + * + * @throws Exception + */ + @Test + @Mode(TestMode.FULL) + @AllowedFFDC({ "com.ibm.wsspi.channelfw.exception.ChannelException", "io.openliberty.netty.internal.exception.NettyException" }) + public void testMaxOpenConnections_invalid_updateEndpointName() throws Exception { + String expectedFFDC; + + if (!runningNetty) { + expectedFFDC = "com.ibm.wsspi.channelfw.exception.ChannelException"; + } else { + expectedFFDC = "io.openliberty.netty.internal.exception.NettyException"; + } + + ServerConfiguration configuration = server.getServerConfiguration(); + LOG.info("Server configuration that the test started with: " + configuration); + + HttpEndpoint httpEndpoint = configuration.getHttpEndpoints().getById("defaultHttpEndpoint"); + httpEndpoint.setId("updatedHttpEndpoint"); + httpEndpoint.getTcpOptions().setMaxOpenConnections(-1); + + server.setMarkToEndOfLog(); + server.setTraceMarkToEndOfDefaultTrace(); + server.updateServerConfiguration(configuration); + server.waitForConfigUpdateInLogUsingMark(null); + + // Validate error messages due to an invalid configuration. This message should reference the updated httpEndpoint. + // When not running Netty there are two unique message, with Netty there is only one. + if (!runningNetty) { + assertNotNull("CWWKO0211E was not found and should have been!", server.waitForStringInLogUsingMark("CWWKO0211E: TCP Channel updatedHttpEndpoint")); + assertNotNull("CWWKO0029E was not found and should have been!", server.waitForStringInLogUsingMark("CWWKO0029E")); + } else { + // PAN: We fail here b/c there is only one message when we update the endpoint name, looking at the trace we look to then start using the old endpoint name. + // To be clear the message is only output one time! + //assertTrue("CWWKO0211E was not found twice and should have been!", server.waitForMultipleStringsInLogUsingMark(2, "CWWKO0211E: TCP Channel updatedHttpEndpoint") == 2); + // For example the below also fails: + assertTrue("CWWKO0211E was not found twice and should have been!", server.waitForMultipleStringsInLogUsingMark(2, "CWWKO0211E") == 2); + } + + assertTrue("There were not two FFDCs created!", server.waitForMultipleStringsInLogUsingMark(2, "FFDC1015I") == 2); + + List ffdcFileNames = server.listFFDCFiles(server.getServerName()); + + // There should be at least 2 FFDCs depending on test ordering there could be more. + assertTrue("There was not exactly two FFDCs generated!", ffdcFileNames.size() >= 2); + + // Get the latest two FFDCs. + RemoteFile ffdcFile1 = server.getFFDCLogFile(ffdcFileNames.get(ffdcFileNames.size() - 1)); + RemoteFile ffdcFile2 = server.getFFDCLogFile(ffdcFileNames.get(ffdcFileNames.size() - 2)); + + List lines = server.findStringsInFileInLibertyServerRoot(expectedFFDC, "logs/ffdc/" + ffdcFile1.getName()); + assertTrue("The expected FFDC: " + expectedFFDC + " was not found!", lines.size() > 0); + + lines = server.findStringsInFileInLibertyServerRoot(expectedFFDC, "logs/ffdc/" + ffdcFile2.getName()); + assertTrue("The expected FFDC: " + expectedFFDC + " was not found!", lines.size() > 0); + } + + /** + * The test will set maxOpenConnections to a value of 2 and validate in the trace file that + * the correct value is being used. + * + * The below configuration will be used to set maxOpenConnections to 2: + * + * + * Three connections are then created. Since the number of connections is 1 greater than maxOpenConnections + * the following warning should be found in the logs: + * CWWKO0222W: TCP Channel defaultHttpEndpoint has exceeded the maximum number of open connections 2. + * + * @throws Exception + */ + @Test + @Mode(TestMode.FULL) + public void testMaxOpenConnections() throws Exception { + URL url = HttpUtils.createURL(server, "/"); + + ServerConfiguration configuration = server.getServerConfiguration(); + LOG.info("Server configuration that the test started with: " + configuration); + + HttpEndpoint httpEndpoint = configuration.getHttpEndpoints().getById("defaultHttpEndpoint"); + httpEndpoint.getTcpOptions().setMaxOpenConnections(2); + + server.setMarkToEndOfLog(); + server.setTraceMarkToEndOfDefaultTrace(); + server.updateServerConfiguration(configuration); + server.waitForConfigUpdateInLogUsingMark(null); + + // Validate that maxOpenConnections is set to 2. + assertNotNull("The configured value of maxOpenConnections was not 2!", server.waitForStringInTraceUsingMark("maxOpenConnections: 2")); + + // Ensure the TCP Channel has started. + // CWWKO0219I: TCP Channel defaultHttpEndpoint has been started and is now listening for requests on host * (IPv4) port 8010. + assertNotNull("The TCP Channel was not started!", server.waitForStringInLogUsingMark("CWWKO0219I: TCP Channel defaultHttpEndpoint")); + + // Create three Socket connections. + Socket socket1 = null; + Socket socket2 = null; + Socket socket3 = null; + try { + LOG.info("Creating the first connection."); + socket1 = new Socket(url.getHost(), url.getPort()); + socket1.setKeepAlive(true); + + LOG.info("Creating the second connection."); + socket2 = new Socket(url.getHost(), url.getPort()); + socket2.setKeepAlive(true); + + LOG.info("Creating the third connection."); + socket3 = new Socket(url.getHost(), url.getPort()); + socket3.setKeepAlive(true); + } catch (Exception e) { + LOG.info("Exception occurred while creating the Sockets: " + e.toString()); + } finally { + // We should get an exception for the third connection since the server has maxOpenConnections set to 2. + // Wait for the message in the logs before closing the sockets to ensure they are not closed prematurely. + try { + assertNotNull("The CWWKO0222W message was not found in the logs!", + server.waitForStringInLogUsingMark("CWWKO0222W: TCP Channel defaultHttpEndpoint has exceeded the maximum number of open connections 2.")); + } finally { + if (socket1 != null) { + LOG.info("Closing the first connection."); + socket1.close(); + } + if (socket2 != null) { + LOG.info("Closing the second connection."); + socket2.close(); + } + if (socket3 != null) { + LOG.info("Closing the third connection."); + socket3.close(); + } + } + } + } + + /** + * The test will set maxOpenConnections to a value of 2 and validate in the trace file that + * the correct value is being used. + * + * The below configuration will be used to set maxOpenConnections to 2: + * + * + * The test will then create a new HttpEndpoint and configure its maxOpenConnections to 1 and verify it is + * configured correctly. + * + * Five connections are then created. Three connections to the defaultHttpEndpoint and two connections to + * httpEndpoint2. Since the number of connections is 1 greater than maxOpenConnections for each of the httpEndpoints + * the following warnings should be found in the logs: + * + * CWWKO0222W: TCP Channel defaultHttpEndpoint has exceeded the maximum number of open connections 2. + * CWWKO0222W: TCP Channel httpEndpoint2 has exceeded the maximum number of open connections 1. + * + * The main purpose of this test is to ensure that if there are two httpEndpoints that the configuration + * is unique to each of the httpEndpoints. + * + * @throws Exception + */ + @Test + @Mode(TestMode.FULL) + public void testMaxOpenConnections_multiple_endpoints() throws Exception { + URL url = HttpUtils.createURL(server, "/"); // defaultHttpEndpoint + URL url2 = new URL("http://" + server.getHostname() + ":" + server.getHttpSecondaryPort() + "/"); // httpEndpoint2 + + ServerConfiguration configuration = server.getServerConfiguration(); + LOG.info("Server configuration that the test started with: " + configuration); + + HttpEndpoint httpEndpoint = configuration.getHttpEndpoints().getById("defaultHttpEndpoint"); + httpEndpoint.getTcpOptions().setMaxOpenConnections(2); + + server.setMarkToEndOfLog(); + server.setTraceMarkToEndOfDefaultTrace(); + server.updateServerConfiguration(configuration); + server.waitForConfigUpdateInLogUsingMark(null); + + // Validate that maxOpenConnections is set to 2. + assertNotNull("The configured value of maxOpenConnections was not 2!", server.waitForStringInTraceUsingMark("maxOpenConnections: 2")); + + // Ensure the TCP Channel has started. + // CWWKO0219I: TCP Channel defaultHttpEndpoint has been started and is now listening for requests on host * (IPv4) port 8010. + assertNotNull("The TCP Channel defaultHttpEndpoint was not started!", server.waitForStringInLogUsingMark("CWWKO0219I: TCP Channel defaultHttpEndpoint")); + + // Add another httpEndpoint and set its maxOpenConnections to 1. + HttpEndpoint endpoint2 = new HttpEndpoint(); + endpoint2.setId("httpEndpoint2"); + endpoint2.setHttpPort(Integer.toString(server.getHttpSecondaryPort())); + endpoint2.getTcpOptions().setMaxOpenConnections(1); + + configuration.getHttpEndpoints().add(endpoint2); + + server.setMarkToEndOfLog(); + server.setTraceMarkToEndOfDefaultTrace(); + server.updateServerConfiguration(configuration); + server.waitForConfigUpdateInLogUsingMark(null); + + // Validate that maxOpenConnections is set to 1. + assertNotNull("The configured value of maxOpenConnections was not 1!", server.waitForStringInTraceUsingMark("maxOpenConnections: 1")); + + // Ensure the TCP Channel has started. + assertNotNull("The TCP Channel httpEndpoint2 was not started!", server.waitForStringInLogUsingMark("CWWKO0219I: TCP Channel httpEndpoint2")); + + // Create 3 connections to the defaultHttpEndpoint. + // Create 2 connections to httpEndpoint2. + Socket socket1 = null; + Socket socket2 = null; + Socket socket3 = null; + Socket socket4 = null; + Socket socket5 = null; + try { + LOG.info("Creating the first connection to the defaultHttpEndpoint."); + socket1 = new Socket(url.getHost(), url.getPort()); + socket1.setKeepAlive(true); + + LOG.info("Creating the second connection to the defaultHttpEndpoint."); + socket2 = new Socket(url.getHost(), url.getPort()); + socket2.setKeepAlive(true); + + LOG.info("Creating the first connection to the httpEndpoint2."); + socket3 = new Socket(url2.getHost(), url2.getPort()); + socket3.setKeepAlive(true); + + // Exceed the maxOpenConnections for the defaultHttpEndpoint. + LOG.info("Creating the third connection to the defaultHttpEndpoint."); + socket4 = new Socket(url.getHost(), url.getPort()); + + // Exceed the maxOpenConnections for the httpEndpoint2. + LOG.info("Creating the second connection to the httpEndpoint2."); + socket5 = new Socket(url2.getHost(), url2.getPort()); + } catch (Exception e) { + LOG.info("Exception occurred when creating Sockets: " + e.toString()); + } finally { + try { + // Verify maxOpenConnections was exceeded for the defaultHttpEndpoint. + assertNotNull("The CWWKO0222W message was not found in the logs for the defaultHttpEndpoing!", + server.waitForStringInLogUsingMark("CWWKO0222W: TCP Channel defaultHttpEndpoint has exceeded the maximum number of open connections 2.")); + + // Verify maxOpenConnections was exceeded for the httpEndpoint2 + assertNotNull("The CWWKO0222W message was not found in the logs for the httpEndpoint2!", + server.waitForStringInLogUsingMark("CWWKO0222W: TCP Channel httpEndpoint2 has exceeded the maximum number of open connections 1.")); + } finally { + // Clean up the Sockets. + if (socket1 != null) { + LOG.info("Closing the first connection to the defaultHttpEndpoint."); + socket1.close(); + } + if (socket2 != null) { + LOG.info("Closing the second connection to the defaultHttpEndpoint."); + socket2.close(); + } + if (socket3 != null) { + LOG.info("Closing the first connection first connection to the httpEndpoint2."); + socket3.close(); + } + + if (socket4 != null) { + LOG.info("Closing the third connection to the defaultHttpEndpoint."); + socket3.close(); + } + + if (socket5 != null) { + LOG.info("Closing the second connection to the httpEndpoint2."); + socket3.close(); + } + } + } + } +}