open-nomad/client/fingerprint/env_aws.go

259 lines
6.9 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
"io/ioutil"
"net/http"
"net/url"
"os"
"regexp"
2015-08-28 16:31:20 +00:00
"strings"
"time"
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 (
// This is where the AWS metadata server normally resides. We hardcode the
// "instance" path as well since it's the only one we access here.
DEFAULT_AWS_URL = "http://169.254.169.254/latest/meta-data/"
// 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
2017-07-21 05:34:24 +00:00
timeout time.Duration
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"),
2017-07-21 05:34:24 +00:00
timeout: AwsMetadataTimeout,
}
2015-08-28 16:31:20 +00:00
return f
}
func (f *EnvAWSFingerprint) Fingerprint(request *FingerprintRequest, response *FingerprintResponse) error {
cfg := request.Config
2017-07-21 05:34:24 +00:00
// Check if we should tighten the timeout
if cfg.ReadBoolDefault(TightenNetworkTimeoutsConfig, false) {
f.timeout = 1 * time.Millisecond
}
2015-12-11 23:02:13 +00:00
if !f.isAWS() {
return nil
}
2015-09-23 04:22:23 +00:00
2018-03-11 17:34:28 +00:00
// newNetwork is populated and added to the Nodes resources
2015-09-23 04:51:56 +00:00
newNetwork := &structs.NetworkResource{
Device: "eth0",
}
2015-09-23 04:22:23 +00:00
2015-08-28 16:31:20 +00:00
metadataURL := os.Getenv("AWS_ENV_URL")
if metadataURL == "" {
2015-11-03 15:06:14 +00:00
metadataURL = DEFAULT_AWS_URL
2015-08-28 16:31:20 +00:00
}
client := &http.Client{
2017-07-21 05:34:24 +00:00
Timeout: f.timeout,
Transport: cleanhttp.DefaultTransport(),
2015-08-28 16:31:20 +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 {
2015-08-28 16:31:20 +00:00
res, err := client.Get(metadataURL + k)
if err != nil {
// if it's a URL error, assume we're not in an AWS environment
// TODO: better way to detect AWS? Check xen virtualization?
if _, ok := err.(*url.Error); ok {
return nil
2015-08-28 16:31:20 +00:00
}
// not sure what other errors it would return
return err
} else if res.StatusCode != http.StatusOK {
f.logger.Debug("could not read attribute value", "attribute", k)
continue
2015-08-28 16:31:20 +00:00
}
resp, err := ioutil.ReadAll(res.Body)
res.Body.Close()
if err != nil {
f.logger.Error("error reading response body for AWS attribute", "attribute", k, "error", 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, strings.Trim(string(resp), "\n"))
}
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)
2016-01-23 02:12:16 +00:00
newNetwork.IP = val
2015-09-23 04:51:56 +00:00
newNetwork.CIDR = newNetwork.IP + "/32"
2015-09-22 21:56:04 +00:00
}
// find LinkSpeed from lookup
throughput := f.linkSpeed()
if cfg.NetworkSpeed != 0 {
throughput = cfg.NetworkSpeed
} else if throughput == 0 {
// Failed to determine speed. Check if the network fingerprint got it
found := false
if request.Node.Resources != nil && len(request.Node.Resources.Networks) > 0 {
for _, n := range request.Node.Resources.Networks {
if n.IP == newNetwork.IP {
throughput = n.MBits
found = true
break
}
}
}
// Nothing detected so default
if !found {
throughput = defaultNetworkSpeed
}
2015-09-22 21:56:04 +00:00
}
newNetwork.MBits = throughput
2018-10-03 16:47:18 +00:00
response.NodeResources = &structs.NodeResources{
Networks: []*structs.NetworkResource{newNetwork},
}
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
}
2015-12-11 23:02:13 +00:00
func (f *EnvAWSFingerprint) isAWS() bool {
// Read the internal metadata URL from the environment, allowing test files to
// provide their own
metadataURL := os.Getenv("AWS_ENV_URL")
if metadataURL == "" {
2015-11-03 15:06:14 +00:00
metadataURL = DEFAULT_AWS_URL
}
client := &http.Client{
2017-07-21 05:34:24 +00:00
Timeout: f.timeout,
Transport: cleanhttp.DefaultTransport(),
}
2018-03-11 19:13:32 +00:00
// Query the metadata url for the ami-id, to verify we're on AWS
resp, err := client.Get(metadataURL + "ami-id")
if err != nil {
f.logger.Debug("error querying AWS Metadata URL, skipping")
return false
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
// URL not found, which indicates that this isn't AWS
return false
}
instanceID, err := ioutil.ReadAll(resp.Body)
if err != nil {
f.logger.Debug("error reading AWS Instance ID, skipping")
return false
}
match, err := regexp.MatchString("ami-*", string(instanceID))
2016-06-16 21:32:07 +00:00
if err != nil || !match {
return false
}
return true
}
2015-09-22 21:56:04 +00:00
2015-09-23 04:51:56 +00:00
// EnvAWSFingerprint uses lookup table to approximate network speeds
func (f *EnvAWSFingerprint) linkSpeed() int {
2015-09-22 21:56:04 +00:00
// Query the API for the instance type, and use the table above to approximate
// the network speed
metadataURL := os.Getenv("AWS_ENV_URL")
if metadataURL == "" {
2015-11-03 15:06:14 +00:00
metadataURL = DEFAULT_AWS_URL
2015-09-22 21:56:04 +00:00
}
client := &http.Client{
2017-07-21 05:34:24 +00:00
Timeout: f.timeout,
Transport: cleanhttp.DefaultTransport(),
2015-09-22 21:56:04 +00:00
}
res, err := client.Get(metadataURL + "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
}
2015-09-22 21:56:04 +00:00
body, err := ioutil.ReadAll(res.Body)
res.Body.Close()
if err != nil {
f.logger.Error("error reading response body for instance-type", "error", err)
return 0
2015-09-22 21:56:04 +00:00
}
key := strings.Trim(string(body), "\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
}