Skip to content

Commit 76e927f

Browse files
authored
fix: Spdy migration to websocket (#6682)
* spdy-migrate to websocket * spdy-migrate to websocket * websocket * refc
1 parent 7231efe commit 76e927f

File tree

3 files changed

+115
-2
lines changed

3 files changed

+115
-2
lines changed

pkg/terminal/fallback_executor.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package terminal
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
"net/url"
8+
9+
"k8s.io/client-go/rest"
10+
"k8s.io/client-go/tools/remotecommand"
11+
)
12+
13+
// FallbackExecutor tries WebSocket first and falls back to SPDY if needed
14+
type FallbackExecutor struct {
15+
executor remotecommand.Executor
16+
}
17+
18+
// NewFallbackExecutor creates a new executor that tries WebSocket first and falls back to SPDY
19+
func NewFallbackExecutor(config *rest.Config, method string, url *url.URL) (remotecommand.Executor, error) {
20+
var wsExecutor, spdyExecutor remotecommand.Executor
21+
var wsErr, spdyErr error
22+
23+
// Try to create WebSocket executor
24+
wsExecutor, wsErr = remotecommand.NewWebSocketExecutor(config, method, url.String())
25+
if wsErr != nil {
26+
log.Printf("Warning: Failed to create WebSocket executor: %v", wsErr)
27+
}
28+
29+
// Try to create SPDY executor
30+
spdyExecutor, spdyErr = remotecommand.NewSPDYExecutor(config, method, url)
31+
if spdyErr != nil {
32+
log.Printf("Warning: Failed to create SPDY executor: %v", spdyErr)
33+
}
34+
35+
// Handle different scenarios
36+
if wsErr != nil && spdyErr != nil {
37+
// Both failed
38+
return nil, fmt.Errorf("failed to create any executor: WebSocket error: %v, SPDY error: %v", wsErr, spdyErr)
39+
}
40+
41+
if wsErr != nil {
42+
// Only WebSocket failed, use SPDY
43+
return spdyExecutor, nil
44+
}
45+
46+
if spdyErr != nil {
47+
// Only SPDY failed, use WebSocket
48+
return wsExecutor, nil
49+
}
50+
51+
// Both succeeded, create fallback executor
52+
fallbackExecutor, err := remotecommand.NewFallbackExecutor(wsExecutor, spdyExecutor, func(err error) bool {
53+
// Fall back to SPDY if WebSocket fails due to connection issues
54+
log.Printf("WebSocket failed, falling back to SPDY: %v", err)
55+
return true
56+
})
57+
if err != nil {
58+
return nil, fmt.Errorf("failed to create fallback executor: %v", err)
59+
}
60+
61+
return &FallbackExecutor{
62+
executor: fallbackExecutor,
63+
}, nil
64+
}
65+
66+
// Stream is deprecated. Please use StreamWithContext.
67+
func (e *FallbackExecutor) Stream(options remotecommand.StreamOptions) error {
68+
return e.executor.Stream(options)
69+
}
70+
71+
// StreamWithContext delegates to the underlying fallback executor
72+
func (e *FallbackExecutor) StreamWithContext(ctx context.Context, options remotecommand.StreamOptions) error {
73+
return e.executor.StreamWithContext(ctx, options)
74+
}

pkg/terminal/terminalSesion.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import (
2424
"encoding/json"
2525
errors2 "errors"
2626
"fmt"
27-
"github.com/devtron-labs/common-lib/async"
2827
"io"
2928
"log"
3029
"net/http"
@@ -33,6 +32,8 @@ import (
3332
"sync"
3433
"time"
3534

35+
"github.com/devtron-labs/common-lib/async"
36+
3637
"github.com/caarlos0/env"
3738
"github.com/devtron-labs/common-lib/utils/k8s"
3839
"github.com/devtron-labs/devtron/internal/middleware"
@@ -358,7 +359,8 @@ func getExecutor(k8sClient kubernetes.Interface, cfg *rest.Config, podName, name
358359
TTY: tty,
359360
}, scheme.ParameterCodec)
360361

361-
exec, err := remotecommand.NewSPDYExecutor(cfg, "POST", req.URL())
362+
// Use the new fallback executor instead of SPDY directly
363+
exec, err := NewFallbackExecutor(cfg, "POST", req.URL())
362364
return exec, err
363365
}
364366

pkg/terminal/websocket_executor.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package terminal
2+
3+
import (
4+
"context"
5+
"net/url"
6+
7+
"k8s.io/client-go/rest"
8+
"k8s.io/client-go/tools/remotecommand"
9+
)
10+
11+
// WebSocketExecutor wraps the Kubernetes WebSocket executor
12+
type WebSocketExecutor struct {
13+
executor remotecommand.Executor
14+
}
15+
16+
// NewWebSocketExecutor creates a new WebSocket-based executor for terminal sessions
17+
func NewWebSocketExecutor(config *rest.Config, method string, url *url.URL) (remotecommand.Executor, error) {
18+
// Use the Kubernetes WebSocket executor directly
19+
executor, err := remotecommand.NewWebSocketExecutor(config, method, url.String())
20+
if err != nil {
21+
return nil, err
22+
}
23+
24+
return &WebSocketExecutor{
25+
executor: executor,
26+
}, nil
27+
}
28+
29+
// Stream is deprecated. Please use StreamWithContext.
30+
func (e *WebSocketExecutor) Stream(options remotecommand.StreamOptions) error {
31+
return e.executor.Stream(options)
32+
}
33+
34+
// StreamWithContext delegates to the underlying Kubernetes WebSocket executor
35+
func (e *WebSocketExecutor) StreamWithContext(ctx context.Context, options remotecommand.StreamOptions) error {
36+
return e.executor.StreamWithContext(ctx, options)
37+
}

0 commit comments

Comments
 (0)