add fingerprint manager

This commit is contained in:
Chelsea Holland Komlo
2018-01-24 08:01:37 -05:00
parent 7b9cf12244
commit cc1213a8bb
3 changed files with 289 additions and 88 deletions

View File

@@ -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)
}

View 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
}

View 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)
})
}