github.com/hashicorp/go-plugin@v1.6.0/server_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package plugin
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"log"
    10  	"net"
    11  	"os"
    12  	"path"
    13  	"runtime"
    14  	"strings"
    15  	"testing"
    16  	"time"
    17  
    18  	hclog "github.com/hashicorp/go-hclog"
    19  )
    20  
    21  func TestServer_testMode(t *testing.T) {
    22  	ctx, cancel := context.WithCancel(context.Background())
    23  	defer cancel()
    24  
    25  	ch := make(chan *ReattachConfig, 1)
    26  	closeCh := make(chan struct{})
    27  	go Serve(&ServeConfig{
    28  		HandshakeConfig: testHandshake,
    29  		Plugins:         testGRPCPluginMap,
    30  		GRPCServer:      DefaultGRPCServer,
    31  		Test: &ServeTestConfig{
    32  			Context:          ctx,
    33  			ReattachConfigCh: ch,
    34  			CloseCh:          closeCh,
    35  		},
    36  	})
    37  
    38  	// We should get a config
    39  	var config *ReattachConfig
    40  	select {
    41  	case config = <-ch:
    42  	case <-time.After(2000 * time.Millisecond):
    43  		t.Fatal("should've received reattach")
    44  	}
    45  	if config == nil {
    46  		t.Fatal("config should not be nil")
    47  	}
    48  
    49  	// Check that the reattach config includes the negotiated protocol version
    50  	if config.ProtocolVersion != int(testHandshake.ProtocolVersion) {
    51  		t.Fatalf("wrong protocol version in reattach config. got %d, expected %d", config.ProtocolVersion, testHandshake.ProtocolVersion)
    52  	}
    53  
    54  	// Connect!
    55  	c := NewClient(&ClientConfig{
    56  		Cmd:              nil,
    57  		HandshakeConfig:  testHandshake,
    58  		Plugins:          testGRPCPluginMap,
    59  		Reattach:         config,
    60  		AllowedProtocols: []Protocol{ProtocolGRPC},
    61  	})
    62  	client, err := c.Client()
    63  	if err != nil {
    64  		t.Fatalf("err: %s", err)
    65  	}
    66  
    67  	// Pinging should work
    68  	if err := client.Ping(); err != nil {
    69  		t.Fatalf("should not err: %s", err)
    70  	}
    71  
    72  	// Kill which should do nothing
    73  	c.Kill()
    74  	if err := client.Ping(); err != nil {
    75  		t.Fatalf("should not err: %s", err)
    76  	}
    77  
    78  	// Canceling should cause an exit
    79  	cancel()
    80  	<-closeCh
    81  	if err := client.Ping(); err == nil {
    82  		t.Fatal("should error")
    83  	}
    84  
    85  	// Try logging, this should show out in tests. We have to manually verify.
    86  	t.Logf("HELLO")
    87  }
    88  
    89  func TestServer_testMode_AutoMTLS(t *testing.T) {
    90  	ctx, cancel := context.WithCancel(context.Background())
    91  	defer cancel()
    92  
    93  	closeCh := make(chan struct{})
    94  	go Serve(&ServeConfig{
    95  		HandshakeConfig: testVersionedHandshake,
    96  		VersionedPlugins: map[int]PluginSet{
    97  			2: testGRPCPluginMap,
    98  		},
    99  		GRPCServer: DefaultGRPCServer,
   100  		Logger:     hclog.NewNullLogger(),
   101  		Test: &ServeTestConfig{
   102  			Context:          ctx,
   103  			ReattachConfigCh: nil,
   104  			CloseCh:          closeCh,
   105  		},
   106  	})
   107  
   108  	// Connect!
   109  	process := helperProcess("test-mtls")
   110  	c := NewClient(&ClientConfig{
   111  		Cmd:             process,
   112  		HandshakeConfig: testVersionedHandshake,
   113  		VersionedPlugins: map[int]PluginSet{
   114  			2: testGRPCPluginMap,
   115  		},
   116  		AllowedProtocols: []Protocol{ProtocolGRPC},
   117  		AutoMTLS:         true,
   118  	})
   119  	client, err := c.Client()
   120  	if err != nil {
   121  		t.Fatalf("err: %s", err)
   122  	}
   123  
   124  	// Pinging should work
   125  	if err := client.Ping(); err != nil {
   126  		t.Fatalf("should not err: %s", err)
   127  	}
   128  
   129  	// Grab the impl
   130  	raw, err := client.Dispense("test")
   131  	if err != nil {
   132  		t.Fatalf("err should be nil, got %s", err)
   133  	}
   134  
   135  	tester, ok := raw.(testInterface)
   136  	if !ok {
   137  		t.Fatalf("bad: %#v", raw)
   138  	}
   139  
   140  	n := tester.Double(3)
   141  	if n != 6 {
   142  		t.Fatal("invalid response", n)
   143  	}
   144  
   145  	// ensure we can make use of bidirectional communication with AutoMTLS
   146  	// enabled
   147  	err = tester.Bidirectional()
   148  	if err != nil {
   149  		t.Fatal("invalid response", err)
   150  	}
   151  
   152  	c.Kill()
   153  	// Canceling should cause an exit
   154  	cancel()
   155  	<-closeCh
   156  }
   157  
   158  func TestServer_RPC(t *testing.T) {
   159  	closeCh := make(chan struct{})
   160  	ctx, cancel := context.WithCancel(context.Background())
   161  	defer cancel()
   162  
   163  	// make a server, but we don't need to attach to it
   164  	ch := make(chan *ReattachConfig, 1)
   165  	go Serve(&ServeConfig{
   166  		HandshakeConfig: testHandshake,
   167  		Plugins:         testPluginMap,
   168  		Test: &ServeTestConfig{
   169  			Context:          ctx,
   170  			CloseCh:          closeCh,
   171  			ReattachConfigCh: ch,
   172  		},
   173  	})
   174  
   175  	// Wait for the server
   176  	select {
   177  	case cfg := <-ch:
   178  		if cfg == nil {
   179  			t.Fatal("attach config should not be nil")
   180  		}
   181  	case <-time.After(2000 * time.Millisecond):
   182  		t.Fatal("should've received reattach")
   183  	}
   184  
   185  	cancel()
   186  	<-closeCh
   187  }
   188  
   189  func TestRmListener_impl(t *testing.T) {
   190  	var _ net.Listener = new(rmListener)
   191  }
   192  
   193  func TestRmListener(t *testing.T) {
   194  	l, err := net.Listen("tcp", "127.0.0.1:0")
   195  	if err != nil {
   196  		t.Fatalf("err: %s", err)
   197  	}
   198  
   199  	tf, err := os.CreateTemp("", "plugin")
   200  	if err != nil {
   201  		t.Fatalf("err: %s", err)
   202  	}
   203  	path := tf.Name()
   204  
   205  	// Close the file
   206  	if err := tf.Close(); err != nil {
   207  		t.Fatalf("err: %s", err)
   208  	}
   209  
   210  	// Create the listener and test close
   211  	rmL := newDeleteFileListener(l, path)
   212  	if err := rmL.Close(); err != nil {
   213  		t.Fatalf("err: %s", err)
   214  	}
   215  
   216  	// File should be goe
   217  	if _, err := os.Stat(path); err == nil || !os.IsNotExist(err) {
   218  		t.Fatalf("err: %s", err)
   219  	}
   220  }
   221  
   222  func TestProtocolSelection_no_server(t *testing.T) {
   223  	conf := &ServeConfig{
   224  		HandshakeConfig: testVersionedHandshake,
   225  		VersionedPlugins: map[int]PluginSet{
   226  			2: testGRPCPluginMap,
   227  		},
   228  		GRPCServer:  DefaultGRPCServer,
   229  		TLSProvider: helperTLSProvider,
   230  	}
   231  
   232  	_, protocol, _ := protocolVersion(conf)
   233  	if protocol != ProtocolGRPC {
   234  		t.Fatalf("bad protocol %s", protocol)
   235  	}
   236  
   237  	conf = &ServeConfig{
   238  		HandshakeConfig: testVersionedHandshake,
   239  		VersionedPlugins: map[int]PluginSet{
   240  			2: testGRPCPluginMap,
   241  		},
   242  		TLSProvider: helperTLSProvider,
   243  	}
   244  
   245  	_, protocol, _ = protocolVersion(conf)
   246  	if protocol != ProtocolNetRPC {
   247  		t.Fatalf("bad protocol %s", protocol)
   248  	}
   249  }
   250  
   251  func TestServer_testStdLogger(t *testing.T) {
   252  	closeCh := make(chan struct{})
   253  	ctx, cancel := context.WithCancel(context.Background())
   254  	defer cancel()
   255  
   256  	var logOut bytes.Buffer
   257  
   258  	hclogger := hclog.New(&hclog.LoggerOptions{
   259  		Name:       "test",
   260  		Level:      hclog.Trace,
   261  		Output:     &logOut,
   262  		JSONFormat: true,
   263  	})
   264  
   265  	// Wrap the hclog.Logger to use it from the default std library logger
   266  	// (and restore the original logger)
   267  	defer func() {
   268  		log.SetOutput(os.Stderr)
   269  		log.SetFlags(log.LstdFlags)
   270  		log.SetPrefix(log.Prefix())
   271  	}()
   272  	log.SetOutput(hclogger.StandardWriter(&hclog.StandardLoggerOptions{InferLevels: true}))
   273  	log.SetFlags(0)
   274  	log.SetPrefix("")
   275  
   276  	// make a server, but we don't need to attach to it
   277  	ch := make(chan *ReattachConfig, 1)
   278  	go Serve(&ServeConfig{
   279  		HandshakeConfig: testHandshake,
   280  		Plugins:         testGRPCPluginMap,
   281  		GRPCServer:      DefaultGRPCServer,
   282  		Logger:          hclog.NewNullLogger(),
   283  		Test: &ServeTestConfig{
   284  			Context:          ctx,
   285  			CloseCh:          closeCh,
   286  			ReattachConfigCh: ch,
   287  		},
   288  	})
   289  
   290  	// Wait for the server
   291  	select {
   292  	case cfg := <-ch:
   293  		if cfg == nil {
   294  			t.Fatal("attach config should not be nil")
   295  		}
   296  	case <-time.After(2000 * time.Millisecond):
   297  		t.Fatal("should've received reattach")
   298  	}
   299  
   300  	log.Println("[DEBUG] test log")
   301  	// shut down the server so there's no race on the buffer
   302  	cancel()
   303  	<-closeCh
   304  
   305  	if !strings.Contains(logOut.String(), "test log") {
   306  		t.Fatalf("expected: %q\ngot: %q", "test log", logOut.String())
   307  	}
   308  }
   309  
   310  func TestUnixSocketDir(t *testing.T) {
   311  	if runtime.GOOS == "windows" {
   312  		t.Skip("go-plugin doesn't support unix sockets on Windows")
   313  	}
   314  
   315  	tmpDir := t.TempDir()
   316  	t.Setenv(EnvUnixSocketDir, tmpDir)
   317  
   318  	closeCh := make(chan struct{})
   319  	ctx, cancel := context.WithCancel(context.Background())
   320  	defer cancel()
   321  
   322  	// make a server, but we don't need to attach to it
   323  	ch := make(chan *ReattachConfig, 1)
   324  	go Serve(&ServeConfig{
   325  		HandshakeConfig: testHandshake,
   326  		Plugins:         testGRPCPluginMap,
   327  		GRPCServer:      DefaultGRPCServer,
   328  		Logger:          hclog.NewNullLogger(),
   329  		Test: &ServeTestConfig{
   330  			Context:          ctx,
   331  			CloseCh:          closeCh,
   332  			ReattachConfigCh: ch,
   333  		},
   334  	})
   335  
   336  	// Wait for the server
   337  	var cfg *ReattachConfig
   338  	select {
   339  	case cfg = <-ch:
   340  		if cfg == nil {
   341  			t.Fatal("attach config should not be nil")
   342  		}
   343  	case <-time.After(2000 * time.Millisecond):
   344  		t.Fatal("should've received reattach")
   345  	}
   346  
   347  	actualDir := path.Clean(path.Dir(cfg.Addr.String()))
   348  	expectedDir := path.Clean(tmpDir)
   349  	if actualDir != expectedDir {
   350  		t.Fatalf("Expected socket in dir: %s, but was in %s", expectedDir, actualDir)
   351  	}
   352  
   353  	cancel()
   354  	<-closeCh
   355  }