Skip to content

Commit 5da9ab2

Browse files
authored
port changes from main for port in use issue (#2947)
1 parent 6645425 commit 5da9ab2

File tree

3 files changed

+142
-47
lines changed

3 files changed

+142
-47
lines changed

integration-tests/src/test/java/oracle/weblogic/kubernetes/extensions/ImageBuilders.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import oracle.weblogic.kubernetes.logging.LoggingFacade;
3131
import oracle.weblogic.kubernetes.utils.ExecCommand;
3232
import oracle.weblogic.kubernetes.utils.ExecResult;
33+
import oracle.weblogic.kubernetes.utils.PortInuseEventWatcher;
3334
import org.awaitility.core.ConditionFactory;
3435
import org.junit.jupiter.api.extension.BeforeAllCallback;
3536
import org.junit.jupiter.api.extension.ExtensionContext;
@@ -112,6 +113,8 @@ public class ImageBuilders implements BeforeAllCallback, ExtensionContext.Store.
112113
.and().with().pollInterval(10, SECONDS)
113114
.atMost(30, MINUTES).await();
114115

116+
PortInuseEventWatcher portInuseEventWatcher;
117+
115118
@Override
116119
public void beforeAll(ExtensionContext context) {
117120
LoggingFacade logger = getLogger();
@@ -125,6 +128,8 @@ public void beforeAll(ExtensionContext context) {
125128
*/
126129
if (!started.getAndSet(true)) {
127130
try {
131+
portInuseEventWatcher = new PortInuseEventWatcher();
132+
portInuseEventWatcher.start();
128133
// clean up the download directory so that we always get the latest
129134
// versions of the WDT and WIT tools in every run of the test suite.
130135
try {
@@ -371,6 +376,7 @@ public void close() {
371376
for (Handler handler : logger.getUnderlyingLogger().getHandlers()) {
372377
handler.close();
373378
}
379+
portInuseEventWatcher.interrupt();
374380
}
375381

376382
private String getOcirToken() {

integration-tests/src/test/java/oracle/weblogic/kubernetes/utils/CommonTestUtils.java

Lines changed: 17 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -844,27 +844,8 @@ public static String getDockerExtraArgs() {
844844
return extraArgs.toString();
845845
}
846846

847-
/**
848-
* Get the next free port between from and to.
849-
*
850-
* @param from range starting point
851-
* @param to range ending point
852-
* @return the next free port number, if there is no free port between the range, return the ending point
853-
*/
854-
public static synchronized int getNextFreePort(int from, int to) {
855-
LoggingFacade logger = getLogger();
856-
int port;
857-
for (port = from; port < to; port++) {
858-
if (isLocalPortFree(port)) {
859-
logger.info("next free port is: {0}", port);
860-
return port;
861-
}
862-
}
863-
logger.info("Can not find free port between {0} and {1}", from, to);
864-
return port;
865-
}
866847

867-
private static int port = 30000;
848+
private static int port = 32000;
868849
private static final int END_PORT = 32767;
869850

870851
/**
@@ -877,15 +858,29 @@ public static synchronized int getNextFreePort() {
877858
int freePort = 0;
878859
while (port <= END_PORT) {
879860
freePort = port++;
880-
if (isLocalPortFree(freePort)) {
881-
logger.info("next free port is: {0}", freePort);
861+
try {
862+
isLocalPortFree(freePort);
863+
} catch (IOException ex) {
882864
return freePort;
883865
}
884866
}
885867
logger.warning("Could not get free port below " + END_PORT);
886868
return -1;
887869
}
888870

871+
/**
872+
* Check if the given port is free. Tries to connect to the given port, if it succeeds it means that
873+
* the given port is already in use by an another process.
874+
*
875+
* @param port port to check
876+
* @throws java.io.IOException when the port is not used by any socket
877+
*/
878+
private static void isLocalPortFree(int port) throws IOException {
879+
try (Socket socket = new Socket(K8S_NODEPORT_HOST, port)) {
880+
getLogger().info("Port {0} is already in use", port);
881+
}
882+
}
883+
889884
/**
890885
* Get current date and timestamp in format yyyy-MM-dd-currentimemillis.
891886
* @return string with date and timestamp
@@ -896,31 +891,6 @@ public static String getDateAndTimeStamp() {
896891
return dateFormat.format(date) + "-" + System.currentTimeMillis();
897892
}
898893

899-
/**
900-
* Check if the given port number is free.
901-
*
902-
* @param port port number to check
903-
* @return true if the port is free, false otherwise
904-
*/
905-
private static boolean isLocalPortFree(int port) {
906-
LoggingFacade logger = getLogger();
907-
Socket socket = null;
908-
try {
909-
socket = new Socket(K8S_NODEPORT_HOST, port);
910-
return false;
911-
} catch (IOException ignored) {
912-
return true;
913-
} finally {
914-
if (socket != null) {
915-
try {
916-
socket.close();
917-
} catch (IOException ex) {
918-
logger.severe("can not close Socket {0}", ex.getMessage());
919-
}
920-
}
921-
}
922-
}
923-
924894
/**
925895
* Evaluates the route host name for OKD env, and host:serviceport for othe env's.
926896
*
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// Copyright (c) 2022, Oracle and/or its affiliates.
2+
// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
3+
4+
package oracle.weblogic.kubernetes.utils;
5+
6+
import java.io.IOException;
7+
import java.nio.charset.StandardCharsets;
8+
import java.nio.file.Files;
9+
import java.nio.file.Path;
10+
import java.nio.file.Paths;
11+
import java.time.Duration;
12+
import java.time.OffsetDateTime;
13+
import java.util.List;
14+
import java.util.concurrent.TimeUnit;
15+
import java.util.logging.Level;
16+
import java.util.logging.Logger;
17+
18+
import io.kubernetes.client.openapi.ApiException;
19+
import io.kubernetes.client.openapi.models.CoreV1Event;
20+
import io.kubernetes.client.util.Yaml;
21+
import oracle.weblogic.kubernetes.TestConstants;
22+
import oracle.weblogic.kubernetes.actions.impl.primitive.Kubernetes;
23+
import oracle.weblogic.kubernetes.logging.LoggingFacade;
24+
25+
import static io.kubernetes.client.util.Yaml.dump;
26+
import static oracle.weblogic.kubernetes.actions.TestActions.now;
27+
import static oracle.weblogic.kubernetes.utils.ThreadSafeLogger.getLogger;
28+
29+
/**
30+
* A utility class to collect logs about services and running processes when a port in use error occur. It spawns a
31+
* thread from ImageBuilders beforeAll method to start watching all events in all namespaces used by the integration
32+
* tests and will collect services logs and processes logs using lsof command. The thread runs every 10 seconds to look
33+
* for the port in use error and writes logs only when the error is thrown.
34+
*
35+
*/
36+
public class PortInuseEventWatcher extends Thread {
37+
38+
static LoggingFacade logger = getLogger();
39+
OffsetDateTime timestamp;
40+
OffsetDateTime begin;
41+
OffsetDateTime end;
42+
int secondsBetweenCollection = 10;
43+
int collectionDelay = 0;
44+
String regex = "(?s).*\\bprovided\\b.*\\bport\\b.*\\bis\\b.*\\balready\\b.*\\ballocated\\b";
45+
46+
@Override
47+
public void run() {
48+
logger.info("Starting port in use debugger thread");
49+
timestamp = now();
50+
while (!this.isInterrupted()) {
51+
timestamp = now().minusSeconds(collectionDelay);
52+
try {
53+
begin = now();
54+
List<String> ns = Kubernetes.listNamespaces();
55+
for (String n : ns) {
56+
if (n.startsWith("ns-") || n.equals("default")) {
57+
List<CoreV1Event> listNamespacedEvents = Kubernetes.listNamespacedEvents(n);
58+
for (CoreV1Event event : listNamespacedEvents) {
59+
if (event != null && event.getLastTimestamp() != null
60+
&& (event.getLastTimestamp().isEqual(timestamp)
61+
|| event.getLastTimestamp().isAfter(timestamp))
62+
&& event.getMessage() != null
63+
&& event.getMessage().matches(regex)) {
64+
logger.info("Port in use issue found in namespace {0}, "
65+
+ "collecting services objects across all namespaces....", n);
66+
logger.info(Yaml.dump(event));
67+
collectLogs(ns);
68+
}
69+
}
70+
}
71+
}
72+
end = now();
73+
} catch (ApiException ex) {
74+
Logger.getLogger(PortInuseEventWatcher.class.getName()).log(Level.SEVERE, null, ex);
75+
}
76+
try {
77+
TimeUnit.SECONDS.sleep(secondsBetweenCollection);
78+
collectionDelay = (int) (secondsBetweenCollection + Duration.between(begin, end).getSeconds());
79+
} catch (InterruptedException ex) {
80+
logger.info("Thread interrupted");
81+
}
82+
}
83+
logger.info("port in use debugger thread ended");
84+
}
85+
86+
private void collectLogs(List<String> namespaces) {
87+
try {
88+
Path dest = Files.createDirectories(Paths.get(TestConstants.LOGS_DIR, "portinuselogs", now().toString()));
89+
//grab all processes using lsof
90+
ExecResult exec = ExecCommand.exec("sudo lsof -i");
91+
if (exec.exitValue() == 0 && exec.stdout() != null) {
92+
writeToFile(exec.stderr(), dest.toString(), "lsof", false);
93+
} else {
94+
logger.warning("Failed to collect lsof logs");
95+
}
96+
for (String ns : namespaces) {
97+
if (ns.startsWith("ns-") || ns.equals("default")) {
98+
writeToFile(Kubernetes.listServices(ns), dest.toString(), ns + ".list.services.log", true);
99+
writeToFile(Kubernetes.listNamespacedEvents(ns), dest.toString(), ns + ".list.events.log", true);
100+
}
101+
}
102+
} catch (IOException | InterruptedException | ApiException ex) {
103+
logger.warning(ex.getMessage());
104+
}
105+
}
106+
107+
private static void writeToFile(Object obj, String resultDir, String fileName, boolean asYaml)
108+
throws IOException {
109+
logger.info("Generating {0}", Paths.get(resultDir, fileName));
110+
if (obj != null) {
111+
Files.createDirectories(Paths.get(resultDir));
112+
Files.write(Paths.get(resultDir, fileName),
113+
(asYaml ? dump(obj).getBytes(StandardCharsets.UTF_8) : ((String) obj).getBytes(StandardCharsets.UTF_8))
114+
);
115+
} else {
116+
logger.info("Nothing to write in {0} list is empty", Paths.get(resultDir, fileName));
117+
}
118+
}
119+
}

0 commit comments

Comments
 (0)