github.com/manicqin/nomad@v0.9.5/client/fingerprint/env_aws.go (about) 1 package fingerprint 2 3 import ( 4 "fmt" 5 "net/http" 6 "net/url" 7 "os" 8 "regexp" 9 "strings" 10 "time" 11 12 "github.com/aws/aws-sdk-go/aws" 13 "github.com/aws/aws-sdk-go/aws/awserr" 14 "github.com/aws/aws-sdk-go/aws/ec2metadata" 15 "github.com/aws/aws-sdk-go/aws/session" 16 log "github.com/hashicorp/go-hclog" 17 18 cleanhttp "github.com/hashicorp/go-cleanhttp" 19 "github.com/hashicorp/nomad/nomad/structs" 20 ) 21 22 const ( 23 // AwsMetadataTimeout is the timeout used when contacting the AWS metadata 24 // service 25 AwsMetadataTimeout = 2 * time.Second 26 ) 27 28 // map of instance type to approximate speed, in Mbits/s 29 // Estimates from http://stackoverflow.com/a/35806587 30 // This data is meant for a loose approximation 31 var ec2InstanceSpeedMap = map[*regexp.Regexp]int{ 32 regexp.MustCompile("t2.nano"): 30, 33 regexp.MustCompile("t2.micro"): 70, 34 regexp.MustCompile("t2.small"): 125, 35 regexp.MustCompile("t2.medium"): 300, 36 regexp.MustCompile("m3.medium"): 400, 37 regexp.MustCompile("c4.8xlarge"): 4000, 38 regexp.MustCompile("x1.16xlarge"): 5000, 39 regexp.MustCompile(`.*\.large`): 500, 40 regexp.MustCompile(`.*\.xlarge`): 750, 41 regexp.MustCompile(`.*\.2xlarge`): 1000, 42 regexp.MustCompile(`.*\.4xlarge`): 2000, 43 regexp.MustCompile(`.*\.8xlarge`): 10000, 44 regexp.MustCompile(`.*\.10xlarge`): 10000, 45 regexp.MustCompile(`.*\.16xlarge`): 10000, 46 regexp.MustCompile(`.*\.32xlarge`): 10000, 47 } 48 49 // EnvAWSFingerprint is used to fingerprint AWS metadata 50 type EnvAWSFingerprint struct { 51 StaticFingerprinter 52 53 // endpoint for EC2 metadata as expected by AWS SDK 54 endpoint string 55 56 logger log.Logger 57 } 58 59 // NewEnvAWSFingerprint is used to create a fingerprint from AWS metadata 60 func NewEnvAWSFingerprint(logger log.Logger) Fingerprint { 61 f := &EnvAWSFingerprint{ 62 logger: logger.Named("env_aws"), 63 endpoint: strings.TrimSuffix(os.Getenv("AWS_ENV_URL"), "/meta-data/"), 64 } 65 return f 66 } 67 68 func (f *EnvAWSFingerprint) Fingerprint(request *FingerprintRequest, response *FingerprintResponse) error { 69 cfg := request.Config 70 71 timeout := AwsMetadataTimeout 72 73 // Check if we should tighten the timeout 74 if cfg.ReadBoolDefault(TightenNetworkTimeoutsConfig, false) { 75 timeout = 1 * time.Millisecond 76 } 77 78 ec2meta, err := ec2MetaClient(f.endpoint, timeout) 79 if err != nil { 80 return fmt.Errorf("failed to setup ec2Metadata client: %v", err) 81 } 82 83 if !ec2meta.Available() { 84 return nil 85 } 86 87 // newNetwork is populated and added to the Nodes resources 88 newNetwork := &structs.NetworkResource{ 89 Device: "eth0", 90 } 91 92 // Keys and whether they should be namespaced as unique. Any key whose value 93 // uniquely identifies a node, such as ip, should be marked as unique. When 94 // marked as unique, the key isn't included in the computed node class. 95 keys := map[string]bool{ 96 "ami-id": false, 97 "hostname": true, 98 "instance-id": true, 99 "instance-type": false, 100 "local-hostname": true, 101 "local-ipv4": true, 102 "public-hostname": true, 103 "public-ipv4": true, 104 "placement/availability-zone": false, 105 } 106 for k, unique := range keys { 107 resp, err := ec2meta.GetMetadata(k) 108 if awsErr, ok := err.(awserr.RequestFailure); ok { 109 f.logger.Debug("could not read attribute value", "attribute", k, "error", awsErr) 110 continue 111 } else if awsErr, ok := err.(awserr.Error); ok { 112 // if it's a URL error, assume we're not in an AWS environment 113 // TODO: better way to detect AWS? Check xen virtualization? 114 if _, ok := awsErr.OrigErr().(*url.Error); ok { 115 return nil 116 } 117 118 // not sure what other errors it would return 119 return err 120 } 121 122 // assume we want blank entries 123 key := "platform.aws." + strings.Replace(k, "/", ".", -1) 124 if unique { 125 key = structs.UniqueNamespace(key) 126 } 127 128 response.AddAttribute(key, strings.Trim(resp, "\n")) 129 } 130 131 // copy over network specific information 132 if val, ok := response.Attributes["unique.platform.aws.local-ipv4"]; ok && val != "" { 133 response.AddAttribute("unique.network.ip-address", val) 134 newNetwork.IP = val 135 newNetwork.CIDR = newNetwork.IP + "/32" 136 } 137 138 // find LinkSpeed from lookup 139 throughput := cfg.NetworkSpeed 140 if throughput == 0 { 141 throughput = f.linkSpeed(ec2meta) 142 } 143 if throughput == 0 { 144 // Failed to determine speed. Check if the network fingerprint got it 145 found := false 146 if request.Node.Resources != nil && len(request.Node.Resources.Networks) > 0 { 147 for _, n := range request.Node.Resources.Networks { 148 if n.IP == newNetwork.IP { 149 throughput = n.MBits 150 found = true 151 break 152 } 153 } 154 } 155 156 // Nothing detected so default 157 if !found { 158 throughput = defaultNetworkSpeed 159 } 160 } 161 162 newNetwork.MBits = throughput 163 response.NodeResources = &structs.NodeResources{ 164 Networks: []*structs.NetworkResource{newNetwork}, 165 } 166 167 // populate Links 168 response.AddLink("aws.ec2", fmt.Sprintf("%s.%s", 169 response.Attributes["platform.aws.placement.availability-zone"], 170 response.Attributes["unique.platform.aws.instance-id"])) 171 response.Detected = true 172 173 return nil 174 } 175 176 // EnvAWSFingerprint uses lookup table to approximate network speeds 177 func (f *EnvAWSFingerprint) linkSpeed(ec2meta *ec2metadata.EC2Metadata) int { 178 179 resp, err := ec2meta.GetMetadata("instance-type") 180 if err != nil { 181 f.logger.Error("error reading instance-type", "error", err) 182 return 0 183 } 184 185 key := strings.Trim(resp, "\n") 186 netSpeed := 0 187 for reg, speed := range ec2InstanceSpeedMap { 188 if reg.MatchString(key) { 189 netSpeed = speed 190 break 191 } 192 } 193 194 return netSpeed 195 } 196 197 func ec2MetaClient(endpoint string, timeout time.Duration) (*ec2metadata.EC2Metadata, error) { 198 client := &http.Client{ 199 Timeout: timeout, 200 Transport: cleanhttp.DefaultTransport(), 201 } 202 203 c := aws.NewConfig().WithHTTPClient(client).WithMaxRetries(0) 204 if endpoint != "" { 205 c = c.WithEndpoint(endpoint) 206 } 207 208 session, err := session.NewSession(c) 209 if err != nil { 210 return nil, err 211 } 212 return ec2metadata.New(session, c), nil 213 }