Skip to content

Commit 305d1e9

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

File tree

4 files changed

+150
-0
lines changed

4 files changed

+150
-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.ApiServerURLRewrite) {
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+
// ApiServerURLRewrite is a feature gate for rewrite apiserver request's URL
13+
// owner: @huiwq1990
14+
// alpha: v0.7.0
15+
ApiServerURLRewrite featuregate.Feature = "ApiServerURLRewrite"
16+
)
17+
18+
func init() {
19+
runtime.Must(clusterpediafeature.MutableFeatureGate.Add(defaultApiServerFeatureGates))
20+
}
21+
22+
// defaultApiServerFeatureGates 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 defaultApiServerFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
25+
ApiServerURLRewrite: {Default: false, PreRelease: featuregate.Alpha},
26+
}

pkg/utils/filters/rewrite.go

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
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 OldResourceApiServerPrefix = "/apis/clusterpedia.io/v1beta1/resources"
13+
14+
func WithRewriteFilter(handler http.Handler) http.Handler {
15+
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
16+
if !doUrlRewrite(req) {
17+
klog.V(5).InfoS("request not need rewrite", "path", req.URL.EscapedPath())
18+
}
19+
20+
handler.ServeHTTP(w, req)
21+
})
22+
}
23+
24+
func doUrlRewrite(req *http.Request) bool {
25+
oldPath := req.URL.EscapedPath()
26+
if strings.HasPrefix(oldPath, OldResourceApiServerPrefix) {
27+
return false
28+
}
29+
30+
rewritePath, err := url.JoinPath(OldResourceApiServerPrefix, req.URL.Path)
31+
if err != nil {
32+
return false
33+
}
34+
req.URL.Path = rewritePath
35+
req.Header.Set(OriginPathHeaderKey, oldPath)
36+
37+
klog.V(5).InfoS("Rewrite url", "oldPath", oldPath, "newPath", req.URL.EscapedPath())
38+
39+
return true
40+
}

pkg/utils/filters/rewrite_test.go

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package filters
2+
3+
import (
4+
"net/http"
5+
"net/http/httptest"
6+
"testing"
7+
)
8+
9+
type testCase struct {
10+
name string
11+
urls []kubeRequest
12+
}
13+
14+
type kubeRequest struct {
15+
from string
16+
to string
17+
}
18+
19+
func TestRewrite(t *testing.T) {
20+
tests := []testCase{
21+
{
22+
name: "do rewrite",
23+
urls: []kubeRequest{
24+
{from: "/api/v1/namespaces/default/pods?limit=100", to: "/apis/clusterpedia.io/v1beta1/resources/api/v1/namespaces/default/pods?limit=100"},
25+
{from: "/apis/clusterpedia.io/v1beta1/clusters", to: "/apis/clusterpedia.io/v1beta1/resources/apis/clusterpedia.io/v1beta1/clusters"},
26+
},
27+
},
28+
{
29+
name: "not need rewrite",
30+
urls: []kubeRequest{
31+
{from: "/apis/clusterpedia.io/v1beta1/resources/api/v1/namespaces/default/pods", to: "/apis/clusterpedia.io/v1beta1/resources/api/v1/namespaces/default/pods"},
32+
{from: "/apis/clusterpedia.io/v1beta1/resources/apis/clusterpedia.io/v1beta1/clusters", to: "/apis/clusterpedia.io/v1beta1/resources/apis/clusterpedia.io/v1beta1/clusters"},
33+
},
34+
},
35+
{
36+
name: "special cases",
37+
urls: []kubeRequest{
38+
{from: "/api/v1/namespaces/default/pods?name=abc#xx", to: "/apis/clusterpedia.io/v1beta1/resources/api/v1/namespaces/default/pods?name=abc#xx"},
39+
},
40+
},
41+
}
42+
43+
for _, test := range tests {
44+
t.Logf("Test - name: %s", test.name)
45+
46+
for _, tmp := range test.urls {
47+
req, err := http.NewRequest("GET", tmp.from, nil)
48+
if err != nil {
49+
t.Fatalf("create HTTP request error: %v", err)
50+
}
51+
52+
oldPath := req.URL.EscapedPath()
53+
54+
h := WithRewriteFilter(
55+
http.HandlerFunc(func(_ http.ResponseWriter, req *http.Request) {
56+
}),
57+
)
58+
59+
t.Logf("From: %s", req.URL.String())
60+
61+
res := httptest.NewRecorder()
62+
h.ServeHTTP(res, req)
63+
64+
t.Logf("Rewrited: %s", req.URL.String())
65+
if req.URL.String() != tmp.to {
66+
t.Errorf("Test failed \n from : %s \n to : %s \n result: %s",
67+
tmp.from, tmp.to, req.URL.RequestURI())
68+
}
69+
70+
if oldHeaderPath := req.Header.Get(OriginPathHeaderKey); oldHeaderPath != "" {
71+
if oldPath != oldHeaderPath {
72+
t.Error("incorrect flag")
73+
}
74+
}
75+
}
76+
}
77+
}

0 commit comments

Comments
 (0)