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