Skip to content

Commit 99f0dce

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

File tree

4 files changed

+234
-0
lines changed

4 files changed

+234
-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

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

pkg/utils/filters/rewrite_test.go

+160
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
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/cluster.clusterpedia.io/v1beta1/resourcesany",
27+
to: "/apis/clusterpedia.io/v1beta1/resources/apis/cluster.clusterpedia.io/v1beta1/resourcesany",
28+
rewrite: true,
29+
},
30+
{
31+
from: "/api/v1/namespaces/default/pods?limit=100",
32+
to: "/apis/clusterpedia.io/v1beta1/resources/api/v1/namespaces/default/pods?limit=100",
33+
rewrite: true,
34+
},
35+
},
36+
},
37+
{
38+
name: "none resources paths",
39+
urls: []kubeRequest{
40+
{
41+
from: "/livez",
42+
to: "/livez",
43+
rewrite: false,
44+
},
45+
{
46+
from: "/readyz",
47+
to: "/readyz",
48+
rewrite: false,
49+
},
50+
},
51+
},
52+
{
53+
name: "not need rewrite",
54+
urls: []kubeRequest{
55+
{
56+
from: "/apis/clusterpedia.io",
57+
to: "/apis/clusterpedia.io",
58+
rewrite: false,
59+
},
60+
{
61+
from: "/apis/clusterpedia.io/",
62+
to: "/apis/clusterpedia.io/",
63+
rewrite: false,
64+
},
65+
{
66+
from: "/apis/clusterpedia.io/any",
67+
to: "/apis/clusterpedia.io/any",
68+
rewrite: false,
69+
},
70+
{
71+
from: "/apis/clusterpedia.io/v1beta1/resources/api/v1/namespaces/default/pods",
72+
to: "/apis/clusterpedia.io/v1beta1/resources/api/v1/namespaces/default/pods",
73+
rewrite: false,
74+
},
75+
{
76+
from: "/apis/clusterpedia.io/v1beta1/resources/apis/clusterpedia.io/v1beta1/clusters",
77+
to: "/apis/clusterpedia.io/v1beta1/resources/apis/clusterpedia.io/v1beta1/clusters",
78+
rewrite: false,
79+
},
80+
},
81+
},
82+
{
83+
name: "special cases",
84+
urls: []kubeRequest{
85+
{
86+
from: "/api/v1/namespaces/default/pods?name=abc#xx",
87+
to: "/apis/clusterpedia.io/v1beta1/resources/api/v1/namespaces/default/pods?name=abc#xx",
88+
rewrite: true,
89+
},
90+
},
91+
},
92+
}
93+
94+
func TestUrlPrefixRewrite(t *testing.T) {
95+
for _, test := range tests {
96+
t.Logf("Test - name: %s", test.name)
97+
98+
for _, tmp := range test.urls {
99+
fromPath, err := url.Parse(tmp.from)
100+
if err != nil {
101+
t.Error(err)
102+
}
103+
104+
rewritePath, doRewrite := urlPrefixRewrite(fromPath.EscapedPath())
105+
if doRewrite != tmp.rewrite {
106+
t.Errorf("Test failed \n from : %s \n to : %s \n needRewrite: %v \n doRewrite: %v",
107+
tmp.from, tmp.to, tmp.rewrite, doRewrite)
108+
}
109+
110+
if doRewrite {
111+
oldURL, err := url.Parse(tmp.to)
112+
if err != nil {
113+
t.Error(err)
114+
}
115+
116+
if oldURL.EscapedPath() != rewritePath {
117+
t.Errorf("Test failed \n from : %s \n to : %s \n oldPath: %s \n rewritePath: %s",
118+
tmp.from, tmp.to, oldURL.EscapedPath(), rewritePath)
119+
}
120+
}
121+
}
122+
}
123+
}
124+
125+
func TestRewrite(t *testing.T) {
126+
for _, test := range tests {
127+
t.Logf("Test - name: %s", test.name)
128+
129+
for _, tmp := range test.urls {
130+
req, err := http.NewRequest("GET", tmp.from, nil)
131+
if err != nil {
132+
t.Fatalf("create HTTP request error: %v", err)
133+
}
134+
135+
oldPath := req.URL.EscapedPath()
136+
137+
h := WithRewriteFilter(
138+
http.HandlerFunc(func(_ http.ResponseWriter, req *http.Request) {
139+
}),
140+
)
141+
142+
t.Logf("From: %s", req.URL.String())
143+
144+
res := httptest.NewRecorder()
145+
h.ServeHTTP(res, req)
146+
147+
t.Logf("Rewrited: %s", req.URL.String())
148+
if req.URL.String() != tmp.to {
149+
t.Errorf("Test failed \n from : %s \n to : %s \n result: %s",
150+
tmp.from, tmp.to, req.URL.RequestURI())
151+
}
152+
153+
if oldHeaderPath := req.Header.Get(OriginPathHeaderKey); oldHeaderPath != "" {
154+
if oldPath != oldHeaderPath {
155+
t.Error("incorrect flag")
156+
}
157+
}
158+
}
159+
}
160+
}

0 commit comments

Comments
 (0)