mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-21 08:10:24 +08:00
feat: support hashicorp plugin in gPRC mode
This commit is contained in:
@@ -3,6 +3,8 @@ package pluginInternal
|
||||
import "github.com/hashicorp/go-plugin"
|
||||
|
||||
const PluginName = "debugtalk"
|
||||
const RPCPluginName = PluginName + "_rpc"
|
||||
const GRPCPluginName = PluginName + "_grpc"
|
||||
|
||||
// handshakeConfigs are used to just do a basic handshake between
|
||||
// a plugin and host. If the handshake fails, a user friendly error is shown.
|
||||
|
||||
108
plugin/inner/grpc.go
Normal file
108
plugin/inner/grpc.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package pluginInternal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/hashicorp/go-plugin"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/httprunner/hrp/plugin/proto"
|
||||
)
|
||||
|
||||
// functionGRPCClient runs on the host side, it implements FuncCaller interface
|
||||
type functionGRPCClient struct {
|
||||
client proto.DebugTalkClient
|
||||
}
|
||||
|
||||
func (m *functionGRPCClient) GetNames() ([]string, error) {
|
||||
log.Info().Msg("function GetNames called on host side")
|
||||
resp, err := m.client.GetNames(context.Background(), &proto.Empty{})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("gRPC call GetNames() failed")
|
||||
return nil, err
|
||||
}
|
||||
return resp.Names, err
|
||||
}
|
||||
|
||||
func (m *functionGRPCClient) Call(funcName string, funcArgs ...interface{}) (interface{}, error) {
|
||||
log.Info().Str("funcName", funcName).Interface("funcArgs", funcArgs).Msg("call function via gRPC")
|
||||
|
||||
req := &proto.CallRequest{
|
||||
Name: funcName,
|
||||
}
|
||||
funcArgBytes, err := json.Marshal(funcArgs)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to marshal Call() funcArgs")
|
||||
}
|
||||
req.Args = funcArgBytes
|
||||
|
||||
response, err := m.client.Call(context.Background(), req)
|
||||
if err != nil {
|
||||
log.Error().Err(err).
|
||||
Str("funcName", funcName).Interface("funcArgs", funcArgs).
|
||||
Msg("gRPC Call() failed")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resp interface{}
|
||||
err = json.Unmarshal(response.Value, &resp)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal Call() response")
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Here is the gRPC server that functionGRPCClient talks to.
|
||||
type functionGRPCServer struct {
|
||||
proto.UnimplementedDebugTalkServer
|
||||
Impl FuncCaller
|
||||
}
|
||||
|
||||
func (m *functionGRPCServer) GetNames(ctx context.Context, req *proto.Empty) (*proto.GetNamesResponse, error) {
|
||||
log.Info().Interface("req", req).Msg("gRPC GetNames() called on plugin side")
|
||||
v, err := m.Impl.GetNames()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("gRPC GetNames execution failed")
|
||||
return nil, err
|
||||
}
|
||||
return &proto.GetNamesResponse{Names: v}, err
|
||||
}
|
||||
|
||||
func (m *functionGRPCServer) Call(ctx context.Context, req *proto.CallRequest) (*proto.CallResponse, error) {
|
||||
var funcArgs []interface{}
|
||||
if err := json.Unmarshal(req.Args, &funcArgs); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal Call() funcArgs")
|
||||
}
|
||||
|
||||
log.Info().Interface("req", req).Msg("gRPC Call() called on plugin side")
|
||||
|
||||
v, err := m.Impl.Call(req.Name, funcArgs...)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Interface("req", req).Msg("gRPC Call() execution failed")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
value, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to marshal Call() response")
|
||||
}
|
||||
return &proto.CallResponse{Value: value}, err
|
||||
}
|
||||
|
||||
// HRPPlugin implements hashicorp's plugin.GRPCPlugin.
|
||||
type GRPCPlugin struct {
|
||||
plugin.Plugin
|
||||
Impl FuncCaller
|
||||
}
|
||||
|
||||
func (p *GRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error {
|
||||
proto.RegisterDebugTalkServer(s, &functionGRPCServer{Impl: p.Impl})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *GRPCPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) {
|
||||
return &functionGRPCClient{client: proto.NewDebugTalkClient(c)}, nil
|
||||
}
|
||||
@@ -19,8 +19,9 @@ type HashicorpPlugin struct {
|
||||
}
|
||||
|
||||
func (p *HashicorpPlugin) Init(path string) error {
|
||||
pluginName := GRPCPluginName
|
||||
loggerOptions := &hclog.LoggerOptions{
|
||||
Name: PluginName,
|
||||
Name: pluginName,
|
||||
Output: os.Stdout,
|
||||
}
|
||||
if p.logOn {
|
||||
@@ -32,10 +33,15 @@ func (p *HashicorpPlugin) Init(path string) error {
|
||||
client = plugin.NewClient(&plugin.ClientConfig{
|
||||
HandshakeConfig: HandshakeConfig,
|
||||
Plugins: map[string]plugin.Plugin{
|
||||
PluginName: &HRPPlugin{},
|
||||
RPCPluginName: &RPCPlugin{},
|
||||
GRPCPluginName: &GRPCPlugin{},
|
||||
},
|
||||
Cmd: exec.Command(path),
|
||||
Logger: hclog.New(loggerOptions),
|
||||
AllowedProtocols: []plugin.Protocol{
|
||||
plugin.ProtocolNetRPC,
|
||||
plugin.ProtocolGRPC,
|
||||
},
|
||||
})
|
||||
|
||||
// Connect via RPC
|
||||
@@ -46,7 +52,7 @@ func (p *HashicorpPlugin) Init(path string) error {
|
||||
}
|
||||
|
||||
// Request the plugin
|
||||
raw, err := rpcClient.Dispense(PluginName)
|
||||
raw, err := rpcClient.Dispense(pluginName)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("request plugin failed")
|
||||
return err
|
||||
|
||||
@@ -18,12 +18,12 @@ type funcData struct {
|
||||
Args []interface{} // function arguments
|
||||
}
|
||||
|
||||
// functionRPC runs on the host side.
|
||||
type functionRPC struct {
|
||||
// functionRPCClient runs on the host side, it implements FuncCaller interface
|
||||
type functionRPCClient struct {
|
||||
client *rpc.Client
|
||||
}
|
||||
|
||||
func (g *functionRPC) GetNames() ([]string, error) {
|
||||
func (g *functionRPCClient) GetNames() ([]string, error) {
|
||||
var resp []string
|
||||
err := g.client.Call("Plugin.GetNames", new(interface{}), &resp)
|
||||
if err != nil {
|
||||
@@ -34,7 +34,7 @@ func (g *functionRPC) GetNames() ([]string, error) {
|
||||
}
|
||||
|
||||
// host -> plugin
|
||||
func (g *functionRPC) Call(funcName string, funcArgs ...interface{}) (interface{}, error) {
|
||||
func (g *functionRPCClient) Call(funcName string, funcArgs ...interface{}) (interface{}, error) {
|
||||
log.Info().Str("funcName", funcName).Interface("funcArgs", funcArgs).Msg("call function via RPC")
|
||||
f := funcData{
|
||||
Name: funcName,
|
||||
@@ -47,7 +47,7 @@ func (g *functionRPC) Call(funcName string, funcArgs ...interface{}) (interface{
|
||||
if err != nil {
|
||||
log.Error().Err(err).
|
||||
Str("funcName", funcName).Interface("funcArgs", funcArgs).
|
||||
Msg("rpc call Call() failed")
|
||||
Msg("rpc Call() failed")
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
@@ -60,11 +60,11 @@ type functionRPCServer struct {
|
||||
|
||||
// plugin execution
|
||||
func (s *functionRPCServer) GetNames(args interface{}, resp *[]string) error {
|
||||
log.Info().Interface("args", args).Msg("GetNames called on plugin side")
|
||||
log.Info().Interface("args", args).Msg("rpc GetNames() called on plugin side")
|
||||
var err error
|
||||
*resp, err = s.Impl.GetNames()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("GetNames execution failed")
|
||||
log.Error().Err(err).Msg("rpc GetNames() execution failed")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -72,26 +72,26 @@ func (s *functionRPCServer) GetNames(args interface{}, resp *[]string) error {
|
||||
|
||||
// plugin execution
|
||||
func (s *functionRPCServer) Call(args interface{}, resp *interface{}) error {
|
||||
log.Info().Interface("args", args).Msg("function called on plugin side")
|
||||
log.Info().Interface("args", args).Msg("rpc Call() called on plugin side")
|
||||
f := args.(*funcData)
|
||||
var err error
|
||||
*resp, err = s.Impl.Call(f.Name, f.Args...)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Interface("args", args).Msg("function execution failed")
|
||||
log.Error().Err(err).Interface("args", args).Msg("rpc Call() execution failed")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// HRPPlugin implements hashicorp's plugin.Plugin.
|
||||
type HRPPlugin struct {
|
||||
// RPCPlugin implements hashicorp's plugin.Plugin.
|
||||
type RPCPlugin struct {
|
||||
Impl FuncCaller
|
||||
}
|
||||
|
||||
func (p *HRPPlugin) Server(*plugin.MuxBroker) (interface{}, error) {
|
||||
func (p *RPCPlugin) Server(*plugin.MuxBroker) (interface{}, error) {
|
||||
return &functionRPCServer{Impl: p.Impl}, nil
|
||||
}
|
||||
|
||||
func (HRPPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
|
||||
return &functionRPC{client: c}, nil
|
||||
func (RPCPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
|
||||
return &functionRPCClient{client: c}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user