github.com/dkerwin/nomad@v0.3.3-0.20160525181927-74554135514b/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 if node.Resources == nil { 164 node.Resources = &structs.Resources{} 165 } 166 node.Resources.Networks = append(node.Resources.Networks, newNetwork) 167 168 // populate Node Network Resources 169 170 // populate Links 171 node.Links["aws.ec2"] = fmt.Sprintf("%s.%s", 172 node.Attributes["platform.aws.placement.availability-zone"], 173 node.Attributes["unique.platform.aws.instance-id"]) 174 175 return true, nil 176 } 177 178 func (f *EnvAWSFingerprint) isAWS() bool { 179 // Read the internal metadata URL from the environment, allowing test files to 180 // provide their own 181 metadataURL := os.Getenv("AWS_ENV_URL") 182 if metadataURL == "" { 183 metadataURL = DEFAULT_AWS_URL 184 } 185 186 // assume 2 seconds is enough time for inside AWS network 187 client := &http.Client{ 188 Timeout: 2 * time.Second, 189 Transport: cleanhttp.DefaultTransport(), 190 } 191 192 // Query the metadata url for the ami-id, to veryify we're on AWS 193 resp, err := client.Get(metadataURL + "ami-id") 194 if err != nil { 195 f.logger.Printf("[DEBUG] fingerprint.env_aws: Error querying AWS Metadata URL, skipping") 196 return false 197 } 198 defer resp.Body.Close() 199 200 if resp.StatusCode >= 400 { 201 // URL not found, which indicates that this isn't AWS 202 return false 203 } 204 205 instanceID, err := ioutil.ReadAll(resp.Body) 206 if err != nil { 207 f.logger.Printf("[DEBUG] fingerprint.env_aws: Error reading AWS Instance ID, skipping") 208 return false 209 } 210 211 match, err := regexp.MatchString("ami-*", string(instanceID)) 212 if !match { 213 return false 214 } 215 216 return true 217 } 218 219 // EnvAWSFingerprint uses lookup table to approximate network speeds 220 func (f *EnvAWSFingerprint) linkSpeed() int { 221 222 // Query the API for the instance type, and use the table above to approximate 223 // the network speed 224 metadataURL := os.Getenv("AWS_ENV_URL") 225 if metadataURL == "" { 226 metadataURL = DEFAULT_AWS_URL 227 } 228 229 // assume 2 seconds is enough time for inside AWS network 230 client := &http.Client{ 231 Timeout: 2 * time.Second, 232 Transport: cleanhttp.DefaultTransport(), 233 } 234 235 res, err := client.Get(metadataURL + "instance-type") 236 body, err := ioutil.ReadAll(res.Body) 237 res.Body.Close() 238 if err != nil { 239 f.logger.Printf("[ERR]: fingerprint.env_aws: Error reading response body for instance-type") 240 return 0 241 } 242 243 key := strings.Trim(string(body), "\n") 244 v, ok := ec2InstanceSpeedMap[key] 245 if !ok { 246 return 0 247 } 248 249 return v 250 }