Skip to content

Commit b402e62

Browse files
committed
support apiserver url rewrite
Signed-off-by: huiwq1990 <[email protected]>
1 parent 630cf19 commit b402e62

File tree

4 files changed

+206
-0
lines changed

4 files changed

+206
-0
lines changed

pkg/apiserver/apiserver.go

+7
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,18 @@ import (
1717
"k8s.io/client-go/discovery"
1818
clientrest "k8s.io/client-go/rest"
1919
"k8s.io/client-go/restmapper"
20+
"k8s.io/klog/v2"
2021

2122
internal "github.com/clusterpedia-io/api/clusterpedia"
2223
"github.com/clusterpedia-io/api/clusterpedia/install"
24+
"github.com/clusterpedia-io/clusterpedia/pkg/apiserver/features"
2325
"github.com/clusterpedia-io/clusterpedia/pkg/apiserver/registry/clusterpedia/collectionresources"
2426
"github.com/clusterpedia-io/clusterpedia/pkg/apiserver/registry/clusterpedia/resources"
2527
"github.com/clusterpedia-io/clusterpedia/pkg/generated/clientset/versioned"
2628
informers "github.com/clusterpedia-io/clusterpedia/pkg/generated/informers/externalversions"
2729
"github.com/clusterpedia-io/clusterpedia/pkg/kubeapiserver"
2830
"github.com/clusterpedia-io/clusterpedia/pkg/storage"
31+
clusterpediafeature "github.com/clusterpedia-io/clusterpedia/pkg/utils/feature"
2932
"github.com/clusterpedia-io/clusterpedia/pkg/utils/filters"
3033
)
3134

@@ -139,6 +142,10 @@ func (config completedConfig) New() (*ClusterPediaServer, error) {
139142
handler := handlerChainFunc(apiHandler, c)
140143
handler = filters.WithRequestQuery(handler)
141144
handler = filters.WithAcceptHeader(handler)
145+
if clusterpediafeature.FeatureGate.Enabled(features.ResourcePathWithoutClusterpediaPrefix) {
146+
klog.InfoS("Enable rewrite apiserver url")
147+
handler = filters.WithRewriteFilter(handler)
148+
}
142149
return handler
143150
}
144151

pkg/apiserver/features/features.go

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package features
2+
3+
import (
4+
"k8s.io/apimachinery/pkg/util/runtime"
5+
"k8s.io/component-base/featuregate"
6+
7+
clusterpediafeature "github.com/clusterpedia-io/clusterpedia/pkg/utils/feature"
8+
)
9+
10+
const (
11+
12+
// ResourcePathWithoutClusterpediaPrefix is a feature gate for rewrite apiserver request's URL
13+
// owner: @huiwq1990
14+
// alpha: v0.8.0
15+
ResourcePathWithoutClusterpediaPrefix featuregate.Feature = "ResourcePathWithoutClusterpediaPrefix"
16+
)
17+
18+
func init() {
19+
runtime.Must(clusterpediafeature.MutableFeatureGate.Add(defaultResourcePathWithoutClusterpediaPrefixFeatureGates))
20+
}
21+
22+
// defaultResourcePathWithoutClusterpediaPrefixFeatureGates consists of all known apiserver feature keys.
23+
// To add a new feature, define a key for it above and add it here.
24+
var defaultResourcePathWithoutClusterpediaPrefixFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
25+
ResourcePathWithoutClusterpediaPrefix: {Default: false, PreRelease: featuregate.Alpha},
26+
}

pkg/utils/filters/rewrite.go

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package filters
2+
3+
import (
4+
"net/http"
5+
"net/url"
6+
"strings"
7+
8+
"k8s.io/klog/v2"
9+
)
10+
11+
const OriginPathHeaderKey = "X-Rewrite-Original-Path"
12+
const OldResourceApiServerPrefixWithoutSlash = "/apis/clusterpedia.io/v1beta1/resources"
13+
const OldResourceApiServerPrefix = OldResourceApiServerPrefixWithoutSlash + "/"
14+
15+
func WithRewriteFilter(handler http.Handler) http.Handler {
16+
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
17+
oldPath := req.URL.EscapedPath()
18+
if rewritePath, ok := urlPrefixRewrite(oldPath); ok {
19+
req.URL.Path = rewritePath
20+
req.Header.Set(OriginPathHeaderKey, oldPath)
21+
klog.V(5).InfoS("request need rewrite", "oldPath", oldPath, "newPath", req.URL.EscapedPath())
22+
}
23+
handler.ServeHTTP(w, req)
24+
})
25+
}
26+
27+
func urlPrefixRewrite(oldPath string) (string, bool) {
28+
if strings.HasPrefix(oldPath, OldResourceApiServerPrefix) || oldPath == OldResourceApiServerPrefixWithoutSlash || oldPath == "" {
29+
return "", false
30+
}
31+
32+
rewritePath, err := url.JoinPath(OldResourceApiServerPrefix, oldPath)
33+
if err != nil {
34+
return "", false
35+
}
36+
return rewritePath, true
37+
}

