mirror of
https://github.com/kemko/nomad.git
synced 2026-01-06 10:25:42 +03:00
add fingerprint manager
This commit is contained in:
116
client/client.go
116
client/client.go
@@ -230,13 +230,24 @@ func NewClient(cfg *config.Config, consulCatalog consul.CatalogAPI, consulServic
|
||||
return nil, fmt.Errorf("node setup failed: %v", err)
|
||||
}
|
||||
|
||||
fingerprintManager := &FingerprintManager{
|
||||
getConfig: func() *config.Config {
|
||||
c.configLock.Lock()
|
||||
defer c.configLock.Unlock()
|
||||
return c.config
|
||||
},
|
||||
node: c.config.Node,
|
||||
client: c,
|
||||
logger: c.logger,
|
||||
}
|
||||
|
||||
// Fingerprint the node
|
||||
if err := c.fingerprint(); err != nil {
|
||||
if err := c.fingerprint(fingerprintManager); err != nil {
|
||||
return nil, fmt.Errorf("fingerprinting failed: %v", err)
|
||||
}
|
||||
|
||||
// Scan for drivers
|
||||
if err := c.setupDrivers(); err != nil {
|
||||
if err := c.setupDrivers(fingerprintManager); err != nil {
|
||||
return nil, fmt.Errorf("driver setup failed: %v", err)
|
||||
}
|
||||
|
||||
@@ -925,14 +936,14 @@ func (c *Client) reservePorts() {
|
||||
}
|
||||
|
||||
// fingerprint is used to fingerprint the client and setup the node
|
||||
func (c *Client) fingerprint() error {
|
||||
func (c *Client) fingerprint(fingerprintManager *FingerprintManager) 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 availableFingerprints []string
|
||||
var skippedFingerprints []string
|
||||
for _, name := range fingerprint.BuiltinFingerprints() {
|
||||
// Skip modules that are not in the whitelist if it is enabled.
|
||||
@@ -946,78 +957,29 @@ func (c *Client) fingerprint() error {
|
||||
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)
|
||||
}
|
||||
availableFingerprints = append(availableFingerprints, name)
|
||||
}
|
||||
|
||||
if err := fingerprintManager.SetupFingerprints(availableFingerprints); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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 {
|
||||
func (c *Client) setupDrivers(fingerprintManager *FingerprintManager) 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 availDrivers []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.
|
||||
@@ -1031,35 +993,13 @@ func (c *Client) setupDrivers() error {
|
||||
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)
|
||||
}
|
||||
|
||||
availDrivers = append(availDrivers, name)
|
||||
}
|
||||
|
||||
if err := fingerprintManager.SetupDrivers(availDrivers); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
122
client/fingerprint_manager.go
Normal file
122
client/fingerprint_manager.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"log"
|
||||
"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
|
||||
client *Client
|
||||
logger *log.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] 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] fingerprint_manager: periodic fingerprinting for %v failed: %+v", name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
case <-fm.client.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] 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] fingerprint_manager: available 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 ro 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
|
||||
err := f.Fingerprint(request, &response)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
fm.client.updateNodeFromFingerprint(&response)
|
||||
return response.Detected, nil
|
||||
}
|
||||
|
||||
// setupDrivers is used to fingerprint the node to see if these attributes are
|
||||
// supported
|
||||
func (fm *FingerprintManager) SetupFingerprints(fingerprints []string) error {
|
||||
var appliedFingerprints []string
|
||||
|
||||
for _, name := range fingerprints {
|
||||
f, err := fingerprint.NewFingerprint(name, fm.logger)
|
||||
|
||||
if err != nil {
|
||||
fm.logger.Printf("[DEBUG] 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] fingerprint_manager: applied fingerprints %v", appliedFingerprints)
|
||||
return nil
|
||||
}
|
||||
139
client/fingerprint_manager_test.go
Normal file
139
client/fingerprint_manager_test.go
Normal file
@@ -0,0 +1,139 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/nomad/client/config"
|
||||
"github.com/hashicorp/nomad/client/driver"
|
||||
"github.com/hashicorp/nomad/nomad/structs"
|
||||
"github.com/hashicorp/nomad/testutil"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// test that the driver manager updates a node when its attributes change
|
||||
func TestFingerprintManager_Fingerprint_MockDriver(t *testing.T) {
|
||||
if _, ok := driver.BuiltinDrivers["mock_driver"]; !ok {
|
||||
t.Skip(`test requires mock_driver; run with "-tags nomad_test"`)
|
||||
}
|
||||
t.Parallel()
|
||||
require := require.New(t)
|
||||
|
||||
node := &structs.Node{
|
||||
Attributes: make(map[string]string, 0),
|
||||
}
|
||||
mockConfig := &config.Config{
|
||||
Node: node,
|
||||
}
|
||||
c := &Client{
|
||||
config: mockConfig,
|
||||
}
|
||||
getConfig := func() *config.Config {
|
||||
return mockConfig
|
||||
}
|
||||
|
||||
fm := FingerprintManager{
|
||||
getConfig: getConfig,
|
||||
node: node,
|
||||
client: c,
|
||||
logger: testLogger(),
|
||||
}
|
||||
|
||||
// test setting up a mock driver
|
||||
drivers := []string{"mock_driver"}
|
||||
err := fm.SetupDrivers(drivers)
|
||||
require.Nil(err)
|
||||
|
||||
require.NotEqual("", node.Attributes["driver.mock_driver"])
|
||||
}
|
||||
|
||||
func TestFingerprintManager_Fingerprint_RawExec(t *testing.T) {
|
||||
t.Parallel()
|
||||
require := require.New(t)
|
||||
|
||||
node := &structs.Node{
|
||||
Attributes: make(map[string]string, 0),
|
||||
}
|
||||
mockConfig := &config.Config{
|
||||
Node: node,
|
||||
Options: map[string]string{
|
||||
"driver.raw_exec.enable": "true",
|
||||
},
|
||||
}
|
||||
c := &Client{
|
||||
config: mockConfig,
|
||||
}
|
||||
getConfig := func() *config.Config {
|
||||
return mockConfig
|
||||
}
|
||||
|
||||
fm := FingerprintManager{
|
||||
getConfig: getConfig,
|
||||
node: node,
|
||||
client: c,
|
||||
logger: testLogger(),
|
||||
}
|
||||
|
||||
// test setting up a mock driver
|
||||
drivers := []string{"raw_exec"}
|
||||
err := fm.SetupDrivers(drivers)
|
||||
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),
|
||||
}
|
||||
mockConfig := &config.Config{
|
||||
Node: node,
|
||||
Options: map[string]string{
|
||||
"test.shutdown_periodic_after": "true",
|
||||
"test.shutdown_periodic_duration": "3",
|
||||
},
|
||||
}
|
||||
c := &Client{
|
||||
config: mockConfig,
|
||||
}
|
||||
getConfig := func() *config.Config {
|
||||
return mockConfig
|
||||
}
|
||||
|
||||
fm := FingerprintManager{
|
||||
getConfig: getConfig,
|
||||
node: node,
|
||||
client: c,
|
||||
logger: testLogger(),
|
||||
}
|
||||
|
||||
// test setting up a mock driver
|
||||
drivers := []string{"mock_driver"}
|
||||
err := fm.SetupDrivers(drivers)
|
||||
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)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user