open-nomad/client/fingerprint/env_aws.go

225 lines
6 KiB
Go
Raw Normal View History

2015-08-28 16:31:20 +00:00
package fingerprint
import (
2016-01-23 02:12:16 +00:00
"fmt"
2015-08-28 16:31:20 +00:00
"net/http"
"net/url"
"os"
"regexp"
2015-08-28 16:31:20 +00:00
"strings"
"time"
2019-11-25 20:00:50 +00:00
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/session"
log "github.com/hashicorp/go-hclog"
2019-01-15 19:46:12 +00:00
cleanhttp "github.com/hashicorp/go-cleanhttp"
2015-08-28 16:31:20 +00:00
"github.com/hashicorp/nomad/nomad/structs"
)
2017-07-21 05:34:24 +00:00
const (
// AwsMetadataTimeout is the timeout used when contacting the AWS metadata
// service
AwsMetadataTimeout = 2 * time.Second
)
2015-11-03 15:06:14 +00:00
2015-09-23 04:51:56 +00:00
// map of instance type to approximate speed, in Mbits/s
// Estimates from http://stackoverflow.com/a/35806587
2015-09-23 04:51:56 +00:00
// This data is meant for a loose approximation
var ec2InstanceSpeedMap = map[*regexp.Regexp]int{
regexp.MustCompile("t2.nano"): 30,
regexp.MustCompile("t2.micro"): 70,
regexp.MustCompile("t2.small"): 125,
regexp.MustCompile("t2.medium"): 300,
regexp.MustCompile("m3.medium"): 400,
regexp.MustCompile("c4.8xlarge"): 4000,
regexp.MustCompile("x1.16xlarge"): 5000,
regexp.MustCompile(`.*\.large`): 500,
regexp.MustCompile(`.*\.xlarge`): 750,
regexp.MustCompile(`.*\.2xlarge`): 1000,
regexp.MustCompile(`.*\.4xlarge`): 2000,
regexp.MustCompile(`.*\.8xlarge`): 10000,
regexp.MustCompile(`.*\.10xlarge`): 10000,
regexp.MustCompile(`.*\.16xlarge`): 10000,
regexp.MustCompile(`.*\.32xlarge`): 10000,
}
// EnvAWSFingerprint is used to fingerprint AWS metadata
2015-08-28 16:31:20 +00:00
type EnvAWSFingerprint struct {
2015-11-05 21:46:02 +00:00
StaticFingerprinter
// endpoint for EC2 metadata as expected by AWS SDK
endpoint string
2019-11-25 20:00:50 +00:00
logger log.Logger
2015-08-28 16:31:20 +00:00
}
// NewEnvAWSFingerprint is used to create a fingerprint from AWS metadata
func NewEnvAWSFingerprint(logger log.Logger) Fingerprint {
2017-07-21 05:34:24 +00:00
f := &EnvAWSFingerprint{
logger: logger.Named("env_aws"),
endpoint: strings.TrimSuffix(os.Getenv("AWS_ENV_URL"), "/meta-data/"),
2017-07-21 05:34:24 +00:00
}
2015-08-28 16:31:20 +00:00
return f
}
func (f *EnvAWSFingerprint) Fingerprint(request *FingerprintRequest, response *FingerprintResponse) error {
cfg := request.Config
2019-11-25 20:00:50 +00:00
timeout := AwsMetadataTimeout
2017-07-21 05:34:24 +00:00
// Check if we should tighten the timeout
if cfg.ReadBoolDefault(TightenNetworkTimeoutsConfig, false) {
2019-11-25 20:00:50 +00:00
timeout = 1 * time.Millisecond
2017-07-21 05:34:24 +00:00
}
ec2meta, err := ec2MetaClient(f.endpoint, timeout)
if err != nil {
return fmt.Errorf("failed to setup ec2Metadata client: %v", err)
}
2019-11-25 20:00:50 +00:00
if !isAWS(ec2meta) {
return nil
}
2015-09-23 04:22:23 +00:00
2016-01-26 22:55:38 +00:00
// Keys and whether they should be namespaced as unique. Any key whose value
// uniquely identifies a node, such as ip, should be marked as unique. When
// marked as unique, the key isn't included in the computed node class.
keys := map[string]bool{
"ami-id": false,
2016-01-26 22:55:38 +00:00
"hostname": true,
"instance-id": true,
"instance-type": false,
"local-hostname": true,
"local-ipv4": true,
"public-hostname": true,
"public-ipv4": true,
"placement/availability-zone": false,
2015-08-28 16:31:20 +00:00
}
2016-01-26 22:55:38 +00:00
for k, unique := range keys {
2019-11-25 20:00:50 +00:00
resp, err := ec2meta.GetMetadata(k)
v := strings.TrimSpace(resp)
if v == "" {
f.logger.Debug("read an empty value", "attribute", k)
continue
} else if awsErr, ok := err.(awserr.RequestFailure); ok {
2019-11-25 20:00:50 +00:00
f.logger.Debug("could not read attribute value", "attribute", k, "error", awsErr)
continue
} else if awsErr, ok := err.(awserr.Error); ok {
2015-08-28 16:31:20 +00:00
// if it's a URL error, assume we're not in an AWS environment
// TODO: better way to detect AWS? Check xen virtualization?
2019-11-25 20:00:50 +00:00
if _, ok := awsErr.OrigErr().(*url.Error); ok {
return nil
2015-08-28 16:31:20 +00:00
}
2019-11-25 20:00:50 +00:00
2015-08-28 16:31:20 +00:00
// not sure what other errors it would return
return err
2015-08-28 16:31:20 +00:00
}
// assume we want blank entries
2016-01-23 02:12:16 +00:00
key := "platform.aws." + strings.Replace(k, "/", ".", -1)
2016-01-26 22:55:38 +00:00
if unique {
2016-01-23 02:12:16 +00:00
key = structs.UniqueNamespace(key)
}
response.AddAttribute(key, v)
}
// newNetwork is populated and added to the Nodes resources
var newNetwork *structs.NetworkResource
2015-09-23 04:22:23 +00:00
// copy over network specific information
if val, ok := response.Attributes["unique.platform.aws.local-ipv4"]; ok && val != "" {
response.AddAttribute("unique.network.ip-address", val)
2015-09-22 21:56:04 +00:00
newNetwork = &structs.NetworkResource{
Device: "eth0",
IP: val,
CIDR: val + "/32",
MBits: f.throughput(request, ec2meta, val),
}
response.NodeResources = &structs.NodeResources{
Networks: []*structs.NetworkResource{newNetwork},
}
2015-09-22 21:56:04 +00:00
}
2015-09-23 04:22:23 +00:00
// populate Links
response.AddLink("aws.ec2", fmt.Sprintf("%s.%s",
response.Attributes["platform.aws.placement.availability-zone"],
response.Attributes["unique.platform.aws.instance-id"]))
2018-01-31 22:03:55 +00:00
response.Detected = true
2015-08-28 16:31:20 +00:00
return nil
2015-08-28 16:31:20 +00:00
}
func (f *EnvAWSFingerprint) throughput(request *FingerprintRequest, ec2meta *ec2metadata.EC2Metadata, ip string) int {
throughput := request.Config.NetworkSpeed
if throughput != 0 {
return throughput
}
throughput = f.linkSpeed(ec2meta)
if throughput != 0 {
return throughput
}
if request.Node.Resources != nil && len(request.Node.Resources.Networks) > 0 {
for _, n := range request.Node.Resources.Networks {
if n.IP == ip {
return n.MBits
}
}
}
return defaultNetworkSpeed
}
2015-09-23 04:51:56 +00:00
// EnvAWSFingerprint uses lookup table to approximate network speeds
2019-11-25 20:00:50 +00:00
func (f *EnvAWSFingerprint) linkSpeed(ec2meta *ec2metadata.EC2Metadata) int {
2015-09-22 21:56:04 +00:00
2019-11-25 20:00:50 +00:00
resp, err := ec2meta.GetMetadata("instance-type")
2017-09-26 22:26:33 +00:00
if err != nil {
f.logger.Error("error reading instance-type", "error", err)
2017-09-26 22:26:33 +00:00
return 0
}
2019-11-25 20:00:50 +00:00
key := strings.Trim(resp, "\n")
netSpeed := 0
for reg, speed := range ec2InstanceSpeedMap {
if reg.MatchString(key) {
netSpeed = speed
break
}
2015-09-22 21:56:04 +00:00
}
return netSpeed
2015-09-22 21:56:04 +00:00
}
func ec2MetaClient(endpoint string, timeout time.Duration) (*ec2metadata.EC2Metadata, error) {
client := &http.Client{
Timeout: timeout,
Transport: cleanhttp.DefaultTransport(),
}
c := aws.NewConfig().WithHTTPClient(client).WithMaxRetries(0)
if endpoint != "" {
c = c.WithEndpoint(endpoint)
}
session, err := session.NewSession(c)
if err != nil {
return nil, err
}
return ec2metadata.New(session, c), nil
}
func isAWS(ec2meta *ec2metadata.EC2Metadata) bool {
v, err := ec2meta.GetMetadata("ami-id")
v = strings.TrimSpace(v)
return err == nil && v != ""
}