mirror of
https://github.com/kemko/nomad.git
synced 2026-01-01 16:05:42 +03:00
Merge pull request #3807 from hashicorp/f-client-add-fingerprint-manager
Add fingerprint manager to manage fingerprinting node
This commit is contained in:
164
client/client.go
164
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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
13
client/driver/testing.go
Normal file
13
client/driver/testing.go
Normal file
@@ -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"`)
|
||||
}
|
||||
}
|
||||
220
client/fingerprint_manager.go
Normal file
220
client/fingerprint_manager.go
Normal file
@@ -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
|
||||
}
|
||||
427
client/fingerprint_manager_test.go
Normal file
427
client/fingerprint_manager_test.go
Normal file
@@ -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"], "")
|
||||
}
|
||||
Reference in New Issue
Block a user