package plugin import ( "crypto/tls" "crypto/x509" "errors" "fmt" "io/ioutil" "log" "net/rpc" "os" "os/exec" "testing" "time" hclog "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-plugin/test/grpc" "golang.org/x/net/context" "google.golang.org/grpc" ) // Test that NetRPCUnsupportedPlugin implements the correct interfaces. var _ Plugin = new(NetRPCUnsupportedPlugin) // testAPIVersion is the ProtocolVersion we use for testing. var testHandshake = HandshakeConfig{ ProtocolVersion: 1, MagicCookieKey: "TEST_MAGIC_COOKIE", MagicCookieValue: "test", } // testInterface is the test interface we use for plugins. type testInterface interface { Double(int) int PrintKV(string, interface{}) Bidirectional() error } // testInterfacePlugin is the implementation of Plugin to create // RPC client/server implementations for testInterface. type testInterfacePlugin struct { Impl testInterface } func (p *testInterfacePlugin) Server(b *MuxBroker) (interface{}, error) { return &testInterfaceServer{Impl: p.impl()}, nil } func (p *testInterfacePlugin) Client(b *MuxBroker, c *rpc.Client) (interface{}, error) { return &testInterfaceClient{Client: c}, nil } func (p *testInterfacePlugin) GRPCServer(b *GRPCBroker, s *grpc.Server) error { grpctest.RegisterTestServer(s, &testGRPCServer{broker: b, Impl: p.impl()}) return nil } func (p *testInterfacePlugin) GRPCClient(doneCtx context.Context, b *GRPCBroker, c *grpc.ClientConn) (interface{}, error) { return &testGRPCClient{broker: b, Client: grpctest.NewTestClient(c)}, nil } func (p *testInterfacePlugin) impl() testInterface { if p.Impl != nil { return p.Impl } return &testInterfaceImpl{ logger: hclog.New(&hclog.LoggerOptions{ Level: hclog.Trace, Output: os.Stderr, JSONFormat: true, }), } } // testInterfaceImpl implements testInterface concretely type testInterfaceImpl struct { logger hclog.Logger } func (i *testInterfaceImpl) Double(v int) int { return v * 2 } func (i *testInterfaceImpl) PrintKV(key string, value interface{}) { i.logger.Info("PrintKV called", key, value) } func (i *testInterfaceImpl) Bidirectional() error { return nil } // testInterfaceClient implements testInterface to communicate over RPC type testInterfaceClient struct { Client *rpc.Client } func (impl *testInterfaceClient) Double(v int) int { var resp int err := impl.Client.Call("Plugin.Double", v, &resp) if err != nil { panic(err) } return resp } func (impl *testInterfaceClient) PrintKV(key string, value interface{}) { err := impl.Client.Call("Plugin.PrintKV", map[string]interface{}{ "key": key, "value": value, }, &struct{}{}) if err != nil { panic(err) } } func (impl *testInterfaceClient) Bidirectional() error { return nil } // testInterfaceServer is the RPC server for testInterfaceClient type testInterfaceServer struct { Broker *MuxBroker Impl testInterface } func (s *testInterfaceServer) Double(arg int, resp *int) error { *resp = s.Impl.Double(arg) return nil } func (s *testInterfaceServer) PrintKV(args map[string]interface{}, _ *struct{}) error { s.Impl.PrintKV(args["key"].(string), args["value"]) return nil } // testPluginMap can be used for tests as a plugin map var testPluginMap = map[string]Plugin{ "test": new(testInterfacePlugin), } // testGRPCServer is the implementation of our GRPC service. type testGRPCServer struct { Impl testInterface broker *GRPCBroker } func (s *testGRPCServer) Double( ctx context.Context, req *grpctest.TestRequest) (*grpctest.TestResponse, error) { return &grpctest.TestResponse{ Output: int32(s.Impl.Double(int(req.Input))), }, nil } func (s *testGRPCServer) PrintKV( ctx context.Context, req *grpctest.PrintKVRequest) (*grpctest.PrintKVResponse, error) { var v interface{} switch rv := req.Value.(type) { case *grpctest.PrintKVRequest_ValueString: v = rv.ValueString case *grpctest.PrintKVRequest_ValueInt: v = rv.ValueInt default: panic(fmt.Sprintf("unknown value: %#v", req.Value)) } s.Impl.PrintKV(req.Key, v) return &grpctest.PrintKVResponse{}, nil } func (s *testGRPCServer) Bidirectional(ctx context.Context, req *grpctest.BidirectionalRequest) (*grpctest.BidirectionalResponse, error) { conn, err := s.broker.Dial(req.Id) if err != nil { return nil, err } pingPongClient := grpctest.NewPingPongClient(conn) resp, err := pingPongClient.Ping(ctx, &grpctest.PingRequest{}) if err != nil { return nil, err } if resp.Msg != "pong" { return nil, errors.New("Bad PingPong") } nextID := s.broker.NextId() go s.broker.AcceptAndServe(nextID, func(opts []grpc.ServerOption) *grpc.Server { s := grpc.NewServer(opts...) grpctest.RegisterPingPongServer(s, &pingPongServer{}) return s }) return &grpctest.BidirectionalResponse{ Id: nextID, }, nil } type pingPongServer struct{} func (p *pingPongServer) Ping(ctx context.Context, req *grpctest.PingRequest) (*grpctest.PongResponse, error) { return &grpctest.PongResponse{ Msg: "pong", }, nil } // testGRPCClient is an implementation of TestInterface that communicates // over gRPC. type testGRPCClient struct { Client grpctest.TestClient broker *GRPCBroker } func (c *testGRPCClient) Double(v int) int { resp, err := c.Client.Double(context.Background(), &grpctest.TestRequest{ Input: int32(v), }) if err != nil { panic(err) } return int(resp.Output) } func (c *testGRPCClient) PrintKV(key string, value interface{}) { req := &grpctest.PrintKVRequest{Key: key} switch v := value.(type) { case string: req.Value = &grpctest.PrintKVRequest_ValueString{ ValueString: v, } case int: req.Value = &grpctest.PrintKVRequest_ValueInt{ ValueInt: int32(v), } default: panic(fmt.Sprintf("unknown type: %T", value)) } _, err := c.Client.PrintKV(context.Background(), req) if err != nil { panic(err) } } func (c *testGRPCClient) Bidirectional() error { nextID := c.broker.NextId() go c.broker.AcceptAndServe(nextID, func(opts []grpc.ServerOption) *grpc.Server { s := grpc.NewServer(opts...) grpctest.RegisterPingPongServer(s, &pingPongServer{}) return s }) resp, err := c.Client.Bidirectional(context.Background(), &grpctest.BidirectionalRequest{ Id: nextID, }) if err != nil { return err } conn, err := c.broker.Dial(resp.Id) if err != nil { return err } pingPongClient := grpctest.NewPingPongClient(conn) pResp, err := pingPongClient.Ping(context.Background(), &grpctest.PingRequest{}) if err != nil { return err } if pResp.Msg != "pong" { return errors.New("Bad PingPong") } return nil } func helperProcess(s ...string) *exec.Cmd { cs := []string{"-test.run=TestHelperProcess", "--"} cs = append(cs, s...) env := []string{ "GO_WANT_HELPER_PROCESS=1", "PLUGIN_MIN_PORT=10000", "PLUGIN_MAX_PORT=25000", } cmd := exec.Command(os.Args[0], cs...) cmd.Env = append(env, os.Environ()...) return cmd } // This is not a real test. This is just a helper process kicked off by // tests. func TestHelperProcess(*testing.T) { if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { return } defer os.Exit(0) args := os.Args for len(args) > 0 { if args[0] == "--" { args = args[1:] break } args = args[1:] } if len(args) == 0 { fmt.Fprintf(os.Stderr, "No command\n") os.Exit(2) } // override testPluginMap with one that uses // hclog logger on its implementation pluginLogger := hclog.New(&hclog.LoggerOptions{ Level: hclog.Trace, Output: os.Stderr, JSONFormat: true, }) testPlugin := &testInterfaceImpl{ logger: pluginLogger, } testPluginMap := map[string]Plugin{ "test": &testInterfacePlugin{Impl: testPlugin}, } cmd, args := args[0], args[1:] switch cmd { case "bad-version": // If we have an arg, we write there on start if len(args) > 0 { path := args[0] err := ioutil.WriteFile(path, []byte("foo"), 0644) if err != nil { panic(err) } } fmt.Printf("%d|%d1|tcp|:1234\n", CoreProtocolVersion, testHandshake.ProtocolVersion) <-make(chan int) case "invalid-rpc-address": fmt.Println("lolinvalid") case "mock": fmt.Printf("%d|%d|tcp|:1234\n", CoreProtocolVersion, testHandshake.ProtocolVersion) <-make(chan int) case "start-timeout": time.Sleep(1 * time.Minute) os.Exit(1) case "stderr": fmt.Printf("%d|%d|tcp|:1234\n", CoreProtocolVersion, testHandshake.ProtocolVersion) os.Stderr.WriteString("HELLO\n") os.Stderr.WriteString("WORLD\n") case "stderr-json": // write values that might be JSON, but aren't KVs fmt.Printf("%d|%d|tcp|:1234\n", CoreProtocolVersion, testHandshake.ProtocolVersion) os.Stderr.WriteString("[\"HELLO\"]\n") os.Stderr.WriteString("12345\n") case "stdin": fmt.Printf("%d|%d|tcp|:1234\n", CoreProtocolVersion, testHandshake.ProtocolVersion) data := make([]byte, 5) if _, err := os.Stdin.Read(data); err != nil { log.Printf("stdin read error: %s", err) os.Exit(100) } if string(data) == "hello" { os.Exit(0) } os.Exit(1) case "cleanup": // Create a defer to write the file. This tests that we get cleaned // up properly versus just calling os.Exit path := args[0] defer func() { err := ioutil.WriteFile(path, []byte("foo"), 0644) if err != nil { panic(err) } }() Serve(&ServeConfig{ HandshakeConfig: testHandshake, Plugins: testPluginMap, }) // Exit return case "test-grpc": Serve(&ServeConfig{ HandshakeConfig: testHandshake, Plugins: testPluginMap, GRPCServer: DefaultGRPCServer, }) // Shouldn't reach here but make sure we exit anyways os.Exit(0) case "test-grpc-tls": // Serve! Serve(&ServeConfig{ HandshakeConfig: testHandshake, Plugins: testPluginMap, GRPCServer: DefaultGRPCServer, TLSProvider: helperTLSProvider, }) // Shouldn't reach here but make sure we exit anyways os.Exit(0) case "test-interface": Serve(&ServeConfig{ HandshakeConfig: testHandshake, Plugins: testPluginMap, }) // Shouldn't reach here but make sure we exit anyways os.Exit(0) case "test-interface-logger-netrpc": Serve(&ServeConfig{ HandshakeConfig: testHandshake, Plugins: testPluginMap, }) // Shouldn't reach here but make sure we exit anyways os.Exit(0) case "test-interface-logger-grpc": Serve(&ServeConfig{ HandshakeConfig: testHandshake, Plugins: testPluginMap, GRPCServer: DefaultGRPCServer, }) // Shouldn't reach here but make sure we exit anyways os.Exit(0) case "test-interface-daemon": // Serve! Serve(&ServeConfig{ HandshakeConfig: testHandshake, Plugins: testPluginMap, }) // Shouldn't reach here but make sure we exit anyways os.Exit(0) case "test-interface-tls": // Serve! Serve(&ServeConfig{ HandshakeConfig: testHandshake, Plugins: testPluginMap, TLSProvider: helperTLSProvider, }) // Shouldn't reach here but make sure we exit anyways os.Exit(0) default: fmt.Fprintf(os.Stderr, "Unknown command: %q\n", cmd) os.Exit(2) } } func helperTLSProvider() (*tls.Config, error) { serverCert, err := tls.X509KeyPair([]byte(TestClusterServerCert), []byte(TestClusterServerKey)) if err != nil { return nil, err } rootCAs := x509.NewCertPool() rootCAs.AppendCertsFromPEM([]byte(TestClusterCACert)) tlsConfig := &tls.Config{ Certificates: []tls.Certificate{serverCert}, RootCAs: rootCAs, ClientCAs: rootCAs, ClientAuth: tls.VerifyClientCertIfGiven, ServerName: "127.0.0.1", } tlsConfig.BuildNameToCertificate() return tlsConfig, nil } const ( TestClusterCACert = `-----BEGIN CERTIFICATE----- MIIDPjCCAiagAwIBAgIUfIKsF2VPT7sdFcKOHJH2Ii6K4MwwDQYJKoZIhvcNAQEL BQAwFjEUMBIGA1UEAxMLbXl2YXVsdC5jb20wIBcNMTYwNTAyMTYwNTQyWhgPMjA2 NjA0MjAxNjA2MTJaMBYxFDASBgNVBAMTC215dmF1bHQuY29tMIIBIjANBgkqhkiG 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuOimEXawD2qBoLCFP3Skq5zi1XzzcMAJlfdS xz9hfymuJb+cN8rB91HOdU9wQCwVKnkUtGWxUnMp0tT0uAZj5NzhNfyinf0JGAbP 67HDzVZhGBHlHTjPX0638yaiUx90cTnucX0N20SgCYct29dMSgcPl+W78D3Jw3xE JsHQPYS9ASe2eONxG09F/qNw7w/RO5/6WYoV2EmdarMMxq52pPe2chtNMQdSyOUb cCcIZyk4QVFZ1ZLl6jTnUPb+JoCx1uMxXvMek4NF/5IL0Wr9dw2gKXKVKoHDr6SY WrCONRw61A5Zwx1V+kn73YX3USRlkufQv/ih6/xThYDAXDC9cwIDAQABo4GBMH8w DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOuKvPiU G06iHkRXAOeMiUdBfHFyMB8GA1UdIwQYMBaAFOuKvPiUG06iHkRXAOeMiUdBfHFy MBwGA1UdEQQVMBOCC215dmF1bHQuY29thwR/AAABMA0GCSqGSIb3DQEBCwUAA4IB AQBcN/UdAMzc7UjRdnIpZvO+5keBGhL/vjltnGM1dMWYHa60Y5oh7UIXF+P1RdNW n7g80lOyvkSR15/r1rDkqOK8/4oruXU31EcwGhDOC4hU6yMUy4ltV/nBoodHBXNh MfKiXeOstH1vdI6G0P6W93Bcww6RyV1KH6sT2dbETCw+iq2VN9CrruGIWzd67UT/ spe/kYttr3UYVV3O9kqgffVVgVXg/JoRZ3J7Hy2UEXfh9UtWNanDlRuXaZgE9s/d CpA30CHpNXvKeyNeW2ktv+2nAbSpvNW+e6MecBCTBIoDSkgU8ShbrzmDKVwNN66Q 5gn6KxUPBKHEtNzs5DgGM7nq -----END CERTIFICATE-----` TestClusterServerCert = `-----BEGIN CERTIFICATE----- MIIDtzCCAp+gAwIBAgIUBLqh6ctGWVDUxFhxJX7m6S/bnrcwDQYJKoZIhvcNAQEL BQAwFjEUMBIGA1UEAxMLbXl2YXVsdC5jb20wIBcNMTYwNTAyMTYwOTI2WhgPMjA2 NjA0MjAxNTA5NTZaMBsxGTAXBgNVBAMTEGNlcnQubXl2YXVsdC5jb20wggEiMA0G CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDY3gPB29kkdbu0mPO6J0efagQhSiXB 9OyDuLf5sMk6CVDWVWal5hISkyBmw/lXgF7qC2XFKivpJOrcGQd5Ep9otBqyJLzI b0IWdXuPIrVnXDwcdWr86ybX2iC42zKWfbXgjzGijeAVpl0UJLKBj+fk5q6NvkRL 5FUL6TRV7Krn9mrmnrV9J5IqV15pTd9W2aVJ6IqWvIPCACtZKulqWn4707uy2X2W 1Stq/5qnp1pDshiGk1VPyxCwQ6yw3iEcgecbYo3vQfhWcv7Q8LpSIM9ZYpXu6OmF +czqRZS9gERl+wipmmrN1MdYVrTuQem21C/PNZ4jo4XUk1SFx6JrcA+lAgMBAAGj gfUwgfIwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBSe Cl9WV3BjGCwmS/KrDSLRjfwyqjAfBgNVHSMEGDAWgBTrirz4lBtOoh5EVwDnjIlH QXxxcjA7BggrBgEFBQcBAQQvMC0wKwYIKwYBBQUHMAKGH2h0dHA6Ly8xMjcuMC4w LjE6ODIwMC92MS9wa2kvY2EwIQYDVR0RBBowGIIQY2VydC5teXZhdWx0LmNvbYcE fwAAATAxBgNVHR8EKjAoMCagJKAihiBodHRwOi8vMTI3LjAuMC4xOjgyMDAvdjEv cGtpL2NybDANBgkqhkiG9w0BAQsFAAOCAQEAWGholPN8buDYwKbUiDavbzjsxUIX lU4MxEqOHw7CD3qIYIauPboLvB9EldBQwhgOOy607Yvdg3rtyYwyBFwPhHo/hK3Z 6mn4hc6TF2V+AUdHBvGzp2dbYLeo8noVoWbQ/lBulggwlIHNNF6+a3kALqsqk1Ch f/hzsjFnDhAlNcYFgG8TgfE2lE/FckvejPqBffo7Q3I+wVAw0buqiz5QL81NOT+D Y2S9LLKLRaCsWo9wRU1Az4Rhd7vK5SEMh16jJ82GyEODWPvuxOTI1MnzfnbWyLYe TTp6YBjGMVf1I6NEcWNur7U17uIOiQjMZ9krNvoMJ1A/cxCoZ98QHgcIPg== -----END CERTIFICATE-----` TestClusterServerKey = `-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEA2N4DwdvZJHW7tJjzuidHn2oEIUolwfTsg7i3+bDJOglQ1lVm peYSEpMgZsP5V4Be6gtlxSor6STq3BkHeRKfaLQasiS8yG9CFnV7jyK1Z1w8HHVq /Osm19oguNsyln214I8xoo3gFaZdFCSygY/n5Oaujb5ES+RVC+k0Veyq5/Zq5p61 fSeSKldeaU3fVtmlSeiKlryDwgArWSrpalp+O9O7stl9ltUrav+ap6daQ7IYhpNV T8sQsEOssN4hHIHnG2KN70H4VnL+0PC6UiDPWWKV7ujphfnM6kWUvYBEZfsIqZpq zdTHWFa07kHpttQvzzWeI6OF1JNUhceia3APpQIDAQABAoIBAQCH3vEzr+3nreug RoPNCXcSJXXY9X+aeT0FeeGqClzIg7Wl03OwVOjVwl/2gqnhbIgK0oE8eiNwurR6 mSPZcxV0oAJpwiKU4T/imlCDaReGXn86xUX2l82KRxthNdQH/VLKEmzij0jpx4Vh bWx5SBPdkbmjDKX1dmTiRYWIn/KjyNPvNvmtwdi8Qluhf4eJcNEUr2BtblnGOmfL FdSu+brPJozpoQ1QdDnbAQRgqnh7Shl0tT85whQi0uquqIj1gEOGVjmBvDDnL3GV WOENTKqsmIIoEzdZrql1pfmYTk7WNaD92bfpN128j8BF7RmAV4/DphH0pvK05y9m tmRhyHGxAoGBAOV2BBocsm6xup575VqmFN+EnIOiTn+haOvfdnVsyQHnth63fOQx PNtMpTPR1OMKGpJ13e2bV0IgcYRsRkScVkUtoa/17VIgqZXffnJJ0A/HT67uKBq3 8o7RrtyK5N20otw0lZHyqOPhyCdpSsurDhNON1kPVJVYY4N1RiIxfut/AoGBAPHz HfsJ5ZkyELE9N/r4fce04lprxWH+mQGK0/PfjS9caXPhj/r5ZkVMvzWesF3mmnY8 goE5S35TuTvV1+6rKGizwlCFAQlyXJiFpOryNWpLwCmDDSzLcm+sToAlML3tMgWU jM3dWHx3C93c3ft4rSWJaUYI9JbHsMzDW6Yh+GbbAoGBANIbKwxh5Hx5XwEJP2yu kIROYCYkMy6otHLujgBdmPyWl+suZjxoXWoMl2SIqR8vPD+Jj6mmyNJy9J6lqf3f DRuQ+fEuBZ1i7QWfvJ+XuN0JyovJ5Iz6jC58D1pAD+p2IX3y5FXcVQs8zVJRFjzB p0TEJOf2oqORaKWRd6ONoMKvAoGALKu6aVMWdQZtVov6/fdLIcgf0pn7Q3CCR2qe X3Ry2L+zKJYIw0mwvDLDSt8VqQCenB3n6nvtmFFU7ds5lvM67rnhsoQcAOaAehiS rl4xxoJd5Ewx7odRhZTGmZpEOYzFo4odxRSM9c30/u18fqV1Mm0AZtHYds4/sk6P aUj0V+kCgYBMpGrJk8RSez5g0XZ35HfpI4ENoWbiwB59FIpWsLl2LADEh29eC455 t9Muq7MprBVBHQo11TMLLFxDIjkuMho/gcKgpYXCt0LfiNm8EZehvLJUXH+3WqUx we6ywrbFCs6LaxaOCtTiLsN+GbZCatITL0UJaeBmTAbiw0KQjUuZPQ== -----END RSA PRIVATE KEY-----` )