Skip to content

Commit fc70187

Browse files
committed
complete port-forward
1 parent 1a00ba7 commit fc70187

1 file changed

Lines changed: 126 additions & 25 deletions

File tree

pkg/portforward/portforward.go

Lines changed: 126 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,63 +10,111 @@ import (
1010
"os/signal"
1111
"strconv"
1212
"sync"
13+
"time"
1314

1415
"github.com/revolyssup/k8sdebug/pkg"
1516
"github.com/spf13/cobra"
17+
v1 "k8s.io/api/core/v1"
1618
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
19+
"k8s.io/apimachinery/pkg/watch"
1720
"k8s.io/client-go/kubernetes"
1821
"k8s.io/client-go/tools/clientcmd"
1922
"k8s.io/client-go/tools/portforward"
2023
"k8s.io/client-go/transport/spdy"
2124
)
2225

26+
var startingHostPort = 8080
2327
var (
24-
namespace string
25-
typ string
26-
hostport string
27-
containerPort string
28+
namespace string
29+
typ string
30+
hostport string
31+
containerPort string
32+
labels string
33+
policy string
34+
connPool []string
35+
freeList = make([]int, 0)
36+
podNameToPoolIndex map[string]int
37+
fromWatch = -1
38+
indexToStopChan = make(map[int]chan struct{}) // Track stop channels by index to close previous port forwards
2839
)
29-
var labels string
30-
var startingHostPort = 8080
3140

3241
func forwardToPod(hostConn net.Conn, podCon net.Conn) {
3342
//Copy data bidirectionally
3443
go io.Copy(hostConn, podCon)
3544
io.Copy(podCon, hostConn)
3645
}
3746

38-
var connPool []string
39-
40-
var policy string
41-
4247
func getPodConnection(fw forwarder) (net.Conn, error) {
43-
podConn, err := net.Dial("tcp", fmt.Sprintf(":%d", fw.Port()))
48+
port := fw.Port()
49+
podConn, err := net.Dial("tcp", fmt.Sprintf(":%s", port))
4450
if err != nil {
4551
return nil, err
4652
}
4753
return podConn, nil
4854
}
4955

5056
type forwarder interface {
51-
Port() int
57+
Port() string
5258
}
5359

5460
type roundRobin struct {
5561
connNumber int
62+
mx sync.Mutex
5663
}
5764

