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 }