Skip to content

Commit

Permalink
fingerprint: handle incomplete AWS immitation APIs
Browse files Browse the repository at this point in the history
Fix a regression where we accidentally started treating non-AWS
environments as AWS environments, resulting in bad networking settings.

Two factors some at play:

First, in [1], we accidentally switched the ultimate AWS test from
checking `ami-id` to `instance-id`.  This means that nomad started
treating more environments as AWS; e.g. Hetzner implements `instance-id`
but not `ami-id`.

Second, some of these environments return empty values instead of
errors!  Hetzner returns empty 200 response for `local-ipv4`, resulting
into bad networking configuration.

This change fix the situation by restoring the check to `ami-id` and
ensuring that we only set network configuration when the ip address is
not-empty.  Also, be more defensive around response whitespace input.

[1] #6779
  • Loading branch information
Mahmood Ali committed Mar 26, 2020
1 parent 500c3c2 commit e37f7af
Showing 1 changed file with 45 additions and 35 deletions.
80 changes: 45 additions & 35 deletions client/fingerprint/env_aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,10 @@ func (f *EnvAWSFingerprint) Fingerprint(request *FingerprintRequest, response *F
return fmt.Errorf("failed to setup ec2Metadata client: %v", err)
}

if !ec2meta.Available() {
if !isAWS(ec2meta) {
return nil
}

// newNetwork is populated and added to the Nodes resources
newNetwork := &structs.NetworkResource{
Device: "eth0",
}

// 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.
Expand All @@ -103,9 +98,14 @@ func (f *EnvAWSFingerprint) Fingerprint(request *FingerprintRequest, response *F
"public-ipv4": true,
"placement/availability-zone": false,
}

for k, unique := range keys {
resp, err := ec2meta.GetMetadata(k)
if awsErr, ok := err.(awserr.RequestFailure); ok {
v := strings.TrimSpace(strings.Trim(resp, "\n"))
if v == "" {
f.logger.Debug("read an empty value", "attribute", k)
continue
} else if awsErr, ok := err.(awserr.RequestFailure); ok {
f.logger.Debug("could not read attribute value", "attribute", k, "error", awsErr)
continue
} else if awsErr, ok := err.(awserr.Error); ok {
Expand All @@ -125,45 +125,28 @@ func (f *EnvAWSFingerprint) Fingerprint(request *FingerprintRequest, response *F
key = structs.UniqueNamespace(key)
}

response.AddAttribute(key, strings.Trim(resp, "\n"))
response.AddAttribute(key, v)
}

// newNetwork is populated and added to the Nodes resources
var newNetwork *structs.NetworkResource

// copy over network specific information
if val, ok := response.Attributes["unique.platform.aws.local-ipv4"]; ok && val != "" {
response.AddAttribute("unique.network.ip-address", val)
newNetwork.IP = val
newNetwork.CIDR = newNetwork.IP + "/32"
}

// find LinkSpeed from lookup
throughput := cfg.NetworkSpeed
if throughput == 0 {
throughput = f.linkSpeed(ec2meta)
}
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
}
}
newNetwork = &structs.NetworkResource{
Device: "eth0",
IP: val,
CIDR: val + "/32",
MBits: f.throughput(request, ec2meta, val),
}

// Nothing detected so default
if !found {
throughput = defaultNetworkSpeed
response.NodeResources = &structs.NodeResources{
Networks: []*structs.NetworkResource{newNetwork},
}
}

newNetwork.MBits = throughput
response.NodeResources = &structs.NodeResources{
Networks: []*structs.NetworkResource{newNetwork},
}

// populate Links
response.AddLink("aws.ec2", fmt.Sprintf("%s.%s",
response.Attributes["platform.aws.placement.availability-zone"],
Expand All @@ -173,6 +156,28 @@ func (f *EnvAWSFingerprint) Fingerprint(request *FingerprintRequest, response *F
return nil
}

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
}

// EnvAWSFingerprint uses lookup table to approximate network speeds
func (f *EnvAWSFingerprint) linkSpeed(ec2meta *ec2metadata.EC2Metadata) int {

Expand Down Expand Up @@ -211,3 +216,8 @@ func ec2MetaClient(endpoint string, timeout time.Duration) (*ec2metadata.EC2Meta
}
return ec2metadata.New(session, c), nil
}

func isAWS(ec2meta *ec2metadata.EC2Metadata) bool {
v, err := ec2meta.GetMetadata("ami-id")
return err == nil && v != ""
}

0 comments on commit e37f7af

Please sign in to comment.