github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/helper/pluginutils/singleton/singleton_test.go (about)

     1  package singleton
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  	"testing"
     7  	"time"
     8  
     9  	log "github.com/hashicorp/go-hclog"
    10  	plugin "github.com/hashicorp/go-plugin"
    11  	"github.com/hashicorp/nomad/helper/pluginutils/loader"
    12  	"github.com/hashicorp/nomad/helper/testlog"
    13  	"github.com/hashicorp/nomad/plugins/base"
    14  	"github.com/stretchr/testify/require"
    15  )
    16  
    17  func harness(t *testing.T) (*SingletonLoader, *loader.MockCatalog) {
    18  	c := &loader.MockCatalog{}
    19  	s := NewSingletonLoader(testlog.HCLogger(t), c)
    20  	return s, c
    21  }
    22  
    23  // Test that multiple dispenses return the same instance
    24  func TestSingleton_Dispense(t *testing.T) {
    25  	t.Parallel()
    26  	require := require.New(t)
    27  
    28  	dispenseCalled := 0
    29  	s, c := harness(t)
    30  	c.DispenseF = func(_, _ string, _ *base.AgentConfig, _ log.Logger) (loader.PluginInstance, error) {
    31  		p := &base.MockPlugin{}
    32  		i := &loader.MockInstance{
    33  			ExitedF: func() bool { return false },
    34  			PluginF: func() interface{} { return p },
    35  		}
    36  		dispenseCalled++
    37  		return i, nil
    38  	}
    39  
    40  	// Retrieve the plugin many times in parallel
    41  	const count = 128
    42  	var l sync.Mutex
    43  	var wg sync.WaitGroup
    44  	plugins := make(map[interface{}]struct{}, 1)
    45  	waitCh := make(chan struct{})
    46  	for i := 0; i < count; i++ {
    47  		wg.Add(1)
    48  		go func() {
    49  			// Wait for unblock
    50  			<-waitCh
    51  
    52  			// Retrieve the plugin
    53  			p1, err := s.Dispense("foo", "bar", nil, testlog.HCLogger(t))
    54  			require.NotNil(p1)
    55  			require.NoError(err)
    56  			i1 := p1.Plugin()
    57  			require.NotNil(i1)
    58  			l.Lock()
    59  			plugins[i1] = struct{}{}
    60  			l.Unlock()
    61  			wg.Done()
    62  		}()
    63  	}
    64  	time.Sleep(10 * time.Millisecond)
    65  	close(waitCh)
    66  	wg.Wait()
    67  	require.Len(plugins, 1)
    68  	require.Equal(1, dispenseCalled)
    69  }
    70  
    71  // Test that after a plugin is dispensed, if it exits, an error is returned on
    72  // the next dispense
    73  func TestSingleton_Dispense_Exit_Dispense(t *testing.T) {
    74  	t.Parallel()
    75  	require := require.New(t)
    76  
    77  	exited := false
    78  	dispenseCalled := 0
    79  	s, c := harness(t)
    80  	c.DispenseF = func(_, _ string, _ *base.AgentConfig, _ log.Logger) (loader.PluginInstance, error) {
    81  		p := &base.MockPlugin{}
    82  		i := &loader.MockInstance{
    83  			ExitedF: func() bool { return exited },
    84  			PluginF: func() interface{} { return p },
    85  		}
    86  		dispenseCalled++
    87  		return i, nil
    88  	}
    89  
    90  	// Retrieve the plugin
    91  	logger := testlog.HCLogger(t)
    92  	p1, err := s.Dispense("foo", "bar", nil, logger)
    93  	require.NotNil(p1)
    94  	require.NoError(err)
    95  
    96  	i1 := p1.Plugin()
    97  	require.NotNil(i1)
    98  	require.Equal(1, dispenseCalled)
    99  
   100  	// Mark the plugin as exited and retrieve again
   101  	exited = true
   102  	_, err = s.Dispense("foo", "bar", nil, logger)
   103  	require.Error(err)
   104  	require.Contains(err.Error(), "exited")
   105  	require.Equal(1, dispenseCalled)
   106  
   107  	// Mark the plugin as non-exited and retrieve again
   108  	exited = false
   109  	p2, err := s.Dispense("foo", "bar", nil, logger)
   110  	require.NotNil(p2)
   111  	require.NoError(err)
   112  	require.Equal(2, dispenseCalled)
   113  
   114  	i2 := p2.Plugin()
   115  	require.NotNil(i2)
   116  	if i1 == i2 {
   117  		t.Fatalf("i1 and i2 shouldn't be the same instance: %p vs %p", i1, i2)
   118  	}
   119  }
   120  
   121  // Test that if a plugin errors while being dispensed, the error is returned but
   122  // not saved
   123  func TestSingleton_DispenseError_Dispense(t *testing.T) {
   124  	t.Parallel()
   125  	require := require.New(t)
   126  
   127  	dispenseCalled := 0
   128  	good := func(_, _ string, _ *base.AgentConfig, _ log.Logger) (loader.PluginInstance, error) {
   129  		p := &base.MockPlugin{}
   130  		i := &loader.MockInstance{
   131  			ExitedF: func() bool { return false },
   132  			PluginF: func() interface{} { return p },
   133  		}
   134  		dispenseCalled++
   135  		return i, nil
   136  	}
   137  
   138  	bad := func(_, _ string, _ *base.AgentConfig, _ log.Logger) (loader.PluginInstance, error) {
   139  		dispenseCalled++
   140  		return nil, fmt.Errorf("bad")
   141  	}
   142  
   143  	s, c := harness(t)
   144  	c.DispenseF = bad
   145  
   146  	// Retrieve the plugin
   147  	logger := testlog.HCLogger(t)
   148  	p1, err := s.Dispense("foo", "bar", nil, logger)
   149  	require.Nil(p1)
   150  	require.Error(err)
   151  	require.Equal(1, dispenseCalled)
   152  
   153  	// Dispense again and ensure the same error isn't saved
   154  	c.DispenseF = good
   155  	p2, err := s.Dispense("foo", "bar", nil, logger)
   156  	require.NotNil(p2)
   157  	require.NoError(err)
   158  	require.Equal(2, dispenseCalled)
   159  
   160  	i2 := p2.Plugin()
   161  	require.NotNil(i2)
   162  }
   163  
   164  // Test that if a plugin errors while being reattached, the error is returned but
   165  // not saved
   166  func TestSingleton_ReattachError_Dispense(t *testing.T) {
   167  	t.Parallel()
   168  	require := require.New(t)
   169  
   170  	dispenseCalled, reattachCalled := 0, 0
   171  	s, c := harness(t)
   172  	c.DispenseF = func(_, _ string, _ *base.AgentConfig, _ log.Logger) (loader.PluginInstance, error) {
   173  		p := &base.MockPlugin{}
   174  		i := &loader.MockInstance{
   175  			ExitedF: func() bool { return false },
   176  			PluginF: func() interface{} { return p },
   177  		}
   178  		dispenseCalled++
   179  		return i, nil
   180  	}
   181  	c.ReattachF = func(_, _ string, _ *plugin.ReattachConfig) (loader.PluginInstance, error) {
   182  		reattachCalled++
   183  		return nil, fmt.Errorf("bad")
   184  	}
   185  
   186  	// Retrieve the plugin
   187  	logger := testlog.HCLogger(t)
   188  	p1, err := s.Reattach("foo", "bar", nil)
   189  	require.Nil(p1)
   190  	require.Error(err)
   191  	require.Equal(0, dispenseCalled)
   192  	require.Equal(1, reattachCalled)
   193  
   194  	// Dispense and ensure the same error isn't saved
   195  	p2, err := s.Dispense("foo", "bar", nil, logger)
   196  	require.NotNil(p2)
   197  	require.NoError(err)
   198  	require.Equal(1, dispenseCalled)
   199  	require.Equal(1, reattachCalled)
   200  
   201  	i2 := p2.Plugin()
   202  	require.NotNil(i2)
   203  }
   204  
   205  // Test that after reattaching, dispense returns the same instance
   206  func TestSingleton_Reattach_Dispense(t *testing.T) {
   207  	t.Parallel()
   208  	require := require.New(t)
   209  
   210  	dispenseCalled, reattachCalled := 0, 0
   211  	s, c := harness(t)
   212  	c.DispenseF = func(_, _ string, _ *base.AgentConfig, _ log.Logger) (loader.PluginInstance, error) {
   213  		dispenseCalled++
   214  		return nil, fmt.Errorf("bad")
   215  	}
   216  	c.ReattachF = func(_, _ string, _ *plugin.ReattachConfig) (loader.PluginInstance, error) {
   217  		p := &base.MockPlugin{}
   218  		i := &loader.MockInstance{
   219  			ExitedF: func() bool { return false },
   220  			PluginF: func() interface{} { return p },
   221  		}
   222  		reattachCalled++
   223  		return i, nil
   224  	}
   225  
   226  	// Retrieve the plugin
   227  	logger := testlog.HCLogger(t)
   228  	p1, err := s.Reattach("foo", "bar", nil)
   229  	require.NotNil(p1)
   230  	require.NoError(err)
   231  	require.Equal(0, dispenseCalled)
   232  	require.Equal(1, reattachCalled)
   233  
   234  	i1 := p1.Plugin()
   235  	require.NotNil(i1)
   236  
   237  	// Dispense and ensure the same instance returned
   238  	p2, err := s.Dispense("foo", "bar", nil, logger)
   239  	require.NotNil(p2)
   240  	require.NoError(err)
   241  	require.Equal(0, dispenseCalled)
   242  	require.Equal(1, reattachCalled)
   243  
   244  	i2 := p2.Plugin()
   245  	require.NotNil(i2)
   246  	if i1 != i2 {
   247  		t.Fatalf("i1 and i2 should be the same instance: %p vs %p", i1, i2)
   248  	}
   249  }