summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorzhengxinwei <zhengxinwei@huawei.com>2023-07-22 14:55:12 +0800
committerzhengxinwei <zhengxinwei@huawei.com>2023-10-07 09:46:55 +0800
commitaa3ba72d383d125ec3d84783a539d5b0d4c5c62c (patch)
treecac86dfb50fb6608e1b77c2e722d0661593fdabf
parentMerge pull request #5040 from Onion-of-dreamed/fix/without-mqtt-tag (diff)
downloadkubeedge-aa3ba72d383d125ec3d84783a539d5b0d4c5c62c.tar.gz
metaserver support pass through API, open /version path
Signed-off-by: zhengxinwei <zhengxinwei@huawei.com>
-rw-r--r--LICENSES/vendor/github.com/agiledragon/gomonkey/LICENSE25
-rw-r--r--cloud/pkg/dynamiccontroller/application/application.go48
-rw-r--r--cloud/pkg/dynamiccontroller/application/application_test.go86
-rw-r--r--edge/pkg/metamanager/metaserver/handlerfactory/handler.go17
-rw-r--r--edge/pkg/metamanager/metaserver/kubernetes/storage/sqlite/imitator/client.go2
-rw-r--r--edge/pkg/metamanager/metaserver/kubernetes/storage/sqlite/imitator/fake/fake.go75
-rw-r--r--edge/pkg/metamanager/metaserver/kubernetes/storage/sqlite/imitator/imitator.go35
-rw-r--r--edge/pkg/metamanager/metaserver/kubernetes/storage/storage.go38
-rw-r--r--edge/pkg/metamanager/metaserver/kubernetes/storage/storage_test.go128
-rw-r--r--edge/pkg/metamanager/metaserver/server.go16
-rw-r--r--go.mod1
-rw-r--r--go.sum2
-rw-r--r--pkg/metaserver/key.go5
-rw-r--r--pkg/metaserver/key_test.go5
-rw-r--r--pkg/util/pass-through/pass_through.go16
-rw-r--r--pkg/util/pass-through/pass_through_test.go36
-rw-r--r--vendor/github.com/agiledragon/gomonkey/LICENSE21
-rw-r--r--vendor/github.com/agiledragon/gomonkey/README.md37
-rw-r--r--vendor/github.com/agiledragon/gomonkey/jmp_amd64.go18
-rw-r--r--vendor/github.com/agiledragon/gomonkey/modify_binary_darwin.go19
-rw-r--r--vendor/github.com/agiledragon/gomonkey/modify_binary_linux.go19
-rw-r--r--vendor/github.com/agiledragon/gomonkey/modify_binary_windows.go25
-rw-r--r--vendor/github.com/agiledragon/gomonkey/patch.go232
-rw-r--r--vendor/modules.txt3
24 files changed, 892 insertions, 17 deletions
diff --git a/LICENSES/vendor/github.com/agiledragon/gomonkey/LICENSE b/LICENSES/vendor/github.com/agiledragon/gomonkey/LICENSE
new file mode 100644
index 000000000..4662d141d
--- /dev/null
+++ b/LICENSES/vendor/github.com/agiledragon/gomonkey/LICENSE
@@ -0,0 +1,25 @@
+= vendor/github.com/agiledragon/gomonkey licensed under: =
+
+MIT License
+
+Copyright (c) 2018 Zhang Xiaolong
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+= vendor/github.com/agiledragon/gomonkey/LICENSE 9bd88aaa83a25e41d110ebfa6571e8cf
diff --git a/cloud/pkg/dynamiccontroller/application/application.go b/cloud/pkg/dynamiccontroller/application/application.go
index a13882702..00b48fb48 100644
--- a/cloud/pkg/dynamiccontroller/application/application.go
+++ b/cloud/pkg/dynamiccontroller/application/application.go
@@ -11,6 +11,7 @@ import (
"k8s.io/apimachinery/pkg/fields"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/dynamic/dynamicinformer"
+ "k8s.io/client-go/kubernetes"
"k8s.io/klog/v2"
"github.com/kubeedge/beehive/pkg/core/model"
@@ -20,18 +21,21 @@ import (
"github.com/kubeedge/kubeedge/cloud/pkg/dynamiccontroller/filter"
"github.com/kubeedge/kubeedge/edge/pkg/common/message"
"github.com/kubeedge/kubeedge/pkg/metaserver"
+ "github.com/kubeedge/kubeedge/pkg/util/pass-through"
)
type Center struct {
HandlerCenter
- messageLayer messagelayer.MessageLayer
- kubeclient dynamic.Interface
+ messageLayer messagelayer.MessageLayer
+ dynamicClient dynamic.Interface
+ kubeClient kubernetes.Interface
}
func NewApplicationCenter(dynamicSharedInformerFactory dynamicinformer.DynamicSharedInformerFactory) *Center {
a := &Center{
HandlerCenter: NewHandlerCenter(dynamicSharedInformerFactory),
- kubeclient: client.GetDynamicClient(),
+ dynamicClient: client.GetDynamicClient(),
+ kubeClient: client.GetKubeClient(),
messageLayer: messagelayer.DynamicControllerMessageLayer(),
}
return a
@@ -55,6 +59,17 @@ func (c *Center) Process(msg model.Message) {
klog.Infof("[metaserver/ApplicationCenter] get a Application %v", app.String())
+ if passthrough.IsPassThroughPath(app.Key, string(app.Verb)) {
+ resp, err := c.passThroughRequest(app)
+ if err != nil {
+ c.Response(app, msg.GetID(), metaserver.Rejected, err, nil)
+ klog.Errorf("[metaserver/passThrough]failed to process Application(%+v), %v", app, err)
+ return
+ }
+ c.Response(app, msg.GetID(), metaserver.Approved, nil, resp)
+ return
+ }
+
resp, err := c.ProcessApplication(app)
if err != nil {
c.Response(app, msg.GetID(), metaserver.Rejected, err, nil)
@@ -80,7 +95,7 @@ func (c *Center) ProcessApplication(app *metaserver.Application) (interface{}, e
if err := app.OptionTo(option); err != nil {
return nil, err
}
- list, err := c.kubeclient.Resource(gvr).Namespace(ns).List(context.TODO(), *option)
+ list, err := c.dynamicClient.Resource(gvr).Namespace(ns).List(context.TODO(), *option)
if err != nil {
return nil, fmt.Errorf("get current list error: %v", err)
}
@@ -100,7 +115,7 @@ func (c *Center) ProcessApplication(app *metaserver.Application) (interface{}, e
if err := app.OptionTo(option); err != nil {
return nil, err
}
- retObj, err := c.kubeclient.Resource(gvr).Namespace(ns).Get(context.TODO(), name, *option)
+ retObj, err := c.dynamicClient.Resource(gvr).Namespace(ns).Get(context.TODO(), name, *option)
if err != nil {
return nil, err
}
@@ -117,9 +132,9 @@ func (c *Center) ProcessApplication(app *metaserver.Application) (interface{}, e
var retObj interface{}
var err error
if app.Subresource == "" {
- retObj, err = c.kubeclient.Resource(gvr).Namespace(ns).Create(context.TODO(), obj, *option)
+ retObj, err = c.dynamicClient.Resource(gvr).Namespace(ns).Create(context.TODO(), obj, *option)
} else {
- retObj, err = c.kubeclient.Resource(gvr).Namespace(ns).Create(context.TODO(), obj, *option, app.Subresource)
+ retObj, err = c.dynamicClient.Resource(gvr).Namespace(ns).Create(context.TODO(), obj, *option, app.Subresource)
}
if err != nil {
return nil, err
@@ -130,7 +145,7 @@ func (c *Center) ProcessApplication(app *metaserver.Application) (interface{}, e
if err := app.OptionTo(&option); err != nil {
return nil, err
}
- if err := c.kubeclient.Resource(gvr).Namespace(ns).Delete(context.TODO(), name, *option); err != nil {
+ if err := c.dynamicClient.Resource(gvr).Namespace(ns).Delete(context.TODO(), name, *option); err != nil {
return nil, err
}
return nil, nil
@@ -146,9 +161,9 @@ func (c *Center) ProcessApplication(app *metaserver.Application) (interface{}, e
var retObj interface{}
var err error
if app.Subresource == "" {
- retObj, err = c.kubeclient.Resource(gvr).Namespace(ns).Update(context.TODO(), obj, *option)
+ retObj, err = c.dynamicClient.Resource(gvr).Namespace(ns).Update(context.TODO(), obj, *option)
} else {
- retObj, err = c.kubeclient.Resource(gvr).Namespace(ns).Update(context.TODO(), obj, *option, app.Subresource)
+ retObj, err = c.dynamicClient.Resource(gvr).Namespace(ns).Update(context.TODO(), obj, *option, app.Subresource)
}
if err != nil {
return nil, err
@@ -163,7 +178,7 @@ func (c *Center) ProcessApplication(app *metaserver.Application) (interface{}, e
if err := app.ReqBodyTo(obj); err != nil {
return nil, err
}
- retObj, err := c.kubeclient.Resource(gvr).Namespace(ns).UpdateStatus(context.TODO(), obj, *option)
+ retObj, err := c.dynamicClient.Resource(gvr).Namespace(ns).UpdateStatus(context.TODO(), obj, *option)
if err != nil {
return nil, err
}
@@ -173,7 +188,7 @@ func (c *Center) ProcessApplication(app *metaserver.Application) (interface{}, e
if err := app.OptionTo(pi); err != nil {
return nil, err
}
- retObj, err := c.kubeclient.Resource(gvr).Namespace(ns).Patch(context.TODO(), pi.Name, pi.PatchType, pi.Data, pi.Options, pi.Subresources...)
+ retObj, err := c.dynamicClient.Resource(gvr).Namespace(ns).Patch(context.TODO(), pi.Name, pi.PatchType, pi.Data, pi.Options, pi.Subresources...)
if err != nil {
return nil, err
}
@@ -183,6 +198,15 @@ func (c *Center) ProcessApplication(app *metaserver.Application) (interface{}, e
}
}
+func (c *Center) passThroughRequest(app *metaserver.Application) (interface{}, error) {
+ kubeClient, ok := c.kubeClient.(*kubernetes.Clientset)
+ if !ok {
+ return nil, fmt.Errorf("converting kubeClient to *kubernetes.Clientset type failed")
+ }
+ verb := strings.ToUpper(string(app.Verb))
+ return kubeClient.RESTClient().Verb(verb).AbsPath(app.Key).Body(app.ReqBody).Do(context.TODO()).Raw()
+}
+
// Response update application, generate and send resp message to edge
func (c *Center) Response(app *metaserver.Application, parentID string, status metaserver.ApplicationStatus, err error, respContent interface{}) {
app.Status = status
diff --git a/cloud/pkg/dynamiccontroller/application/application_test.go b/cloud/pkg/dynamiccontroller/application/application_test.go
new file mode 100644
index 000000000..bf9234469
--- /dev/null
+++ b/cloud/pkg/dynamiccontroller/application/application_test.go
@@ -0,0 +1,86 @@
+package application
+
+import (
+ "io"
+ "net/http"
+ "reflect"
+ "strings"
+ "testing"
+
+ "k8s.io/client-go/discovery"
+ "k8s.io/client-go/kubernetes"
+ "k8s.io/client-go/kubernetes/scheme"
+ fakerest "k8s.io/client-go/rest/fake"
+
+ "github.com/kubeedge/kubeedge/pkg/metaserver"
+)
+
+func TestCenter_passThroughRequest(t *testing.T) {
+ failureResp := &http.Response{
+ Status: "500 Internal Error",
+ StatusCode: http.StatusInternalServerError,
+ }
+ successResp := &http.Response{
+ Status: "200 ok",
+ StatusCode: http.StatusOK,
+ Body: io.NopCloser(strings.NewReader("{version: 1.27}")),
+ }
+ getVersions := func(key, verb string) *fakerest.RESTClient {
+ if key == "/version" && verb == "get" {
+ return &fakerest.RESTClient{
+ Client: fakerest.CreateHTTPClient(func(request *http.Request) (*http.Response, error) {
+ return successResp, nil
+ }),
+ NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
+ }
+ }
+ return &fakerest.RESTClient{
+ Client: fakerest.CreateHTTPClient(func(request *http.Request) (*http.Response, error) {
+ return failureResp, nil
+ }),
+ NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
+ }
+ }
+
+ tests := []struct {
+ name string
+ app *metaserver.Application
+ want interface{}
+ wantErr bool
+ }{
+ {
+ name: "get version success",
+ app: &metaserver.Application{
+ Key: "/version",
+ Verb: "get",
+ },
+ want: []byte("{version: 1.27}"),
+ wantErr: false,
+ }, {
+ name: "pass through failed",
+ app: &metaserver.Application{
+ Key: "/healthz",
+ Verb: "get",
+ },
+ want: []byte{},
+ wantErr: true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ center := &Center{
+ kubeClient: &kubernetes.Clientset{
+ DiscoveryClient: discovery.NewDiscoveryClient(getVersions(tt.app.Key, string(tt.app.Verb))),
+ },
+ }
+ got, err := center.passThroughRequest(tt.app)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("passThroughRequest() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("passThroughRequest() got = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/edge/pkg/metamanager/metaserver/handlerfactory/handler.go b/edge/pkg/metamanager/metaserver/handlerfactory/handler.go
index 1a33ec65b..777c4102c 100644
--- a/edge/pkg/metamanager/metaserver/handlerfactory/handler.go
+++ b/edge/pkg/metamanager/metaserver/handlerfactory/handler.go
@@ -111,6 +111,23 @@ func (f *Factory) Update(req *request.RequestInfo) http.Handler {
return h
}
+// PassThrough
+// handel with the pass through request
+func (f *Factory) PassThrough() http.Handler {
+ h := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ options := metav1.GetOptions{}
+ result, err := f.storage.PassThrough(req.Context(), &options)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ w.Write(result)
+ })
+ return h
+}
+
func (f *Factory) Patch(reqInfo *request.RequestInfo) http.Handler {
scope := wrapScope{RequestScope: scope.NewRequestScope()}
scope.Kind = schema.GroupVersionKind{
diff --git a/edge/pkg/metamanager/metaserver/kubernetes/storage/sqlite/imitator/client.go b/edge/pkg/metamanager/metaserver/kubernetes/storage/sqlite/imitator/client.go
index 15c2f3116..5afc461f0 100644
--- a/edge/pkg/metamanager/metaserver/kubernetes/storage/sqlite/imitator/client.go
+++ b/edge/pkg/metamanager/metaserver/kubernetes/storage/sqlite/imitator/client.go
@@ -27,6 +27,8 @@ type Client interface {
Inject(msg model.Message)
InsertOrUpdateObj(ctx context.Context, obj runtime.Object) error
DeleteObj(ctx context.Context, obj runtime.Object) error
+ InsertOrUpdatePassThroughObj(ctx context.Context, obj []byte, key string) error
+ GetPassThroughObj(ctx context.Context, key string) ([]byte, error)
GetRevision() uint64
SetRevision(version interface{})
diff --git a/edge/pkg/metamanager/metaserver/kubernetes/storage/sqlite/imitator/fake/fake.go b/edge/pkg/metamanager/metaserver/kubernetes/storage/sqlite/imitator/fake/fake.go
new file mode 100644
index 000000000..2d3d5c0c8
--- /dev/null
+++ b/edge/pkg/metamanager/metaserver/kubernetes/storage/sqlite/imitator/fake/fake.go
@@ -0,0 +1,75 @@
+package fake
+
+import (
+ "context"
+
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/watch"
+
+ "github.com/kubeedge/beehive/pkg/core/model"
+ "github.com/kubeedge/kubeedge/edge/pkg/metamanager/metaserver/kubernetes/storage/sqlite/imitator"
+)
+
+// Client fake
+type Client struct {
+ InjectF func(msg model.Message)
+ InsertOrUpdateObjF func(ctx context.Context, obj runtime.Object) error
+ DeleteObjF func(ctx context.Context, obj runtime.Object) error
+ InsertOrUpdatePassThroughObjF func(ctx context.Context, obj []byte, key string) error
+ GetPassThroughObjF func(ctx context.Context, key string) ([]byte, error)
+ GetRevisionF func() uint64
+ SetRevisionF func(version interface{})
+ ListF func(ctx context.Context, key string) (imitator.Resp, error)
+ GetF func(ctx context.Context, key string) (imitator.Resp, error)
+ WatchF func(ctx context.Context, key string, ResourceVersion uint64) <-chan watch.Event
+}
+
+// Inject fake
+func (c Client) Inject(msg model.Message) {
+ c.InjectF(msg)
+}
+
+// InsertOrUpdateObj fake
+func (c Client) InsertOrUpdateObj(ctx context.Context, obj runtime.Object) error {
+ return c.InsertOrUpdateObjF(ctx, obj)
+}
+
+// DeleteObj fake
+func (c Client) DeleteObj(ctx context.Context, obj runtime.Object) error {
+ return c.DeleteObjF(ctx, obj)
+}
+
+// InsertOrUpdatePassThroughObj fake
+func (c Client) InsertOrUpdatePassThroughObj(ctx context.Context, obj []byte, key string) error {
+ return c.InsertOrUpdatePassThroughObjF(ctx, obj, key)
+}
+
+// GetPassThroughObj fake
+func (c Client) GetPassThroughObj(ctx context.Context, key string) ([]byte, error) {
+ return c.GetPassThroughObjF(ctx, key)
+}
+
+// GetRevision fake
+func (c Client) GetRevision() uint64 {
+ return c.GetRevisionF()
+}
+
+// SetRevision fake
+func (c Client) SetRevision(version interface{}) {
+ c.SetRevisionF(version)
+}
+
+// List fake
+func (c Client) List(ctx context.Context, key string) (imitator.Resp, error) {
+ return c.ListF(ctx, key)
+}
+
+// Get fake
+func (c Client) Get(ctx context.Context, key string) (imitator.Resp, error) {
+ return c.GetF(ctx, key)
+}
+
+// Watch fake
+func (c Client) Watch(ctx context.Context, key string, ResourceVersion uint64) <-chan watch.Event {
+ return c.WatchF(ctx, key, ResourceVersion)
+}
diff --git a/edge/pkg/metamanager/metaserver/kubernetes/storage/sqlite/imitator/imitator.go b/edge/pkg/metamanager/metaserver/kubernetes/storage/sqlite/imitator/imitator.go
index 04d1be044..75415d7b2 100644
--- a/edge/pkg/metamanager/metaserver/kubernetes/storage/sqlite/imitator/imitator.go
+++ b/edge/pkg/metamanager/metaserver/kubernetes/storage/sqlite/imitator/imitator.go
@@ -83,9 +83,13 @@ func (s *imitator) InsertOrUpdateObj(ctx context.Context, obj runtime.Object) er
ResourceVersion: objRv,
Value: buf.String(),
}
+ return s.insertOrReplaceMetaV2(m, objRv)
+}
+
+func (s *imitator) insertOrReplaceMetaV2(m v2.MetaV2, objRv uint64) error {
s.lock.Lock()
defer s.lock.Unlock()
- _, err = dbm.DBAccess.Raw("INSERT OR REPLACE INTO meta_v2 (key, groupversionresource, namespace,name,resourceversion,value) VALUES (?,?,?,?,?,?)", m.Key, m.GroupVersionResource, m.Namespace, m.Name, m.ResourceVersion, m.Value).Exec()
+ _, err := dbm.DBAccess.Raw("INSERT OR REPLACE INTO meta_v2 (key, groupversionresource, namespace,name,resourceversion,value) VALUES (?,?,?,?,?,?)", m.Key, m.GroupVersionResource, m.Namespace, m.Name, m.ResourceVersion, m.Value).Exec()
var maxRetryTimes = 3
for i := 1; err != nil; i++ {
klog.Errorf("failed to access database:%v", err)
@@ -97,9 +101,36 @@ func (s *imitator) InsertOrUpdateObj(ctx context.Context, obj runtime.Object) er
if objRv > s.GetRevision() {
s.SetRevision(objRv)
}
- klog.V(4).Infof("[metaserver]successfully insert or update obj:%v", key)
+ klog.V(4).Infof("[metaserver]successfully insert or update obj:%v", m.Key)
return nil
}
+
+func (s *imitator) InsertOrUpdatePassThroughObj(ctx context.Context, obj []byte, key string) error {
+ m := v2.MetaV2{
+ Key: key,
+ Value: string(obj),
+ }
+ return s.insertOrReplaceMetaV2(m, 0)
+}
+
+func (s *imitator) GetPassThroughObj(ctx context.Context, key string) ([]byte, error) {
+ s.lock.Lock()
+ defer s.lock.Unlock()
+ results := new([]v2.MetaV2)
+ _, err := dbm.DBAccess.QueryTable(v2.NewMetaTableName).Filter(v2.KEY, key).All(results)
+ if err != nil {
+ return nil, err
+ }
+
+ switch {
+ case len(*results) == 1:
+ klog.V(4).Infof("[metaserver]successfully insert or update obj:%v", key)
+ return []byte((*results)[0].Value), nil
+ default:
+ return nil, fmt.Errorf("the server could not find the requested resource")
+ }
+}
+
func (s *imitator) Delete(ctx context.Context, key string) error {
m := v2.MetaV2{
Key: key,
diff --git a/edge/pkg/metamanager/metaserver/kubernetes/storage/storage.go b/edge/pkg/metamanager/metaserver/kubernetes/storage/storage.go
index e82964112..f28a6307b 100644
--- a/edge/pkg/metamanager/metaserver/kubernetes/storage/storage.go
+++ b/edge/pkg/metamanager/metaserver/kubernetes/storage/storage.go
@@ -114,6 +114,44 @@ func (r *REST) Get(ctx context.Context, name string, options *metav1.GetOptions)
return obj, err
}
+// PassThrough
+// The request is routed to the dynamic controller via the metaServer.
+// If the request is approved, the response will be saved to local storage.
+// It will be acquired from local data storage if it fails.
+func (r *REST) PassThrough(ctx context.Context, options *metav1.GetOptions) ([]byte, error) {
+ info, _ := apirequest.RequestInfoFrom(ctx)
+ resp, err := func() ([]byte, error) {
+ app, err := r.Agent.Generate(ctx, metaserver.ApplicationVerb(info.Verb), *options, nil)
+ if err != nil {
+ klog.Errorf("[metaserver/passThrough] failed to generate application: %v", err)
+ return nil, err
+ }
+ err = r.Agent.Apply(app)
+ defer app.Close()
+ if err != nil {
+ klog.Errorf("[metaserver/passThrough] failed to request from cloud: %v", err)
+ return nil, err
+ }
+
+ err = imitator.DefaultV2Client.InsertOrUpdatePassThroughObj(context.TODO(), app.RespBody, app.Key)
+ if err != nil {
+ klog.Warningf("[metaserver/passThrough] failed to insert version information into database: %v", err)
+ }
+ return app.RespBody, nil
+ }()
+ if err != nil {
+ resp, err = imitator.DefaultV2Client.GetPassThroughObj(ctx, info.Path)
+ if err != nil {
+ klog.Errorf("[metaserver/reststorage] failed to get req at local: %v", err)
+ return nil, errors.NewNotFound(schema.GroupResource{Group: info.APIGroup, Resource: info.Resource}, info.Name)
+ }
+ klog.Infof("[metaserver/reststorage] successfully process get req (%v) at local", info.Path)
+ }
+
+ klog.Infof("[metaserver/passThrough] successfully process request (%v)", info.Path)
+ return resp, nil
+}
+
func (r *REST) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) {
info, _ := apirequest.RequestInfoFrom(ctx)
// First try to list the object from remote cloud
diff --git a/edge/pkg/metamanager/metaserver/kubernetes/storage/storage_test.go b/edge/pkg/metamanager/metaserver/kubernetes/storage/storage_test.go
new file mode 100644
index 000000000..298d734dd
--- /dev/null
+++ b/edge/pkg/metamanager/metaserver/kubernetes/storage/storage_test.go
@@ -0,0 +1,128 @@
+package storage
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "reflect"
+ "sync"
+ "testing"
+ "time"
+
+ "github.com/agiledragon/gomonkey"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apiserver/pkg/endpoints/request"
+
+ beehiveContext "github.com/kubeedge/beehive/pkg/core/context"
+ "github.com/kubeedge/beehive/pkg/core/model"
+ connect "github.com/kubeedge/kubeedge/edge/pkg/common/cloudconnection"
+ "github.com/kubeedge/kubeedge/edge/pkg/metamanager/metaserver/agent"
+ "github.com/kubeedge/kubeedge/edge/pkg/metamanager/metaserver/kubernetes/storage/sqlite/imitator"
+ fakeclient "github.com/kubeedge/kubeedge/edge/pkg/metamanager/metaserver/kubernetes/storage/sqlite/imitator/fake"
+ "github.com/kubeedge/kubeedge/pkg/metaserver"
+)
+
+func TestREST_PassThrough(t *testing.T) {
+ type testCase struct {
+ isConnectFailed bool
+ isSendSyncFailed bool
+ isLocalStored bool
+ isInsertLocalStorageFailed bool
+ }
+ cases := testCase{}
+
+ fakeClient := fakeclient.Client{
+ InsertOrUpdatePassThroughObjF: func(ctx context.Context, obj []byte, key string) error {
+ if cases.isInsertLocalStorageFailed {
+ return fmt.Errorf("insert local storage failed")
+ }
+ return nil
+ },
+ GetPassThroughObjF: func(ctx context.Context, key string) ([]byte, error) {
+ if !cases.isLocalStored {
+ return nil, fmt.Errorf("local does not store it")
+ }
+ return []byte("test"), nil
+ },
+ }
+ patch := gomonkey.NewPatches()
+ defer patch.Reset()
+ patch.ApplyFunc(connect.IsConnected, func() bool {
+ return !cases.isConnectFailed
+ }).ApplyFunc(beehiveContext.SendSync, func(string, model.Message, time.Duration) (model.Message, error) {
+ app := metaserver.Application{
+ RespBody: []byte("test"),
+ Status: metaserver.Approved,
+ Reason: "ok",
+ }
+ if cases.isSendSyncFailed {
+ app.Status = metaserver.Failed
+ app.Reason = "isSendSyncFailed"
+ }
+ content, _ := json.Marshal(app)
+ return model.Message{
+ Content: content,
+ }, nil
+ }).ApplyGlobalVar(&imitator.DefaultV2Client, fakeClient)
+
+ var tests = []struct {
+ name string
+ rest *REST
+ info request.RequestInfo
+ cases testCase
+ want []byte
+ wantErr bool
+ }{
+ {
+ name: "test isConnectFailed ",
+ info: request.RequestInfo{},
+ cases: testCase{isConnectFailed: true},
+ wantErr: true,
+ }, {
+ name: "test isSendSyncFailed ",
+ info: request.RequestInfo{},
+ cases: testCase{isSendSyncFailed: true},
+ wantErr: true,
+ }, {
+ name: "test get version from cloud failed, but local stored",
+ info: request.RequestInfo{
+ Path: "/versions",
+ Verb: "get",
+ },
+ cases: testCase{isSendSyncFailed: true, isLocalStored: true},
+ want: []byte("test"),
+ }, {
+ name: "test successfully get the version from the cloud, but insert local storage failed ",
+ info: request.RequestInfo{
+ Path: "/versions",
+ Verb: "get",
+ },
+ cases: testCase{isInsertLocalStorageFailed: true},
+ want: []byte("test"),
+ }, {
+ name: "test successfully get the version from the cloud ",
+ info: request.RequestInfo{
+ Path: "/versions",
+ Verb: "get",
+ },
+ want: []byte("test"),
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ ctx := request.WithRequestInfo(context.TODO(), &tt.info)
+ rest := &REST{
+ Agent: &agent.Agent{Applications: sync.Map{}},
+ }
+ cases = tt.cases
+ got, err := rest.PassThrough(ctx, &metav1.GetOptions{})
+ if (err != nil) != tt.wantErr {
+ t.Errorf("PassThrough() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("PassThrough() got = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/edge/pkg/metamanager/metaserver/server.go b/edge/pkg/metamanager/metaserver/server.go
index 66c3684c5..7bc1b9d49 100644
--- a/edge/pkg/metamanager/metaserver/server.go
+++ b/edge/pkg/metamanager/metaserver/server.go
@@ -40,6 +40,7 @@ import (
"github.com/kubeedge/kubeedge/edge/pkg/metamanager/metaserver/handlerfactory"
"github.com/kubeedge/kubeedge/edge/pkg/metamanager/metaserver/kubernetes/serializer"
kefeatures "github.com/kubeedge/kubeedge/pkg/features"
+ "github.com/kubeedge/kubeedge/pkg/util/pass-through"
)
// MetaServer is simplification of server.GenericAPIServer
@@ -214,7 +215,13 @@ func (ls *MetaServer) BuildBasicHandler() http.Handler {
reqInfo, ok := apirequest.RequestInfoFrom(ctx)
//klog.Infof("[metaserver]get a req(%v)(%v)", reqInfo.Path, reqInfo.Verb)
//klog.Infof("[metaserver]get a req(\nPath:%v; \nVerb:%v; \nHeader:%+v)", reqInfo.Path, reqInfo.Verb, req.Header)
- if ok && reqInfo.IsResourceRequest {
+ if !ok {
+ err := fmt.Errorf("invalid request")
+ responsewriters.ErrorNegotiated(errors.NewInternalError(err), ls.NegotiatedSerializer, schema.GroupVersion{}, w, req)
+ return
+ }
+
+ if reqInfo.IsResourceRequest {
switch {
case reqInfo.Verb == "get":
ls.Factory.Get().ServeHTTP(w, req)
@@ -235,7 +242,12 @@ func (ls *MetaServer) BuildBasicHandler() http.Handler {
return
}
- err := fmt.Errorf("not a resource req")
+ if passthrough.IsPassThroughPath(reqInfo.Path, reqInfo.Verb) {
+ ls.Factory.PassThrough().ServeHTTP(w, req)
+ return
+ }
+
+ err := fmt.Errorf("request[%s::%s] isn't supported", reqInfo.Path, reqInfo.Verb)
responsewriters.ErrorNegotiated(errors.NewInternalError(err), ls.NegotiatedSerializer, schema.GroupVersion{}, w, req)
})
}
diff --git a/go.mod b/go.mod
index dcedbfd8f..5ab54f4e6 100644
--- a/go.mod
+++ b/go.mod
@@ -56,6 +56,7 @@ require (
)
require (
+ github.com/agiledragon/gomonkey v2.0.2+incompatible
github.com/onsi/ginkgo/v2 v2.6.0
github.com/pkg/errors v0.9.1
go.opentelemetry.io/otel/trace v1.10.0
diff --git a/go.sum b/go.sum
index 80acf6fbd..0a590b849 100644
--- a/go.sum
+++ b/go.sum
@@ -119,6 +119,8 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdko
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs=
github.com/abiosoft/ishell v2.0.0+incompatible/go.mod h1:HQR9AqF2R3P4XXpMpI0NAzgHf/aS6+zVXRj14cVk9qg=
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db/go.mod h1:rB3B4rKii8V21ydCbIzH5hZiCQE7f5E9SzUb/ZZx530=
+github.com/agiledragon/gomonkey v2.0.2+incompatible h1:eXKi9/piiC3cjJD1658mEE2o3NjkJ5vDLgYjCQu0Xlw=
+github.com/agiledragon/gomonkey v2.0.2+incompatible/go.mod h1:2NGfXu1a80LLr2cmWXGBDaHEjb1idR6+FVlX5T3D9hw=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
diff --git a/pkg/metaserver/key.go b/pkg/metaserver/key.go
index bb0691e81..fcbfc8ee1 100644
--- a/pkg/metaserver/key.go
+++ b/pkg/metaserver/key.go
@@ -56,9 +56,12 @@ func KeyFuncObj(obj runtime.Object) (string, error) {
// KeyFuncReq generate key from req context
func KeyFuncReq(ctx context.Context, _ string) (string, error) {
info, ok := apirequest.RequestInfoFrom(ctx)
- if !ok || !info.IsResourceRequest {
+ if !ok {
return "", fmt.Errorf("no request info in context")
}
+ if !info.IsResourceRequest {
+ return info.Path, nil
+ }
group := ""
switch info.APIPrefix {
diff --git a/pkg/metaserver/key_test.go b/pkg/metaserver/key_test.go
index f1d2c0b3d..d1829ddfa 100644
--- a/pkg/metaserver/key_test.go
+++ b/pkg/metaserver/key_test.go
@@ -115,6 +115,9 @@ func TestKeyFuncReq(t *testing.T) {
// api version identification
{"POST", "/apis/extensions/v1beta3/namespaces/other/pods", "create", "api", "extensions", "v1beta3", "other", "pods", "", "", []string{"pods"}},
+
+ // non-resource api pass through
+ {method: "GET", url: "/version"},
}
stdResult := []string{
"/core/v1/namespaces/null/null",
@@ -146,6 +149,8 @@ func TestKeyFuncReq(t *testing.T) {
"/extensions/v1/pods/other/null",
"/extensions/v1beta3/pods/other/null",
+
+ "/version",
}
resolver := newTestRequestInfoResolver()
for k, v := range Cases {
diff --git a/pkg/util/pass-through/pass_through.go b/pkg/util/pass-through/pass_through.go
new file mode 100644
index 000000000..225512546
--- /dev/null
+++ b/pkg/util/pass-through/pass_through.go
@@ -0,0 +1,16 @@
+package passthrough
+
+type passRequest string
+
+const (
+ versionRequest passRequest = "/version::get"
+)
+
+var passThroughMap = map[passRequest]bool{
+ versionRequest: true,
+}
+
+// IsPassThroughPath determining whether the uri can be passed through
+func IsPassThroughPath(path, verb string) bool {
+ return passThroughMap[passRequest(path+"::"+verb)]
+}
diff --git a/pkg/util/pass-through/pass_through_test.go b/pkg/util/pass-through/pass_through_test.go
new file mode 100644
index 000000000..ddbd21b02
--- /dev/null
+++ b/pkg/util/pass-through/pass_through_test.go
@@ -0,0 +1,36 @@
+package passthrough
+
+import "testing"
+
+func TestIsPassThroughPath(t *testing.T) {
+ tests := []struct {
+ name string
+ path string
+ verb string
+ want bool
+ }{
+ {
+ name: "/healthz::get is not pass through path",
+ path: "/healthz",
+ verb: "get",
+ want: false,
+ }, {
+ name: "/version::post is not pass through path",
+ path: "/version",
+ verb: "post",
+ want: false,
+ }, {
+ name: "/version::get is pass through path",
+ path: "/version",
+ verb: "get",
+ want: true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := IsPassThroughPath(tt.path, tt.verb); got != tt.want {
+ t.Errorf("IsPassThroughPath() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/vendor/github.com/agiledragon/gomonkey/LICENSE b/vendor/github.com/agiledragon/gomonkey/LICENSE
new file mode 100644
index 000000000..d75dc90e6
--- /dev/null
+++ b/vendor/github.com/agiledragon/gomonkey/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Zhang Xiaolong
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/agiledragon/gomonkey/README.md b/vendor/github.com/agiledragon/gomonkey/README.md
new file mode 100644
index 000000000..04d9e73c0
--- /dev/null
+++ b/vendor/github.com/agiledragon/gomonkey/README.md
@@ -0,0 +1,37 @@
+# gomonkey
+
+gomonkey is a library to make monkey patching in unit tests easy.
+
+## Features
+
++ support a patch for a function
++ support a patch for a member method
++ support a patch for a interface
++ support a patch for a function variable
++ support a patch for a global variable
++ support patches of a specified sequence for a function
++ support patches of a specified sequence for a member method
++ support patches of a specified sequence for a interface
++ support patches of a specified sequence for a function variable
+
+## Notes
++ gomonkey fails to patch a function or a member method if inlining is enabled, please running your tests with inlining disabled by adding the command line argument that is `-gcflags=-l`(below go1.10) or `-gcflags=all=-l`(go1.10 and above).
++ gomonkey should work on any amd64 system.
++ A panic may happen when a goroutine is patching a function or a member method that is visited by another goroutine at the same time. That is to say, gomonkey is not threadsafe.
++ go1.6 version of the reflection mechanism supports the query of private member methods, but go1.7 and above does not support it. However, all versions of the reflection mechanism support the query of private functions, so gomonkey will trigger a `panic` for only patching a private member method when go1.7 and above is used.
+
+
+## Supported Platform:
+
+- MAC OS X amd64
+- Linux amd64
+- Windows amd64
+
+## Installation
+```go
+$ go get github.com/agiledragon/gomonkey
+```
+## Using gomonkey
+
+Please refer to the test cases as idioms, very complete and detailed.
+
diff --git a/vendor/github.com/agiledragon/gomonkey/jmp_amd64.go b/vendor/github.com/agiledragon/gomonkey/jmp_amd64.go
new file mode 100644
index 000000000..02c1c42c7
--- /dev/null
+++ b/vendor/github.com/agiledragon/gomonkey/jmp_amd64.go
@@ -0,0 +1,18 @@
+package gomonkey
+
+func buildJmpDirective(double uintptr) []byte {
+ d0 := byte(double)
+ d1 := byte(double >> 8)
+ d2 := byte(double >> 16)
+ d3 := byte(double >> 24)
+ d4 := byte(double >> 32)
+ d5 := byte(double >> 40)
+ d6 := byte(double >> 48)
+ d7 := byte(double >> 56)
+
+ return []byte{
+ 0x48, 0xBA, d0, d1, d2, d3, d4, d5, d6, d7, // MOV rdx, double
+ 0xFF, 0x22, // JMP [rdx]
+ }
+}
+
diff --git a/vendor/github.com/agiledragon/gomonkey/modify_binary_darwin.go b/vendor/github.com/agiledragon/gomonkey/modify_binary_darwin.go
new file mode 100644
index 000000000..458aa3381
--- /dev/null
+++ b/vendor/github.com/agiledragon/gomonkey/modify_binary_darwin.go
@@ -0,0 +1,19 @@
+package gomonkey
+
+import "syscall"
+
+func modifyBinary(target uintptr, bytes []byte) {
+ function := entryAddress(target, len(bytes))
+
+ page := entryAddress(pageStart(target), syscall.Getpagesize())
+ err := syscall.Mprotect(page, syscall.PROT_READ|syscall.PROT_WRITE|syscall.PROT_EXEC)
+ if err != nil {
+ panic(err)
+ }
+ copy(function, bytes)
+
+ err = syscall.Mprotect(page, syscall.PROT_READ|syscall.PROT_EXEC)
+ if err != nil {
+ panic(err)
+ }
+}
diff --git a/vendor/github.com/agiledragon/gomonkey/modify_binary_linux.go b/vendor/github.com/agiledragon/gomonkey/modify_binary_linux.go
new file mode 100644
index 000000000..458aa3381
--- /dev/null
+++ b/vendor/github.com/agiledragon/gomonkey/modify_binary_linux.go
@@ -0,0 +1,19 @@
+package gomonkey
+
+import "syscall"
+
+func modifyBinary(target uintptr, bytes []byte) {
+ function := entryAddress(target, len(bytes))
+
+ page := entryAddress(pageStart(target), syscall.Getpagesize())
+ err := syscall.Mprotect(page, syscall.PROT_READ|syscall.PROT_WRITE|syscall.PROT_EXEC)
+ if err != nil {
+ panic(err)
+ }
+ copy(function, bytes)
+
+ err = syscall.Mprotect(page, syscall.PROT_READ|syscall.PROT_EXEC)
+ if err != nil {
+ panic(err)
+ }
+}
diff --git a/vendor/github.com/agiledragon/gomonkey/modify_binary_windows.go b/vendor/github.com/agiledragon/gomonkey/modify_binary_windows.go
new file mode 100644
index 000000000..ef0dbc756
--- /dev/null
+++ b/vendor/github.com/agiledragon/gomonkey/modify_binary_windows.go
@@ -0,0 +1,25 @@
+package gomonkey
+
+import (
+ "syscall"
+ "unsafe"
+)
+
+func modifyBinary(target uintptr, bytes []byte) {
+ function := entryAddress(target, len(bytes))
+
+ proc := syscall.NewLazyDLL("kernel32.dll").NewProc("VirtualProtect")
+ const PROT_READ_WRITE = 0x40
+ var old uint32
+ result, _, _ := proc.Call(target, uintptr(len(bytes)), uintptr(PROT_READ_WRITE), uintptr(unsafe.Pointer(&old)))
+ if result == 0 {
+ panic(result)
+ }
+ copy(function, bytes)
+
+ var ignore uint32
+ result, _, _ = proc.Call(target, uintptr(len(bytes)), uintptr(old), uintptr(unsafe.Pointer(&ignore)))
+ if result == 0 {
+ panic(result)
+ }
+} \ No newline at end of file
diff --git a/vendor/github.com/agiledragon/gomonkey/patch.go b/vendor/github.com/agiledragon/gomonkey/patch.go
new file mode 100644
index 000000000..99df38851
--- /dev/null
+++ b/vendor/github.com/agiledragon/gomonkey/patch.go
@@ -0,0 +1,232 @@
+package gomonkey
+
+import (
+ "fmt"
+ "reflect"
+ "syscall"
+ "unsafe"
+)
+
+type Patches struct {
+ originals map[reflect.Value][]byte
+ values map[reflect.Value]reflect.Value
+ valueHolders map[reflect.Value]reflect.Value
+}
+
+type Params []interface{}
+type OutputCell struct {
+ Values Params
+ Times int
+}
+
+func ApplyFunc(target, double interface{}) *Patches {
+ return create().ApplyFunc(target, double)
+}
+
+func ApplyMethod(target reflect.Type, methodName string, double interface{}) *Patches {
+ return create().ApplyMethod(target, methodName, double)
+}
+
+func ApplyGlobalVar(target, double interface{}) *Patches {
+ return create().ApplyGlobalVar(target, double)
+}
+
+func ApplyFuncVar(target, double interface{}) *Patches {
+ return create().ApplyFuncVar(target, double)
+}
+
+func ApplyFuncSeq(target interface{}, outputs []OutputCell) *Patches {
+ return create().ApplyFuncSeq(target, outputs)
+}
+
+func ApplyMethodSeq(target reflect.Type, methodName string, outputs []OutputCell) *Patches {
+ return create().ApplyMethodSeq(target, methodName, outputs)
+}
+
+func ApplyFuncVarSeq(target interface{}, outputs []OutputCell) *Patches {
+ return create().ApplyFuncVarSeq(target, outputs)
+}
+
+func create() *Patches {
+ return &Patches{originals: make(map[reflect.Value][]byte), values: make(map[reflect.Value]reflect.Value), valueHolders: make(map[reflect.Value]reflect.Value)}
+}
+
+func NewPatches() *Patches {
+ return create()
+}
+
+func (this *Patches) ApplyFunc(target, double interface{}) *Patches {
+ t := reflect.ValueOf(target)
+ d := reflect.ValueOf(double)
+ return this.ApplyCore(t, d)
+}
+
+func (this *Patches) ApplyMethod(target reflect.Type, methodName string, double interface{}) *Patches {
+ m, ok := target.MethodByName(methodName)
+ if !ok {
+ panic("retrieve method by name failed")
+ }
+ d := reflect.ValueOf(double)
+ return this.ApplyCore(m.Func, d)
+}
+
+func (this *Patches) ApplyGlobalVar(target, double interface{}) *Patches {
+ t := reflect.ValueOf(target)
+ if t.Type().Kind() != reflect.Ptr {
+ panic("target is not a pointer")
+ }
+
+ this.values[t] = reflect.ValueOf(t.Elem().Interface())
+ d := reflect.ValueOf(double)
+ t.Elem().Set(d)
+ return this
+}
+
+func (this *Patches) ApplyFuncVar(target, double interface{}) *Patches {
+ t := reflect.ValueOf(target)
+ d := reflect.ValueOf(double)
+ if t.Type().Kind() != reflect.Ptr {
+ panic("target is not a pointer")
+ }
+ this.check(t.Elem(), d)
+ return this.ApplyGlobalVar(target, double)
+}
+
+func (this *Patches) ApplyFuncSeq(target interface{}, outputs []OutputCell) *Patches {
+ funcType := reflect.TypeOf(target)
+ t := reflect.ValueOf(target)
+ d := getDoubleFunc(funcType, outputs)
+ return this.ApplyCore(t, d)
+}
+
+func (this *Patches) ApplyMethodSeq(target reflect.Type, methodName string, outputs []OutputCell) *Patches {
+ m, ok := target.MethodByName(methodName)
+ if !ok {
+ panic("retrieve method by name failed")
+ }
+ d := getDoubleFunc(m.Type, outputs)
+ return this.ApplyCore(m.Func, d)
+}
+
+func (this *Patches) ApplyFuncVarSeq(target interface{}, outputs []OutputCell) *Patches {
+ t := reflect.ValueOf(target)
+ if t.Type().Kind() != reflect.Ptr {
+ panic("target is not a pointer")
+ }
+ if t.Elem().Kind() != reflect.Func {
+ panic("target is not a func")
+ }
+
+ funcType := reflect.TypeOf(target).Elem()
+ double := getDoubleFunc(funcType, outputs).Interface()
+ return this.ApplyGlobalVar(target, double)
+}
+
+func (this *Patches) Reset() {
+ for target, bytes := range this.originals {
+ modifyBinary(*(*uintptr)(getPointer(target)), bytes)
+ delete(this.originals, target)
+ }
+
+ for target, variable := range this.values {
+ target.Elem().Set(variable)
+ }
+}
+
+func (this *Patches) ApplyCore(target, double reflect.Value) *Patches {
+ this.check(target, double)
+ if _, ok := this.originals[target]; ok {
+ panic("patch has been existed")
+ }
+
+ this.valueHolders[double] = double
+ original := replace(*(*uintptr)(getPointer(target)), uintptr(getPointer(double)))
+ this.originals[target] = original
+ return this
+}
+
+func (this *Patches) check(target, double reflect.Value) {
+ if target.Kind() != reflect.Func {
+ panic("target is not a func")
+ }
+
+ if double.Kind() != reflect.Func {
+ panic("double is not a func")
+ }
+
+ if target.Type() != double.Type() {
+ panic(fmt.Sprintf("target type(%s) and double type(%s) are different", target.Type(), double.Type()))
+ }
+}
+
+func replace(target, double uintptr) []byte {
+ code := buildJmpDirective(double)
+ bytes := entryAddress(target, len(code))
+ original := make([]byte, len(bytes))
+ copy(original, bytes)
+ modifyBinary(target, code)
+ return original
+}
+
+func getDoubleFunc(funcType reflect.Type, outputs []OutputCell) reflect.Value {
+ if funcType.NumOut() != len(outputs[0].Values) {
+ panic(fmt.Sprintf("func type has %v return values, but only %v values provided as double",
+ funcType.NumOut(), len(outputs[0].Values)))
+ }
+
+ slice := make([]Params, 0)
+ for _, output := range outputs {
+ t := 0
+ if output.Times <= 1 {
+ t = 1
+ } else {
+ t = output.Times
+ }
+ for j := 0; j < t; j++ {
+ slice = append(slice, output.Values)
+ }
+ }
+
+ i := 0
+ len := len(slice)
+ return reflect.MakeFunc(funcType, func(_ []reflect.Value) []reflect.Value {
+ if i < len {
+ i++
+ return GetResultValues(funcType, slice[i-1]...)
+ }
+ panic("double seq is less than call seq")
+ })
+}
+
+func GetResultValues(funcType reflect.Type, results ...interface{}) []reflect.Value {
+ var resultValues []reflect.Value
+ for i, r := range results {
+ var resultValue reflect.Value
+ if r == nil {
+ resultValue = reflect.Zero(funcType.Out(i))
+ } else {
+ v := reflect.New(funcType.Out(i))
+ v.Elem().Set(reflect.ValueOf(r))
+ resultValue = v.Elem()
+ }
+ resultValues = append(resultValues, resultValue)
+ }
+ return resultValues
+}
+
+type funcValue struct {
+ _ uintptr
+ p unsafe.Pointer
+}
+
+func getPointer(v reflect.Value) unsafe.Pointer {
+ return (*funcValue)(unsafe.Pointer(&v)).p
+}
+
+func entryAddress(p uintptr, l int) []byte {
+ return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{Data: p, Len: l, Cap: l}))
+}
+
+func pageStart(ptr uintptr) uintptr {
+ return ptr & ^(uintptr(syscall.Getpagesize() - 1))
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 31fe74b50..04b7708c8 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -103,6 +103,9 @@ github.com/Microsoft/hcsshim/osversion
# github.com/NYTimes/gziphandler v1.1.1
## explicit; go 1.11
github.com/NYTimes/gziphandler
+# github.com/agiledragon/gomonkey v2.0.2+incompatible
+## explicit
+github.com/agiledragon/gomonkey
# github.com/antlr/antlr4/runtime/Go/antlr v1.4.10
## explicit; go 1.16
github.com/antlr/antlr4/runtime/Go/antlr