Repository: karmada-io/karmada
Pull request #6326: fix proxy request error with pb content type
Author: login: ikaven1024
Labels: name: kind/bug
description: Categorizes issue or PR as related to a bug.
name: approved
description: Indicates a PR has been approved by an approver from all required OWNERS files.
name: size/L
description: Denotes a PR that changes 100-499 lines, ignoring generated files.
name: lgtm
description: Indicates that a PR is ready to be merged.
Created: 2025-04-26T06:56:28Z | Merged: 2025-04-27T06:51:11Z

**What type of PR is this?**
/kind bug

**What this PR does / why we need it**:

**Which issue(s) this PR fixes**:
Fixes #

**Special notes for your reviewer**:

**Does this PR introduce a user-facing change?**:
<!--
If no, just write "NONE" in the release-note block below.
If yes, a release note is required.
-->
```release-note
`karmada-search`: Suppress protocol buffer serialization explicitly from the server side. 
```




Diff:
diff --git a/pkg/search/proxy/controller.go b/pkg/search/proxy/controller.go
index 15b5bebeefbc..39f5bdc67032 100644
--- a/pkg/search/proxy/controller.go
+++ b/pkg/search/proxy/controller.go
@@ -291,6 +291,7 @@ func (ctl *Controller) Connect(ctx context.Context, proxyPath string, responder
 		}
 
 		h, err := ctl.proxy.Connect(newCtx, framework.ProxyRequest{
+			RestMapper:           ctl.restMapper,
 			RequestInfo:          requestInfo,
 			GroupVersionResource: gvr,
 			ProxyPath:            proxyPath,
diff --git a/pkg/search/proxy/framework/interface.go b/pkg/search/proxy/framework/interface.go
index fec1714203ce..43c8d0877990 100644
--- a/pkg/search/proxy/framework/interface.go
+++ b/pkg/search/proxy/framework/interface.go
@@ -20,6 +20,7 @@ import (
 	"context"
 	"net/http"
 
+	"k8s.io/apimachinery/pkg/api/meta"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/apiserver/pkg/endpoints/request"
 	"k8s.io/apiserver/pkg/registry/rest"
@@ -74,6 +75,7 @@ type Plugin interface {
 
 // ProxyRequest holds parameter for Proxy.Connect()
 type ProxyRequest struct {
+	RestMapper           meta.RESTMapper
 	RequestInfo          *request.RequestInfo
 	GroupVersionResource schema.GroupVersionResource
 	ProxyPath            string
diff --git a/pkg/search/proxy/framework/plugins/cache/cache.go b/pkg/search/proxy/framework/plugins/cache/cache.go
index 48858fb1c811..9ea68856e79d 100644
--- a/pkg/search/proxy/framework/plugins/cache/cache.go
+++ b/pkg/search/proxy/framework/plugins/cache/cache.go
@@ -25,6 +25,7 @@ import (
 	metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructuredscheme"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/apimachinery/pkg/watch"
@@ -109,7 +110,7 @@ func (c *Cache) Connect(_ context.Context, request framework.ProxyRequest) (http
 			Namer:         meta.NewAccessor(),
 			ClusterScoped: mapping.Scope.Name() == meta.RESTScopeNameRoot,
 		},
-		Serializer:       scheme.Codecs.WithoutConversion(),
+		Serializer:       cacheSerializer,
 		Convertor:        cacheScheme,
 		Subresource:      requestInfo.Subresource,
 		MetaGroupVersion: metav1.SchemeGroupVersion,
@@ -126,6 +127,22 @@ func (c *Cache) Connect(_ context.Context, request framework.ProxyRequest) (http
 	return h, nil
 }
 
+var (
+	cacheSerializer = &customSerializer{
+		NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
+		supportedMediaTypes:  unstructuredscheme.NewUnstructuredNegotiatedSerializer().SupportedMediaTypes(),
+	}
+)
+
+type customSerializer struct {
+	runtime.NegotiatedSerializer
+	supportedMediaTypes []runtime.SerializerInfo
+}
+
+func (u *customSerializer) SupportedMediaTypes() []runtime.SerializerInfo {
+	return u.supportedMediaTypes
+}
+
 type rester struct {
 	store          store.Store
 	gvr            schema.GroupVersionResource
diff --git a/pkg/search/proxy/framework/plugins/cluster/cluster.go b/pkg/search/proxy/framework/plugins/cluster/cluster.go
index 9434fb7e3330..efdf0c1bca9a 100644
--- a/pkg/search/proxy/framework/plugins/cluster/cluster.go
+++ b/pkg/search/proxy/framework/plugins/cluster/cluster.go
@@ -25,7 +25,10 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/client-go/kubernetes/scheme"
 	listcorev1 "k8s.io/client-go/listers/core/v1"
+	"k8s.io/klog/v2"
 
 	clusterapis "github.com/karmada-io/karmada/pkg/apis/cluster"
 	clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
@@ -118,7 +121,7 @@ func (c *Cluster) Connect(ctx context.Context, request framework.ProxyRequest) (
 	// Objects get by client via proxy are edited some fields, different from objects in member clusters.
 	// So before update, we shall recover these fields.
 	return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
-		if err = modifyRequest(req, clusterName); err != nil {
+		if err = modifyRequest(req, request, clusterName); err != nil {
 			request.Responder.Error(err)
 			return
 		}
@@ -126,7 +129,7 @@ func (c *Cluster) Connect(ctx context.Context, request framework.ProxyRequest) (
 	}), nil
 }
 
-func modifyRequest(req *http.Request, cluster string) error {
+func modifyRequest(req *http.Request, requestInfo framework.ProxyRequest, cluster string) error {
 	if req.ContentLength == 0 {
 		return nil
 	}
@@ -143,9 +146,16 @@ func modifyRequest(req *http.Request, cluster string) error {
 		req.ContentLength = int64(body.Len())
 	}()
 
-	obj := &unstructured.Unstructured{}
-	_, _, err = unstructured.UnstructuredJSONScheme.Decode(body.Bytes(), nil, obj)
+	encoder, decoder, obj, err := getSerializer(req, requestInfo)
 	if err != nil {
+		klog.Warningf("modifyRequest getSerializer error for request %s: %v", req.RequestURI, err)
+		// ignore error
+		return nil
+	}
+
+	_, _, err = decoder.Decode(body.Bytes(), nil, obj)
+	if err != nil {
+		klog.Warningf("modifyRequest decode error for request %s: %v", req.RequestURI, err)
 		// ignore error
 		return nil
 	}
@@ -156,8 +166,23 @@ func modifyRequest(req *http.Request, cluster string) error {
 	if changed {
 		// write changed object into body
 		body.Reset()
-		return unstructured.UnstructuredJSONScheme.Encode(obj, body)
+		return encoder.Encode(obj, body)
 	}
 
 	return nil
 }
+
+func getSerializer(req *http.Request, requestInfo framework.ProxyRequest) (runtime.Encoder, runtime.Decoder, runtime.Object, error) {
+	gvk, _ := requestInfo.RestMapper.KindFor(requestInfo.GroupVersionResource)
+	if scheme.Scheme.Recognizes(gvk) {
+		negotiator := runtime.NewClientNegotiator(scheme.Codecs.WithoutConversion(), requestInfo.GroupVersionResource.GroupVersion())
+		contentType := req.Header.Get("Content-Type")
+		encoder, _ := negotiator.Encoder(contentType, nil)
+		decoder, _ := negotiator.Decoder(contentType, nil)
+
+		obj, err := scheme.Scheme.New(gvk)
+		return encoder, decoder, obj, err
+	}
+
+	return unstructured.UnstructuredJSONScheme, unstructured.UnstructuredJSONScheme, &unstructured.Unstructured{}, nil
+}
diff --git a/pkg/search/proxy/framework/plugins/cluster/cluster_test.go b/pkg/search/proxy/framework/plugins/cluster/cluster_test.go
index 6f3b82ee0e91..5b9fc41b9b69 100644
--- a/pkg/search/proxy/framework/plugins/cluster/cluster_test.go
+++ b/pkg/search/proxy/framework/plugins/cluster/cluster_test.go
@@ -26,12 +26,12 @@ import (
 	"net/http"
 	"net/http/httptest"
 	"net/url"
-	"reflect"
 	"testing"
 
-	"github.com/google/go-cmp/cmp"
+	"github.com/stretchr/testify/require"
 	corev1 "k8s.io/api/core/v1"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/api/meta"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime"
@@ -40,6 +40,7 @@ import (
 	"k8s.io/apiserver/pkg/endpoints/request"
 	"k8s.io/client-go/informers"
 	"k8s.io/client-go/kubernetes/fake"
+	"k8s.io/client-go/kubernetes/scheme"
 
 	clusterapis "github.com/karmada-io/karmada/pkg/apis/cluster"
 	clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
@@ -52,21 +53,64 @@ import (
 )
 
 func TestModifyRequest(t *testing.T) {
-	newObjectFunc := func(annotations map[string]string, resourceVersion string) *unstructured.Unstructured {
-		obj := &unstructured.Unstructured{}
-		obj.SetAPIVersion("v1")
-		obj.SetKind("Pod")
-		obj.SetAnnotations(annotations)
-		obj.SetResourceVersion(resourceVersion)
-		return obj
+	const (
+		contentTypeJSON     = "application/json"
+		contentTypeProtobuf = "application/vnd.kubernetes.protobuf"
+	)
+
+	makePod := func(annotations map[string]string, resourceVersion string) *corev1.Pod {
+		return &corev1.Pod{
+			TypeMeta: metav1.TypeMeta{
+				APIVersion: "v1",
+				Kind:       "Pod",
+			},
+			ObjectMeta: metav1.ObjectMeta{
+				Annotations:     annotations,
+				ResourceVersion: resourceVersion,
+			},
+		}
+	}
+
+	jsonEncode := func(t *testing.T, obj runtime.Object) []byte {
+		buf := &bytes.Buffer{}
+		require.NoError(t, json.NewEncoder(buf).Encode(obj))
+		return buf.Bytes()
+	}
+
+	protobufEncode := func(t *testing.T, obj runtime.Object) []byte {
+		buf := &bytes.Buffer{}
+		encoder, err := runtime.NewClientNegotiator(scheme.Codecs.WithoutConversion(), corev1.SchemeGroupVersion).Encoder(contentTypeProtobuf, nil)
+		require.NoError(t, err)
+		require.NoError(t, encoder.Encode(obj, buf))
+		return buf.Bytes()
+	}
+
+	makeRequest := func(t *testing.T, contentType string, obj runtime.Object) *http.Request {
+		var body io.Reader
+		if obj != nil {
+			switch contentType {
+			case contentTypeJSON:
+				body = bytes.NewBuffer(jsonEncode(t, obj))
+			case contentTypeProtobuf:
+				body = bytes.NewBuffer(protobufEncode(t, obj))
+			default:
+				t.Fatalf("unknown content-type %s", contentType)
+			}
+		}
+
+		req, err := http.NewRequest(http.MethodPut, "", body)
+		require.NoError(t, err)
+		req.Header.Add("Content-Type", contentType)
+		return req
 	}
 
 	type args struct {
-		body    interface{}
-		cluster string
+		reqFunc     func(t *testing.T) *http.Request
+		requestInfo framework.ProxyRequest
+		cluster     string
 	}
 	type want struct {
-		body interface{}
+		bodyFunc func(t *testing.T) []byte
 	}
 
 	tests := []struct {
@@ -77,102 +121,169 @@ func TestModifyRequest(t *testing.T) {
 		{
 			name: "Empty body",
 			args: args{
-				body: nil,
+				reqFunc: func(t *testing.T) *http.Request {
+					return makeRequest(t, contentTypeJSON, nil)
+				},
+				requestInfo: framework.ProxyRequest{
+					RestMapper: &mockRestMapper{},
+				},
 			},
 			want: want{
-				body: nil,
+				bodyFunc: func(*testing.T) []byte {
+					return nil
+				},
 			},
 		},
 		{
 			name: "Body with nil annotations",
 			args: args{
-				body: newObjectFunc(nil, ""),
+				reqFunc: func(t *testing.T) *http.Request {
+					return makeRequest(t, contentTypeJSON, makePod(nil, ""))
+				},
+				requestInfo: framework.ProxyRequest{
+					RestMapper:           &mockRestMapper{},
+					GroupVersionResource: proxytest.PodGVR,
+				},
 			},
 			want: want{
-				body: newObjectFunc(nil, ""),
+				bodyFunc: func(t *testing.T) []byte {
+					return jsonEncode(t, makePod(nil, ""))
+				},
 			},
 		},
 		{
 			name: "Body with empty annotations",
 			args: args{
-				body: newObjectFunc(map[string]string{}, ""),
+				reqFunc: func(t *testing.T) *http.Request {
+					return makeRequest(t, contentTypeJSON, makePod(map[string]string{}, ""))
+				},
+				requestInfo: framework.ProxyRequest{
+					RestMapper:           &mockRestMapper{},
+					GroupVersionResource: proxytest.PodGVR,
+				},
 			},
 			want: want{
-				body: newObjectFunc(map[string]string{}, ""),
+				bodyFunc: func(t *testing.T) []byte {
+					return jsonEncode(t, makePod(map[string]string{}, ""))
+				},
 			},
 		},
 		{
 			name: "Body with cache source annotation",
 			args: args{
-				body: newObjectFunc(map[string]string{clusterv1alpha1.CacheSourceAnnotationKey: "bar"}, ""),
+				reqFunc: func(t *testing.T) *http.Request {
+					obj := makePod(map[string]string{clusterv1alpha1.CacheSourceAnnotationKey: "bar"}, "")
+					return makeRequest(t, contentTypeJSON, obj)
+				},
+				requestInfo: framework.ProxyRequest{
+					RestMapper:           &mockRestMapper{},
+					GroupVersionResource: proxytest.PodGVR,
+				},
 			},
 			want: want{
-				body: newObjectFunc(map[string]string{}, ""),
+				bodyFunc: func(t *testing.T) []byte {
+					return jsonEncode(t, makePod(nil, ""))
+				},
 			},
 		},
 		{
 			name: "Body with single cluster resource version",
 			args: args{
-				body:    newObjectFunc(nil, "1234"),
+				reqFunc: func(t *testing.T) *http.Request {
+					return makeRequest(t, contentTypeJSON, makePod(nil, "1234"))
+				},
+				requestInfo: framework.ProxyRequest{
+					RestMapper:           &mockRestMapper{},
+					GroupVersionResource: proxytest.PodGVR,
+				},
 				cluster: "cluster1",
 			},
 			want: want{
-				body: newObjectFunc(nil, "1234"),
+				bodyFunc: func(t *testing.T) []byte {
+					return jsonEncode(t, makePod(nil, "1234"))
+				},
 			},
 		},
 		{
 			name: "Body with multi cluster resource version",
 			args: args{
-				body:    newObjectFunc(nil, store.BuildMultiClusterResourceVersion(map[string]string{"cluster1": "1234", "cluster2": "5678"})),
+				reqFunc: func(t *testing.T) *http.Request {
+					obj := makePod(nil, store.BuildMultiClusterResourceVersion(map[string]string{"cluster1": "1234", "cluster2": "5678"}))
+					return makeRequest(t, contentTypeJSON, obj)
+				},
+				requestInfo: framework.ProxyRequest{
+					RestMapper:           &mockRestMapper{},
+					GroupVersionResource: proxytest.PodGVR,
+				},
 				cluster: "cluster1",
 			},
 			want: want{
-				body: newObjectFunc(nil, "1234"),
+				bodyFunc: func(t *testing.T) []byte {
+					return jsonEncode(t, makePod(nil, "1234"))
+				},
+			},
+		},
+		{
+			name: "pod with protobuf",
+			args: args{
+				reqFunc: func(t *testing.T) *http.Request {
+					obj := makePod(map[string]string{clusterv1alpha1.CacheSourceAnnotationKey: "cluster1"}, "1234")
+					return makeRequest(t, contentTypeProtobuf, obj)
+				},
+				requestInfo: framework.ProxyRequest{
+					RestMapper:           &mockRestMapper{},
+					GroupVersionResource: proxytest.PodGVR,
+				},
+				cluster: "cluster1",
+			},
+			want: want{
+				bodyFunc: func(t *testing.T) []byte {
+					return protobufEncode(t, makePod(nil, "1234"))
+				},
+			},
+		},
+		{
+			name: "crd request",
+			args: args{
+				reqFunc: func(t *testing.T) *http.Request {
+					obj := &unstructured.Unstructured{}
+					obj.SetAPIVersion("testing.com/v1")
+					obj.SetKind("TestCRD")
+					obj.SetAnnotations(map[string]string{clusterv1alpha1.CacheSourceAnnotationKey: "cluster1"})
+					return makeRequest(t, contentTypeJSON, obj)
+				},
+				requestInfo: framework.ProxyRequest{
+					RestMapper:           &mockRestMapper{},
+					GroupVersionResource: schema.GroupVersionResource{Group: "testing.com", Version: "v1", Resource: "testcrds"},
+				},
+				cluster: "cluster1",
+			},
+			want: want{
+				bodyFunc: func(t *testing.T) []byte {
+					obj := &unstructured.Unstructured{}
+					obj.SetAPIVersion("testing.com/v1")
+					obj.SetKind("TestCRD")
+					obj.SetAnnotations(map[string]string{})
+					return jsonEncode(t, obj)
+				},
 			},
 		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			var body io.Reader
-			if tt.args.body != nil {
-				buf := bytes.NewBuffer(nil)
-				err := json.NewEncoder(buf).Encode(tt.args.body)
-				if err != nil {
-					t.Error(err)
-					return
-				}
-				body = buf
-			}
-			req, _ := http.NewRequest(http.MethodPut, "/api/v1/namespaces/default/pods/foo", body)
-			err := modifyRequest(req, tt.args.cluster)
-			if err != nil {
-				t.Error(err)
-				return
-			}
-
-			var get runtime.Object
-			if req.ContentLength != 0 {
-				data, err := io.ReadAll(req.Body)
-				if err != nil {
-					t.Error(err)
-					return
-				}
+			var err error
+			req := tt.args.reqFunc(t)
 
-				if int64(len(data)) != req.ContentLength {
-					t.Errorf("expect contentLength %v, but got %v", len(data), req.ContentLength)
-					return
-				}
-
-				get, _, err = unstructured.UnstructuredJSONScheme.Decode(data, nil, nil)
-				if err != nil {
-					t.Error(err)
-					return
-				}
+			require.NoError(t, modifyRequest(req, tt.args.requestInfo, tt.args.cluster))
+			var got []byte
+			if req.Body != nil {
+				got, err = io.ReadAll(req.Body)
+				require.NoError(t, err)
+				require.Equal(t, int(req.ContentLength), len(got), "ContentLength")
 			}
 
-			if !reflect.DeepEqual(tt.want.body, get) {
-				t.Errorf("get body diff: %v", cmp.Diff(tt.want.body, get))
-			}
+			w := tt.want.bodyFunc(t)
+			require.Equal(t, w, got)
 		})
 	}
 }
@@ -457,3 +568,18 @@ type alwaysErrorReader struct{}
 func (alwaysErrorReader) Read([]byte) (int, error) {
 	return 0, io.ErrUnexpectedEOF
 }
+
+type mockRestMapper struct {
+	meta.RESTMapper
+}
+
+func (mockRestMapper) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) {
+	switch resource {
+	case corev1.SchemeGroupVersion.WithResource("pods"):
+		return resource.GroupVersion().WithKind("Pod"), nil
+	case corev1.SchemeGroupVersion.WithResource("nodes"):
+		return resource.GroupVersion().WithKind("Node"), nil
+	default:
+		return schema.GroupVersionKind{}, fmt.Errorf("unknown resource %s", resource)
+	}
+}
diff --git a/test/e2e/suites/base/search_test.go b/test/e2e/suites/base/search_test.go
index bf10025e10f4..494390efda33 100644
--- a/test/e2e/suites/base/search_test.go
+++ b/test/e2e/suites/base/search_test.go
@@ -30,6 +30,7 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/rand"
 	"k8s.io/apimachinery/pkg/util/sets"
@@ -378,6 +379,8 @@ var _ = ginkgo.Describe("[karmada-search] karmada search testing", ginkgo.Ordere
 
 			proxyConfig := *restConfig
 			proxyConfig.Host += "/apis/search.karmada.io/v1alpha1/proxying/karmada/proxy"
+			proxyConfig.ContentType = runtime.ContentTypeProtobuf
+			proxyConfig.AcceptContentTypes = "application/vnd.kubernetes.protobuf,application/json"
 			proxyClient = kubernetes.NewForConfigOrDie(&proxyConfig)
 		})
 


Reviews:
user: login: RainbowMango
body: /assign
Great thanks for your help~
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-27T02:08:00Z
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR fixes proxy request errors when using the protobuf content type by updating request construction, modifying request serialization in the cluster plugin, and adjusting related test logic.  
- Updates e2e and unit tests to use helper functions for constructing requests with JSON and protobuf bodies.  
- Modifies the request serializer retrieval and error handling in the cluster plugin.  
- Introduces a custom serializer for cache proxy connections and integrates the RestMapper into relevant components.

### Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

<details>
<summary>Show a summary per file</summary>

| File | Description |
| ---- | ----------- |
| test/e2e/suites/base/search_test.go | Sets the correct protobuf ContentType and Accept headers for proxy requests. |
| pkg/search/proxy/framework/plugins/cluster/cluster_test.go | Refactors test helpers and request body encoding to support both JSON and Protobuf. |
| pkg/search/proxy/framework/plugins/cluster/cluster.go | Updates modifyRequest to use a serializer from request information and adjusts error handling. |
| pkg/search/proxy/framework/plugins/cache/cache.go | Introduces a custom serializer for cache proxy responses. |
| pkg/search/proxy/framework/interface.go | Adds a RestMapper field to ProxyRequest for improved serializer resolution. |
| pkg/search/proxy/controller.go | Passes the RestMapper from the controller to the proxy plugin. |
</details>





state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-27T02:08:52Z
user: login: RainbowMango
body: /lgtm
/approve
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-27T06:50:19Z

Review comments:
diff_hunk: @@ -143,9 +146,16 @@ func modifyRequest(req *http.Request, cluster string) error {
 		req.ContentLength = int64(body.Len())
 	}()
 
-	obj := &unstructured.Unstructured{}
-	_, _, err = unstructured.UnstructuredJSONScheme.Decode(body.Bytes(), nil, obj)
+	encoder, decoder, obj, err := getSerializer(req, requestInfo)
 	if err != nil {
+		klog.Warningf("modifyRequest getSerializer error for request %s: %v", req.RequestURI, err)
+		// ignore error
+		return nil
path: pkg/search/proxy/framework/plugins/cluster/cluster.go
user: login: Copilot
body: Errors from getSerializer are logged as warnings and then suppressed, potentially concealing serialization issues. Consider returning the error instead of silently ignoring it for better observability and debugging.
```suggestion
		return err
```
created_at: 2025-04-27T02:08:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -156,8 +166,23 @@ func modifyRequest(req *http.Request, cluster string) error {
 	if changed {
 		// write changed object into body
 		body.Reset()
-		return unstructured.UnstructuredJSONScheme.Encode(obj, body)
+		return encoder.Encode(obj, body)
 	}
 
 	return nil
 }
+
+func getSerializer(req *http.Request, requestInfo framework.ProxyRequest) (runtime.Encoder, runtime.Decoder, runtime.Object, error) {
+	gvk, _ := requestInfo.RestMapper.KindFor(requestInfo.GroupVersionResource)
+	if scheme.Scheme.Recognizes(gvk) {
+		negotiator := runtime.NewClientNegotiator(scheme.Codecs.WithoutConversion(), requestInfo.GroupVersionResource.GroupVersion())
+		contentType := req.Header.Get("Content-Type")
+		encoder, _ := negotiator.Encoder(contentType, nil)
+		decoder, _ := negotiator.Decoder(contentType, nil)
+
+		obj, err := scheme.Scheme.New(gvk)
+		return encoder, decoder, obj, err
+	}
+
+	return unstructured.UnstructuredJSONScheme, unstructured.UnstructuredJSONScheme, &unstructured.Unstructured{}, nil
path: pkg/search/proxy/framework/plugins/cluster/cluster.go
user: login: Copilot
body: The fallback in getSerializer always returns the unstructured JSON scheme when the concrete type is not recognized, which might hide potential mismatches in expected serialization. Review whether propagating an error or handling this case differently would be more appropriate.
created_at: 2025-04-27T02:08:52Z
author_association: CONTRIBUTOR

PR comments:
user: login: codecov-commenter
created_at: 2025-04-26T12:27:12Z
body: :warning: Please install the !['codecov app svg image'](https://github.com/codecov/engineering-team/assets/152432831/e90313f4-9d3a-4b63-8b54-cfe14e7ec20d) to ensure uploads and comments are reliably processed by Codecov.

## [Codecov](https://app.codecov.io/gh/karmada-io/karmada/pull/6326?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=karmada-io) Report
:x: Patch coverage is `77.77778%` with `6 lines` in your changes missing coverage. Please review.
:white_check_mark: Project coverage is 48.45%. Comparing base ([`c31c8e4`](https://app.codecov.io/gh/karmada-io/karmada/commit/c31c8e4a58430bc8bdf4b28fa7232dffbc3fce49?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=karmada-io)) to head ([`7835614`](https://app.codecov.io/gh/karmada-io/karmada/commit/7835614378a8eacfd7938468f6344acf596d2a9e?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=karmada-io)).
:warning: Report is 1008 commits behind head on master.

| [Files with missing lines](https://app.codecov.io/gh/karmada-io/karmada/pull/6326?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=karmada-io) | Patch % | Lines |
|---|---|---|
| [.../search/proxy/framework/plugins/cluster/cluster.go](https://app.codecov.io/gh/karmada-io/karmada/pull/6326?src=pr&el=tree&filepath=pkg%2Fsearch%2Fproxy%2Fframework%2Fplugins%2Fcluster%2Fcluster.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=karmada-io#diff-cGtnL3NlYXJjaC9wcm94eS9mcmFtZXdvcmsvcGx1Z2lucy9jbHVzdGVyL2NsdXN0ZXIuZ28=) | 73.91% | [5 Missing and 1 partial :warning: ](https://app.codecov.io/gh/karmada-io/karmada/pull/6326?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=karmada-io) |
:exclamation: Your organization needs to install the [Codecov GitHub app](https://github.com/apps/codecov/installations/select_target) to enable full functionality.

<details><summary>Additional details and impacted files</summary>



```diff
@@            Coverage Diff             @@
##           master    #6326      +/-   ##
==========================================
+ Coverage   48.44%   48.45%   +0.01%     
==========================================
  Files         677      677              
  Lines       56067    56088      +21     
==========================================
+ Hits        27164    27180      +16     
- Misses      27123    27126       +3     
- Partials     1780     1782       +2     
```

| [Flag](https://app.codecov.io/gh/karmada-io/karmada/pull/6326/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=karmada-io) | Coverage Δ | |
|---|---|---|
| [unittests](https://app.codecov.io/gh/karmada-io/karmada/pull/6326/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=karmada-io) | `48.45% <77.77%> (+0.01%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=karmada-io#carryforward-flags-in-the-pull-request-comment) to find out more.
</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/karmada-io/karmada/pull/6326?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=karmada-io).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=karmada-io).
<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE
user: login: ikaven1024
created_at: 2025-04-26T15:01:24Z
body: @RainbowMango 
author_association: MEMBER
user: login: karmada-bot
created_at: 2025-04-27T06:50:25Z
body: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/karmada-io/karmada/pull/6326#pullrequestreview-2797446629" title="Approved">RainbowMango</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=karmada-io%2Fkarmada).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[pkg/search/OWNERS](https://github.com/karmada-io/karmada/blob/master/pkg/search/OWNERS)~~ [RainbowMango]
- ~~[test/OWNERS](https://github.com/karmada-io/karmada/blob/master/test/OWNERS)~~ [RainbowMango]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
author_association: CONTRIBUTOR