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