@@ -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
2327var (
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
3241func 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-
4247func 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
5056type forwarder interface {
51- Port () int
57+ Port () string
5258}
5359
5460type 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+
6389func 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+ }
70118func 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