github.com/jmitchell/nomad@v0.1.3-0.20151007230021-7ab84c2862d8/client/fingerprint/env_aws.go (about) 1 package fingerprint 2 3 import ( 4 "io/ioutil" 5 "log" 6 "net/http" 7 "net/url" 8 "os" 9 "regexp" 10 "strings" 11 "time" 12 13 "github.com/hashicorp/nomad/client/config" 14 "github.com/hashicorp/nomad/nomad/structs" 15 ) 16 17 // map of instance type to approximate speed, in Mbits/s 18 // http://serverfault.com/questions/324883/aws-bandwidth-and-content-delivery/326797#326797 19 // which itself cites these sources: 20 // - http://blog.rightscale.com/2007/10/28/network-performance-within-amazon-ec2-and-to-amazon-s3/ 21 // - http://www.soc.napier.ac.uk/~bill/chris_p.pdf 22 // 23 // This data is meant for a loose approximation 24 var ec2InstanceSpeedMap = map[string]int{ 25 "m4.large": 80, 26 "m3.medium": 80, 27 "m3.large": 80, 28 "c4.large": 80, 29 "c3.large": 80, 30 "c3.xlarge": 80, 31 "r3.large": 80, 32 "r3.xlarge": 80, 33 "i2.xlarge": 80, 34 "d2.xlarge": 80, 35 "t2.micro": 16, 36 "t2.small": 16, 37 "t2.medium": 16, 38 "t2.large": 16, 39 "m4.xlarge": 760, 40 "m4.2xlarge": 760, 41 "m4.4xlarge": 760, 42 "m3.xlarge": 760, 43 "m3.2xlarge": 760, 44 "c4.xlarge": 760, 45 "c4.2xlarge": 760, 46 "c4.4xlarge": 760, 47 "c3.2xlarge": 760, 48 "c3.4xlarge": 760, 49 "g2.2xlarge": 760, 50 "r3.2xlarge": 760, 51 "r3.4xlarge": 760, 52 "i2.2xlarge": 760, 53 "i2.4xlarge": 760, 54 "d2.2xlarge": 760, 55 "d2.4xlarge": 760, 56 "m4.10xlarge": 10000, 57 "c4.8xlarge": 10000, 58 "c3.8xlarge": 10000, 59 "g2.8xlarge": 10000, 60 "r3.8xlarge": 10000, 61 "i2.8xlarge": 10000, 62 "d2.8xlarge": 10000, 63 } 64 65 // EnvAWSFingerprint is used to fingerprint the CPU 66 type EnvAWSFingerprint struct { 67 logger *log.Logger 68 } 69 70 // NewEnvAWSFingerprint is used to create a CPU fingerprint 71 func NewEnvAWSFingerprint(logger *log.Logger) Fingerprint { 72 f := &EnvAWSFingerprint{logger: logger} 73 return f 74 } 75 76 func (f *EnvAWSFingerprint) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) { 77 if !isAWS() { 78 return false, nil 79 } 80 81 // newNetwork is populated and addded to the Nodes resources 82 newNetwork := &structs.NetworkResource{ 83 Device: "eth0", 84 } 85 86 if node.Links == nil { 87 node.Links = make(map[string]string) 88 } 89 metadataURL := os.Getenv("AWS_ENV_URL") 90 if metadataURL == "" { 91 metadataURL = "http://169.254.169.254/latest/meta-data/" 92 } 93 94 // assume 2 seconds is enough time for inside AWS network 95 client := &http.Client{ 96 Timeout: 2 * time.Second, 97 } 98 99 keys := []string{ 100 "ami-id", 101 "hostname", 102 "instance-id", 103 "instance-type", 104 "local-hostname", 105 "local-ipv4", 106 "public-hostname", 107 "public-ipv4", 108 "placement/availability-zone", 109 } 110 for _, k := range keys { 111 res, err := client.Get(metadataURL + k) 112 if err != nil { 113 // if it's a URL error, assume we're not in an AWS environment 114 // TODO: better way to detect AWS? Check xen virtualization? 115 if _, ok := err.(*url.Error); ok { 116 return false, nil 117 } 118 // not sure what other errors it would return 119 return false, err 120 } 121 resp, err := ioutil.ReadAll(res.Body) 122 res.Body.Close() 123 if err != nil { 124 f.logger.Printf("[ERR]: fingerprint.env_aws: Error reading response body for AWS %s", k) 125 } 126 127 // assume we want blank entries 128 key := strings.Replace(k, "/", ".", -1) 129 node.Attributes["platform.aws."+key] = strings.Trim(string(resp), "\n") 130 } 131 132 // copy over network specific information 133 if node.Attributes["platform.aws.local-ipv4"] != "" { 134 node.Attributes["network.ip-address"] = node.Attributes["platform.aws.local-ipv4"] 135 newNetwork.IP = node.Attributes["platform.aws.local-ipv4"] 136 newNetwork.CIDR = newNetwork.IP + "/32" 137 } 138 139 // find LinkSpeed from lookup 140 if throughput := f.linkSpeed(); throughput > 0 { 141 newNetwork.MBits = throughput 142 } 143 144 if node.Resources == nil { 145 node.Resources = &structs.Resources{} 146 } 147 node.Resources.Networks = append(node.Resources.Networks, newNetwork) 148 149 // populate Node Network Resources 150 151 // populate Links 152 node.Links["aws.ec2"] = node.Attributes["platform.aws.placement.availability-zone"] + "." + node.Attributes["platform.aws.instance-id"] 153 154 return true, nil 155 } 156 157 func isAWS() bool { 158 // Read the internal metadata URL from the environment, allowing test files to 159 // provide their own 160 metadataURL := os.Getenv("AWS_ENV_URL") 161 if metadataURL == "" { 162 metadataURL = "http://169.254.169.254/latest/meta-data/" 163 } 164 165 // assume 2 seconds is enough time for inside AWS network 166 client := &http.Client{ 167 Timeout: 2 * time.Second, 168 } 169 170 // Query the metadata url for the ami-id, to veryify we're on AWS 171 resp, err := client.Get(metadataURL + "ami-id") 172 173 if err != nil { 174 log.Printf("[ERR] fingerprint.env_aws: Error querying AWS Metadata URL, skipping") 175 return false 176 } 177 defer resp.Body.Close() 178 179 instanceID, err := ioutil.ReadAll(resp.Body) 180 if err != nil { 181 log.Printf("[ERR] fingerprint.env_aws: Error reading AWS Instance ID, skipping") 182 return false 183 } 184 185 match, err := regexp.MatchString("ami-*", string(instanceID)) 186 if !match { 187 return false 188 } 189 190 return true 191 } 192 193 // EnvAWSFingerprint uses lookup table to approximate network speeds 194 func (f *EnvAWSFingerprint) linkSpeed() int { 195 196 // Query the API for the instance type, and use the table above to approximate 197 // the network speed 198 metadataURL := os.Getenv("AWS_ENV_URL") 199 if metadataURL == "" { 200 metadataURL = "http://169.254.169.254/latest/meta-data/" 201 } 202 203 // assume 2 seconds is enough time for inside AWS network 204 client := &http.Client{ 205 Timeout: 2 * time.Second, 206 } 207 208 res, err := client.Get(metadataURL + "instance-type") 209 body, err := ioutil.ReadAll(res.Body) 210 res.Body.Close() 211 if err != nil { 212 f.logger.Printf("[ERR]: fingerprint.env_aws: Error reading response body for instance-type") 213 return 0 214 } 215 216 key := strings.Trim(string(body), "\n") 217 v, ok := ec2InstanceSpeedMap[key] 218 if !ok { 219 return 0 220 } 221 222 return v 223 }