2015-08-27 00:07:31 +00:00
|
|
|
package fingerprint
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"log"
|
2015-08-27 23:03:09 +00:00
|
|
|
"os"
|
2015-08-27 00:07:31 +00:00
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
|
|
|
"regexp"
|
|
|
|
"runtime"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/hashicorp/nomad/client/config"
|
|
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
|
|
)
|
|
|
|
|
2015-08-27 19:37:05 +00:00
|
|
|
// StorageFingerprint is used to measure the amount of storage free for
|
2015-08-27 00:07:31 +00:00
|
|
|
// applications that the Nomad agent will run on this machine.
|
|
|
|
type StorageFingerprint struct {
|
2015-11-05 21:46:02 +00:00
|
|
|
StaticFingerprinter
|
2015-08-27 00:07:31 +00:00
|
|
|
logger *log.Logger
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
reWindowsTotalSpace = regexp.MustCompile("Total # of bytes\\s+: (\\d+)")
|
|
|
|
reWindowsFreeSpace = regexp.MustCompile("Total # of free bytes\\s+: (\\d+)")
|
|
|
|
)
|
|
|
|
|
|
|
|
func NewStorageFingerprint(logger *log.Logger) Fingerprint {
|
2015-08-27 00:16:34 +00:00
|
|
|
fp := &StorageFingerprint{logger: logger}
|
2015-08-27 00:07:31 +00:00
|
|
|
return fp
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *StorageFingerprint) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) {
|
|
|
|
|
|
|
|
// Initialize these to empty defaults
|
|
|
|
node.Attributes["storage.volume"] = ""
|
|
|
|
node.Attributes["storage.bytestotal"] = ""
|
2015-08-27 19:37:05 +00:00
|
|
|
node.Attributes["storage.bytesfree"] = ""
|
|
|
|
if node.Resources == nil {
|
|
|
|
node.Resources = &structs.Resources{}
|
|
|
|
}
|
2015-08-27 00:07:31 +00:00
|
|
|
|
2015-08-27 23:03:09 +00:00
|
|
|
// Guard against unset AllocDir
|
|
|
|
storageDir := cfg.AllocDir
|
|
|
|
if storageDir == "" {
|
|
|
|
var err error
|
|
|
|
storageDir, err = os.Getwd()
|
|
|
|
if err != nil {
|
|
|
|
return false, fmt.Errorf("Unable to get CWD from filesystem: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-27 00:07:31 +00:00
|
|
|
if runtime.GOOS == "windows" {
|
2015-08-27 23:03:09 +00:00
|
|
|
path, err := filepath.Abs(storageDir)
|
2015-08-27 00:07:31 +00:00
|
|
|
if err != nil {
|
2015-08-27 23:03:09 +00:00
|
|
|
return false, fmt.Errorf("Failed to detect volume for storage directory %s: %s", storageDir, err)
|
2015-08-27 00:07:31 +00:00
|
|
|
}
|
|
|
|
volume := filepath.VolumeName(path)
|
|
|
|
node.Attributes["storage.volume"] = volume
|
|
|
|
out, err := exec.Command("fsutil", "volume", "diskfree", volume).Output()
|
|
|
|
if err != nil {
|
2015-08-27 00:16:34 +00:00
|
|
|
return false, fmt.Errorf("Failed to inspect free space from volume %s: %s", volume, err)
|
2015-08-27 00:07:31 +00:00
|
|
|
}
|
|
|
|
outstring := string(out)
|
|
|
|
|
|
|
|
totalMatches := reWindowsTotalSpace.FindStringSubmatch(outstring)
|
|
|
|
if len(totalMatches) == 2 {
|
|
|
|
node.Attributes["storage.bytestotal"] = totalMatches[1]
|
2015-08-27 19:37:05 +00:00
|
|
|
total, err := strconv.ParseInt(totalMatches[1], 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
return false, fmt.Errorf("Failed to parse storage.bytestotal in bytes: %s", err)
|
|
|
|
}
|
|
|
|
// Convert from bytes to to MB
|
|
|
|
node.Resources.DiskMB = int(total / 1024 / 1024)
|
2015-08-27 00:07:31 +00:00
|
|
|
} else {
|
2015-08-27 19:37:05 +00:00
|
|
|
return false, fmt.Errorf("Failed to parse output from fsutil")
|
2015-08-27 00:07:31 +00:00
|
|
|
}
|
2015-08-27 19:37:05 +00:00
|
|
|
|
2015-08-27 00:07:31 +00:00
|
|
|
freeMatches := reWindowsFreeSpace.FindStringSubmatch(outstring)
|
|
|
|
if len(freeMatches) == 2 {
|
2015-08-27 19:37:05 +00:00
|
|
|
node.Attributes["storage.bytesfree"] = freeMatches[1]
|
|
|
|
_, err := strconv.ParseInt(freeMatches[1], 10, 64)
|
2015-08-27 00:16:34 +00:00
|
|
|
if err != nil {
|
2015-08-27 19:37:05 +00:00
|
|
|
return false, fmt.Errorf("Failed to parse storage.bytesfree in bytes: %s", err)
|
2015-08-27 00:16:34 +00:00
|
|
|
}
|
2015-08-27 19:37:05 +00:00
|
|
|
|
2015-08-27 00:07:31 +00:00
|
|
|
} else {
|
2015-08-27 19:37:05 +00:00
|
|
|
return false, fmt.Errorf("Failed to parse output from fsutil")
|
2015-08-27 00:07:31 +00:00
|
|
|
}
|
|
|
|
} else {
|
2015-08-27 23:03:09 +00:00
|
|
|
path, err := filepath.Abs(storageDir)
|
2015-08-27 00:07:31 +00:00
|
|
|
if err != nil {
|
2015-08-27 23:03:09 +00:00
|
|
|
return false, fmt.Errorf("Failed to determine absolute path for %s", storageDir)
|
2015-08-27 00:07:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Use -k to standardize the output values between darwin and linux
|
2015-09-29 11:23:41 +00:00
|
|
|
var dfArgs string
|
|
|
|
if runtime.GOOS == "linux" {
|
|
|
|
// df on linux needs the -P option to prevent linebreaks on long filesystem paths
|
|
|
|
dfArgs = "-kP"
|
|
|
|
} else {
|
|
|
|
dfArgs = "-k"
|
|
|
|
}
|
|
|
|
|
|
|
|
mountOutput, err := exec.Command("df", dfArgs, path).Output()
|
2015-08-27 00:07:31 +00:00
|
|
|
if err != nil {
|
2015-08-27 00:16:34 +00:00
|
|
|
return false, fmt.Errorf("Failed to determine mount point for %s", path)
|
2015-08-27 00:07:31 +00:00
|
|
|
}
|
|
|
|
// Output looks something like:
|
|
|
|
// Filesystem 1024-blocks Used Available Capacity iused ifree %iused Mounted on
|
|
|
|
// /dev/disk1 487385240 423722532 63406708 87% 105994631 15851677 87% /
|
2015-08-27 19:37:05 +00:00
|
|
|
// [0] volume [1] capacity [2] SKIP [3] free
|
2015-08-27 19:42:58 +00:00
|
|
|
lines := strings.Split(string(mountOutput), "\n")
|
|
|
|
if len(lines) < 2 {
|
2015-08-27 21:30:20 +00:00
|
|
|
return false, fmt.Errorf("Failed to parse `df` output; expected at least 2 lines")
|
2015-08-27 19:42:58 +00:00
|
|
|
}
|
|
|
|
fields := strings.Fields(lines[1])
|
2015-08-27 21:30:20 +00:00
|
|
|
if len(fields) < 4 {
|
|
|
|
return false, fmt.Errorf("Failed to parse `df` output; expected at least 4 columns")
|
2015-08-27 19:42:58 +00:00
|
|
|
}
|
2015-08-27 00:07:31 +00:00
|
|
|
node.Attributes["storage.volume"] = fields[0]
|
|
|
|
|
|
|
|
total, err := strconv.ParseInt(fields[1], 10, 64)
|
|
|
|
if err != nil {
|
2015-08-27 00:16:34 +00:00
|
|
|
return false, fmt.Errorf("Failed to parse storage.bytestotal size in kilobytes")
|
2015-08-27 00:07:31 +00:00
|
|
|
}
|
|
|
|
node.Attributes["storage.bytestotal"] = strconv.FormatInt(total*1024, 10)
|
|
|
|
|
2015-08-27 19:37:05 +00:00
|
|
|
free, err := strconv.ParseInt(fields[3], 10, 64)
|
2015-08-27 00:07:31 +00:00
|
|
|
if err != nil {
|
2015-08-27 19:37:05 +00:00
|
|
|
return false, fmt.Errorf("Failed to parse storage.bytesfree size in kilobytes")
|
2015-08-27 00:07:31 +00:00
|
|
|
}
|
2015-08-27 00:16:34 +00:00
|
|
|
// Convert from KB to MB
|
2015-08-27 19:37:05 +00:00
|
|
|
node.Resources.DiskMB = int(free / 1024)
|
2015-08-27 00:16:34 +00:00
|
|
|
// Convert from KB to bytes
|
2015-08-27 19:37:05 +00:00
|
|
|
node.Attributes["storage.bytesfree"] = strconv.FormatInt(free*1024, 10)
|
2015-08-27 00:07:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return true, nil
|
|
|
|
}
|