@@ -27,6 +27,7 @@ import (
27
27
28
28
"github.com/hashicorp/go-hclog"
29
29
"github.com/hashicorp/go-plugin/internal/cmdrunner"
30
+ "github.com/hashicorp/go-plugin/internal/grpcmux"
30
31
"github.com/hashicorp/go-plugin/runner"
31
32
"google.golang.org/grpc"
32
33
)
63
64
// ErrSecureConfigAndReattach is returned when both Reattach and
64
65
// SecureConfig are set.
65
66
ErrSecureConfigAndReattach = errors .New ("only one of Reattach or SecureConfig can be set" )
67
+
68
+ // ErrGRPCBrokerMuxNotSupported is returned when the client requests
69
+ // multiplexing over the gRPC broker, but the plugin does not support the
70
+ // feature. In most cases, this should be resolvable by updating and
71
+ // rebuilding the plugin, or restarting the plugin with
72
+ // ClientConfig.GRPCBrokerMultiplex set to false.
73
+ ErrGRPCBrokerMuxNotSupported = errors .New ("client requested gRPC broker multiplexing but plugin does not support the feature" )
66
74
)
67
75
68
76
// Client handles the lifecycle of a plugin application. It launches
@@ -102,6 +110,9 @@ type Client struct {
102
110
processKilled bool
103
111
104
112
unixSocketCfg UnixSocketConfig
113
+
114
+ grpcMuxerOnce sync.Once
115
+ grpcMuxer * grpcmux.GRPCClientMuxer
105
116
}
106
117
107
118
// NegotiatedVersion returns the protocol version negotiated with the server.
@@ -237,6 +248,19 @@ type ClientConfig struct {
237
248
// protocol.
238
249
GRPCDialOptions []grpc.DialOption
239
250
251
+ // GRPCBrokerMultiplex turns on multiplexing for the gRPC broker. The gRPC
252
+ // broker will multiplex all brokered gRPC servers over the plugin's original
253
+ // listener socket instead of making a new listener for each server. The
254
+ // go-plugin library currently only includes a Go implementation for the
255
+ // server (i.e. plugin) side of gRPC broker multiplexing.
256
+ //
257
+ // Does not support reattaching.
258
+ //
259
+ // Multiplexed gRPC streams MUST be established sequentially, i.e. after
260
+ // calling AcceptAndServe from one side, wait for the other side to Dial
261
+ // before calling AcceptAndServe again.
262
+ GRPCBrokerMultiplex bool
263
+
240
264
// SkipHostEnv allows plugins to run without inheriting the parent process'
241
265
// environment variables.
242
266
SkipHostEnv bool
@@ -352,7 +376,7 @@ func CleanupClients() {
352
376
wg .Wait ()
353
377
}
354
378
355
- // Creates a new plugin client which manages the lifecycle of an external
379
+ // NewClient creates a new plugin client which manages the lifecycle of an external
356
380
// plugin and gets the address for the RPC connection.
357
381
//
358
382
// The client must be cleaned up at some point by calling Kill(). If
@@ -374,10 +398,10 @@ func NewClient(config *ClientConfig) (c *Client) {
374
398
}
375
399
376
400
if config .SyncStdout == nil {
377
- config .SyncStdout = ioutil .Discard
401
+ config .SyncStdout = io .Discard
378
402
}
379
403
if config .SyncStderr == nil {
380
- config .SyncStderr = ioutil .Discard
404
+ config .SyncStderr = io .Discard
381
405
}
382
406
383
407
if config .AllowedProtocols == nil {
@@ -572,6 +596,10 @@ func (c *Client) Start() (addr net.Addr, err error) {
572
596
if c .config .SecureConfig != nil && c .config .Reattach != nil {
573
597
return nil , ErrSecureConfigAndReattach
574
598
}
599
+
600
+ if c .config .GRPCBrokerMultiplex && c .config .Reattach != nil {
601
+ return nil , fmt .Errorf ("gRPC broker multiplexing is not supported with Reattach config" )
602
+ }
575
603
}
576
604
577
605
if c .config .Reattach != nil {
@@ -603,6 +631,9 @@ func (c *Client) Start() (addr net.Addr, err error) {
603
631
fmt .Sprintf ("PLUGIN_MAX_PORT=%d" , c .config .MaxPort ),
604
632
fmt .Sprintf ("PLUGIN_PROTOCOL_VERSIONS=%s" , strings .Join (versionStrings , "," )),
605
633
}
634
+ if c .config .GRPCBrokerMultiplex {
635
+ env = append (env , fmt .Sprintf ("%s=true" , envMultiplexGRPC ))
636
+ }
606
637
607
638
cmd := c .config .Cmd
608
639
if cmd == nil {
@@ -790,7 +821,7 @@ func (c *Client) Start() (addr net.Addr, err error) {
790
821
// Trim the line and split by "|" in order to get the parts of
791
822
// the output.
792
823
line = strings .TrimSpace (line )
793
- parts := strings .SplitN (line , "|" , 6 )
824
+ parts := strings .Split (line , "|" )
794
825
if len (parts ) < 4 {
795
826
errText := fmt .Sprintf ("Unrecognized remote plugin message: %s" , line )
796
827
if ! ok {
@@ -878,6 +909,18 @@ func (c *Client) Start() (addr net.Addr, err error) {
878
909
return nil , fmt .Errorf ("error parsing server cert: %s" , err )
879
910
}
880
911
}
912
+
913
+ if c .config .GRPCBrokerMultiplex && c .protocol == ProtocolGRPC {
914
+ if len (parts ) <= 6 {
915
+ return nil , fmt .Errorf ("%w; for Go plugins, you will need to update the " +
916
+ "github.com/hashicorp/go-plugin dependency and recompile" , ErrGRPCBrokerMuxNotSupported )
917
+ }
918
+ if muxSupported , err := strconv .ParseBool (parts [6 ]); err != nil {
919
+ return nil , fmt .Errorf ("error parsing %q as a boolean for gRPC broker multiplexing support" , parts [6 ])
920
+ } else if ! muxSupported {
921
+ return nil , ErrGRPCBrokerMuxNotSupported
922
+ }
923
+ }
881
924
}
882
925
883
926
c .address = addr
@@ -951,12 +994,11 @@ func (c *Client) reattach() (net.Addr, error) {
951
994
952
995
if c .config .Reattach .Test {
953
996
c .negotiatedVersion = c .config .Reattach .ProtocolVersion
954
- }
955
-
956
- // If we're in test mode, we do NOT set the process. This avoids the
957
- // process being killed (the only purpose we have for c.process), since
958
- // in test mode the process is responsible for exiting on its own.
959
- if ! c .config .Reattach .Test {
997
+ } else {
998
+ // If we're in test mode, we do NOT set the runner. This avoids the
999
+ // runner being killed (the only purpose we have for setting c.runner
1000
+ // when reattaching), since in test mode the process is responsible for
1001
+ // exiting on its own.
960
1002
c .runner = r
961
1003
}
962
1004
@@ -1061,11 +1103,24 @@ func netAddrDialer(addr net.Addr) func(string, time.Duration) (net.Conn, error)
1061
1103
// dialer is compatible with grpc.WithDialer and creates the connection
1062
1104
// to the plugin.
1063
1105
func (c * Client ) dialer (_ string , timeout time.Duration ) (net.Conn , error ) {
1064
- conn , err := netAddrDialer (c .address )( "" , timeout )
1106
+ muxer , err := c . getGRPCMuxer (c .address )
1065
1107
if err != nil {
1066
1108
return nil , err
1067
1109
}
1068
1110
1111
+ var conn net.Conn
1112
+ if muxer .Enabled () {
1113
+ conn , err = muxer .Dial ()
1114
+ if err != nil {
1115
+ return nil , err
1116
+ }
1117
+ } else {
1118
+ conn , err = netAddrDialer (c .address )("" , timeout )
1119
+ if err != nil {
1120
+ return nil , err
1121
+ }
1122
+ }
1123
+
1069
1124
// If we have a TLS config we wrap our connection. We only do this
1070
1125
// for net/rpc since gRPC uses its own mechanism for TLS.
1071
1126
if c .protocol == ProtocolNetRPC && c .config .TLSConfig != nil {
@@ -1075,6 +1130,22 @@ func (c *Client) dialer(_ string, timeout time.Duration) (net.Conn, error) {
1075
1130
return conn , nil
1076
1131
}
1077
1132
1133
+ func (c * Client ) getGRPCMuxer (addr net.Addr ) (* grpcmux.GRPCClientMuxer , error ) {
1134
+ if c .protocol != ProtocolGRPC || ! c .config .GRPCBrokerMultiplex {
1135
+ return nil , nil
1136
+ }
1137
+
1138
+ var err error
1139
+ c .grpcMuxerOnce .Do (func () {
1140
+ c .grpcMuxer , err = grpcmux .NewGRPCClientMuxer (c .logger , addr )
1141
+ })
1142
+ if err != nil {
1143
+ return nil , err
1144
+ }
1145
+
1146
+ return c .grpcMuxer , nil
1147
+ }
1148
+
1078
1149
var stdErrBufferSize = 64 * 1024
1079
1150
1080
1151
func (c * Client ) logStderr (name string , r io.Reader ) {
0 commit comments