Skip to content

Commit 4a89d29

Browse files
mx-psicrobert-1
authored andcommitted
[processor/resourcedetection] Add host.mac to system detector (open-telemetry#29588)
**Description:** Adds support for `host.mac` detection to the `system` detector on the resource detection processor. This convention is defined in the specification [on the host document](https://github.com/open-telemetry/semantic-conventions/blob/v1.23.1/docs/resource/host.md). **Link to tracking Issue:** Fixes open-telemetry#29587 and therefore fixes open-telemetry#22045 **Testing:** Unit tests; manually Tested on my laptop with the following configuration: ``` receivers: hostmetrics: collection_interval: 10s scrapers: load: processors: resourcedetection: detectors: ["system"] system: resource_attributes: host.mac: enabled: true exporters: debug: verbosity: detailed service: pipelines: metrics: receivers: [hostmetrics] processors: [resourcedetection] exporters: [debug] ``` --------- Co-authored-by: Curtis Robert <[email protected]>
1 parent eb435d0 commit 4a89d29

File tree

11 files changed

+142
-1
lines changed

11 files changed

+142
-1
lines changed

.chloggen/mx-psi_host.mac.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: enhancement
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
7+
component: resourcedetectionprocessor
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Add detection of host.mac to system detector.
11+
12+
# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
13+
issues: [29587]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext:
19+
20+
# If your change doesn't affect end users or the exported elements of any package,
21+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
22+
# Optional: The change log or logs in which this entry should be included.
23+
# e.g. '[user]' or '[user, api]'
24+
# Include 'user' if the change is relevant to end users.
25+
# Include 'api' if there is a change to a library API.
26+
# Default: '[user]'
27+
change_logs: []

internal/metadataproviders/system/metadata.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ type Provider interface {
6666

6767
// HostIPs returns the host's IP interfaces
6868
HostIPs() ([]net.IP, error)
69+
70+
// HostMACs returns the host's MAC addresses
71+
HostMACs() ([]net.HardwareAddr, error)
6972
}
7073

7174
type systemMetadataProvider struct {
@@ -196,3 +199,20 @@ func (p systemMetadataProvider) HostIPs() (ips []net.IP, err error) {
196199
}
197200
return ips, err
198201
}
202+
203+
func (p systemMetadataProvider) HostMACs() (macs []net.HardwareAddr, err error) {
204+
ifaces, err := net.Interfaces()
205+
if err != nil {
206+
return nil, err
207+
}
208+
209+
for _, iface := range ifaces {
210+
// skip if the interface is down or is a loopback interface
211+
if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
212+
continue
213+
}
214+
215+
macs = append(macs, iface.HardwareAddr)
216+
}
217+
return macs, err
218+
}

processor/resourcedetectionprocessor/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ Queries the host machine to retrieve the following resource attributes:
4949
* host.name
5050
* host.id
5151
* host.ip
52+
* host.mac
5253
* host.cpu.vendor.id
5354
* host.cpu.family
5455
* host.cpu.model.id

processor/resourcedetectionprocessor/internal/system/internal/metadata/generated_config.go

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

processor/resourcedetectionprocessor/internal/system/internal/metadata/generated_config_test.go

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

processor/resourcedetectionprocessor/internal/system/internal/metadata/generated_resource.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

processor/resourcedetectionprocessor/internal/system/internal/metadata/generated_resource_test.go

Lines changed: 7 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

processor/resourcedetectionprocessor/internal/system/internal/metadata/testdata/config.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ all_set:
1919
enabled: true
2020
host.ip:
2121
enabled: true
22+
host.mac:
23+
enabled: true
2224
host.name:
2325
enabled: true
2426
os.description:
@@ -45,6 +47,8 @@ none_set:
4547
enabled: false
4648
host.ip:
4749
enabled: false
50+
host.mac:
51+
enabled: false
4852
host.name:
4953
enabled: false
5054
os.description:

processor/resourcedetectionprocessor/internal/system/metadata.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ resource_attributes:
2727
description: IP addresses for the host
2828
type: slice
2929
enabled: false
30+
host.mac:
31+
description: MAC addresses for the host
32+
type: slice
33+
enabled: false
3034
host.cpu.vendor.id:
3135
description: The host.cpu.vendor.id
3236
type: string

processor/resourcedetectionprocessor/internal/system/system.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import (
77
"context"
88
"errors"
99
"fmt"
10+
"net"
1011
"strconv"
12+
"strings"
1113

1214
"github.com/shirou/gopsutil/v3/cpu"
1315
"go.opentelemetry.io/collector/featuregate"
@@ -69,6 +71,17 @@ func NewDetector(p processor.CreateSettings, dcfg internal.DetectorConfig) (inte
6971
}, nil
7072
}
7173

74+
// toIEEERA converts a MAC address to IEEE RA format.
75+
// Per the spec: "MAC Addresses MUST be represented in IEEE RA hexadecimal form: as hyphen-separated
76+
// octets in uppercase hexadecimal form from most to least significant."
77+
// Golang returns MAC addresses as colon-separated octets in lowercase hexadecimal form from most
78+
// to least significant, so we need to:
79+
// - Replace colons with hyphens
80+
// - Convert to uppercase
81+
func toIEEERA(mac net.HardwareAddr) string {
82+
return strings.ToUpper(strings.ReplaceAll(mac.String(), ":", "-"))
83+
}
84+
7285
// Detect detects system metadata and returns a resource with the available ones
7386
func (d *Detector) Detect(ctx context.Context) (resource pcommon.Resource, schemaURL string, err error) {
7487
var hostname string
@@ -94,6 +107,17 @@ func (d *Detector) Detect(ctx context.Context) (resource pcommon.Resource, schem
94107
}
95108
}
96109

110+
var hostMACAttribute []any
111+
if d.cfg.ResourceAttributes.HostMac.Enabled {
112+
hostMACs, errMACs := d.provider.HostMACs()
113+
if errMACs != nil {
114+
return pcommon.NewResource(), "", fmt.Errorf("failed to get host MAC addresses: %w", errMACs)
115+
}
116+
for _, mac := range hostMACs {
117+
hostMACAttribute = append(hostMACAttribute, toIEEERA(mac))
118+
}
119+
}
120+
97121
osDescription, err := d.provider.OSDescription(ctx)
98122
if err != nil {
99123
return pcommon.NewResource(), "", fmt.Errorf("failed getting OS description: %w", err)
@@ -119,6 +143,7 @@ func (d *Detector) Detect(ctx context.Context) (resource pcommon.Resource, schem
119143
}
120144
d.rb.SetHostArch(hostArch)
121145
d.rb.SetHostIP(hostIPAttribute)
146+
d.rb.SetHostMac(hostMACAttribute)
122147
d.rb.SetOsDescription(osDescription)
123148
if len(cpuInfo) > 0 {
124149
err = setHostCPUInfo(d, cpuInfo[0])

processor/resourcedetectionprocessor/internal/system/system_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,17 @@ func (m *mockMetadata) HostIPs() ([]net.IP, error) {
7272
return args.Get(0).([]net.IP), args.Error(1)
7373
}
7474

75+
func (m *mockMetadata) HostMACs() ([]net.HardwareAddr, error) {
76+
args := m.MethodCalled("HostMACs")
77+
return args.Get(0).([]net.HardwareAddr), args.Error(1)
78+
}
79+
7580
var (
7681
testIPsAttribute = []any{"192.168.1.140", "fe80::abc2:4a28:737a:609e"}
7782
testIPsAddresses = []net.IP{net.ParseIP(testIPsAttribute[0].(string)), net.ParseIP(testIPsAttribute[1].(string))}
83+
84+
testMACsAttribute = []any{"00-00-00-00-00-01", "DE-AD-BE-EF-00-00"}
85+
testMACsAddresses = []net.HardwareAddr{{0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x00}}
7886
)
7987

8088
func TestNewDetector(t *testing.T) {
@@ -104,11 +112,34 @@ func TestNewDetector(t *testing.T) {
104112
}
105113
}
106114

115+
func TestToIEEERA(t *testing.T) {
116+
tests := []struct {
117+
addr net.HardwareAddr
118+
expected string
119+
}{
120+
{
121+
addr: testMACsAddresses[0],
122+
expected: testMACsAttribute[0].(string),
123+
},
124+
{
125+
addr: testMACsAddresses[1],
126+
expected: testMACsAttribute[1].(string),
127+
},
128+
}
129+
130+
for _, tt := range tests {
131+
t.Run(tt.expected, func(t *testing.T) {
132+
assert.Equal(t, tt.expected, toIEEERA(tt.addr))
133+
})
134+
}
135+
}
136+
107137
func allEnabledConfig() metadata.ResourceAttributesConfig {
108138
cfg := metadata.DefaultResourceAttributesConfig()
109139
cfg.HostArch.Enabled = true
110140
cfg.HostID.Enabled = true
111141
cfg.HostIP.Enabled = true
142+
cfg.HostMac.Enabled = true
112143
cfg.OsDescription.Enabled = true
113144
return cfg
114145
}
@@ -121,6 +152,7 @@ func TestDetectFQDNAvailable(t *testing.T) {
121152
md.On("HostID").Return("2", nil)
122153
md.On("HostArch").Return("amd64", nil)
123154
md.On("HostIPs").Return(testIPsAddresses, nil)
155+
md.On("HostMACs").Return(testMACsAddresses, nil)
124156

125157
detector := newTestDetector(md, []string{"dns"}, allEnabledConfig())
126158
res, schemaURL, err := detector.Detect(context.Background())
@@ -135,6 +167,7 @@ func TestDetectFQDNAvailable(t *testing.T) {
135167
conventions.AttributeHostID: "2",
136168
conventions.AttributeHostArch: conventions.AttributeHostArchAMD64,
137169
"host.ip": testIPsAttribute,
170+
"host.mac": testMACsAttribute,
138171
}
139172

140173
assert.Equal(t, expected, res.Attributes().AsRaw())
@@ -174,6 +207,7 @@ func TestEnableHostID(t *testing.T) {
174207
mdHostname.On("HostID").Return("3", nil)
175208
mdHostname.On("HostArch").Return("amd64", nil)
176209
mdHostname.On("HostIPs").Return(testIPsAddresses, nil)
210+
mdHostname.On("HostMACs").Return(testMACsAddresses, nil)
177211

178212
detector := newTestDetector(mdHostname, []string{"dns", "os"}, allEnabledConfig())
179213
res, schemaURL, err := detector.Detect(context.Background())
@@ -188,6 +222,7 @@ func TestEnableHostID(t *testing.T) {
188222
conventions.AttributeHostID: "3",
189223
conventions.AttributeHostArch: conventions.AttributeHostArchAMD64,
190224
"host.ip": testIPsAttribute,
225+
"host.mac": testMACsAttribute,
191226
}
192227

193228
assert.Equal(t, expected, res.Attributes().AsRaw())
@@ -201,6 +236,7 @@ func TestUseHostname(t *testing.T) {
201236
mdHostname.On("HostID").Return("1", nil)
202237
mdHostname.On("HostArch").Return("amd64", nil)
203238
mdHostname.On("HostIPs").Return(testIPsAddresses, nil)
239+
mdHostname.On("HostMACs").Return(testMACsAddresses, nil)
204240

205241
detector := newTestDetector(mdHostname, []string{"os"}, allEnabledConfig())
206242
res, schemaURL, err := detector.Detect(context.Background())
@@ -215,6 +251,7 @@ func TestUseHostname(t *testing.T) {
215251
conventions.AttributeHostID: "1",
216252
conventions.AttributeHostArch: conventions.AttributeHostArchAMD64,
217253
"host.ip": testIPsAttribute,
254+
"host.mac": testMACsAttribute,
218255
}
219256

220257
assert.Equal(t, expected, res.Attributes().AsRaw())
@@ -230,6 +267,7 @@ func TestDetectError(t *testing.T) {
230267
mdFQDN.On("HostID").Return("", errors.New("err"))
231268
mdFQDN.On("HostArch").Return("amd64", nil)
232269
mdFQDN.On("HostIPs").Return(testIPsAddresses, nil)
270+
mdFQDN.On("HostMACs").Return(testMACsAddresses, nil)
233271

234272
detector := newTestDetector(mdFQDN, []string{"dns"}, allEnabledConfig())
235273
res, schemaURL, err := detector.Detect(context.Background())
@@ -245,6 +283,7 @@ func TestDetectError(t *testing.T) {
245283
mdHostname.On("HostID").Return("", errors.New("err"))
246284
mdHostname.On("HostArch").Return("amd64", nil)
247285
mdHostname.On("HostIPs").Return(testIPsAddresses, nil)
286+
mdHostname.On("HostMACs").Return(testMACsAddresses, nil)
248287

249288
detector = newTestDetector(mdHostname, []string{"os"}, allEnabledConfig())
250289
res, schemaURL, err = detector.Detect(context.Background())
@@ -275,6 +314,7 @@ func TestDetectError(t *testing.T) {
275314
mdHostID.On("HostID").Return("", errors.New("err"))
276315
mdHostID.On("HostArch").Return("arm64", nil)
277316
mdHostID.On("HostIPs").Return(testIPsAddresses, nil)
317+
mdHostID.On("HostMACs").Return(testMACsAddresses, nil)
278318

279319
detector = newTestDetector(mdHostID, []string{"os"}, allEnabledConfig())
280320
res, schemaURL, err = detector.Detect(context.Background())
@@ -286,6 +326,7 @@ func TestDetectError(t *testing.T) {
286326
conventions.AttributeOSType: "linux",
287327
conventions.AttributeHostArch: conventions.AttributeHostArchARM64,
288328
"host.ip": testIPsAttribute,
329+
"host.mac": testMACsAttribute,
289330
}, res.Attributes().AsRaw())
290331
}
291332

0 commit comments

Comments
 (0)