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