diff --git a/CHANGELOG.md b/CHANGELOG.md index a9729f821b6..9cfca29a9f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * Fix #4985: triggering the immediate cleanup of the okhttp idle task * fix #5002: Jetty response completion accounts for header processing * Fix #5009: addressing issue with serialization of wrapped polymophic types +* Fix #5033: port forwarding for clients other than okhttp needs to specify the subprotocol #### Improvements * Fix #4434: Update CronJobIT to use `batch/v1` CronJob instead diff --git a/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/PortForwarderWebsocket.java b/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/PortForwarderWebsocket.java index 72740c98e2b..337a896eb1c 100644 --- a/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/PortForwarderWebsocket.java +++ b/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/PortForwarderWebsocket.java @@ -173,6 +173,7 @@ public PortForward forward(URL resourceBaseUrl, int port, final ReadableByteChan CompletableFuture socket = client .newWebSocketBuilder() .uri(URI.create(URLUtils.join(resourceBaseUrl.toString(), "portforward?ports=" + port))) + .subprotocol("v4.channel.k8s.io") .buildAsync(listener); socket.whenComplete((w, t) -> { diff --git a/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/PortForwarderWebsocketListener.java b/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/PortForwarderWebsocketListener.java index 770a5bd82f5..60ebcf798ab 100644 --- a/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/PortForwarderWebsocketListener.java +++ b/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/PortForwarderWebsocketListener.java @@ -117,9 +117,8 @@ public void onMessage(WebSocket webSocket, ByteBuffer buffer) { closeBothWays(webSocket, 1002, PROTOCOL_ERROR); } else if (channel == 1) { // Error channel - // TODO: read the error KubernetesClientException e = new KubernetesClientException( - String.format("Received an error from the remote socket")); + String.format("Received an error from the remote socket %s", ExecWebSocketListener.toString(buffer))); serverThrowables.add(e); logger.debug("Server error", e); closeForwarder(); diff --git a/kubernetes-itests/src/test/java/io/fabric8/kubernetes/PodIT.java b/kubernetes-itests/src/test/java/io/fabric8/kubernetes/PodIT.java index 8e33d9e5a66..6e49ca76c47 100644 --- a/kubernetes-itests/src/test/java/io/fabric8/kubernetes/PodIT.java +++ b/kubernetes-itests/src/test/java/io/fabric8/kubernetes/PodIT.java @@ -24,6 +24,7 @@ import io.fabric8.kubernetes.api.model.PodList; import io.fabric8.kubernetes.api.model.Status; import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.LocalPortForward; import io.fabric8.kubernetes.client.dsl.ExecListener; import io.fabric8.kubernetes.client.dsl.ExecWatch; import io.fabric8.kubernetes.client.dsl.LogWatch; @@ -31,6 +32,7 @@ import io.fabric8.kubernetes.client.utils.IOHelpers; import io.fabric8.kubernetes.client.utils.InputStreamPumper; import org.awaitility.Awaitility; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; @@ -44,6 +46,10 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.SocketException; +import java.net.URL; +import java.nio.channels.SocketChannel; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -444,4 +450,40 @@ void listFromServer() { assertEquals(pod1.getMetadata().getName(), fromServerPod.getMetadata().getName()); } + @Test + void portForward() throws IOException, InterruptedException { + client.pods().withName("nginx").waitUntilReady(POD_READY_WAIT_IN_MILLIS, TimeUnit.SECONDS); + LocalPortForward portForward = client.pods().withName("nginx").portForward(80); + boolean failed = false; + try (SocketChannel channel = SocketChannel.open()) { + + int localPort = portForward.getLocalPort(); + + URL url = new URL("http://localhost:" + localPort); + + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + + InputStream content = (InputStream) conn.getContent(); + + // make sure we got data, should be the welcome page + assertTrue(content.read() != -1); + + content.close(); + } catch (SocketException e) { + failed = true; + } finally { + portForward.close(); + } + + if (failed) { + // not all kube versions nor runtimes can to port forwarding - the nodes need socat installed + portForward.getServerThrowables().stream() + .filter(t -> !t.getMessage().contains("unable to do port forwarding: socat not found")).findFirst() + .ifPresent(Assertions::fail); + } else { + assertThat(portForward.getServerThrowables()).isEmpty(); + } + assertThat(portForward.getClientThrowables()).isEmpty(); + } + } diff --git a/kubernetes-itests/src/test/resources/pod-it.yml b/kubernetes-itests/src/test/resources/pod-it.yml index 4002b576054..1ba2d264c45 100644 --- a/kubernetes-itests/src/test/resources/pod-it.yml +++ b/kubernetes-itests/src/test/resources/pod-it.yml @@ -56,3 +56,14 @@ spec: command: ["timeout", "-s", "SIGKILL", "36000", "sh", "-i"] stdin: true tty: true +--- +apiVersion: v1 +kind: Pod +metadata: + name: nginx +spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80