open-nomad/client/fingerprint_manager.go

208 lines
6.2 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package client
import (
"sync"
"time"
log "github.com/hashicorp/go-hclog"
"github.com/hashicorp/nomad/client/config"
"github.com/hashicorp/nomad/client/fingerprint"
"github.com/hashicorp/nomad/helper/pluginutils/loader"
"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 {
singletonLoader loader.PluginCatalog
getConfig func() *config.Config
node *structs.Node
nodeLock sync.Mutex
shutdownCh chan struct{}
// updateNodeAttributes is a callback to the client to update the state of its
// associated node
updateNodeAttributes func(*fingerprint.FingerprintResponse) *structs.Node
reloadableFps map[string]fingerprint.ReloadableFingerprint
logger log.Logger
}
// NewFingerprintManager is a constructor that creates and returns an instance
// of FingerprintManager
func NewFingerprintManager(
singletonLoader loader.PluginCatalog,
getConfig func() *config.Config,
node *structs.Node,
shutdownCh chan struct{},
updateNodeAttributes func(*fingerprint.FingerprintResponse) *structs.Node,
logger log.Logger) *FingerprintManager {
return &FingerprintManager{
singletonLoader: singletonLoader,
getConfig: getConfig,
updateNodeAttributes: updateNodeAttributes,
node: node,
shutdownCh: shutdownCh,
logger: logger.Named("fingerprint_mgr"),
reloadableFps: make(map[string]fingerprint.ReloadableFingerprint),
}
}
// setNode updates the current client node
func (fm *FingerprintManager) setNode(node *structs.Node) {
fm.nodeLock.Lock()
defer fm.nodeLock.Unlock()
fm.node = node
}
// getNode returns the current client node
func (fm *FingerprintManager) getNode() *structs.Node {
fm.nodeLock.Lock()
defer fm.nodeLock.Unlock()
return fm.node
}
// Run starts the process of fingerprinting the node. It does an initial pass,
// identifying allowlisted and denylisted fingerprints/drivers. Then, for
// those which require periotic checking, it starts a periodic process for
// each.
func (fm *FingerprintManager) Run() error {
// First, set up all fingerprints
cfg := fm.getConfig()
// COMPAT(1.0) using inclusive language, whitelist is kept for backward compatibility.
allowlistFingerprints := cfg.ReadStringListToMap("fingerprint.allowlist", "fingerprint.whitelist")
allowlistFingerprintsEnabled := len(allowlistFingerprints) > 0
// COMPAT(1.0) using inclusive language, blacklist is kept for backward compatibility.
denylistFingerprints := cfg.ReadStringListToMap("fingerprint.denylist", "fingerprint.blacklist")
fm.logger.Debug("built-in fingerprints", "fingerprinters", fingerprint.BuiltinFingerprints())
var availableFingerprints []string
var skippedFingerprints []string
for _, name := range fingerprint.BuiltinFingerprints() {
// Skip modules that are not in the allowlist if it is enabled.
if _, ok := allowlistFingerprints[name]; allowlistFingerprintsEnabled && !ok {
skippedFingerprints = append(skippedFingerprints, name)
continue
}
// Skip modules that are in the denylist
if _, ok := denylistFingerprints[name]; ok {
skippedFingerprints = append(skippedFingerprints, name)
continue
}
availableFingerprints = append(availableFingerprints, name)
}
if err := fm.setupFingerprinters(availableFingerprints); err != nil {
return err
}
if len(skippedFingerprints) != 0 {
fm.logger.Debug("fingerprint modules skipped due to allow/denylist",
"skipped_fingerprinters", skippedFingerprints)
}
return nil
}
// Reload will reload any registered ReloadableFingerprinters and immediately call Fingerprint
func (fm *FingerprintManager) Reload() {
for name, fp := range fm.reloadableFps {
fm.logger.Info("reloading fingerprinter", "fingerprinter", name)
fp.Reload()
if _, err := fm.fingerprint(name, fp); err != nil {
fm.logger.Warn("error fingerprinting after reload", "fingerprinter", name, "error", err)
}
}
}
// 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.Error("error fingerprinting", "error", err, "fingerprinter", name)
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, _ := f.Periodic()
if p {
go fm.runFingerprint(f, name)
}
if rfp, ok := f.(fingerprint.ReloadableFingerprint); ok {
fm.reloadableFps[name] = rfp
}
}
fm.logger.Debug("detected fingerprints", "node_attrs", appliedFingerprints)
return nil
}
// runFingerprint runs each fingerprinter individually on an ongoing basis
func (fm *FingerprintManager) runFingerprint(f fingerprint.Fingerprint, name string) {
_, period := f.Periodic()
fm.logger.Debug("fingerprinting periodically", "fingerprinter", name, "initial_period", period)
timer := time.NewTimer(period)
defer timer.Stop()
for {
select {
case <-timer.C:
_, err := fm.fingerprint(name, f)
if err != nil {
fm.logger.Debug("error periodic fingerprinting", "error", err, "fingerprinter", name)
continue
}
_, period = f.Periodic()
timer.Reset(period)
case <-fm.shutdownCh:
return
}
}
}
// 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) {
var response fingerprint.FingerprintResponse
fm.nodeLock.Lock()
request := &fingerprint.FingerprintRequest{Config: fm.getConfig(), Node: fm.node}
err := f.Fingerprint(request, &response)
fm.nodeLock.Unlock()
if err != nil {
return false, err
}
if node := fm.updateNodeAttributes(&response); node != nil {
fm.setNode(node)
}
return response.Detected, nil
}