github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/client/pluginmanager/csimanager/manager_test.go (about)

     1  package csimanager
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/hashicorp/nomad/client/dynamicplugins"
    10  	"github.com/hashicorp/nomad/client/pluginmanager"
    11  	"github.com/hashicorp/nomad/helper/testlog"
    12  	"github.com/hashicorp/nomad/nomad/structs"
    13  	"github.com/stretchr/testify/require"
    14  )
    15  
    16  var _ pluginmanager.PluginManager = (*csiManager)(nil)
    17  
    18  func fakePlugin(idx int, pluginType string) *dynamicplugins.PluginInfo {
    19  	id := fmt.Sprintf("alloc-%d", idx)
    20  	return &dynamicplugins.PluginInfo{
    21  		Name:    "my-plugin",
    22  		Type:    pluginType,
    23  		Version: fmt.Sprintf("v%d", idx),
    24  		ConnectionInfo: &dynamicplugins.PluginConnectionInfo{
    25  			SocketPath: "/var/data/alloc/" + id + "/csi.sock"},
    26  		AllocID: id,
    27  	}
    28  }
    29  
    30  func testManager(t *testing.T, registry dynamicplugins.Registry, resyncPeriod time.Duration) *csiManager {
    31  	return New(&Config{
    32  		Logger:                testlog.HCLogger(t),
    33  		DynamicRegistry:       registry,
    34  		UpdateNodeCSIInfoFunc: func(string, *structs.CSIInfo) {},
    35  		PluginResyncPeriod:    resyncPeriod,
    36  	}).(*csiManager)
    37  }
    38  
    39  func setupRegistry(reg *MemDB) dynamicplugins.Registry {
    40  	return dynamicplugins.NewRegistry(
    41  		reg,
    42  		map[string]dynamicplugins.PluginDispenser{
    43  			"csi-controller": func(i *dynamicplugins.PluginInfo) (interface{}, error) {
    44  				return i, nil
    45  			},
    46  			"csi-node": func(i *dynamicplugins.PluginInfo) (interface{}, error) {
    47  				return i, nil
    48  			},
    49  		})
    50  }
    51  
    52  func TestManager_RegisterPlugin(t *testing.T) {
    53  	registry := setupRegistry(nil)
    54  	defer registry.Shutdown()
    55  	pm := testManager(t, registry, time.Hour)
    56  	defer pm.Shutdown()
    57  
    58  	plugin := fakePlugin(0, dynamicplugins.PluginTypeCSIController)
    59  	err := registry.RegisterPlugin(plugin)
    60  	require.NoError(t, err)
    61  
    62  	pm.Run()
    63  
    64  	require.Eventually(t, func() bool {
    65  		im := instanceManagerByTypeAndName(pm, plugin.Type, plugin.Name)
    66  		return im != nil
    67  	}, 5*time.Second, 10*time.Millisecond)
    68  }
    69  
    70  func TestManager_DeregisterPlugin(t *testing.T) {
    71  	registry := setupRegistry(nil)
    72  	defer registry.Shutdown()
    73  	pm := testManager(t, registry, 500*time.Millisecond)
    74  	defer pm.Shutdown()
    75  
    76  	plugin := fakePlugin(0, dynamicplugins.PluginTypeCSIController)
    77  	err := registry.RegisterPlugin(plugin)
    78  	require.NoError(t, err)
    79  
    80  	pm.Run()
    81  
    82  	require.Eventually(t, func() bool {
    83  		im := instanceManagerByTypeAndName(pm, plugin.Type, plugin.Name)
    84  		return im != nil
    85  	}, 5*time.Second, 10*time.Millisecond)
    86  
    87  	err = registry.DeregisterPlugin(plugin.Type, plugin.Name, "alloc-0")
    88  	require.NoError(t, err)
    89  
    90  	require.Eventually(t, func() bool {
    91  		im := instanceManagerByTypeAndName(pm, plugin.Type, plugin.Name)
    92  		return im == nil
    93  	}, 5*time.Second, 10*time.Millisecond)
    94  }
    95  
    96  // TestManager_MultiplePlugins ensures that multiple plugins with the same
    97  // name but different types (as found with monolith plugins) don't interfere
    98  // with each other.
    99  func TestManager_MultiplePlugins(t *testing.T) {
   100  	registry := setupRegistry(nil)
   101  	defer registry.Shutdown()
   102  
   103  	pm := testManager(t, registry, 500*time.Millisecond)
   104  	defer pm.Shutdown()
   105  
   106  	controllerPlugin := fakePlugin(0, dynamicplugins.PluginTypeCSIController)
   107  	err := registry.RegisterPlugin(controllerPlugin)
   108  	require.NoError(t, err)
   109  
   110  	nodePlugin := fakePlugin(0, dynamicplugins.PluginTypeCSINode)
   111  	err = registry.RegisterPlugin(nodePlugin)
   112  	require.NoError(t, err)
   113  
   114  	pm.Run()
   115  
   116  	require.Eventually(t, func() bool {
   117  		im := instanceManagerByTypeAndName(pm, controllerPlugin.Type, controllerPlugin.Name)
   118  		return im != nil
   119  	}, 5*time.Second, 10*time.Millisecond)
   120  
   121  	require.Eventually(t, func() bool {
   122  		im := instanceManagerByTypeAndName(pm, nodePlugin.Type, nodePlugin.Name)
   123  		return im != nil
   124  	}, 5*time.Second, 10*time.Millisecond)
   125  
   126  	err = registry.DeregisterPlugin(controllerPlugin.Type, controllerPlugin.Name, "alloc-0")
   127  	require.NoError(t, err)
   128  
   129  	require.Eventually(t, func() bool {
   130  		im := instanceManagerByTypeAndName(pm, controllerPlugin.Type, controllerPlugin.Name)
   131  		return im == nil
   132  	}, 5*time.Second, 10*time.Millisecond)
   133  }
   134  
   135  // TestManager_ConcurrentPlugins exercises the behavior when multiple
   136  // allocations for the same plugin interact
   137  func TestManager_ConcurrentPlugins(t *testing.T) {
   138  
   139  	t.Run("replacement races on host restart", func(t *testing.T) {
   140  		plugin0 := fakePlugin(0, dynamicplugins.PluginTypeCSINode)
   141  		plugin1 := fakePlugin(1, dynamicplugins.PluginTypeCSINode)
   142  		plugin2 := fakePlugin(2, dynamicplugins.PluginTypeCSINode)
   143  
   144  		db := &MemDB{}
   145  		registry := setupRegistry(db)
   146  		pm := testManager(t, registry, time.Hour) // no resync except from events
   147  		pm.Run()
   148  
   149  		require.NoError(t, registry.RegisterPlugin(plugin0))
   150  		require.NoError(t, registry.RegisterPlugin(plugin1))
   151  		require.Eventuallyf(t, func() bool {
   152  			im := instanceManagerByTypeAndName(pm, plugin0.Type, plugin0.Name)
   153  			return im != nil &&
   154  				im.info.ConnectionInfo.SocketPath == "/var/data/alloc/alloc-1/csi.sock" &&
   155  				im.allocID == "alloc-1"
   156  		}, 5*time.Second, 10*time.Millisecond, "alloc-1 plugin did not become active plugin")
   157  
   158  		pm.Shutdown()
   159  		registry.Shutdown()
   160  
   161  		// client restarts and we load state from disk.
   162  		// most recently inserted plugin is current
   163  
   164  		registry = setupRegistry(db)
   165  		defer registry.Shutdown()
   166  		pm = testManager(t, registry, time.Hour)
   167  		defer pm.Shutdown()
   168  		pm.Run()
   169  
   170  		require.Eventuallyf(t, func() bool {
   171  			im := instanceManagerByTypeAndName(pm, plugin0.Type, plugin0.Name)
   172  			return im != nil &&
   173  				im.info.ConnectionInfo.SocketPath == "/var/data/alloc/alloc-1/csi.sock" &&
   174  				im.allocID == "alloc-1"
   175  		}, 5*time.Second, 10*time.Millisecond, "alloc-1 plugin was not active after state reload")
   176  
   177  		// RestoreTask fires for all allocations but none of them are
   178  		// running because we restarted the whole host. Server gives
   179  		// us a replacement alloc
   180  
   181  		require.NoError(t, registry.RegisterPlugin(plugin2))
   182  		require.Eventuallyf(t, func() bool {
   183  			im := instanceManagerByTypeAndName(pm, plugin0.Type, plugin0.Name)
   184  			return im != nil &&
   185  				im.info.ConnectionInfo.SocketPath == "/var/data/alloc/alloc-2/csi.sock" &&
   186  				im.allocID == "alloc-2"
   187  		}, 5*time.Second, 10*time.Millisecond, "alloc-2 plugin was not active after replacement")
   188  
   189  	})
   190  
   191  	t.Run("interleaved register and deregister", func(t *testing.T) {
   192  		plugin0 := fakePlugin(0, dynamicplugins.PluginTypeCSINode)
   193  		plugin1 := fakePlugin(1, dynamicplugins.PluginTypeCSINode)
   194  
   195  		db := &MemDB{}
   196  		registry := setupRegistry(db)
   197  		defer registry.Shutdown()
   198  
   199  		pm := testManager(t, registry, time.Hour) // no resync except from events
   200  		defer pm.Shutdown()
   201  		pm.Run()
   202  
   203  		require.NoError(t, registry.RegisterPlugin(plugin0))
   204  		require.NoError(t, registry.RegisterPlugin(plugin1))
   205  
   206  		require.Eventuallyf(t, func() bool {
   207  			im := instanceManagerByTypeAndName(pm, plugin0.Type, plugin0.Name)
   208  			return im != nil &&
   209  				im.info.ConnectionInfo.SocketPath == "/var/data/alloc/alloc-1/csi.sock" &&
   210  				im.allocID == "alloc-1"
   211  		}, 5*time.Second, 10*time.Millisecond, "alloc-1 plugin did not become active plugin")
   212  
   213  		registry.DeregisterPlugin(dynamicplugins.PluginTypeCSINode, "my-plugin", "alloc-0")
   214  
   215  		require.Eventuallyf(t, func() bool {
   216  			im := instanceManagerByTypeAndName(pm, plugin0.Type, plugin0.Name)
   217  			return im != nil &&
   218  				im.info.ConnectionInfo.SocketPath == "/var/data/alloc/alloc-1/csi.sock"
   219  		}, 5*time.Second, 10*time.Millisecond, "alloc-1 plugin should still be active plugin")
   220  	})
   221  }
   222  
   223  // instanceManagerByTypeAndName is a test helper to get the instance
   224  // manager for the plugin, protected by the lock that the csiManager
   225  // will normally do internally
   226  func instanceManagerByTypeAndName(mgr *csiManager, pluginType, pluginName string) *instanceManager {
   227  	mgr.instancesLock.RLock()
   228  	defer mgr.instancesLock.RUnlock()
   229  	im, _ := mgr.instances[pluginType][pluginName]
   230  	return im
   231  }
   232  
   233  // MemDB implements a StateDB that stores data in memory and should only be
   234  // used for testing. All methods are safe for concurrent use. This is a
   235  // partial implementation of the MemDB in the client/state package, copied
   236  // here to avoid circular dependencies.
   237  type MemDB struct {
   238  	dynamicManagerPs *dynamicplugins.RegistryState
   239  	mu               sync.RWMutex
   240  }
   241  
   242  func (m *MemDB) GetDynamicPluginRegistryState() (*dynamicplugins.RegistryState, error) {
   243  	if m == nil {
   244  		return nil, nil
   245  	}
   246  	m.mu.Lock()
   247  	defer m.mu.Unlock()
   248  	return m.dynamicManagerPs, nil
   249  }
   250  
   251  func (m *MemDB) PutDynamicPluginRegistryState(ps *dynamicplugins.RegistryState) error {
   252  	if m == nil {
   253  		return nil
   254  	}
   255  	m.mu.Lock()
   256  	defer m.mu.Unlock()
   257  	m.dynamicManagerPs = ps
   258  	return nil
   259  }