Skip to content

Commit 632bb4f

Browse files
authored
feat(sandbox-gateway): support parse sandboxID from domain related he… (#234)
* feat(sandbox-gateway): support parse sandboxID from domain related header Signed-off-by: xianying.czy <xianying.czy@alibaba-inc.com>
1 parent f9769af commit 632bb4f

File tree

15 files changed

+920
-85
lines changed

15 files changed

+920
-85
lines changed

.github/workflows/e2e-e2b-2.4.1.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,5 @@ jobs:
101101
export KUBECONFIG=/home/runner/.kube/config
102102
export E2B_DOMAIN=localhost
103103
export E2B_API_KEY=some-api-key
104-
sudo -E kubectl port-forward services/sandbox-manager 80:7788 -n sandbox-system &
105104
bash hack/run-e2b-e2e-test.sh --e2b-version 2.8.1 --sdk-version 2.4.1
106105

.github/workflows/e2e-e2b-latest.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,5 @@ jobs:
102102
export KUBECONFIG=/home/runner/.kube/config
103103
export E2B_DOMAIN=localhost
104104
export E2B_API_KEY=some-api-key
105-
sudo -E kubectl port-forward services/sandbox-manager 80:7788 -n sandbox-system &
106105
bash hack/run-e2b-e2e-test.sh
107106

config/sandbox-gateway/configmap.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ data:
2626
typed_config:
2727
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
2828
stat_prefix: ingress_http
29+
access_log:
30+
- name: envoy.access_loggers.stdout
31+
typed_config:
32+
"@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
33+
log_format:
34+
text_format: "[%START_TIME%] %REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL% %RESPONSE_CODE% %BYTES_SENT% %DURATION%ms req_id=%REQ(X-REQUEST-ID)%\n"
2935
route_config:
3036
name: local_route
3137
virtual_hosts:
@@ -45,6 +51,11 @@ data:
4551
plugin_name: sandbox-gateway
4652
plugin_config:
4753
"@type": type.googleapis.com/xds.type.v3.TypedStruct
54+
value:
55+
host-header-name: "Host"
56+
sandbox-header-name: "e2b-sandbox-id"
57+
sandbox-port-header: "e2b-sandbox-port"
58+
default-port: "49983"
4859
- name: envoy.filters.http.router
4960
typed_config:
5061
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router

config/sandbox-gateway/deployment.yaml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ metadata:
44
name: sandbox-gateway
55
namespace: sandbox-system
66
labels:
7+
component: sandbox-manager
78
app.kubernetes.io/name: sandbox-gateway
89
app.kubernetes.io/managed-by: kustomize
910
spec:
10-
replicas: 2
11+
replicas: 1
1112
selector:
1213
matchLabels:
1314
app.kubernetes.io/name: sandbox-gateway
15+
component: sandbox-manager
1416
strategy:
1517
type: RollingUpdate
1618
rollingUpdate:
@@ -20,6 +22,7 @@ spec:
2022
metadata:
2123
labels:
2224
app.kubernetes.io/name: sandbox-gateway
25+
component: sandbox-manager
2326
spec:
2427
serviceAccountName: sandbox-gateway
2528
initContainers:
@@ -68,6 +71,10 @@ spec:
6871
env:
6972
- name: GOMAXPROCS
7073
value: "2"
74+
- name: PEER_NAMESPACE
75+
value: "sandbox-system"
76+
- name: PEER_LABEL_SELECTOR
77+
value: "component=sandbox-manager"
7178
ports:
7279
- name: http
7380
containerPort: 10000
@@ -110,4 +117,5 @@ spec:
110117
labelSelector:
111118
matchLabels:
112119
app.kubernetes.io/name: sandbox-gateway
120+
app.kubernetes.io/instance: sandbox-manager
113121
topologyKey: kubernetes.io/hostname

config/sandbox-gateway/rbac.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ rules:
1212
- apiGroups: ["agents.kruise.io"]
1313
resources: ["sandboxes/status"]
1414
verbs: ["get"]
15+
- apiGroups: [""]
16+
resources: ["pods"]
17+
verbs: ["list"]
1518
---
1619
apiVersion: rbac.authorization.k8s.io/v1
1720
kind: ClusterRoleBinding

config/sandbox-gateway/service.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ metadata:
99
spec:
1010
type: ClusterIP
1111
ports:
12-
- port: 10000
12+
- port: 7788
1313
targetPort: 10000
1414
protocol: TCP
1515
name: http

hack/run-e2b-e2e-test.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ kubectl wait --for=condition=ready pod \
6464

6565
echo "All sandbox-manager pods are ready"
6666

67+
sudo -E kubectl port-forward svc/sandbox-manager 80:7788 -n sandbox-system &
68+
6769
# Step 2: Install e2b-code-interpreter
6870
echo "Installing dependencies..."
6971
pip install -r $TEST_DIR/requirements.txt
Lines changed: 130 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,145 @@
11
package filter
22

33
import (
4+
"encoding/json"
5+
"fmt"
6+
"regexp"
7+
8+
v3 "github.com/cncf/xds/go/xds/type/v3"
49
"github.com/envoyproxy/envoy/contrib/golang/common/go/api"
510
"google.golang.org/protobuf/types/known/anypb"
611
)
712

8-
type config struct{}
13+
// hostPattern matches the host format: {port}-{namespace}--{name}.{domain}
14+
// Group 1: port (digits), Group 2: namespace--name (alphanumeric and hyphens)
15+
var hostPattern = regexp.MustCompile(`^(\d+)-([a-zA-Z0-9\-]+)\.`)
16+
17+
const (
18+
DefaultHostHeaderName = "Host"
19+
DefaultSandboxHeaderName = "e2b-sandbox-id"
20+
DefaultSandboxPortHeader = "e2b-sandbox-port"
21+
DefaultSandboxPort = "49983"
22+
)
23+
24+
// Config holds the filter configuration
25+
type Config struct {
26+
// SandboxHeaderName is the header name for sandbox ID (checked first)
27+
SandboxHeaderName string `json:"sandbox-header-name,omitempty"`
28+
// SandboxPortHeader is the header name for sandbox port
29+
SandboxPortHeader string `json:"sandbox-port-header,omitempty"`
30+
// HostHeaderName is the header name for host matching (fallback when sandbox header not found)
31+
HostHeaderName string `json:"host-header-name,omitempty"`
32+
// DefaultPort is the default port if not specified
33+
DefaultPort string `json:"default-port,omitempty"`
34+
}
35+
36+
// DefaultConfig returns default configuration
37+
func DefaultConfig() *Config {
38+
return &Config{
39+
SandboxHeaderName: DefaultSandboxHeaderName,
40+
SandboxPortHeader: DefaultSandboxPortHeader,
41+
HostHeaderName: DefaultHostHeaderName,
42+
DefaultPort: DefaultSandboxPort,
43+
}
44+
}
45+
46+
// Validate checks configuration validity
47+
func (c *Config) Validate() error {
48+
return nil
49+
}
50+
51+
// GetSandboxHeaderName returns the effective sandbox header name
52+
func (c *Config) GetSandboxHeaderName() string {
53+
if c.SandboxHeaderName != "" {
54+
return c.SandboxHeaderName
55+
}
56+
return DefaultSandboxHeaderName
57+
}
58+
59+
// GetHostHeaderName returns the effective host header name
60+
func (c *Config) GetHostHeaderName() string {
61+
if c.HostHeaderName != "" {
62+
return c.HostHeaderName
63+
}
64+
return DefaultHostHeaderName
65+
}
66+
67+
// ExtractHostInfo extracts both host key and port from the header in one regex call
68+
// Only for host mode: extracts both from the host format (<port>-<namespace>--<name>.domain)
69+
// Returns (hostKey, port) - both empty if parsing fails
70+
func (c *Config) ExtractHostInfo(headerValue string) (string, string) {
71+
if headerValue == "" {
72+
return "", ""
73+
}
74+
75+
// Use regex to extract both port and namespace--name from host format
76+
// e.g., "8080-abc--def.example.com" -> hostKey: "abc--def", port: "8080"
77+
matches := hostPattern.FindStringSubmatch(headerValue)
78+
if len(matches) < 3 {
79+
return "", ""
80+
}
81+
return matches[2], matches[1]
82+
}
983

1084
type ConfigParser struct{}
1185

1286
func (p *ConfigParser) Parse(any *anypb.Any, callbacks api.ConfigCallbackHandler) (interface{}, error) {
13-
return &config{}, nil
87+
cfg := DefaultConfig()
88+
89+
// Unmarshal the xds.type.v3.TypedStruct protobuf message
90+
typedStruct := &v3.TypedStruct{}
91+
if err := any.UnmarshalTo(typedStruct); err != nil {
92+
return nil, fmt.Errorf("failed to unmarshal TypedStruct: %w", err)
93+
}
94+
95+
// Get the value from TypedStruct which contains the actual config as Struct
96+
valueStruct := typedStruct.GetValue()
97+
if valueStruct == nil {
98+
// No value field, use defaults
99+
return cfg, nil
100+
}
101+
102+
// Convert the struct to JSON
103+
configBytes, err := json.Marshal(valueStruct.AsMap())
104+
if err != nil {
105+
return nil, fmt.Errorf("failed to marshal config value to JSON: %w", err)
106+
}
107+
108+
// Parse actual config from JSON
109+
if len(configBytes) > 0 && string(configBytes) != "null" {
110+
if err := json.Unmarshal(configBytes, cfg); err != nil {
111+
return nil, fmt.Errorf("failed to parse config: %w", err)
112+
}
113+
}
114+
115+
// Validate
116+
if err := cfg.Validate(); err != nil {
117+
return nil, err
118+
}
119+
120+
return cfg, nil
14121
}
15122

16123
func (p *ConfigParser) Merge(parent interface{}, child interface{}) interface{} {
17-
return child
124+
parentCfg := parent.(*Config)
125+
childCfg := child.(*Config)
126+
127+
// Child overrides parent for all fields
128+
merged := DefaultConfig()
129+
*merged = *parentCfg
130+
131+
if childCfg.SandboxHeaderName != "" {
132+
merged.SandboxHeaderName = childCfg.SandboxHeaderName
133+
}
134+
if childCfg.SandboxPortHeader != "" {
135+
merged.SandboxPortHeader = childCfg.SandboxPortHeader
136+
}
137+
if childCfg.HostHeaderName != "" {
138+
merged.HostHeaderName = childCfg.HostHeaderName
139+
}
140+
if childCfg.DefaultPort != "" {
141+
merged.DefaultPort = childCfg.DefaultPort
142+
}
143+
144+
return merged
18145
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package filter
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestConfigValidate(t *testing.T) {
8+
tests := []struct {
9+
name string
10+
cfg *Config
11+
wantErr bool
12+
}{
13+
{
14+
name: "default config",
15+
cfg: DefaultConfig(),
16+
wantErr: false,
17+
},
18+
{
19+
name: "custom sandbox header",
20+
cfg: &Config{
21+
SandboxHeaderName: "custom-sandbox-id",
22+
SandboxPortHeader: "custom-sandbox-port",
23+
HostHeaderName: "X-Host",
24+
DefaultPort: "8080",
25+
},
26+
wantErr: false,
27+
},
28+
{
29+
name: "empty config",
30+
cfg: &Config{},
31+
wantErr: false,
32+
},
33+
}
34+
35+
for _, tt := range tests {
36+
t.Run(tt.name, func(t *testing.T) {
37+
err := tt.cfg.Validate()
38+
if (err != nil) != tt.wantErr {
39+
t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
40+
}
41+
})
42+
}
43+
}
44+
45+
func TestGetSandboxHeaderName(t *testing.T) {
46+
tests := []struct {
47+
name string
48+
cfg *Config
49+
wantHeaderName string
50+
}{
51+
{
52+
name: "empty config uses default",
53+
cfg: &Config{},
54+
wantHeaderName: "e2b-sandbox-id",
55+
},
56+
{
57+
name: "custom sandbox header name",
58+
cfg: &Config{
59+
SandboxHeaderName: "custom-sandbox-id",
60+
},
61+
wantHeaderName: "custom-sandbox-id",
62+
},
63+
{
64+
name: "default config",
65+
cfg: DefaultConfig(),
66+
wantHeaderName: "e2b-sandbox-id",
67+
},
68+
}
69+
70+
for _, tt := range tests {
71+
t.Run(tt.name, func(t *testing.T) {
72+
got := tt.cfg.GetSandboxHeaderName()
73+
if got != tt.wantHeaderName {
74+
t.Errorf("GetSandboxHeaderName() = %q, want %q", got, tt.wantHeaderName)
75+
}
76+
})
77+
}
78+
}
79+
80+
func TestGetHostHeaderName(t *testing.T) {
81+
tests := []struct {
82+
name string
83+
cfg *Config
84+
wantHeaderName string
85+
}{
86+
{
87+
name: "empty config uses default",
88+
cfg: &Config{},
89+
wantHeaderName: "Host",
90+
},
91+
{
92+
name: "custom host header name",
93+
cfg: &Config{
94+
HostHeaderName: "X-Forwarded-Host",
95+
},
96+
wantHeaderName: "X-Forwarded-Host",
97+
},
98+
{
99+
name: "default config",
100+
cfg: DefaultConfig(),
101+
wantHeaderName: "Host",
102+
},
103+
}
104+
105+
for _, tt := range tests {
106+
t.Run(tt.name, func(t *testing.T) {
107+
got := tt.cfg.GetHostHeaderName()
108+
if got != tt.wantHeaderName {
109+
t.Errorf("GetHostHeaderName() = %q, want %q", got, tt.wantHeaderName)
110+
}
111+
})
112+
}
113+
}
114+
115+
func TestDefaultConfig(t *testing.T) {
116+
cfg := DefaultConfig()
117+
if cfg.SandboxHeaderName != "e2b-sandbox-id" {
118+
t.Errorf("DefaultConfig().SandboxHeaderName = %q, want %q", cfg.SandboxHeaderName, "e2b-sandbox-id")
119+
}
120+
if cfg.SandboxPortHeader != "e2b-sandbox-port" {
121+
t.Errorf("DefaultConfig().SandboxPortHeader = %q, want %q", cfg.SandboxPortHeader, "e2b-sandbox-port")
122+
}
123+
if cfg.HostHeaderName != "Host" {
124+
t.Errorf("DefaultConfig().HostHeaderName = %q, want %q", cfg.HostHeaderName, "Host")
125+
}
126+
if cfg.DefaultPort != "49983" {
127+
t.Errorf("DefaultConfig().DefaultPort = %q, want %q", cfg.DefaultPort, "49983")
128+
}
129+
}

0 commit comments

Comments
 (0)