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 }