Skip to content

Commit 8438bde

Browse files
committed
Add support for PortForward.
1 parent 39bb616 commit 8438bde

File tree

3 files changed

+262
-1
lines changed

3 files changed

+262
-1
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
Copyright 2017 The Kubernetes Authors.
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
package io.kubernetes.client.examples;
14+
15+
import io.kubernetes.client.ApiClient;
16+
import io.kubernetes.client.ApiException;
17+
import io.kubernetes.client.Configuration;
18+
import io.kubernetes.client.PortForward;
19+
import io.kubernetes.client.apis.CoreV1Api;
20+
import io.kubernetes.client.models.V1Pod;
21+
import io.kubernetes.client.models.V1PodList;
22+
import io.kubernetes.client.util.Config;
23+
24+
import com.google.common.io.ByteStreams;
25+
26+
import java.io.BufferedReader;
27+
import java.io.InputStreamReader;
28+
import java.io.IOException;
29+
import java.io.OutputStream;
30+
import java.net.ServerSocket;
31+
import java.net.Socket;
32+
import java.util.ArrayList;
33+
import java.util.List;
34+
35+
36+
/**
37+
* A simple example of how to use the Java API
38+
*
39+
* Easiest way to run this:
40+
* mvn exec:java -Dexec.mainClass="io.kubernetes.client.examples.PortForwardExample"
41+
* from inside $REPO_DIR/examples
42+
*
43+
* Then:
44+
* curl localhost:8080
45+
* from a different terminal (but be quick about it, the socket times out pretty fast...)
46+
*
47+
*/
48+
public class PortForwardExample {
49+
public static void main(String[] args) throws IOException, ApiException, InterruptedException {
50+
ApiClient client = Config.defaultClient();
51+
Configuration.setDefaultApiClient(client);
52+
53+
PortForward forward = new PortForward();
54+
List<Integer> ports = new ArrayList<>();
55+
ports.add(80);
56+
final PortForward.PortForwardResult result =
57+
forward.forward("default", "nginx-4217019353-fg6zx", ports);
58+
59+
ServerSocket ss = new ServerSocket(8080);
60+
61+
final Socket s = ss.accept();
62+
System.out.println("Connected!");
63+
64+
new Thread(new Runnable() {
65+
public void run() {
66+
try {
67+
ByteStreams.copy(result.getInputStream(80), s.getOutputStream());
68+
} catch (IOException ex) {
69+
ex.printStackTrace();
70+
} catch (Exception ex) {
71+
ex.printStackTrace();
72+
}
73+
}
74+
}).start();
75+
76+
new Thread(new Runnable() {
77+
public void run() {
78+
try {
79+
ByteStreams.copy(s.getInputStream(), result.getOutboundStream(80));
80+
} catch (IOException ex) {
81+
ex.printStackTrace();
82+
} catch (Exception ex) {
83+
ex.printStackTrace();
84+
}
85+
}
86+
}).start();
87+
88+
Thread.sleep(10 * 1000);
89+
90+
System.exit(0);
91+
}
92+
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package io.kubernetes.client;
2+
3+
import io.kubernetes.client.Configuration;
4+
import io.kubernetes.client.models.V1Pod;
5+
import io.kubernetes.client.util.WebSockets;
6+
import io.kubernetes.client.util.WebSocketStreamHandler;
7+
8+
import java.io.InputStream;
9+
import java.io.IOException;
10+
import java.io.OutputStream;
11+
import java.util.ArrayList;
12+
import java.util.HashMap;
13+
import java.util.List;
14+
15+
import com.squareup.okhttp.Call;
16+
17+
public class PortForward {
18+
private ApiClient apiClient;
19+
20+
/**
21+
* Simple PortForward API constructor, uses default configuration
22+
*/
23+
public PortForward() {
24+
this(Configuration.getDefaultApiClient());
25+
}
26+
27+
/**
28+
* PortForward API Constructor
29+
* @param apiClient The api client to use.
30+
*/
31+
public PortForward(ApiClient apiClient) {
32+
this.apiClient = apiClient;
33+
}
34+
35+
/**
36+
* Get the API client for these PortForward operations.
37+
* @returns The API client that will be used.
38+
*/
39+
public ApiClient getApiClient() {
40+
return apiClient;
41+
}
42+
43+
/**
44+
* Set the API client for subsequent PortForward operations.
45+
* @param apiClient The new API client to use.
46+
*/
47+
public void setApiClient(ApiClient apiClient) {
48+
this.apiClient = apiClient;
49+
}
50+
51+
private String makePath(String namespace, String name) {
52+
return "/api/v1/namespaces/" +
53+
namespace +
54+
"/pods/" +
55+
name +
56+
"/portforward";
57+
}
58+
59+
/**
60+
* PortForward to a container
61+
*
62+
* @param pod The pod where the command is run.
63+
* @param ports The ports to forward
64+
*/
65+
public PortForwardResult forward(V1Pod pod, List<Integer> ports) throws ApiException, IOException {
66+
return forward(pod.getMetadata().getNamespace(), pod.getMetadata().getNamespace(), ports);
67+
}
68+
69+
/**
70+
* PortForward to a container.
71+
*
72+
* @param namespace The namespace of the Pod
73+
* @param name The name of the Pod
74+
* @param ports The ports to forward
75+
*/
76+
public PortForwardResult forward(String namespace, String name, List<Integer> ports) throws ApiException, IOException {
77+
String path = makePath(namespace, name);
78+
WebSocketStreamHandler handler = new WebSocketStreamHandler();
79+
PortForwardResult result = new PortForwardResult(handler, ports);
80+
List<Pair> queryParams = new ArrayList<>();
81+
queryParams.add(new Pair("ports", "80"));
82+
WebSockets.stream(path, "GET", queryParams, apiClient, handler);
83+
84+
// Wait for streams to start.
85+
result.init();
86+
87+
return result;
88+
}
89+
90+
/**
91+
* PortForwardResult contains the result of an Attach call, it includes streams for stdout
92+
* stderr and stdin.
93+
*/
94+
public static class PortForwardResult {
95+
private WebSocketStreamHandler handler;
96+
private HashMap<Integer, Integer> streams;
97+
private List<Integer> ports;
98+
99+
public PortForwardResult(WebSocketStreamHandler handler, List<Integer> ports) throws IOException {
100+
this.handler = handler;
101+
this.streams = new HashMap<>();
102+
this.ports = ports;
103+
}
104+
105+
public void init() throws IOException {
106+
for (int i = 0; i < ports.size(); i++) {
107+
InputStream is = handler.getInputStream(i);
108+
byte[] data = new byte[2];
109+
is.read(data);
110+
int port = data[0] + data[1] * 256;
111+
streams.put(port, i);
112+
}
113+
}
114+
115+
private int findPortIndex(int portNumber) {
116+
Integer ix = streams.get(portNumber);
117+
if (ix == null) {
118+
return -1;
119+
}
120+
return ix.intValue();
121+
}
122+
123+
/**
124+
* Get the stream for the specified port index.
125+
* Note that the first 2 bytes of the stream will be the port number.
126+
* @param portNumber The port number to get the stream for.
127+
*/
128+
public OutputStream getOutboundStream(int port) {
129+
int portIndex = findPortIndex(port);
130+
if (portIndex == -1) {
131+
throw new IllegalArgumentException("No such port!");
132+
}
133+
return handler.getOutputStream(portIndex * 2);
134+
}
135+
136+
public OutputStream getErrorStream(int port) {
137+
int portIndex = findPortIndex(port);
138+
if (portIndex == -1) {
139+
throw new IllegalArgumentException("No such port!");
140+
}
141+
return handler.getOutputStream(portIndex * 2 + 1);
142+
}
143+
144+
public InputStream getInputStream(int port) throws IOException {
145+
int portIndex = findPortIndex(port);
146+
if (portIndex == -1) {
147+
throw new IllegalArgumentException("No such port!");
148+
}
149+
return handler.getInputStream(portIndex * 2);
150+
}
151+
}
152+
}

util/src/main/java/io/kubernetes/client/util/WebSockets.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
package io.kubernetes.client.util;
1414

1515
import com.google.common.net.HttpHeaders;
16+
import com.squareup.okhttp.Call;
1617
import com.squareup.okhttp.Request;
1718
import com.squareup.okhttp.Response;
1819
import com.squareup.okhttp.ResponseBody;
@@ -30,6 +31,7 @@
3031
import java.io.InputStream;
3132
import java.io.OutputStream;
3233
import java.io.Reader;
34+
import java.util.List;
3335
import java.util.ArrayList;
3436
import java.util.HashMap;
3537

@@ -81,13 +83,28 @@ public interface SocketListener {
8183
* @param listener The socket listener to handle socket events
8284
*/
8385
public static void stream(String path, String method, ApiClient client, SocketListener listener) throws ApiException, IOException {
86+
stream(path, method, new ArrayList<Pair>(), client, listener);
87+
}
88+
89+
public static void stream(String path, String method, List<Pair> queryParams, ApiClient client, SocketListener listener) throws ApiException, IOException {
90+
8491
HashMap<String, String> headers = new HashMap<String, String>();
8592
String allProtocols = String.format("%s,%s,%s,%s", V4_STREAM_PROTOCOL, V3_STREAM_PROTOCOL, V2_STREAM_PROTOCOL, V1_STREAM_PROTOCOL);
8693
headers.put(STREAM_PROTOCOL_HEADER, allProtocols);
8794
headers.put(HttpHeaders.CONNECTION, HttpHeaders.UPGRADE);
8895
headers.put(HttpHeaders.UPGRADE, SPDY_3_1);
8996

90-
Request request = client.buildRequest(path, method, new ArrayList<Pair>(), new ArrayList<Pair>(), null, headers, new HashMap<String, Object>(), new String[0], null);
97+
Request request = client.buildRequest(path, method, queryParams, new ArrayList<Pair>(), null, headers, new HashMap<String, Object>(), new String[0], null);
98+
streamRequest(request, client, listener);
99+
}
100+
101+
/** If we ever upgrade to okhttp 3...
102+
public static void stream(Call call, ApiClient client, SocketListener listener) {
103+
streamRequest(call.request(), client, listener);
104+
}
105+
*/
106+
107+
private static void streamRequest(Request request, ApiClient client, SocketListener listener) {
91108
WebSocketCall.create(client.getHttpClient(), request).enqueue(new Listener(listener));
92109
}
93110

0 commit comments

Comments
 (0)