58-
func (rr *roundRobin) Port() int {
59-
portNum := 8080 + (rr.connNumber % len(connPool))
60-
rr.connNumber++
61-
return portNum
65+
func (rr *roundRobin) Port() string {
66+
rr.mx.Lock()
67+
defer rr.mx.Unlock()
68+
69+
initial := rr.connNumber
70+
for {
71+
rr.connNumber = (rr.connNumber + 1) % len(connPool)
72+
portNum := connPool[rr.connNumber]
73+
if portNum != "" {
74+
// Check if port is actually listening
75+
conn, err := net.DialTimeout("tcp", ":"+portNum, 50*time.Millisecond)
76+
if err == nil {
77+
conn.Close()
78+
fmt.Println("PORT RETURNED ", portNum)
79+
return portNum
80+
}
81+
}
82+
if rr.connNumber == initial {
83+
break // Avoid infinite loop
84+
}
85+
}
86+
return ""
6287
}
88+
6389
func getForwarder(policy string) forwarder {
6490
switch policy {
6591
case "round-robin":
6692
return &roundRobin{}
6793
}
6894
return nil
6995
}
96+
97+
var mx sync.Mutex
98+
99+
func getFreeIndex() int {
100+
mx.Lock()
101+
defer mx.Unlock()
102+
if len(freeList) != 0 {
103+
i := freeList[0]
104+
connPool[i] = ""
105+
freeList = freeList[1:]
106+
return i
107+
}
108+
connPool = append(connPool, "") //Placeholder to increase the size
109+
return len(connPool) - 1
110+
}
111+
112+
func addFreeIndex(i int) {
113+
mx.Lock()
114+
defer mx.Unlock()
115+
freeList = append(freeList, i)
116+
connPool[i] = "" // RESET
117+
}
70118
func NewCommand() *cobra.Command {
71119
cmd := &cobra.Command{
72120
Use: "port-forward",
@@ -114,14 +162,23 @@ func NewCommand() *cobra.Command {
114162
if labels != "" {
115163
opts.LabelSelector = labels
116164
}
117-
initialList, err := cs.CoreV1().Pods(namespace).List(context.TODO(), opts)
165+
initialList, err := cs.CoreV1().Pods(namespace).List(ctx, opts)
118166
if err != nil {
119167
panic("Exiting runner..." + err.Error())
120168
}
121-
connPool = make([]string, 0)
122169
pods := initialList.Items
170+
connPool = make([]string, len(pods))
171+
podNameToPoolIndex = make(map[string]int, len(pods))
123172
fmt.Printf(pkg.ColorLine(fmt.Sprintf("listening on %s using %s policy across %d pods\n", hostport, policy, len(pods)), pkg.ColorGreen))
124-
for i, pod := range pods {
173+
startPortForward := func(i int, pod v1.Pod) (string, error) {
174+
if i == fromWatch {
175+
i = getFreeIndex()
176+
}
177+
if stopChan, exists := indexToStopChan[i]; exists {
178+
close(stopChan)
179+
delete(indexToStopChan, i)
180+
}
181+
fmt.Println("WILL TRY TO CREATE AT INDEX", i)
125182
req := cs.CoreV1().RESTClient().Post().
126183
Resource("pods").
127184
Namespace(namespace).
@@ -130,27 +187,71 @@ func NewCommand() *cobra.Command {
130187
transporter, upgrader, err := spdy.RoundTripperFor(config)
131188
if err != nil {
132189
fmt.Println("coudnot open connection for pod", pod.Name)
133-
continue
190+
return "", err
134191
}
135192
dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transporter}, "POST", req.URL())
136193
stopChan := make(chan struct{})
194+
indexToStopChan[i] = stopChan // Track new stop channel
137195
readyChan := make(chan struct{})
138196
hostPort := startingHostPort + i
197+
139198
hostPortStr := fmt.Sprintf("%s:%s", strconv.Itoa(hostPort), containerPort)
140-
connPool = append(connPool, hostPortStr)
199+
connPool[i] = strconv.Itoa(hostPort)
200+
podNameToPoolIndex[pod.Name] = i
141201
forwarder, err := portforward.New(dialer, []string{hostPortStr}, stopChan, readyChan, os.Stdout, os.Stderr)
142202
if err != nil {
143-
fmt.Println("coudnot forward connection for pod", pod.Name, " :", err.Error())
144-
continue
203+
connPool[i] = "" // Reset connPool entry
204+
addFreeIndex(i) // Return index to freeList
205+
return "", fmt.Errorf("port-forward setup failed: %v", err)
145206
}
146207
go func() {
208+
fmt.Println("FORWARDER WILL RUN FOR ", hostPortStr)
147209
if err := forwarder.ForwardPorts(); err != nil {
148-
// errChan <- fmt.Errorf("port forwarding failed: %v", err)
149-
fmt.Println("coudnot forward connection for pod", pod.Name)
210+
fmt.Println("coud not forward connection for pod", pod.Name)
150211
}
151212
}()
213+
return hostPortStr, nil
214+
}
215+
for i, pod := range pods {
216+
startPortForward(i, pod)
217+
}
218+
opts.ResourceVersion = initialList.ResourceVersion
219+
watcher, err := cs.CoreV1().Pods(namespace).Watch(ctx, opts)
220+
if err != nil {
221+
fmt.Println("could not create a watcher for pods")
222+
return
152223
}
153224
var wg sync.WaitGroup
225+
go func() {
226+
for event := range watcher.ResultChan() {
227+
switch event.Type {
228+
case watch.Added:
229+
pod := event.Object.(*v1.Pod)
230+
if pod == nil {
231+
continue
232+
}
233+
fmt.Printf("new pod recieved: %s. will try to create portforward\n", pod.Name)
234+
//TODO: find a better way
235+
time.Sleep(2 * time.Second) //Wait for pod to start
236+
addr, err := startPortForward(fromWatch, *pod)
237+
if err != nil {
238+
fmt.Printf("could not create port forward for %s\n", pod.Name)
239+
continue
240+
}
241+
fmt.Println(pkg.ColorLine(fmt.Sprintf("New port forward created for pod %s on %s", pod.Name, addr), pkg.ColorGreen))
242+
case watch.Deleted:
243+
pod := event.Object.(*v1.Pod)
244+
i := podNameToPoolIndex[pod.Name]
245+
if stopChan, exists := indexToStopChan[i]; exists {
246+
close(stopChan) // Signal to stop port-forward
247+
delete(indexToStopChan, i)
248+
}
249+
addFreeIndex(i)
250+
fmt.Println(pkg.ColorLine("New freelist: ", pkg.ColorYellow), freeList)
251+
}
252+
}
253+
}()
254+
154255
sigchan := make(chan os.Signal)
155256
signal.Notify(sigchan, os.Interrupt)
156257
wg.Add(1)

0 commit comments

Comments
 (0)