Skip to content

Commit

Permalink
support apiserver url rewrite
Browse files Browse the repository at this point in the history
Signed-off-by: huiwq1990 <[email protected]>
  • Loading branch information
huiwq1990 committed Nov 22, 2023
1 parent 630cf19 commit 305d1e9
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 0 deletions.
7 changes: 7 additions & 0 deletions pkg/apiserver/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,18 @@ import (
"k8s.io/client-go/discovery"
clientrest "k8s.io/client-go/rest"
"k8s.io/client-go/restmapper"
"k8s.io/klog/v2"

internal "github.com/clusterpedia-io/api/clusterpedia"
"github.com/clusterpedia-io/api/clusterpedia/install"
"github.com/clusterpedia-io/clusterpedia/pkg/apiserver/features"
"github.com/clusterpedia-io/clusterpedia/pkg/apiserver/registry/clusterpedia/collectionresources"
"github.com/clusterpedia-io/clusterpedia/pkg/apiserver/registry/clusterpedia/resources"
"github.com/clusterpedia-io/clusterpedia/pkg/generated/clientset/versioned"
informers "github.com/clusterpedia-io/clusterpedia/pkg/generated/informers/externalversions"
"github.com/clusterpedia-io/clusterpedia/pkg/kubeapiserver"
"github.com/clusterpedia-io/clusterpedia/pkg/storage"
clusterpediafeature "github.com/clusterpedia-io/clusterpedia/pkg/utils/feature"
"github.com/clusterpedia-io/clusterpedia/pkg/utils/filters"
)

Expand Down Expand Up @@ -139,6 +142,10 @@ func (config completedConfig) New() (*ClusterPediaServer, error) {
handler := handlerChainFunc(apiHandler, c)
handler = filters.WithRequestQuery(handler)
handler = filters.WithAcceptHeader(handler)
if clusterpediafeature.FeatureGate.Enabled(features.ApiServerURLRewrite) {
klog.InfoS("Enable rewrite apiserver url")
handler = filters.WithRewriteFilter(handler)
}
return handler
}

Expand Down
26 changes: 26 additions & 0 deletions pkg/apiserver/features/features.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package features

import (
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/component-base/featuregate"

clusterpediafeature "github.com/clusterpedia-io/clusterpedia/pkg/utils/feature"
)

const (

// ApiServerURLRewrite is a feature gate for rewrite apiserver request's URL
// owner: @huiwq1990
// alpha: v0.7.0
ApiServerURLRewrite featuregate.Feature = "ApiServerURLRewrite"
)

func init() {
runtime.Must(clusterpediafeature.MutableFeatureGate.Add(defaultApiServerFeatureGates))
}

// defaultApiServerFeatureGates consists of all known apiserver feature keys.
// To add a new feature, define a key for it above and add it here.
var defaultApiServerFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
ApiServerURLRewrite: {Default: false, PreRelease: featuregate.Alpha},
}
40 changes: 40 additions & 0 deletions pkg/utils/filters/rewrite.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package filters

import (
"net/http"
"net/url"
"strings"

"k8s.io/klog/v2"
)

const OriginPathHeaderKey = "X-Rewrite-Original-Path"
const OldResourceApiServerPrefix = "/apis/clusterpedia.io/v1beta1/resources"

func WithRewriteFilter(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
if !doUrlRewrite(req) {
klog.V(5).InfoS("request not need rewrite", "path", req.URL.EscapedPath())
}

handler.ServeHTTP(w, req)
})
}

func doUrlRewrite(req *http.Request) bool {
oldPath := req.URL.EscapedPath()
if strings.HasPrefix(oldPath, OldResourceApiServerPrefix) {
return false
}

rewritePath, err := url.JoinPath(OldResourceApiServerPrefix, req.URL.Path)
if err != nil {
return false
}
req.URL.Path = rewritePath
req.Header.Set(OriginPathHeaderKey, oldPath)

klog.V(5).InfoS("Rewrite url", "oldPath", oldPath, "newPath", req.URL.EscapedPath())

return true
}
77 changes: 77 additions & 0 deletions pkg/utils/filters/rewrite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package filters

import (
"net/http"
"net/http/httptest"
"testing"
)

type testCase struct {
name string
urls []kubeRequest
}

type kubeRequest struct {
from string
to string
}

func TestRewrite(t *testing.T) {
tests := []testCase{
{
name: "do rewrite",
urls: []kubeRequest{
{from: "/api/v1/namespaces/default/pods?limit=100", to: "/apis/clusterpedia.io/v1beta1/resources/api/v1/namespaces/default/pods?limit=100"},
{from: "/apis/clusterpedia.io/v1beta1/clusters", to: "/apis/clusterpedia.io/v1beta1/resources/apis/clusterpedia.io/v1beta1/clusters"},
},
},
{
name: "not need rewrite",
urls: []kubeRequest{
{from: "/apis/clusterpedia.io/v1beta1/resources/api/v1/namespaces/default/pods", to: "/apis/clusterpedia.io/v1beta1/resources/api/v1/namespaces/default/pods"},
{from: "/apis/clusterpedia.io/v1beta1/resources/apis/clusterpedia.io/v1beta1/clusters", to: "/apis/clusterpedia.io/v1beta1/resources/apis/clusterpedia.io/v1beta1/clusters"},
},
},
{
name: "special cases",
urls: []kubeRequest{
{from: "/api/v1/namespaces/default/pods?name=abc#xx", to: "/apis/clusterpedia.io/v1beta1/resources/api/v1/namespaces/default/pods?name=abc#xx"},
},
},
}

for _, test := range tests {
t.Logf("Test - name: %s", test.name)

for _, tmp := range test.urls {
req, err := http.NewRequest("GET", tmp.from, nil)
if err != nil {
t.Fatalf("create HTTP request error: %v", err)
}

oldPath := req.URL.EscapedPath()

h := WithRewriteFilter(
http.HandlerFunc(func(_ http.ResponseWriter, req *http.Request) {
}),
)

t.Logf("From: %s", req.URL.String())

res := httptest.NewRecorder()
h.ServeHTTP(res, req)

t.Logf("Rewrited: %s", req.URL.String())
if req.URL.String() != tmp.to {
t.Errorf("Test failed \n from : %s \n to : %s \n result: %s",
tmp.from, tmp.to, req.URL.RequestURI())
}

if oldHeaderPath := req.Header.Get(OriginPathHeaderKey); oldHeaderPath != "" {
if oldPath != oldHeaderPath {
t.Error("incorrect flag")
}
}
}
}
}

0 comments on commit 305d1e9

Please sign in to comment.