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

     1  package drivermanager
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sync"
     7  	"testing"
     8  	"time"
     9  
    10  	log "github.com/hashicorp/go-hclog"
    11  	plugin "github.com/hashicorp/go-plugin"
    12  	"github.com/hashicorp/nomad/ci"
    13  	"github.com/hashicorp/nomad/client/pluginmanager"
    14  	"github.com/hashicorp/nomad/client/state"
    15  	"github.com/hashicorp/nomad/helper/pluginutils/loader"
    16  	"github.com/hashicorp/nomad/helper/testlog"
    17  	"github.com/hashicorp/nomad/nomad/structs"
    18  	"github.com/hashicorp/nomad/plugins/base"
    19  	"github.com/hashicorp/nomad/plugins/drivers"
    20  	dtu "github.com/hashicorp/nomad/plugins/drivers/testutils"
    21  	"github.com/hashicorp/nomad/testutil"
    22  	"github.com/stretchr/testify/assert"
    23  	"github.com/stretchr/testify/require"
    24  )
    25  
    26  var _ Manager = (*manager)(nil)
    27  var _ pluginmanager.PluginManager = (*manager)(nil)
    28  
    29  func testSetup(t *testing.T) (chan *drivers.Fingerprint, chan *drivers.TaskEvent, *manager) {
    30  	fpChan := make(chan *drivers.Fingerprint)
    31  	evChan := make(chan *drivers.TaskEvent)
    32  	drv := mockDriver(fpChan, evChan)
    33  	cat := mockCatalog(map[string]drivers.DriverPlugin{"mock": drv})
    34  	cfg := &Config{
    35  		Logger:              testlog.HCLogger(t),
    36  		Loader:              cat,
    37  		PluginConfig:        &base.AgentConfig{},
    38  		Updater:             noopUpdater,
    39  		EventHandlerFactory: noopEventHandlerFactory,
    40  		State:               state.NoopDB{},
    41  		AllowedDrivers:      make(map[string]struct{}),
    42  		BlockedDrivers:      make(map[string]struct{}),
    43  	}
    44  
    45  	mgr := New(cfg)
    46  	return fpChan, evChan, mgr
    47  }
    48  
    49  func mockDriver(fpChan chan *drivers.Fingerprint, evChan chan *drivers.TaskEvent) drivers.DriverPlugin {
    50  	return &dtu.MockDriver{
    51  		FingerprintF: func(ctx context.Context) (<-chan *drivers.Fingerprint, error) {
    52  			return fpChan, nil
    53  		},
    54  		TaskEventsF: func(ctx context.Context) (<-chan *drivers.TaskEvent, error) {
    55  			return evChan, nil
    56  		},
    57  	}
    58  }
    59  
    60  func mockCatalog(drivers map[string]drivers.DriverPlugin) *loader.MockCatalog {
    61  	cat := map[string][]*base.PluginInfoResponse{
    62  		base.PluginTypeDriver: {},
    63  	}
    64  	for d := range drivers {
    65  		cat[base.PluginTypeDriver] = append(cat[base.PluginTypeDriver], &base.PluginInfoResponse{
    66  			Name: d,
    67  			Type: base.PluginTypeDriver,
    68  		})
    69  	}
    70  
    71  	return &loader.MockCatalog{
    72  		DispenseF: func(name, pluginType string, cfg *base.AgentConfig, logger log.Logger) (loader.PluginInstance, error) {
    73  			d, ok := drivers[name]
    74  			if !ok {
    75  				return nil, fmt.Errorf("driver not found")
    76  			}
    77  			return loader.MockBasicExternalPlugin(d, "0.1.0"), nil
    78  		},
    79  		ReattachF: func(name, pluginType string, config *plugin.ReattachConfig) (loader.PluginInstance, error) {
    80  			d, ok := drivers[name]
    81  			if !ok {
    82  				return nil, fmt.Errorf("driver not found")
    83  			}
    84  			return loader.MockBasicExternalPlugin(d, "0.1.0"), nil
    85  		},
    86  		CatalogF: func() map[string][]*base.PluginInfoResponse {
    87  			return cat
    88  		},
    89  	}
    90  }
    91  
    92  func mockTaskEvent(taskID string) *drivers.TaskEvent {
    93  	return &drivers.TaskEvent{
    94  		TaskID:      taskID,
    95  		Timestamp:   time.Now(),
    96  		Annotations: map[string]string{},
    97  		Message:     "event from " + taskID,
    98  	}
    99  }
   100  
   101  func noopUpdater(string, *structs.DriverInfo)             {}
   102  func noopEventHandlerFactory(string, string) EventHandler { return nil }
   103  
   104  func TestManager_Fingerprint(t *testing.T) {
   105  	ci.Parallel(t)
   106  	require := require.New(t)
   107  	fpChan, _, mgr := testSetup(t)
   108  	var infos []*structs.DriverInfo
   109  	mgr.updater = func(d string, i *structs.DriverInfo) {
   110  		infos = append(infos, i)
   111  	}
   112  	go mgr.Run()
   113  	defer mgr.Shutdown()
   114  	fpChan <- &drivers.Fingerprint{Health: drivers.HealthStateHealthy}
   115  	testutil.WaitForResult(func() (bool, error) {
   116  		mgr.instancesMu.Lock()
   117  		defer mgr.instancesMu.Unlock()
   118  		return len(mgr.instances) == 1, fmt.Errorf("manager should have registered 1 instance")
   119  	}, func(err error) {
   120  		require.NoError(err)
   121  	})
   122  
   123  	testutil.WaitForResult(func() (bool, error) {
   124  		mgr.instancesMu.Lock()
   125  		defer mgr.instancesMu.Unlock()
   126  		if mgr.instances["mock"].getLastHealth() != drivers.HealthStateHealthy {
   127  			return false, fmt.Errorf("mock instance should be healthy")
   128  		}
   129  		return true, nil
   130  	}, func(err error) {
   131  		require.NoError(err)
   132  	})
   133  
   134  	fpChan <- &drivers.Fingerprint{
   135  		Health: drivers.HealthStateUnhealthy,
   136  	}
   137  	testutil.WaitForResult(func() (bool, error) {
   138  		mgr.instancesMu.Lock()
   139  		defer mgr.instancesMu.Unlock()
   140  		if mgr.instances["mock"].getLastHealth() == drivers.HealthStateHealthy {
   141  			return false, fmt.Errorf("mock instance should be unhealthy")
   142  		}
   143  		return true, nil
   144  	}, func(err error) {
   145  		require.NoError(err)
   146  	})
   147  
   148  	fpChan <- &drivers.Fingerprint{
   149  		Health: drivers.HealthStateUndetected,
   150  	}
   151  	testutil.WaitForResult(func() (bool, error) {
   152  		mgr.instancesMu.Lock()
   153  		defer mgr.instancesMu.Unlock()
   154  		if mgr.instances["mock"].getLastHealth() != drivers.HealthStateUndetected {
   155  			return false, fmt.Errorf("mock instance should be undetected")
   156  		}
   157  		return true, nil
   158  	}, func(err error) {
   159  		require.NoError(err)
   160  	})
   161  
   162  	require.Len(infos, 3)
   163  	require.True(infos[0].Healthy)
   164  	require.True(infos[0].Detected)
   165  	require.False(infos[1].Healthy)
   166  	require.True(infos[1].Detected)
   167  	require.False(infos[2].Healthy)
   168  	require.False(infos[2].Detected)
   169  }
   170  
   171  func TestManager_TaskEvents(t *testing.T) {
   172  	ci.Parallel(t)
   173  	require := require.New(t)
   174  	fpChan, evChan, mgr := testSetup(t)
   175  	go mgr.Run()
   176  	defer mgr.Shutdown()
   177  	fpChan <- &drivers.Fingerprint{Health: drivers.HealthStateHealthy}
   178  	testutil.WaitForResult(func() (bool, error) {
   179  		mgr.instancesMu.Lock()
   180  		defer mgr.instancesMu.Unlock()
   181  		return len(mgr.instances) == 1, fmt.Errorf("manager should have registered 1 instance")
   182  	}, func(err error) {
   183  		require.NoError(err)
   184  	})
   185  
   186  	event1 := mockTaskEvent("abc1")
   187  	var wg sync.WaitGroup
   188  	wg.Add(1)
   189  	mgr.instancesMu.Lock()
   190  	mgr.instances["mock"].eventHandlerFactory = func(string, string) EventHandler {
   191  		return func(ev *drivers.TaskEvent) {
   192  			defer wg.Done()
   193  			assert.Exactly(t, event1, ev)
   194  		}
   195  	}
   196  	mgr.instancesMu.Unlock()
   197  
   198  	evChan <- event1
   199  	wg.Wait()
   200  }
   201  
   202  func TestManager_Run_AllowedDrivers(t *testing.T) {
   203  	ci.Parallel(t)
   204  	require := require.New(t)
   205  	fpChan, _, mgr := testSetup(t)
   206  	mgr.allowedDrivers = map[string]struct{}{"foo": {}}
   207  	go mgr.Run()
   208  	defer mgr.Shutdown()
   209  	select {
   210  	case fpChan <- &drivers.Fingerprint{Health: drivers.HealthStateHealthy}:
   211  	default:
   212  	}
   213  	testutil.AssertUntil(200*time.Millisecond, func() (bool, error) {
   214  		mgr.instancesMu.Lock()
   215  		defer mgr.instancesMu.Unlock()
   216  		return len(mgr.instances) == 0, fmt.Errorf("manager should have no registered instances")
   217  	}, func(err error) {
   218  		require.NoError(err)
   219  	})
   220  }
   221  
   222  func TestManager_Run_BlockedDrivers(t *testing.T) {
   223  	ci.Parallel(t)
   224  	require := require.New(t)
   225  	fpChan, _, mgr := testSetup(t)
   226  	mgr.blockedDrivers = map[string]struct{}{"mock": {}}
   227  	go mgr.Run()
   228  	defer mgr.Shutdown()
   229  	select {
   230  	case fpChan <- &drivers.Fingerprint{Health: drivers.HealthStateHealthy}:
   231  	default:
   232  	}
   233  	testutil.AssertUntil(200*time.Millisecond, func() (bool, error) {
   234  		mgr.instancesMu.Lock()
   235  		defer mgr.instancesMu.Unlock()
   236  		return len(mgr.instances) == 0, fmt.Errorf("manager should have no registered instances")
   237  	}, func(err error) {
   238  		require.NoError(err)
   239  	})
   240  }
   241  
   242  func TestManager_Run_AllowedBlockedDrivers_Combined(t *testing.T) {
   243  	ci.Parallel(t)
   244  	require := require.New(t)
   245  	drvs := map[string]drivers.DriverPlugin{}
   246  	fpChs := map[string]chan *drivers.Fingerprint{}
   247  	names := []string{"mock1", "mock2", "mock3", "mock4", "mock5"}
   248  	for _, d := range names {
   249  		ch := make(chan *drivers.Fingerprint)
   250  		drv := mockDriver(ch, nil)
   251  		drvs[d] = drv
   252  		fpChs[d] = ch
   253  	}
   254  	cat := mockCatalog(drvs)
   255  	cfg := &Config{
   256  		Logger:       testlog.HCLogger(t),
   257  		Loader:       cat,
   258  		PluginConfig: nil,
   259  		Updater:      noopUpdater,
   260  		State:        state.NoopDB{},
   261  		AllowedDrivers: map[string]struct{}{
   262  			"mock2": {},
   263  			"mock3": {},
   264  			"mock4": {},
   265  			"foo":   {},
   266  		},
   267  		BlockedDrivers: map[string]struct{}{
   268  			"mock2": {},
   269  			"mock4": {},
   270  			"bar":   {},
   271  		},
   272  	}
   273  	mgr := New(cfg)
   274  
   275  	go mgr.Run()
   276  	defer mgr.Shutdown()
   277  	for _, d := range names {
   278  		go func(drv string) {
   279  			select {
   280  			case fpChs[drv] <- &drivers.Fingerprint{Health: drivers.HealthStateHealthy}:
   281  			case <-time.After(200 * time.Millisecond):
   282  			}
   283  		}(d)
   284  	}
   285  
   286  	testutil.AssertUntil(250*time.Millisecond, func() (bool, error) {
   287  		mgr.instancesMu.Lock()
   288  		defer mgr.instancesMu.Unlock()
   289  		return len(mgr.instances) < 2, fmt.Errorf("manager should have 1 registered instance, %v", len(mgr.instances))
   290  	}, func(err error) {
   291  		require.NoError(err)
   292  	})
   293  	mgr.instancesMu.Lock()
   294  	require.Len(mgr.instances, 1)
   295  	_, ok := mgr.instances["mock3"]
   296  	mgr.instancesMu.Unlock()
   297  	require.True(ok)
   298  }