pkg/utils/filters/rewrite_test.go

+136
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package filters
2+
3+
import (
4+
"net/http"
5+
"net/http/httptest"
6+
"net/url"
7+
"testing"
8+
)
9+
10+
type testCase struct {
11+
name string
12+
urls []kubeRequest
13+
}
14+
15+
type kubeRequest struct {
16+
from string
17+
to string
18+
rewrite bool
19+
}
20+
21+
var tests = []testCase{
22+
{
23+
name: "do rewrite",
24+
urls: []kubeRequest{
25+
{
26+
from: "/apis/clusterpedia.io/v1beta1/resourcesany",
27+
to: "/apis/clusterpedia.io/v1beta1/resources/apis/clusterpedia.io/v1beta1/resourcesany",
28+
rewrite: true,
29+
},
30+
{from: "/api/v1/namespaces/default/pods?limit=100",
31+
to: "/apis/clusterpedia.io/v1beta1/resources/api/v1/namespaces/default/pods?limit=100",
32+
rewrite: true},
33+
34+
{from: "/apis/clusterpedia.io/v1beta1/clusters",
35+
to: "/apis/clusterpedia.io/v1beta1/resources/apis/clusterpedia.io/v1beta1/clusters",
36+
rewrite: true,
37+
},
38+
},
39+
},
40+
{
41+
name: "not need rewrite",
42+
urls: []kubeRequest{
43+
{
44+
from: "/apis/clusterpedia.io/v1beta1/resources",
45+
to: "/apis/clusterpedia.io/v1beta1/resources",
46+
rewrite: false,
47+
},
48+
{from: "/apis/clusterpedia.io/v1beta1/resources/api/v1/namespaces/default/pods",
49+
to: "/apis/clusterpedia.io/v1beta1/resources/api/v1/namespaces/default/pods",
50+
rewrite: false,
51+
},
52+
53+
{from: "/apis/clusterpedia.io/v1beta1/resources/apis/clusterpedia.io/v1beta1/clusters",
54+
to: "/apis/clusterpedia.io/v1beta1/resources/apis/clusterpedia.io/v1beta1/clusters",
55+
rewrite: false,
56+
},
57+
},
58+
},
59+
{
60+
name: "special cases",
61+
urls: []kubeRequest{
62+
{from: "/api/v1/namespaces/default/pods?name=abc#xx",
63+
to: "/apis/clusterpedia.io/v1beta1/resources/api/v1/namespaces/default/pods?name=abc#xx",
64+
rewrite: true,
65+
},
66+
},
67+
},
68+
}
69+
70+
func TestUrlPrefixRewrite(t *testing.T) {
71+
for _, test := range tests {
72+
t.Logf("Test - name: %s", test.name)
73+
74+
for _, tmp := range test.urls {
75+
fromPath, err := url.Parse(tmp.from)
76+
if err != nil {
77+
t.Error(err)
78+
}
79+
80+
rewritePath, doRewrite := urlPrefixRewrite(fromPath.EscapedPath())
81+
if doRewrite != tmp.rewrite {
82+
t.Errorf("Test failed \n from : %s \n to : %s \n needRewrite: %v \n doRewrite: %v",
83+
tmp.from, tmp.to, tmp.rewrite, doRewrite)
84+
}
85+
86+
if doRewrite {
87+
oldURL, err := url.Parse(tmp.to)
88+
if err != nil {
89+
t.Error(err)
90+
}
91+
92+
if oldURL.EscapedPath() != rewritePath {
93+
t.Errorf("Test failed \n from : %s \n to : %s \n oldPath: %s \n rewritePath: %s",
94+
tmp.from, tmp.to, oldURL.EscapedPath(), rewritePath)
95+
}
96+
}
97+
}
98+
}
99+
}
100+
101+
func TestRewrite(t *testing.T) {
102+
for _, test := range tests {
103+
t.Logf("Test - name: %s", test.name)
104+
105+
for _, tmp := range test.urls {
106+
req, err := http.NewRequest("GET", tmp.from, nil)
107+
if err != nil {
108+
t.Fatalf("create HTTP request error: %v", err)
109+
}
110+
111+
oldPath := req.URL.EscapedPath()
112+
113+
h := WithRewriteFilter(
114+
http.HandlerFunc(func(_ http.ResponseWriter, req *http.Request) {
115+
}),
116+
)
117+
118+
t.Logf("From: %s", req.URL.String())
119+
120+
res := httptest.NewRecorder()
121+
h.ServeHTTP(res, req)
122+
123+
t.Logf("Rewrited: %s", req.URL.String())
124+
if req.URL.String() != tmp.to {
125+
t.Errorf("Test failed \n from : %s \n to : %s \n result: %s",
126+
tmp.from, tmp.to, req.URL.RequestURI())
127+
}
128+
129+
if oldHeaderPath := req.Header.Get(OriginPathHeaderKey); oldHeaderPath != "" {
130+
if oldPath != oldHeaderPath {
131+
t.Error("incorrect flag")
132+
}
133+
}
134+
}
135+
}
136+
}

0 commit comments

Comments
 (0)