diff --git a/client/client.go b/client/client.go index 4054fca6b..e27d2d62c 100644 --- a/client/client.go +++ b/client/client.go @@ -20,8 +20,6 @@ import ( multierror "github.com/hashicorp/go-multierror" "github.com/hashicorp/nomad/client/allocdir" "github.com/hashicorp/nomad/client/config" - "github.com/hashicorp/nomad/client/driver" - "github.com/hashicorp/nomad/client/fingerprint" "github.com/hashicorp/nomad/client/stats" cstructs "github.com/hashicorp/nomad/client/structs" "github.com/hashicorp/nomad/client/vaultclient" @@ -230,14 +228,12 @@ func NewClient(cfg *config.Config, consulCatalog consul.CatalogAPI, consulServic return nil, fmt.Errorf("node setup failed: %v", err) } - // Fingerprint the node - if err := c.fingerprint(); err != nil { - return nil, fmt.Errorf("fingerprinting failed: %v", err) - } + fingerprintManager := NewFingerprintManager(c.GetConfig, c.config.Node, + c.shutdownCh, c.updateNodeFromFingerprint, c.logger) - // Scan for drivers - if err := c.setupDrivers(); err != nil { - return nil, fmt.Errorf("driver setup failed: %v", err) + // Fingerprint the node and scan for drivers + if err := fingerprintManager.Run(); err != nil { + return nil, fmt.Errorf("fingerprinting failed: %v", err) } // Setup the reserved resources @@ -400,8 +396,10 @@ func (c *Client) Leave() error { return nil } -// GetConfig returns the config of the client for testing purposes only +// GetConfig returns the config of the client func (c *Client) GetConfig() *config.Config { + c.configLock.Lock() + defer c.configLock.Unlock() return c.config } @@ -924,152 +922,9 @@ func (c *Client) reservePorts() { } } -// fingerprint is used to fingerprint the client and setup the node -func (c *Client) fingerprint() error { - whitelist := c.config.ReadStringListToMap("fingerprint.whitelist") - whitelistEnabled := len(whitelist) > 0 - blacklist := c.config.ReadStringListToMap("fingerprint.blacklist") - - c.logger.Printf("[DEBUG] client: built-in fingerprints: %v", fingerprint.BuiltinFingerprints()) - - var detectedFingerprints []string - var skippedFingerprints []string - for _, name := range fingerprint.BuiltinFingerprints() { - // Skip modules that are not in the whitelist if it is enabled. - if _, ok := whitelist[name]; whitelistEnabled && !ok { - skippedFingerprints = append(skippedFingerprints, name) - continue - } - // Skip modules that are in the blacklist - if _, ok := blacklist[name]; ok { - skippedFingerprints = append(skippedFingerprints, name) - continue - } - - f, err := fingerprint.NewFingerprint(name, c.logger) - if err != nil { - return err - } - - c.configLock.Lock() - request := &cstructs.FingerprintRequest{Config: c.config, Node: c.config.Node} - var response cstructs.FingerprintResponse - err = f.Fingerprint(request, &response) - c.configLock.Unlock() - if err != nil { - return err - } - - // log the fingerprinters which have been applied - if response.Detected { - detectedFingerprints = append(detectedFingerprints, name) - } - - // add the diff found from each fingerprinter - c.updateNodeFromFingerprint(&response) - - p, period := f.Periodic() - if p { - // TODO: If more periodic fingerprinters are added, then - // fingerprintPeriodic should be used to handle all the periodic - // fingerprinters by using a priority queue. - go c.fingerprintPeriodic(name, f, period) - } - } - - c.logger.Printf("[DEBUG] client: detected fingerprints %v", detectedFingerprints) - if len(skippedFingerprints) != 0 { - c.logger.Printf("[DEBUG] client: fingerprint modules skipped due to white/blacklist: %v", skippedFingerprints) - } - return nil -} - -// fingerprintPeriodic runs a fingerprinter at the specified duration. -func (c *Client) fingerprintPeriodic(name string, f fingerprint.Fingerprint, d time.Duration) { - c.logger.Printf("[DEBUG] client: fingerprinting %v every %v", name, d) - for { - select { - case <-time.After(d): - c.configLock.Lock() - request := &cstructs.FingerprintRequest{Config: c.config, Node: c.config.Node} - var response cstructs.FingerprintResponse - err := f.Fingerprint(request, &response) - c.configLock.Unlock() - - if err != nil { - c.logger.Printf("[DEBUG] client: periodic fingerprinting for %v failed: %v", name, err) - } else { - c.updateNodeFromFingerprint(&response) - } - - case <-c.shutdownCh: - return - } - } -} - -// setupDrivers is used to find the available drivers -func (c *Client) setupDrivers() error { - // Build the white/blacklists of drivers. - whitelist := c.config.ReadStringListToMap("driver.whitelist") - whitelistEnabled := len(whitelist) > 0 - blacklist := c.config.ReadStringListToMap("driver.blacklist") - - var detectedDrivers []string - var skippedDrivers []string - driverCtx := driver.NewDriverContext("", "", c.config, c.config.Node, c.logger, nil) - for name := range driver.BuiltinDrivers { - // Skip fingerprinting drivers that are not in the whitelist if it is - // enabled. - if _, ok := whitelist[name]; whitelistEnabled && !ok { - skippedDrivers = append(skippedDrivers, name) - continue - } - // Skip fingerprinting drivers that are in the blacklist - if _, ok := blacklist[name]; ok { - skippedDrivers = append(skippedDrivers, name) - continue - } - - d, err := driver.NewDriver(name, driverCtx) - if err != nil { - return err - } - - c.configLock.Lock() - request := &cstructs.FingerprintRequest{Config: c.config, Node: c.config.Node} - var response cstructs.FingerprintResponse - err = d.Fingerprint(request, &response) - c.configLock.Unlock() - if err != nil { - return err - } - - // log the fingerprinters which have been applied - if response.Detected { - detectedDrivers = append(detectedDrivers, name) - } - - c.updateNodeFromFingerprint(&response) - - p, period := d.Periodic() - if p { - go c.fingerprintPeriodic(name, d, period) - } - - } - - c.logger.Printf("[DEBUG] client: detected drivers %v", detectedDrivers) - if len(skippedDrivers) > 0 { - c.logger.Printf("[DEBUG] client: drivers skipped due to white/blacklist: %v", skippedDrivers) - } - - return nil -} - // updateNodeFromFingerprint updates the node with the result of // fingerprinting the node from the diff that was created -func (c *Client) updateNodeFromFingerprint(response *cstructs.FingerprintResponse) { +func (c *Client) updateNodeFromFingerprint(response *cstructs.FingerprintResponse) *structs.Node { c.configLock.Lock() defer c.configLock.Unlock() for name, val := range response.Attributes { @@ -1093,6 +948,7 @@ func (c *Client) updateNodeFromFingerprint(response *cstructs.FingerprintRespons if response.Resources != nil { c.config.Node.Resources.Merge(response.Resources) } + return c.config.Node } // retryIntv calculates a retry interval value given the base diff --git a/client/client_test.go b/client/client_test.go index 9b16f93e7..da6a14fb7 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -26,6 +26,7 @@ import ( "github.com/hashicorp/nomad/testutil" "github.com/mitchellh/hashstructure" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ctestutil "github.com/hashicorp/nomad/client/testutil" ) @@ -208,17 +209,18 @@ func TestClient_RPC_Passthrough(t *testing.T) { func TestClient_Fingerprint(t *testing.T) { t.Parallel() + require := require.New(t) + + driver.CheckForMockDriver(t) + c := testClient(t, nil) defer c.Shutdown() - // Ensure kernel and arch are always present + // Ensure default values are present node := c.Node() - if node.Attributes["kernel.name"] == "" { - t.Fatalf("missing kernel.name") - } - if node.Attributes["cpu.arch"] == "" { - t.Fatalf("missing cpu arch") - } + require.NotEqual("", node.Attributes["kernel.name"]) + require.NotEqual("", node.Attributes["cpu.arch"]) + require.NotEqual("", node.Attributes["driver.mock_driver"]) } func TestClient_HasNodeChanged(t *testing.T) { @@ -254,9 +256,7 @@ func TestClient_HasNodeChanged(t *testing.T) { } func TestClient_Fingerprint_Periodic(t *testing.T) { - if _, ok := driver.BuiltinDrivers["mock_driver"]; !ok { - t.Skip(`test requires mock_driver; run with "-tags nomad_test"`) - } + driver.CheckForMockDriver(t) t.Parallel() // these constants are only defined when nomad_test is enabled, so these fail @@ -295,168 +295,6 @@ func TestClient_Fingerprint_Periodic(t *testing.T) { }) } -func TestClient_Fingerprint_InWhitelist(t *testing.T) { - t.Parallel() - c := testClient(t, func(c *config.Config) { - if c.Options == nil { - c.Options = make(map[string]string) - } - - // Weird spacing to test trimming. Whitelist all modules expect cpu. - c.Options["fingerprint.whitelist"] = " arch, consul,cpu,env_aws,env_gce,host,memory,network,storage,foo,bar " - }) - defer c.Shutdown() - - node := c.Node() - if node.Attributes["cpu.frequency"] == "" { - t.Fatalf("missing cpu fingerprint module") - } -} - -func TestClient_Fingerprint_InBlacklist(t *testing.T) { - t.Parallel() - c := testClient(t, func(c *config.Config) { - if c.Options == nil { - c.Options = make(map[string]string) - } - - // Weird spacing to test trimming. Blacklist cpu. - c.Options["fingerprint.blacklist"] = " cpu " - }) - defer c.Shutdown() - - node := c.Node() - if node.Attributes["cpu.frequency"] != "" { - t.Fatalf("cpu fingerprint module loaded despite blacklisting") - } -} - -func TestClient_Fingerprint_OutOfWhitelist(t *testing.T) { - t.Parallel() - c := testClient(t, func(c *config.Config) { - if c.Options == nil { - c.Options = make(map[string]string) - } - - c.Options["fingerprint.whitelist"] = "arch,consul,env_aws,env_gce,host,memory,network,storage,foo,bar" - }) - defer c.Shutdown() - - node := c.Node() - if node.Attributes["cpu.frequency"] != "" { - t.Fatalf("found cpu fingerprint module") - } -} - -func TestClient_Fingerprint_WhitelistBlacklistCombination(t *testing.T) { - t.Parallel() - c := testClient(t, func(c *config.Config) { - if c.Options == nil { - c.Options = make(map[string]string) - } - - // With both white- and blacklist, should return the set difference of modules (arch, cpu) - c.Options["fingerprint.whitelist"] = "arch,memory,cpu" - c.Options["fingerprint.blacklist"] = "memory,nomad" - }) - defer c.Shutdown() - - node := c.Node() - // Check expected modules are present - if node.Attributes["cpu.frequency"] == "" { - t.Fatalf("missing cpu fingerprint module") - } - if node.Attributes["cpu.arch"] == "" { - t.Fatalf("missing arch fingerprint module") - } - // Check remainder _not_ present - if node.Attributes["memory.totalbytes"] != "" { - t.Fatalf("found memory fingerprint module") - } - if node.Attributes["nomad.version"] != "" { - t.Fatalf("found nomad fingerprint module") - } -} - -func TestClient_Drivers_InWhitelist(t *testing.T) { - t.Parallel() - c := testClient(t, func(c *config.Config) { - if c.Options == nil { - c.Options = make(map[string]string) - } - - // Weird spacing to test trimming - c.Options["driver.raw_exec.enable"] = "1" - c.Options["driver.whitelist"] = " raw_exec , foo " - }) - defer c.Shutdown() - - node := c.Node() - if node.Attributes["driver.raw_exec"] == "" { - t.Fatalf("missing raw_exec driver") - } -} - -func TestClient_Drivers_InBlacklist(t *testing.T) { - t.Parallel() - c := testClient(t, func(c *config.Config) { - if c.Options == nil { - c.Options = make(map[string]string) - } - - // Weird spacing to test trimming - c.Options["driver.raw_exec.enable"] = "1" - c.Options["driver.blacklist"] = " raw_exec , foo " - }) - defer c.Shutdown() - - node := c.Node() - if node.Attributes["driver.raw_exec"] != "" { - t.Fatalf("raw_exec driver loaded despite blacklist") - } -} - -func TestClient_Drivers_OutOfWhitelist(t *testing.T) { - t.Parallel() - c := testClient(t, func(c *config.Config) { - if c.Options == nil { - c.Options = make(map[string]string) - } - - c.Options["driver.whitelist"] = "foo,bar,baz" - }) - defer c.Shutdown() - - node := c.Node() - if node.Attributes["driver.exec"] != "" { - t.Fatalf("found exec driver") - } -} - -func TestClient_Drivers_WhitelistBlacklistCombination(t *testing.T) { - t.Parallel() - c := testClient(t, func(c *config.Config) { - if c.Options == nil { - c.Options = make(map[string]string) - } - - // Expected output is set difference (raw_exec) - c.Options["driver.whitelist"] = "raw_exec,exec" - c.Options["driver.blacklist"] = "exec" - }) - defer c.Shutdown() - - node := c.Node() - // Check expected present - if node.Attributes["driver.raw_exec"] == "" { - t.Fatalf("missing raw_exec driver") - } - // Check expected absent - if node.Attributes["driver.exec"] != "" { - t.Fatalf("exec driver loaded despite blacklist") - } -} - // TestClient_MixedTLS asserts that when a server is running with TLS enabled // it will reject any RPC connections from clients that lack TLS. See #2525 func TestClient_MixedTLS(t *testing.T) { diff --git a/client/driver/testing.go b/client/driver/testing.go new file mode 100644 index 000000000..11ea3957b --- /dev/null +++ b/client/driver/testing.go @@ -0,0 +1,13 @@ +package driver + +import ( + "testing" +) + +// CheckForMockDriver is a test helper that ensures the mock driver is enabled. +// If not, it skips the current test. +func CheckForMockDriver(t *testing.T) { + if _, ok := BuiltinDrivers["mock_driver"]; !ok { + t.Skip(`test requires mock_driver; run with "-tags nomad_test"`) + } +} diff --git a/client/fingerprint_manager.go b/client/fingerprint_manager.go new file mode 100644 index 000000000..a191555aa --- /dev/null +++ b/client/fingerprint_manager.go @@ -0,0 +1,220 @@ +package client + +import ( + "log" + "sync" + "time" + + "github.com/hashicorp/nomad/client/config" + "github.com/hashicorp/nomad/client/driver" + "github.com/hashicorp/nomad/client/fingerprint" + cstructs "github.com/hashicorp/nomad/client/structs" + "github.com/hashicorp/nomad/nomad/structs" +) + +// FingerprintManager runs a client fingerprinters on a continuous basis, and +// updates the client when the node has changed +type FingerprintManager struct { + getConfig func() *config.Config + node *structs.Node + nodeLock sync.Mutex + shutdownCh chan struct{} + + // updateNode is a callback to the client to update the state of its + // associated node + updateNode func(*cstructs.FingerprintResponse) *structs.Node + logger *log.Logger +} + +// NewFingerprintManager is a constructor that creates and returns an instance +// of FingerprintManager +func NewFingerprintManager(getConfig func() *config.Config, + node *structs.Node, + shutdownCh chan struct{}, + updateNode func(*cstructs.FingerprintResponse) *structs.Node, + logger *log.Logger) *FingerprintManager { + return &FingerprintManager{ + getConfig: getConfig, + updateNode: updateNode, + node: node, + shutdownCh: shutdownCh, + logger: logger, + } +} + +// run runs each fingerprinter individually on an ongoing basis +func (fm *FingerprintManager) run(f fingerprint.Fingerprint, period time.Duration, name string) { + fm.logger.Printf("[DEBUG] client.fingerprint_manager: fingerprinting %s every %v", name, period) + + for { + select { + case <-time.After(period): + _, err := fm.fingerprint(name, f) + if err != nil { + fm.logger.Printf("[DEBUG] client.fingerprint_manager: periodic fingerprinting for %v failed: %+v", name, err) + continue + } + + case <-fm.shutdownCh: + return + } + } +} + +// setupDrivers is used to fingerprint the node to see if these drivers are +// supported +func (fm *FingerprintManager) setupDrivers(drivers []string) error { + var availDrivers []string + driverCtx := driver.NewDriverContext("", "", fm.getConfig(), fm.node, fm.logger, nil) + for _, name := range drivers { + + d, err := driver.NewDriver(name, driverCtx) + if err != nil { + return err + } + + detected, err := fm.fingerprint(name, d) + if err != nil { + fm.logger.Printf("[DEBUG] client.fingerprint_manager: fingerprinting for %v failed: %+v", name, err) + return err + } + + // log the fingerprinters which have been applied + if detected { + availDrivers = append(availDrivers, name) + } + + p, period := d.Periodic() + if p { + go fm.run(d, period, name) + } + } + + fm.logger.Printf("[DEBUG] client.fingerprint_manager: detected drivers %v", availDrivers) + return nil +} + +// fingerprint does an initial fingerprint of the client. If the fingerprinter +// is meant to be run continuously, a process is launched to perform this +// fingerprint on an ongoing basis in the background. +func (fm *FingerprintManager) fingerprint(name string, f fingerprint.Fingerprint) (bool, error) { + request := &cstructs.FingerprintRequest{Config: fm.getConfig(), Node: fm.node} + var response cstructs.FingerprintResponse + if err := f.Fingerprint(request, &response); err != nil { + return false, err + } + + fm.nodeLock.Lock() + if node := fm.updateNode(&response); node != nil { + fm.node = node + } + fm.nodeLock.Unlock() + + return response.Detected, nil +} + +// setupFingerprints is used to fingerprint the node to see if these attributes are +// supported +func (fm *FingerprintManager) setupFingerprinters(fingerprints []string) error { + var appliedFingerprints []string + + for _, name := range fingerprints { + f, err := fingerprint.NewFingerprint(name, fm.logger) + + if err != nil { + fm.logger.Printf("[DEBUG] client.fingerprint_manager: fingerprinting for %v failed: %+v", name, err) + return err + } + + detected, err := fm.fingerprint(name, f) + if err != nil { + return err + } + + // log the fingerprinters which have been applied + if detected { + appliedFingerprints = append(appliedFingerprints, name) + } + + p, period := f.Periodic() + if p { + go fm.run(f, period, name) + } + } + + fm.logger.Printf("[DEBUG] client.fingerprint_manager: detected fingerprints %v", appliedFingerprints) + return nil +} + +// Run starts the process of fingerprinting the node. It does an initial pass, +// identifying whitelisted and blacklisted fingerprints/drivers. Then, for +// those which require periotic checking, it starts a periodic process for +// each. +func (fp *FingerprintManager) Run() error { + // first, set up all fingerprints + cfg := fp.getConfig() + whitelistFingerprints := cfg.ReadStringListToMap("fingerprint.whitelist") + whitelistFingerprintsEnabled := len(whitelistFingerprints) > 0 + blacklistFingerprints := cfg.ReadStringListToMap("fingerprint.blacklist") + + fp.logger.Printf("[DEBUG] client.fingerprint_manager: built-in fingerprints: %v", fingerprint.BuiltinFingerprints()) + + var availableFingerprints []string + var skippedFingerprints []string + for _, name := range fingerprint.BuiltinFingerprints() { + // Skip modules that are not in the whitelist if it is enabled. + if _, ok := whitelistFingerprints[name]; whitelistFingerprintsEnabled && !ok { + skippedFingerprints = append(skippedFingerprints, name) + continue + } + // Skip modules that are in the blacklist + if _, ok := blacklistFingerprints[name]; ok { + skippedFingerprints = append(skippedFingerprints, name) + continue + } + + availableFingerprints = append(availableFingerprints, name) + } + + if err := fp.setupFingerprinters(availableFingerprints); err != nil { + return err + } + + if len(skippedFingerprints) != 0 { + fp.logger.Printf("[DEBUG] client.fingerprint_manager: fingerprint modules skipped due to white/blacklist: %v", skippedFingerprints) + } + + // next, set up drivers + // Build the white/blacklists of drivers. + whitelistDrivers := cfg.ReadStringListToMap("driver.whitelist") + whitelistDriversEnabled := len(whitelistDrivers) > 0 + blacklistDrivers := cfg.ReadStringListToMap("driver.blacklist") + + var availDrivers []string + var skippedDrivers []string + + for name := range driver.BuiltinDrivers { + // Skip fingerprinting drivers that are not in the whitelist if it is + // enabled. + if _, ok := whitelistDrivers[name]; whitelistDriversEnabled && !ok { + skippedDrivers = append(skippedDrivers, name) + continue + } + // Skip fingerprinting drivers that are in the blacklist + if _, ok := blacklistDrivers[name]; ok { + skippedDrivers = append(skippedDrivers, name) + continue + } + + availDrivers = append(availDrivers, name) + } + + if err := fp.setupDrivers(availDrivers); err != nil { + return err + } + + if len(skippedDrivers) > 0 { + fp.logger.Printf("[DEBUG] client.fingerprint_manager: drivers skipped due to white/blacklist: %v", skippedDrivers) + } + return nil +} diff --git a/client/fingerprint_manager_test.go b/client/fingerprint_manager_test.go new file mode 100644 index 000000000..fe37966e3 --- /dev/null +++ b/client/fingerprint_manager_test.go @@ -0,0 +1,427 @@ +package client + +import ( + "fmt" + "testing" + + "github.com/hashicorp/nomad/client/config" + "github.com/hashicorp/nomad/client/driver" + cstructs "github.com/hashicorp/nomad/client/structs" + "github.com/hashicorp/nomad/nomad/structs" + "github.com/hashicorp/nomad/testutil" + "github.com/stretchr/testify/require" +) + +func TestFingerprintManager_Run_MockDriver(t *testing.T) { + driver.CheckForMockDriver(t) + t.Parallel() + require := require.New(t) + + node := &structs.Node{ + Attributes: make(map[string]string, 0), + } + + updateNode := func(r *cstructs.FingerprintResponse) *structs.Node { + for k, v := range r.Attributes { + node.Attributes[k] = v + } + return node + } + conf := config.DefaultConfig() + getConfig := func() *config.Config { + return conf + } + + fm := NewFingerprintManager( + getConfig, + node, + make(chan struct{}), + updateNode, + testLogger(), + ) + + err := fm.Run() + require.Nil(err) + require.NotEqual("", node.Attributes["driver.mock_driver"]) +} + +func TestFingerprintManager_Fingerprint_Run(t *testing.T) { + t.Parallel() + require := require.New(t) + + node := &structs.Node{ + Attributes: make(map[string]string, 0), + } + + conf := config.DefaultConfig() + conf.Options = map[string]string{"driver.raw_exec.enable": "true"} + getConfig := func() *config.Config { + return conf + } + + updateNode := func(r *cstructs.FingerprintResponse) *structs.Node { + for k, v := range r.Attributes { + node.Attributes[k] = v + } + return node + } + + fm := NewFingerprintManager( + getConfig, + node, + make(chan struct{}), + updateNode, + testLogger(), + ) + + err := fm.Run() + require.Nil(err) + + require.NotEqual("", node.Attributes["driver.raw_exec"]) +} + +func TestFingerprintManager_Fingerprint_Periodic(t *testing.T) { + t.Parallel() + require := require.New(t) + + node := &structs.Node{ + Attributes: make(map[string]string, 0), + } + updateNode := func(r *cstructs.FingerprintResponse) *structs.Node { + for k, v := range r.Attributes { + node.Attributes[k] = v + } + return node + } + conf := config.DefaultConfig() + conf.Options = map[string]string{ + "test.shutdown_periodic_after": "true", + "test.shutdown_periodic_duration": "3", + } + getConfig := func() *config.Config { + return conf + } + + shutdownCh := make(chan struct{}) + defer (func() { + close(shutdownCh) + })() + + fm := NewFingerprintManager( + getConfig, + node, + shutdownCh, + updateNode, + testLogger(), + ) + + err := fm.Run() + require.Nil(err) + + // Ensure the mock driver is registered on the client + testutil.WaitForResult(func() (bool, error) { + mockDriverStatus := node.Attributes["driver.mock_driver"] + if mockDriverStatus == "" { + return false, fmt.Errorf("mock driver attribute should be set on the client") + } + return true, nil + }, func(err error) { + t.Fatalf("err: %v", err) + }) + + // Ensure that the client fingerprinter eventually removes this attribute + testutil.WaitForResult(func() (bool, error) { + mockDriverStatus := node.Attributes["driver.mock_driver"] + if mockDriverStatus != "" { + return false, fmt.Errorf("mock driver attribute should not be set on the client") + } + return true, nil + }, func(err error) { + t.Fatalf("err: %v", err) + }) +} + +func TestFimgerprintManager_Run_InWhitelist(t *testing.T) { + t.Parallel() + require := require.New(t) + + node := &structs.Node{ + Attributes: make(map[string]string, 0), + } + updateNode := func(r *cstructs.FingerprintResponse) *structs.Node { + for k, v := range r.Attributes { + node.Attributes[k] = v + } + return node + } + conf := config.DefaultConfig() + conf.Options = map[string]string{"fingerprint.whitelist": " arch,cpu,memory,network,storage,foo,bar "} + getConfig := func() *config.Config { + return conf + } + + shutdownCh := make(chan struct{}) + defer (func() { + close(shutdownCh) + })() + + fm := NewFingerprintManager( + getConfig, + node, + shutdownCh, + updateNode, + testLogger(), + ) + + err := fm.Run() + require.Nil(err) + require.NotEqual(node.Attributes["cpu.frequency"], "") +} + +func TestFimgerprintManager_Run_InBlacklist(t *testing.T) { + t.Parallel() + require := require.New(t) + + node := &structs.Node{ + Attributes: make(map[string]string, 0), + } + updateNode := func(r *cstructs.FingerprintResponse) *structs.Node { + for k, v := range r.Attributes { + node.Attributes[k] = v + } + return node + } + conf := config.DefaultConfig() + conf.Options = map[string]string{"fingerprint.whitelist": " arch,memory,foo,bar "} + conf.Options = map[string]string{"fingerprint.blacklist": " cpu "} + getConfig := func() *config.Config { + return conf + } + + shutdownCh := make(chan struct{}) + defer (func() { + close(shutdownCh) + })() + + fm := NewFingerprintManager( + getConfig, + node, + shutdownCh, + updateNode, + testLogger(), + ) + + err := fm.Run() + require.Nil(err) + require.Equal(node.Attributes["cpu.frequency"], "") + require.NotEqual(node.Attributes["memory.totalbytes"], "") +} + +func TestFimgerprintManager_Run_Combination(t *testing.T) { + t.Parallel() + require := require.New(t) + + node := &structs.Node{ + Attributes: make(map[string]string, 0), + } + updateNode := func(r *cstructs.FingerprintResponse) *structs.Node { + for k, v := range r.Attributes { + node.Attributes[k] = v + } + return node + } + conf := config.DefaultConfig() + conf.Options = map[string]string{"fingerprint.whitelist": " arch,cpu,memory,foo,bar "} + conf.Options = map[string]string{"fingerprint.blacklist": " memory,nomad "} + getConfig := func() *config.Config { + return conf + } + + shutdownCh := make(chan struct{}) + defer (func() { + close(shutdownCh) + })() + + fm := NewFingerprintManager( + getConfig, + node, + shutdownCh, + updateNode, + testLogger(), + ) + + err := fm.Run() + require.Nil(err) + require.NotEqual(node.Attributes["cpu.frequency"], "") + require.NotEqual(node.Attributes["cpu.arch"], "") + require.Equal(node.Attributes["memory.totalbytes"], "") + require.Equal(node.Attributes["nomad.version"], "") +} + +func TestFimgerprintManager_Run_WhitelistDrivers(t *testing.T) { + t.Parallel() + require := require.New(t) + + node := &structs.Node{ + Attributes: make(map[string]string, 0), + } + updateNode := func(r *cstructs.FingerprintResponse) *structs.Node { + for k, v := range r.Attributes { + node.Attributes[k] = v + } + return node + } + conf := config.DefaultConfig() + conf.Options = map[string]string{ + "driver.raw_exec.enable": "1", + "driver.whitelist": " raw_exec , foo ", + } + getConfig := func() *config.Config { + return conf + } + + shutdownCh := make(chan struct{}) + defer (func() { + close(shutdownCh) + })() + + fm := NewFingerprintManager( + getConfig, + node, + shutdownCh, + updateNode, + testLogger(), + ) + + err := fm.Run() + require.Nil(err) + require.NotEqual(node.Attributes["driver.raw_exec"], "") +} + +func TestFimgerprintManager_Run_AllDriversBlacklisted(t *testing.T) { + t.Parallel() + require := require.New(t) + + node := &structs.Node{ + Attributes: make(map[string]string, 0), + } + updateNode := func(r *cstructs.FingerprintResponse) *structs.Node { + for k, v := range r.Attributes { + node.Attributes[k] = v + } + return node + } + conf := config.DefaultConfig() + conf.Options = map[string]string{ + "driver.whitelist": " foo,bar,baz ", + } + getConfig := func() *config.Config { + return conf + } + + shutdownCh := make(chan struct{}) + defer (func() { + close(shutdownCh) + })() + + fm := NewFingerprintManager( + getConfig, + node, + shutdownCh, + updateNode, + testLogger(), + ) + + err := fm.Run() + require.Nil(err) + require.Equal(node.Attributes["driver.raw_exec"], "") + require.Equal(node.Attributes["driver.exec"], "") + require.Equal(node.Attributes["driver.docker"], "") +} + +func TestFimgerprintManager_Run_DriversWhiteListBlacklistCombination(t *testing.T) { + t.Parallel() + require := require.New(t) + + node := &structs.Node{ + Attributes: make(map[string]string, 0), + } + updateNode := func(r *cstructs.FingerprintResponse) *structs.Node { + for k, v := range r.Attributes { + node.Attributes[k] = v + } + return node + } + conf := config.DefaultConfig() + conf.Options = map[string]string{ + "driver.raw_exec.enable": "1", + "driver.whitelist": " raw_exec,exec,foo,bar,baz ", + "driver.blacklist": " exec,foo,bar,baz ", + } + getConfig := func() *config.Config { + return conf + } + + shutdownCh := make(chan struct{}) + defer (func() { + close(shutdownCh) + })() + + fm := NewFingerprintManager( + getConfig, + node, + shutdownCh, + updateNode, + testLogger(), + ) + + err := fm.Run() + require.Nil(err) + require.NotEqual(node.Attributes["driver.raw_exec"], "") + require.Equal(node.Attributes["driver.exec"], "") + require.Equal(node.Attributes["foo"], "") + require.Equal(node.Attributes["bar"], "") + require.Equal(node.Attributes["baz"], "") +} + +func TestFimgerprintManager_Run_DriversInBlacklist(t *testing.T) { + t.Parallel() + require := require.New(t) + + node := &structs.Node{ + Attributes: make(map[string]string, 0), + } + updateNode := func(r *cstructs.FingerprintResponse) *structs.Node { + for k, v := range r.Attributes { + node.Attributes[k] = v + } + return node + } + conf := config.DefaultConfig() + conf.Options = map[string]string{ + "driver.raw_exec.enable": "1", + "driver.whitelist": " raw_exec,foo,bar,baz ", + "driver.blacklist": " exec,foo,bar,baz ", + } + getConfig := func() *config.Config { + return conf + } + + shutdownCh := make(chan struct{}) + defer (func() { + close(shutdownCh) + })() + + fm := NewFingerprintManager( + getConfig, + node, + shutdownCh, + updateNode, + testLogger(), + ) + + err := fm.Run() + require.Nil(err) + require.NotEqual(node.Attributes["driver.raw_exec"], "") + require.Equal(node.Attributes["driver.exec"], "") +}