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"
|
2015-09-22 21:31:57 +00:00
|
|
|
"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"
|
2018-09-16 00:48:59 +00:00
|
|
|
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
|
2016-11-15 21:55:51 +00:00
|
|
|
// Estimates from http://stackoverflow.com/a/35806587
|
2015-09-23 04:51:56 +00:00
|
|
|
// This data is meant for a loose approximation
|
2016-11-15 21:55:51 +00:00
|
|
|
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,
|
2015-09-23 03:04:20 +00:00
|
|
|
}
|
|
|
|
|
2015-10-12 21:56:33 +00:00
|
|
|
// 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
|
2019-11-26 15:26:25 +00:00
|
|
|
|
|
|
|
// 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
|
|
|
}
|
|
|
|
|
2015-10-12 21:56:33 +00:00
|
|
|
// NewEnvAWSFingerprint is used to create a fingerprint from AWS metadata
|
2018-09-16 00:48:59 +00:00
|
|
|
func NewEnvAWSFingerprint(logger log.Logger) Fingerprint {
|
2017-07-21 05:34:24 +00:00
|
|
|
f := &EnvAWSFingerprint{
|
2019-11-26 15:26:25 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2018-12-01 16:10:39 +00:00
|
|
|
func (f *EnvAWSFingerprint) Fingerprint(request *FingerprintRequest, response *FingerprintResponse) error {
|
2018-01-24 14:09:53 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2019-12-16 09:56:32 +00:00
|
|
|
ec2meta, err := ec2MetaClient(f.endpoint, timeout)
|
|
|
|
if err != nil {
|
2019-12-16 13:48:52 +00:00
|
|
|
return fmt.Errorf("failed to setup ec2Metadata client: %v", err)
|
2019-12-16 09:56:32 +00:00
|
|
|
}
|
2019-11-25 20:00:50 +00:00
|
|
|
|
2020-03-26 15:16:16 +00:00
|
|
|
if !isAWS(ec2meta) {
|
2018-01-24 14:09:53 +00:00
|
|
|
return nil
|
2015-09-22 21:31:57 +00:00
|
|
|
}
|
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{
|
2017-08-09 16:53:54 +00:00
|
|
|
"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
|
|
|
}
|
2020-03-26 15:16:16 +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)
|
2020-03-26 15:37:54 +00:00
|
|
|
v := strings.TrimSpace(resp)
|
2020-03-26 15:16:16 +00:00
|
|
|
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 {
|
2018-01-24 14:09:53 +00:00
|
|
|
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
|
2018-01-24 14:09:53 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2020-03-26 15:16:16 +00:00
|
|
|
response.AddAttribute(key, v)
|
2015-08-31 19:18:40 +00:00
|
|
|
}
|
|
|
|
|
2020-03-26 15:16:16 +00:00
|
|
|
// 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
|
2018-01-30 17:57:37 +00:00
|
|
|
if val, ok := response.Attributes["unique.platform.aws.local-ipv4"]; ok && val != "" {
|
2018-01-26 16:21:07 +00:00
|
|
|
response.AddAttribute("unique.network.ip-address", val)
|
2015-09-22 21:56:04 +00:00
|
|
|
|
2020-03-26 15:16:16 +00:00
|
|
|
newNetwork = &structs.NetworkResource{
|
|
|
|
Device: "eth0",
|
|
|
|
IP: val,
|
|
|
|
CIDR: val + "/32",
|
|
|
|
MBits: f.throughput(request, ec2meta, val),
|
2016-11-15 21:55:51 +00:00
|
|
|
}
|
|
|
|
|
2020-03-26 15:16:16 +00:00
|
|
|
response.NodeResources = &structs.NodeResources{
|
|
|
|
Networks: []*structs.NetworkResource{newNetwork},
|
2016-11-15 21:55:51 +00:00
|
|
|
}
|
2015-09-22 21:56:04 +00:00
|
|
|
}
|
|
|
|
|
2015-09-23 04:22:23 +00:00
|
|
|
// populate Links
|
2018-01-26 16:21:07 +00:00
|
|
|
response.AddLink("aws.ec2", fmt.Sprintf("%s.%s",
|
2018-01-30 17:57:37 +00:00
|
|
|
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
|
|
|
|
2018-01-24 14:09:53 +00:00
|
|
|
return nil
|
2015-08-28 16:31:20 +00:00
|
|
|
}
|
2015-09-22 21:31:57 +00:00
|
|
|
|
2020-03-26 15:16:16 +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 {
|
2018-09-16 00:48:59 +00:00
|
|
|
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")
|
2016-11-15 21:55:51 +00:00
|
|
|
netSpeed := 0
|
|
|
|
for reg, speed := range ec2InstanceSpeedMap {
|
|
|
|
if reg.MatchString(key) {
|
|
|
|
netSpeed = speed
|
|
|
|
break
|
|
|
|
}
|
2015-09-22 21:56:04 +00:00
|
|
|
}
|
|
|
|
|
2016-11-15 21:55:51 +00:00
|
|
|
return netSpeed
|
2015-09-22 21:56:04 +00:00
|
|
|
}
|
2019-11-26 15:26:25 +00:00
|
|
|
|
2019-12-16 09:56:32 +00:00
|
|
|
func ec2MetaClient(endpoint string, timeout time.Duration) (*ec2metadata.EC2Metadata, error) {
|
2019-11-26 15:26:25 +00:00
|
|
|
client := &http.Client{
|
|
|
|
Timeout: timeout,
|
|
|
|
Transport: cleanhttp.DefaultTransport(),
|
|
|
|
}
|
|
|
|
|
2019-12-16 09:56:32 +00:00
|
|
|
c := aws.NewConfig().WithHTTPClient(client).WithMaxRetries(0)
|
2019-11-26 15:26:25 +00:00
|
|
|
if endpoint != "" {
|
|
|
|
c = c.WithEndpoint(endpoint)
|
|
|
|
}
|
2019-12-16 09:56:32 +00:00
|
|
|
|
|
|
|
session, err := session.NewSession(c)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return ec2metadata.New(session, c), nil
|
2019-11-26 15:26:25 +00:00
|
|
|
}
|
2020-03-26 15:16:16 +00:00
|
|
|
|
|
|
|
func isAWS(ec2meta *ec2metadata.EC2Metadata) bool {
|
|
|
|
v, err := ec2meta.GetMetadata("ami-id")
|
2020-03-26 15:37:54 +00:00
|
|
|
v = strings.TrimSpace(v)
|
2020-03-26 15:16:16 +00:00
|
|
|
return err == nil && v != ""
|
|
|
|
}
|