github.com/kardianos/nomad@v0.1.3-0.20151022182107-b13df73ee850/client/fingerprint/storage.go (about)

     1  package fingerprint
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"os"
     7  	"os/exec"
     8  	"path/filepath"
     9  	"regexp"
    10  	"runtime"
    11  	"strconv"
    12  	"strings"
    13  
    14  	"github.com/hashicorp/nomad/client/config"
    15  	"github.com/hashicorp/nomad/nomad/structs"
    16  )
    17  
    18  // StorageFingerprint is used to measure the amount of storage free for
    19  // applications that the Nomad agent will run on this machine.
    20  type StorageFingerprint struct {
    21  	logger *log.Logger
    22  }
    23  
    24  var (
    25  	reWindowsTotalSpace = regexp.MustCompile("Total # of bytes\\s+: (\\d+)")
    26  	reWindowsFreeSpace  = regexp.MustCompile("Total # of free bytes\\s+: (\\d+)")
    27  )
    28  
    29  func NewStorageFingerprint(logger *log.Logger) Fingerprint {
    30  	fp := &StorageFingerprint{logger: logger}
    31  	return fp
    32  }
    33  
    34  func (f *StorageFingerprint) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) {
    35  
    36  	// Initialize these to empty defaults
    37  	node.Attributes["storage.volume"] = ""
    38  	node.Attributes["storage.bytestotal"] = ""
    39  	node.Attributes["storage.bytesfree"] = ""
    40  	if node.Resources == nil {
    41  		node.Resources = &structs.Resources{}
    42  	}
    43  
    44  	// Guard against unset AllocDir
    45  	storageDir := cfg.AllocDir
    46  	if storageDir == "" {
    47  		var err error
    48  		storageDir, err = os.Getwd()
    49  		if err != nil {
    50  			return false, fmt.Errorf("Unable to get CWD from filesystem: %s", err)
    51  		}
    52  	}
    53  
    54  	if runtime.GOOS == "windows" {
    55  		path, err := filepath.Abs(storageDir)
    56  		if err != nil {
    57  			return false, fmt.Errorf("Failed to detect volume for storage directory %s: %s", storageDir, err)
    58  		}
    59  		volume := filepath.VolumeName(path)
    60  		node.Attributes["storage.volume"] = volume
    61  		out, err := exec.Command("fsutil", "volume", "diskfree", volume).Output()
    62  		if err != nil {
    63  			return false, fmt.Errorf("Failed to inspect free space from volume %s: %s", volume, err)
    64  		}
    65  		outstring := string(out)
    66  
    67  		totalMatches := reWindowsTotalSpace.FindStringSubmatch(outstring)
    68  		if len(totalMatches) == 2 {
    69  			node.Attributes["storage.bytestotal"] = totalMatches[1]
    70  			total, err := strconv.ParseInt(totalMatches[1], 10, 64)
    71  			if err != nil {
    72  				return false, fmt.Errorf("Failed to parse storage.bytestotal in bytes: %s", err)
    73  			}
    74  			// Convert from bytes to to MB
    75  			node.Resources.DiskMB = int(total / 1024 / 1024)
    76  		} else {
    77  			return false, fmt.Errorf("Failed to parse output from fsutil")
    78  		}
    79  
    80  		freeMatches := reWindowsFreeSpace.FindStringSubmatch(outstring)
    81  		if len(freeMatches) == 2 {
    82  			node.Attributes["storage.bytesfree"] = freeMatches[1]
    83  			_, err := strconv.ParseInt(freeMatches[1], 10, 64)
    84  			if err != nil {
    85  				return false, fmt.Errorf("Failed to parse storage.bytesfree in bytes: %s", err)
    86  			}
    87  
    88  		} else {
    89  			return false, fmt.Errorf("Failed to parse output from fsutil")
    90  		}
    91  	} else {
    92  		path, err := filepath.Abs(storageDir)
    93  		if err != nil {
    94  			return false, fmt.Errorf("Failed to determine absolute path for %s", storageDir)
    95  		}
    96  
    97  		// Use -k to standardize the output values between darwin and linux
    98  		var dfArgs string
    99  		if runtime.GOOS == "linux" {
   100  			// df on linux needs the -P option to prevent linebreaks on long filesystem paths
   101  			dfArgs = "-kP"
   102  		} else {
   103  			dfArgs = "-k"
   104  		}
   105  
   106  		mountOutput, err := exec.Command("df", dfArgs, path).Output()
   107  		if err != nil {
   108  			return false, fmt.Errorf("Failed to determine mount point for %s", path)
   109  		}
   110  		// Output looks something like:
   111  		//	Filesystem 1024-blocks      Used Available Capacity   iused    ifree %iused  Mounted on
   112  		//	/dev/disk1   487385240 423722532  63406708    87% 105994631 15851677   87%   /
   113  		//	[0] volume [1] capacity [2] SKIP  [3] free
   114  		lines := strings.Split(string(mountOutput), "\n")
   115  		if len(lines) < 2 {
   116  			return false, fmt.Errorf("Failed to parse `df` output; expected at least 2 lines")
   117  		}
   118  		fields := strings.Fields(lines[1])
   119  		if len(fields) < 4 {
   120  			return false, fmt.Errorf("Failed to parse `df` output; expected at least 4 columns")
   121  		}
   122  		node.Attributes["storage.volume"] = fields[0]
   123  
   124  		total, err := strconv.ParseInt(fields[1], 10, 64)
   125  		if err != nil {
   126  			return false, fmt.Errorf("Failed to parse storage.bytestotal size in kilobytes")
   127  		}
   128  		node.Attributes["storage.bytestotal"] = strconv.FormatInt(total*1024, 10)
   129  
   130  		free, err := strconv.ParseInt(fields[3], 10, 64)
   131  		if err != nil {
   132  			return false, fmt.Errorf("Failed to parse storage.bytesfree size in kilobytes")
   133  		}
   134  		// Convert from KB to MB
   135  		node.Resources.DiskMB = int(free / 1024)
   136  		// Convert from KB to bytes
   137  		node.Attributes["storage.bytesfree"] = strconv.FormatInt(free*1024, 10)
   138  	}
   139  
   140  	return true, nil
   141  }