Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[processor/resourcedetection]: add "cname" and "lookup" hostname sources #10015

Merged
merged 4 commits into from
May 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
- `transformprocessor`: Add new `limit` function to allow limiting the number of items in a map, such as the number of attributes in `attributes` or `resource.attributes` (#9552)
- `processor/attributes`: Support attributes set by server authenticator (#9420)
- `datadogexporter`: Experimental support for Exponential Histograms with delta aggregation temporality (#8350)
- `resourcedetectionprocessor`: Add "cname" and "lookup" hostname sources

### 🧰 Bug fixes 🧰

Expand Down
26 changes: 25 additions & 1 deletion processor/resourcedetectionprocessor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ processors:

### System metadata

Note: use the Docker detector (see below) if running the Collector as a Docker container.

Queries the host machine to retrieve the following resource attributes:

* host.name
Expand All @@ -47,8 +49,30 @@ processors:
* all valid options for `hostname_sources`:
* "dns"
* "os"
* "cname"
* "lookup"

Note: use the Docker detector (see below) if running the Collector as a Docker container.
#### Hostname Sources

##### dns

The "dns" hostname source uses multiple sources to get the fully qualified domain name. First, it looks up the
host name in the local machine's `hosts` file. If that fails, it looks up the CNAME. Lastly, if that fails,
it does a reverse DNS query. Note: this hostname source may produce unreliable results on Windows. To produce
a FQDN, Windows hosts might have better results using the "lookup" hostname source, which is mentioned below.

##### os

The "os" hostname source provides the hostname provided by the local machine's kernel.

##### cname

The "cname" hostname source provides the canonical name, as provided by net.LookupCNAME in the Go standard library.
Note: this hostname source may produce unreliable results on Windows.

##### lookup

The "lookup" hostname source does a reverse DNS lookup of the current host's IP address.

### Docker metadata

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,38 @@
package system // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal/system"

import (
"fmt"
"net"
"os"
"runtime"
"strings"

"github.com/Showmax/go-fqdn"

"github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal"
)

type systemMetadata interface {
// nameInfoProvider abstracts domain name resolution so it can be swapped for
// testing
type nameInfoProvider struct {
osHostname func() (string, error)
lookupCNAME func(string) (string, error)
lookupHost func(string) ([]string, error)
lookupAddr func(string) ([]string, error)
}

// newNameInfoProvider creates a name info provider for production use, using
// DNS to resolve domain names
func newNameInfoProvider() nameInfoProvider {
return nameInfoProvider{
osHostname: os.Hostname,
lookupCNAME: net.LookupCNAME,
lookupHost: net.LookupHost,
lookupAddr: net.LookupAddr,
}
}

type metadataProvider interface {
// Hostname returns the OS hostname
Hostname() (string, error)

Expand All @@ -32,18 +55,71 @@ type systemMetadata interface {

// OSType returns the host operating system
OSType() (string, error)

// LookupCNAME returns the canonical name for the current host
LookupCNAME() (string, error)

// ReverseLookupHost does a reverse DNS query on the current host's IP address
ReverseLookupHost() (string, error)
}

type systemMetadataProvider struct {
nameInfoProvider nameInfoProvider
}

type systemMetadataImpl struct{}
func newSystemMetadataProvider() metadataProvider {
return systemMetadataProvider{nameInfoProvider: newNameInfoProvider()}
}

func (*systemMetadataImpl) OSType() (string, error) {
func (systemMetadataProvider) OSType() (string, error) {
return internal.GOOSToOSType(runtime.GOOS), nil
}

func (*systemMetadataImpl) FQDN() (string, error) {
func (systemMetadataProvider) FQDN() (string, error) {
return fqdn.FqdnHostname()
}

func (*systemMetadataImpl) Hostname() (string, error) {
return os.Hostname()
func (p systemMetadataProvider) Hostname() (string, error) {
return p.nameInfoProvider.osHostname()
}

func (p systemMetadataProvider) LookupCNAME() (string, error) {
hostname, err := p.Hostname()
if err != nil {
return "", fmt.Errorf("LookupCNAME failed to get hostname: %w", err)
}
cname, err := p.nameInfoProvider.lookupCNAME(hostname)
if err != nil {
return "", fmt.Errorf("LookupCNAME failed to get CNAME: %w", err)
}
return strings.TrimRight(cname, "."), nil
}

func (p systemMetadataProvider) ReverseLookupHost() (string, error) {
hostname, err := p.Hostname()
if err != nil {
return "", fmt.Errorf("ReverseLookupHost failed to get hostname: %w", err)
}
return p.hostnameToDomainName(hostname)
}

func (p systemMetadataProvider) hostnameToDomainName(hostname string) (string, error) {
ipAddresses, err := p.nameInfoProvider.lookupHost(hostname)
if err != nil {
return "", fmt.Errorf("hostnameToDomainName failed to convert hostname to IP addresses: %w", err)
}
return p.reverseLookup(ipAddresses)
}

func (p systemMetadataProvider) reverseLookup(ipAddresses []string) (string, error) {
var err error
for _, ip := range ipAddresses {
var names []string
names, err = p.nameInfoProvider.lookupAddr(ip)
if err != nil {
continue
}
return strings.TrimRight(names[0], "."), nil
}
return "", fmt.Errorf("reverseLookup failed to convert IP addresses to name: %w", err)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package system

import (
"fmt"
"strings"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestLookupCNAME_Linux(t *testing.T) {
p := fakeLinuxSystemMetadataProvider()
cname, err := p.LookupCNAME()
require.NoError(t, err)
assert.Equal(t, "my-linux-vm.abcdefghijklmnopqrstuvwxyz.xx.internal.foo.net", cname)
}

func TestLookupCNAME_Windows(t *testing.T) {
p := fakeWindowsSystemMetadataProvider()
cname, err := p.LookupCNAME()
require.NoError(t, err)
assert.Equal(t, "my-windows-vm.abcdefghijklmnopqrstuvwxyz.xx.internal.foo.net", cname)
}

func TestReverseLookupHost_Linux(t *testing.T) {
p := fakeLinuxSystemMetadataProvider()
fqdn, err := p.ReverseLookupHost()
require.NoError(t, err)
assert.Equal(t, "my-linux-vm.internal.foo.net", fqdn)
}

func TestReverseLookupHost_Windows(t *testing.T) {
p := fakeWindowsSystemMetadataProvider()
fqdn, err := p.ReverseLookupHost()
require.NoError(t, err)
assert.Equal(t, "my-windows-vm.abcdefghijklmnopqrstuvwxyz.xx.internal.foo.net", fqdn)
}

func fakeLinuxSystemMetadataProvider() *systemMetadataProvider {
return &systemMetadataProvider{
nameInfoProvider: fakeLinuxNameInfoProvider(),
}
}

func fakeWindowsSystemMetadataProvider() *systemMetadataProvider {
return &systemMetadataProvider{
nameInfoProvider: fakeWindowsNameInfoProvider(),
}
}

func fakeLinuxNameInfoProvider() nameInfoProvider {
return nameInfoProvider{
osHostname: func() (string, error) {
return "my-linux-vm", nil
},
lookupCNAME: func(s string) (string, error) {
return "my-linux-vm.abcdefghijklmnopqrstuvwxyz.xx.internal.foo.net.", nil
},
lookupHost: func(s string) ([]string, error) {
return []string{"172.24.0.4"}, nil
},
lookupAddr: func(s string) ([]string, error) {
return []string{"my-linux-vm.internal.foo.net."}, nil
},
}
}

func fakeWindowsNameInfoProvider() nameInfoProvider {
fqdn := "my-windows-vm.abcdefghijklmnopqrstuvwxyz.xx.internal.foo.net."
return nameInfoProvider{
osHostname: func() (string, error) {
return "my-windows-vm", nil
},
lookupCNAME: func(s string) (string, error) {
return fqdn, nil
},
lookupHost: func(s string) ([]string, error) {
return []string{"ffff::0000:1111:2222:3333%Ethernet", "1.2.3.4"}, nil
},
lookupAddr: func(s string) ([]string, error) {
if strings.HasSuffix(s, "%Ethernet") {
return nil, fmt.Errorf("lookup %s: unrecognized address", s)
}
return []string{fqdn}, nil
},
}
}
31 changes: 25 additions & 6 deletions processor/resourcedetectionprocessor/internal/system/system.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,17 @@ const (
)

var hostnameSourcesMap = map[string]func(*Detector) (string, error){
"dns": getFQDN,
"os": getHostname,
"os": getHostname,
"dns": getFQDN,
"cname": lookupCNAME,
"lookup": reverseLookupHost,
}

var _ internal.Detector = (*Detector)(nil)

// Detector is a system metadata detector
type Detector struct {
provider systemMetadata
provider metadataProvider
logger *zap.Logger
hostnameSources []string
}
Expand All @@ -52,7 +54,8 @@ func NewDetector(p component.ProcessorCreateSettings, dcfg internal.DetectorConf
if len(cfg.HostnameSources) == 0 {
cfg.HostnameSources = []string{"dns", "os"}
}
return &Detector{provider: &systemMetadataImpl{}, logger: p.Logger, hostnameSources: cfg.HostnameSources}, nil

return &Detector{provider: newSystemMetadataProvider(), logger: p.Logger, hostnameSources: cfg.HostnameSources}, nil
}

// Detect detects system metadata and returns a resource with the available ones
Expand All @@ -78,7 +81,7 @@ func (d *Detector) Detect(_ context.Context) (resource pcommon.Resource, schemaU
d.logger.Debug(err.Error())
}

return res, "", errors.New("all hostname sources are failed to get hostname")
return res, "", errors.New("all hostname sources failed to get hostname")
}

// getHostname returns OS hostname
Expand All @@ -94,7 +97,23 @@ func getHostname(d *Detector) (string, error) {
func getFQDN(d *Detector) (string, error) {
hostname, err := d.provider.FQDN()
if err != nil {
return "", fmt.Errorf("failed getting FQDN: %w", err)
return "", fmt.Errorf("getFQDN failed getting FQDN: %w", err)
}
return hostname, nil
}

func lookupCNAME(d *Detector) (string, error) {
cname, err := d.provider.LookupCNAME()
if err != nil {
return "", fmt.Errorf("lookupCNAME failed to get CNAME: %w", err)
}
return cname, nil
}

func reverseLookupHost(d *Detector) (string, error) {
hostname, err := d.provider.ReverseLookupHost()
if err != nil {
return "", fmt.Errorf("reverseLookupHost failed to lookup host: %w", err)
}
return hostname, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@ func (m *mockMetadata) OSType() (string, error) {
return args.String(0), args.Error(1)
}

func (m *mockMetadata) LookupCNAME() (string, error) {
args := m.MethodCalled("LookupCNAME")
return args.String(0), args.Error(1)
}

func (m *mockMetadata) ReverseLookupHost() (string, error) {
args := m.MethodCalled("ReverseLookupHost")
return args.String(0), args.Error(1)
}

func TestNewDetector(t *testing.T) {
tests := []struct {
name string
Expand Down