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