Skip to content

Commit

Permalink
fingerprint: add fingerprinting for CNI plugins presense and version
Browse files Browse the repository at this point in the history
This PR adds a fingerprinter to set the attributes
 "cni.plugins" -> "true" or "false" if <cni_path> contains CNI plugins
 "cni.plugins.version" -> version of the CNI plugins if present
  • Loading branch information
shoenig committed Dec 1, 2022
1 parent b23f435 commit edda990
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 14 deletions.
8 changes: 5 additions & 3 deletions client/fingerprint/cni.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@ import (
"strings"

"github.com/containernetworking/cni/libcni"
log "github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/nomad/nomad/structs"
)

// CNIFingerprint creates a fingerprint of the CNI configuration(s) on the
// Nomad client.
type CNIFingerprint struct {
StaticFingerprinter
logger log.Logger
logger hclog.Logger
}

func NewCNIFingerprint(logger log.Logger) Fingerprint {
func NewCNIFingerprint(logger hclog.Logger) Fingerprint {
return &CNIFingerprint{logger: logger}
}

Expand Down
70 changes: 70 additions & 0 deletions client/fingerprint/cni_plugins.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package fingerprint

import (
"context"
"os"
"os/exec"
"path/filepath"
"strings"
"time"

"github.com/hashicorp/go-hclog"
)

const (
defaultCNIPath = "/opt/cni/bin"
)

// CNIPluginsFingerprint creates a fingerprint of the CNI plugins present on the
// CNI plugin path specified for the Nomad client.
type CNIPluginsFingerprint struct {
StaticFingerprinter
logger hclog.Logger
lister func(string) ([]os.DirEntry, error)
}

func NewCNIPluginsFingerprint(logger hclog.Logger) Fingerprint {
return &CNIPluginsFingerprint{
logger: logger.Named("cni_plugins"),
lister: os.ReadDir,
}
}

func (f *CNIPluginsFingerprint) Fingerprint(req *FingerprintRequest, resp *FingerprintResponse) error {
cniPath := req.Config.CNIPath
if cniPath == "" {
cniPath = defaultCNIPath
}

entries, err := f.lister(cniPath)
switch {
case err != nil:
f.logger.Debug("failed to read CNI plugins directory", "cni_path", cniPath, "error", err)
resp.Detected = true
resp.AddAttribute("cni.plugins", "false")
return nil
case len(entries) == 0:
f.logger.Debug("no CNI plugins found", "cni_path", cniPath)
resp.Detected = true
resp.AddAttribute("cni.plugins", "false")
return nil
}

bridgePath := filepath.Join(cniPath, "bridge")
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, bridgePath, "--version")
output, err := cmd.CombinedOutput()
if err != nil {
f.logger.Debug("failed to detect CNI plugins version", "error", err)
resp.AddAttribute("cni.plugins", "false")
return nil
}
tokens := strings.Fields(string(output))
version := tokens[len(tokens)-1]
f.logger.Debug("detected CNI plugins", "version", version)
resp.AddAttribute("cni.plugins", "true")
resp.AddAttribute("cni.plugins.version", version)
resp.Detected = true
return nil
}
64 changes: 64 additions & 0 deletions client/fingerprint/cni_plugins_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package fingerprint

import (
"os"
"testing"

"github.com/hashicorp/nomad/ci"
"github.com/hashicorp/nomad/client/config"
"github.com/hashicorp/nomad/helper/testlog"
"github.com/shoenig/test/must"
)

func TestCNIPluginsFingerprint_Fingerprint_default(t *testing.T) {
ci.Parallel(t)

f := NewCNIPluginsFingerprint(testlog.HCLogger(t))
request := &FingerprintRequest{
Config: new(config.Config),
}
response := new(FingerprintResponse)

err := f.Fingerprint(request, response)
must.NoError(t, err)
must.True(t, response.Detected)
must.Eq(t, "true", response.Attributes["cni.plugins"])
must.StrHasPrefix(t, "v1", response.Attributes["cni.plugins.version"])
}

func TestCNIPluginsFingerprint_Fingerprint_absent(t *testing.T) {
ci.Parallel(t)

f := NewCNIPluginsFingerprint(testlog.HCLogger(t))
request := &FingerprintRequest{
Config: &config.Config{
CNIPath: "/does/not/exist",
},
}
response := new(FingerprintResponse)

err := f.Fingerprint(request, response)
must.False(t, response.Detected)
must.NoError(t, err)
must.Eq(t, "false", response.Attributes["cni.plugins"])
}

func TestCNIPluginsFingerprint_Fingerprint_empty(t *testing.T) {
ci.Parallel(t)

lister := func(string) ([]os.DirEntry, error) {
return nil, nil
}

f := NewCNIPluginsFingerprint(testlog.HCLogger(t))
f.(*CNIPluginsFingerprint).lister = lister
request := &FingerprintRequest{
Config: new(config.Config),
}
response := new(FingerprintResponse)

err := f.Fingerprint(request, response)
must.NoError(t, err)
must.True(t, response.Detected)
must.Eq(t, "false", response.Attributes["cni.plugins"])
}
23 changes: 12 additions & 11 deletions client/fingerprint/fingerprint.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,18 @@ var (
// hostFingerprinters contains the host fingerprints which are available for a
// given platform.
hostFingerprinters = map[string]Factory{
"arch": NewArchFingerprint,
"consul": NewConsulFingerprint,
"cni": NewCNIFingerprint,
"cpu": NewCPUFingerprint,
"host": NewHostFingerprint,
"memory": NewMemoryFingerprint,
"network": NewNetworkFingerprint,
"nomad": NewNomadFingerprint,
"signal": NewSignalFingerprint,
"storage": NewStorageFingerprint,
"vault": NewVaultFingerprint,
"arch": NewArchFingerprint,
"consul": NewConsulFingerprint,
"cni": NewCNIFingerprint,
"cni_plugins": NewCNIPluginsFingerprint,
"cpu": NewCPUFingerprint,
"host": NewHostFingerprint,
"memory": NewMemoryFingerprint,
"network": NewNetworkFingerprint,
"nomad": NewNomadFingerprint,
"signal": NewSignalFingerprint,
"storage": NewStorageFingerprint,
"vault": NewVaultFingerprint,
}

// envFingerprinters contains the fingerprints that are environment specific.
Expand Down

0 comments on commit edda990

Please sign in to comment.