Skip to content

Commit

Permalink
Merge pull request #712 from iverberk/f-cgroup-finterprinter
Browse files Browse the repository at this point in the history
Add periodic cgroup fingerprinter
  • Loading branch information
dadgar committed Jan 29, 2016
2 parents a480c33 + 0289252 commit df22fca
Show file tree
Hide file tree
Showing 7 changed files with 234 additions and 8 deletions.
14 changes: 8 additions & 6 deletions client/driver/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,13 @@ import (
"fmt"
"log"
"path/filepath"
"runtime"
"syscall"
"time"

"github.com/hashicorp/nomad/client/allocdir"
"github.com/hashicorp/nomad/client/config"
"github.com/hashicorp/nomad/client/driver/executor"
cstructs "github.com/hashicorp/nomad/client/driver/structs"
"github.com/hashicorp/nomad/client/fingerprint"
"github.com/hashicorp/nomad/client/getter"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/mitchellh/mapstructure"
Expand All @@ -23,8 +21,8 @@ import (
// features.
type ExecDriver struct {
DriverContext
fingerprint.StaticFingerprinter
}

type ExecDriverConfig struct {
ArtifactSource string `mapstructure:"artifact_source"`
Checksum string `mapstructure:"checksum"`
Expand All @@ -47,9 +45,9 @@ func NewExecDriver(ctx *DriverContext) Driver {
}

func (d *ExecDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) {
// Only enable if we are root on linux.
if runtime.GOOS != "linux" {
d.logger.Printf("[DEBUG] driver.exec: only available on linux, disabling")
// Only enable if cgroups are available and we are root
if _, ok := node.Attributes["unique.cgroup.mountpoint"]; !ok {
d.logger.Printf("[DEBUG] driver.exec: cgroups unavailable, disabling")
return false, nil
} else if syscall.Geteuid() != 0 {
d.logger.Printf("[DEBUG] driver.exec: must run as root user, disabling")
Expand All @@ -60,6 +58,10 @@ func (d *ExecDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool,
return true, nil
}

func (d *ExecDriver) Periodic() (bool, time.Duration) {
return true, 15 * time.Second
}

func (d *ExecDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) {
var driverConfig ExecDriverConfig
if err := mapstructure.WeakDecode(task.Config, &driverConfig); err != nil {
Expand Down
4 changes: 3 additions & 1 deletion client/driver/exec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ func TestExecDriver_Fingerprint(t *testing.T) {
driverCtx, _ := testDriverContexts(&structs.Task{Name: "foo"})
d := NewExecDriver(driverCtx)
node := &structs.Node{
Attributes: make(map[string]string),
Attributes: map[string]string{
"unique.cgroup.mountpoint": "/sys/fs/cgroup",
},
}
apply, err := d.Fingerprint(&config.Config{}, node)
if err != nil {
Expand Down
88 changes: 88 additions & 0 deletions client/fingerprint/cgroup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package fingerprint

import (
"fmt"
"log"
"time"

client "github.com/hashicorp/nomad/client/config"
"github.com/hashicorp/nomad/nomad/structs"
)

const (
cgroupAvailable = "available"
cgroupUnavailable = "unavailable"
interval = 15
)

type CGroupFingerprint struct {
logger *log.Logger
lastState string
mountPointDetector MountPointDetector
}

// An interface to isolate calls to the cgroup library
// This facilitates testing where we can implement
// fake mount points to test various code paths
type MountPointDetector interface {
MountPoint() (string, error)
}

// Implements the interface detector which calls the cgroups library directly
type DefaultMountPointDetector struct {
}

// Call out to the default cgroup library
func (b *DefaultMountPointDetector) MountPoint() (string, error) {
return FindCgroupMountpointDir()
}

// NewCGroupFingerprint returns a new cgroup fingerprinter
func NewCGroupFingerprint(logger *log.Logger) Fingerprint {
f := &CGroupFingerprint{
logger: logger,
lastState: cgroupUnavailable,
mountPointDetector: &DefaultMountPointDetector{},
}
return f
}

// Fingerprint tries to find a valid cgroup moint point
func (f *CGroupFingerprint) Fingerprint(cfg *client.Config, node *structs.Node) (bool, error) {
mount, err := f.mountPointDetector.MountPoint()
if err != nil {
f.clearCGroupAttributes(node)
return false, fmt.Errorf("Failed to discover cgroup mount point: %s", err)
}

// Check if a cgroup mount point was found
if mount == "" {
// Clear any attributes from the previous fingerprint.
f.clearCGroupAttributes(node)

if f.lastState == cgroupAvailable {
f.logger.Printf("[INFO] fingerprint.cgroups: cgroups are unavailable")
}
f.lastState = cgroupUnavailable
return true, nil
}

node.Attributes["unique.cgroup.mountpoint"] = mount

if f.lastState == cgroupUnavailable {
f.logger.Printf("[INFO] fingerprint.cgroups: cgroups are available")
}
f.lastState = cgroupAvailable
return true, nil
}

// clearCGroupAttributes clears any node attributes related to cgroups that might
// have been set in a previous fingerprint run.
func (f *CGroupFingerprint) clearCGroupAttributes(n *structs.Node) {
delete(n.Attributes, "unique.cgroup.mountpoint")
}

// Periodic determines the interval at which the periodic fingerprinter will run.
func (f *CGroupFingerprint) Periodic() (bool, time.Duration) {
return true, interval * time.Second
}
24 changes: 24 additions & 0 deletions client/fingerprint/cgroup_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// +build linux

package fingerprint

import (
"github.com/opencontainers/runc/libcontainer/cgroups"
)

// FindCgroupMountpointDir is used to find the cgroup mount point on a Linux
// system.
func FindCgroupMountpointDir() (string, error) {
mount, err := cgroups.FindCgroupMountpointDir()
if err != nil {
switch e := err.(type) {
case *cgroups.NotFoundError:
// It's okay if the mount point is not discovered
return "", nil
default:
// All other errors are passed back as is
return "", e
}
}
return mount, nil
}
100 changes: 100 additions & 0 deletions client/fingerprint/cgroup_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package fingerprint

import (
"fmt"
"testing"

"github.com/hashicorp/nomad/client/config"
"github.com/hashicorp/nomad/nomad/structs"
)

// A fake mount point detector that returns an empty path
type MountPointDetectorNoMountPoint struct{}

func (m *MountPointDetectorNoMountPoint) MountPoint() (string, error) {
return "", nil
}

// A fake mount point detector that returns an error
type MountPointDetectorMountPointFail struct{}

func (m *MountPointDetectorMountPointFail) MountPoint() (string, error) {
return "", fmt.Errorf("cgroup mountpoint discovery failed")
}

// A fake mount point detector that returns a valid path
type MountPointDetectorValidMountPoint struct{}

func (m *MountPointDetectorValidMountPoint) MountPoint() (string, error) {
return "/sys/fs/cgroup", nil
}

// A fake mount point detector that returns an empty path
type MountPointDetectorEmptyMountPoint struct{}

func (m *MountPointDetectorEmptyMountPoint) MountPoint() (string, error) {
return "", nil
}

func TestCGroupFingerprint(t *testing.T) {
f := &CGroupFingerprint{
logger: testLogger(),
lastState: cgroupUnavailable,
mountPointDetector: &MountPointDetectorMountPointFail{},
}

node := &structs.Node{
Attributes: make(map[string]string),
}

ok, err := f.Fingerprint(&config.Config{}, node)
if err == nil {
t.Fatalf("expected an error")
}
if ok {
t.Fatalf("should not apply")
}
if a, ok := node.Attributes["unique.cgroup.mountpoint"]; ok {
t.Fatalf("unexpected attribute found, %s", a)
}

f = &CGroupFingerprint{
logger: testLogger(),
lastState: cgroupUnavailable,
mountPointDetector: &MountPointDetectorValidMountPoint{},
}

node = &structs.Node{
Attributes: make(map[string]string),
}

ok, err = f.Fingerprint(&config.Config{}, node)
if err != nil {
t.Fatalf("unexpected error, %s", err)
}
if !ok {
t.Fatalf("should apply")
}
assertNodeAttributeContains(t, node, "unique.cgroup.mountpoint")

f = &CGroupFingerprint{
logger: testLogger(),
lastState: cgroupUnavailable,
mountPointDetector: &MountPointDetectorEmptyMountPoint{},
}

node = &structs.Node{
Attributes: make(map[string]string),
}

ok, err = f.Fingerprint(&config.Config{}, node)
if err != nil {
t.Fatalf("unexpected error, %s", err)
}
if !ok {
t.Fatalf("should apply")
}
if a, ok := node.Attributes["unique.cgroup.mountpoint"]; ok {
t.Fatalf("unexpected attribute found, %s", a)
}
}
8 changes: 8 additions & 0 deletions client/fingerprint/cgroup_universal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// +build !linux

package fingerprint

// FindCgroupMountpointDir returns an empty path on non-Linux systems
func FindCgroupMountpointDir() (string, error) {
return "", nil
}
4 changes: 3 additions & 1 deletion client/fingerprint/fingerprint.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ import (
// EmptyDuration is to be used by fingerprinters that are not periodic.
const EmptyDuration = time.Duration(0)

// BuiltinFingerprints is a slice containing the key names of all regestered
// BuiltinFingerprints is a slice containing the key names of all registered
// fingerprints available, to provided an ordered iteration
var BuiltinFingerprints = []string{
"arch",
"cgroup",
"consul",
"cpu",
"env_aws",
Expand All @@ -30,6 +31,7 @@ var BuiltinFingerprints = []string{
// which are available, corresponding to a key found in BuiltinFingerprints
var builtinFingerprintMap = map[string]Factory{
"arch": NewArchFingerprint,
"cgroup": NewCGroupFingerprint,
"consul": NewConsulFingerprint,
"cpu": NewCPUFingerprint,
"env_aws": NewEnvAWSFingerprint,
Expand Down

0 comments on commit df22fca

Please sign in to comment.