Skip to content

Commit 0864d76

Browse files
NR-250191 feat: add host.id to connect metadata (#1838)
* NR-250191 feat: add host.id to connect metadata * make the linter happy * fix tests * linters * remove unnecessary nolint directive
1 parent c60aab1 commit 0864d76

File tree

11 files changed

+304
-66
lines changed

11 files changed

+304
-66
lines changed

internal/agent/agent.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,9 @@ func NewAgent(
340340
return nil, err
341341
}
342342

343-
connectSrv := NewIdentityConnectService(connectClient, fpHarvester)
343+
connectMetadataHarvester := identityapi.NewMetadataHarvesterDefault()
344+
345+
connectSrv := NewIdentityConnectService(connectClient, fpHarvester, connectMetadataHarvester)
344346

345347
// notificationHandler will map ipc messages to functions
346348
notificationHandler := ctl.NewNotificationHandlerWithCancellation(ctx.Ctx)

internal/agent/agent_test.go

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
// Copyright 2020 New Relic Corporation. All rights reserved.
22
// SPDX-License-Identifier: Apache-2.0
3-
3+
//
4+
// nolint:exhaustruct
45
package agent
56

67
import (
78
"bytes"
89
context2 "context"
910
"encoding/json"
1011
"fmt"
11-
"github.com/newrelic/infrastructure-agent/pkg/metrics/types"
1212
"io/ioutil"
1313
"net/http"
1414
"net/http/httptest"
@@ -37,6 +37,7 @@ import (
3737
"github.com/newrelic/infrastructure-agent/pkg/helpers/fingerprint"
3838
"github.com/newrelic/infrastructure-agent/pkg/helpers/metric"
3939
"github.com/newrelic/infrastructure-agent/pkg/log"
40+
"github.com/newrelic/infrastructure-agent/pkg/metrics/types" //nolint:depguard
4041
"github.com/newrelic/infrastructure-agent/pkg/plugins/ids"
4142
"github.com/newrelic/infrastructure-agent/pkg/sample"
4243
"github.com/newrelic/infrastructure-agent/pkg/sysinfo"
@@ -71,8 +72,11 @@ func newTesting(cfg *config.Config) *Agent {
7172
panic(err)
7273
}
7374

75+
metadataHarvester := &identityapi.MetadataHarvesterMock{}
76+
metadataHarvester.ShouldHarvest(identityapi.Metadata{})
77+
7478
registerClient := &identityapi.RegisterClientMock{}
75-
connectSrv := NewIdentityConnectService(&MockIdentityConnectClient{}, fpHarvester)
79+
connectSrv := NewIdentityConnectService(&MockIdentityConnectClient{}, fpHarvester, metadataHarvester)
7680
provideIDs := NewProvideIDs(registerClient, state.NewRegisterSM())
7781

7882
a, err := New(
@@ -89,7 +93,6 @@ func newTesting(cfg *config.Config) *Agent {
8993
fpHarvester,
9094
ctl.NewNotificationHandlerWithCancellation(nil),
9195
)
92-
9396
if err != nil {
9497
panic(err)
9598
}
@@ -143,7 +146,6 @@ func TestIgnoreInventory(t *testing.T) {
143146
}
144147

145148
func TestServicePidMap(t *testing.T) {
146-
147149
ctx := NewContext(&config.Config{}, "", testhelpers.NullHostnameResolver, NilIDLookup, matcher)
148150
svc, ok := ctx.GetServiceForPid(1)
149151
assert.False(t, ok)
@@ -325,16 +327,16 @@ func TestRemoveOutdatedEntities(t *testing.T) {
325327
// With a set of registered entities
326328
for _, id := range []string{"entity:1", "entity:2", "entity:3"} {
327329
agent.registerEntityInventory(entity.NewFromNameWithoutID(id))
328-
assert.NoError(t, os.MkdirAll(filepath.Join(dataDir, aPlugin, helpers.SanitizeFileName(id)), 0755))
329-
assert.NoError(t, os.MkdirAll(filepath.Join(dataDir, anotherPlugin, helpers.SanitizeFileName(id)), 0755))
330+
require.NoError(t, os.MkdirAll(filepath.Join(dataDir, aPlugin, helpers.SanitizeFileName(id)), 0o755))
331+
require.NoError(t, os.MkdirAll(filepath.Join(dataDir, anotherPlugin, helpers.SanitizeFileName(id)), 0o755))
330332
}
331333
// With some entity inventory folders from previous executions
332-
assert.NoError(t, os.MkdirAll(filepath.Join(dataDir, aPlugin, "entity4"), 0755))
333-
assert.NoError(t, os.MkdirAll(filepath.Join(dataDir, aPlugin, "entity5"), 0755))
334-
assert.NoError(t, os.MkdirAll(filepath.Join(dataDir, aPlugin, "entity6"), 0755))
335-
assert.NoError(t, os.MkdirAll(filepath.Join(dataDir, anotherPlugin, "entity4"), 0755))
336-
assert.NoError(t, os.MkdirAll(filepath.Join(dataDir, anotherPlugin, "entity5"), 0755))
337-
assert.NoError(t, os.MkdirAll(filepath.Join(dataDir, anotherPlugin, "entity6"), 0755))
334+
require.NoError(t, os.MkdirAll(filepath.Join(dataDir, aPlugin, "entity4"), 0o755))
335+
require.NoError(t, os.MkdirAll(filepath.Join(dataDir, aPlugin, "entity5"), 0o755))
336+
require.NoError(t, os.MkdirAll(filepath.Join(dataDir, aPlugin, "entity6"), 0o755))
337+
require.NoError(t, os.MkdirAll(filepath.Join(dataDir, anotherPlugin, "entity4"), 0o755))
338+
require.NoError(t, os.MkdirAll(filepath.Join(dataDir, anotherPlugin, "entity5"), 0o755))
339+
require.NoError(t, os.MkdirAll(filepath.Join(dataDir, anotherPlugin, "entity6"), 0o755))
338340

339341
// When not all the entities reported information during the last period
340342
entitiesThatReported := map[string]bool{
@@ -585,11 +587,11 @@ func TestAgent_Run_DontSendInventoryIfFwdOnly(t *testing.T) {
585587
SendInterval: tt.sendInterval,
586588
}
587589
a := newTesting(cfg)
588-
//Give time to at least send one request
590+
// Give time to at least send one request
589591
ctxTimeout, _ := context2.WithTimeout(a.Context.Ctx, time.Millisecond*10)
590592
a.Context.Ctx = ctxTimeout
591593

592-
//Inventory recording calls
594+
// Inventory recording calls
593595
snd := &patchSenderCallRecorder{}
594596
a.inventories = map[string]*inventoryEntity{"test": {sender: snd}}
595597

@@ -913,8 +915,7 @@ func Test_ProcessSampling(t *testing.T) {
913915
}
914916
}
915917

916-
type fakeEventSender struct {
917-
}
918+
type fakeEventSender struct{}
918919

919920
func (f fakeEventSender) QueueEvent(_ sample.Event, _ entity.Key) error {
920921
return nil
@@ -929,7 +930,7 @@ func (f fakeEventSender) Stop() error {
929930
}
930931

931932
func TestContext_SendEvent_LogTruncatedEvent(t *testing.T) {
932-
//Capture the logs
933+
// Capture the logs
933934
var output bytes.Buffer
934935
log.SetOutput(&output)
935936
log.EnableSmartVerboseMode(1000)

internal/agent/connect_service.go

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ package agent
55
import (
66
goContext "context"
77
"errors"
8-
"github.com/newrelic/infrastructure-agent/pkg/config"
8+
"fmt"
99
"time"
1010

1111
"github.com/newrelic/infrastructure-agent/internal/agent/instrumentation"
1212
"github.com/newrelic/infrastructure-agent/pkg/backend/backoff"
1313
"github.com/newrelic/infrastructure-agent/pkg/backend/identityapi"
14+
"github.com/newrelic/infrastructure-agent/pkg/config" //nolint:depguard
1415
"github.com/newrelic/infrastructure-agent/pkg/entity"
1516
"github.com/newrelic/infrastructure-agent/pkg/helpers/fingerprint"
1617
"github.com/newrelic/infrastructure-agent/pkg/log"
@@ -20,17 +21,20 @@ type identityConnectService struct {
2021
fingerprintHarvest fingerprint.Harvester
2122
lastFingerprint fingerprint.Fingerprint
2223
client identityapi.IdentityConnectClient
24+
metadataHarvester identityapi.MetadataHarvester
2325
}
2426

2527
// ErrEmptyEntityID is returned when the entityID is empty.
2628
var ErrEmptyEntityID = errors.New("the agentID provided is empty. make sure you have connected if this is not expected")
2729

2830
var logger = log.WithComponent("IdentityConnectService")
2931

30-
func NewIdentityConnectService(client identityapi.IdentityConnectClient, fingerprintHarvest fingerprint.Harvester) *identityConnectService {
32+
//nolint:revive
33+
func NewIdentityConnectService(client identityapi.IdentityConnectClient, fingerprintHarvest fingerprint.Harvester, metadataHarvester identityapi.MetadataHarvester) *identityConnectService {
3134
return &identityConnectService{
3235
fingerprintHarvest: fingerprintHarvest,
3336
client: client,
37+
metadataHarvester: metadataHarvester,
3438
}
3539
}
3640

@@ -50,7 +54,15 @@ func (ic *identityConnectService) Connect() entity.Identity {
5054

5155
logger.WithField(config.TracesFieldName, config.FeatureTrace).Tracef("connect request with fingerprint: %+v", f)
5256

53-
ids, retry, err := ic.client.Connect(f)
57+
metatada, err := ic.metadataHarvester.Harvest()
58+
if err != nil {
59+
logger.WithError(err).Error("metadata harvest failed")
60+
time.Sleep(1 * time.Second)
61+
62+
continue
63+
}
64+
65+
ids, retry, err := ic.client.Connect(f, metatada)
5466

5567
if !ids.ID.IsEmpty() {
5668
logger.
@@ -106,10 +118,15 @@ func (ic *identityConnectService) ConnectUpdate(agentIdn entity.Identity) (entit
106118
// fingerprint changed, so let's store it for the next round
107119
ic.lastFingerprint = f
108120

121+
metatada, err := ic.metadataHarvester.Harvest()
122+
if err != nil {
123+
return agentIdn, fmt.Errorf("failed to harvest metadata: %w", err)
124+
}
125+
109126
var retryBO *backoff.Backoff
110127
for {
111128
logger.WithField(config.TracesFieldName, config.FeatureTrace).Tracef("connect update request with fingerprint: %+v", f)
112-
retry, entityIdn, err := ic.client.ConnectUpdate(agentIdn, f)
129+
retry, entityIdn, err := ic.client.ConnectUpdate(agentIdn, f, metatada)
113130
if retry.After > 0 {
114131
logger.WithField("retryAfter", retry.After).Debug("Connect update retry requested.")
115132
retryBO = nil
Lines changed: 60 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
// Copyright 2020 New Relic Corporation. All rights reserved.
22
// SPDX-License-Identifier: Apache-2.0
3+
4+
//nolint:exhaustruct
35
package agent
46

57
import (
8+
"errors"
9+
"regexp"
610
"testing"
711

12+
"github.com/newrelic/infrastructure-agent/pkg/log" //nolint:depguard
13+
logHelper "github.com/newrelic/infrastructure-agent/test/log"
14+
"github.com/sirupsen/logrus"
15+
816
"github.com/newrelic/infrastructure-agent/pkg/backend/identityapi"
917

1018
"github.com/stretchr/testify/assert"
@@ -14,45 +22,74 @@ import (
1422
"github.com/newrelic/infrastructure-agent/pkg/helpers/fingerprint"
1523
)
1624

17-
var (
18-
testEntityId = entity.Identity{ID: 999666333}
19-
)
25+
// nolint:gochecknoglobals
26+
var testEntityID = entity.Identity{ID: 999666333}
2027

21-
type MockIdentityConnectClient struct {
22-
}
28+
type MockIdentityConnectClient struct{}
2329

24-
func (icc *MockIdentityConnectClient) Connect(fp fingerprint.Fingerprint) (entity.Identity, backendhttp.RetryPolicy, error) {
30+
func (icc *MockIdentityConnectClient) Connect(_ fingerprint.Fingerprint, _ identityapi.Metadata) (entity.Identity, backendhttp.RetryPolicy, error) {
2531
var retry backendhttp.RetryPolicy
26-
return testEntityId, retry, nil
32+
33+
return testEntityID, retry, nil
2734
}
2835

29-
func (icc *MockIdentityConnectClient) ConnectUpdate(entityID entity.Identity, fp fingerprint.Fingerprint) (backendhttp.RetryPolicy, entity.Identity, error) {
36+
func (icc *MockIdentityConnectClient) ConnectUpdate(_ entity.Identity, _ fingerprint.Fingerprint, _ identityapi.Metadata) (backendhttp.RetryPolicy, entity.Identity, error) {
3037
var retry backendhttp.RetryPolicy
31-
return retry, testEntityId, nil
38+
39+
return retry, testEntityID, nil
3240
}
3341

34-
func (icc *MockIdentityConnectClient) Disconnect(entityID entity.ID, state identityapi.DisconnectReason) error {
42+
func (icc *MockIdentityConnectClient) Disconnect(_ entity.ID, _ identityapi.DisconnectReason) error {
3543
return nil
3644
}
3745

3846
func TestConnect(t *testing.T) {
39-
service := NewIdentityConnectService(&MockIdentityConnectClient{}, &fingerprint.MockHarvestor{})
47+
metadataHarvester := &identityapi.MetadataHarvesterMock{}
48+
defer metadataHarvester.AssertExpectations(t)
49+
metadataHarvester.ShouldHarvest(identityapi.Metadata{})
50+
51+
service := NewIdentityConnectService(&MockIdentityConnectClient{}, &fingerprint.MockHarvestor{}, metadataHarvester)
52+
53+
assert.Equal(t, testEntityID, service.Connect())
54+
}
55+
56+
func TestConnectOnMetadataError(t *testing.T) {
57+
t.Parallel()
58+
59+
metadataHarvester := &identityapi.MetadataHarvesterMock{}
60+
defer metadataHarvester.AssertExpectations(t)
61+
//nolint:goerr113
62+
metadataHarvester.ShouldNotHarvest(errors.New("some error"))
63+
metadataHarvester.ShouldHarvest(identityapi.Metadata{})
64+
65+
// WHEN we add a hook to the log to capture the "error" and "fatal" levels
66+
hook := logHelper.NewInMemoryEntriesHook([]logrus.Level{logrus.ErrorLevel})
67+
log.AddHook(hook)
4068

41-
assert.Equal(t, testEntityId, service.Connect())
69+
service := NewIdentityConnectService(&MockIdentityConnectClient{}, &fingerprint.MockHarvestor{}, metadataHarvester)
70+
71+
assert.Equal(t, testEntityID, service.Connect())
72+
assert.True(t, hook.EntryWithMessageExists(regexp.MustCompile(`metadata harvest failed`)))
4273
}
4374

4475
func TestConnectUpdate(t *testing.T) {
45-
service := NewIdentityConnectService(&MockIdentityConnectClient{}, &fingerprint.MockHarvestor{})
76+
metadataHarvester := &identityapi.MetadataHarvesterMock{}
77+
defer metadataHarvester.AssertExpectations(t)
78+
metadataHarvester.ShouldHarvest(identityapi.Metadata{})
79+
80+
service := NewIdentityConnectService(&MockIdentityConnectClient{}, &fingerprint.MockHarvestor{}, metadataHarvester)
4681
entityIdn, err := service.ConnectUpdate(entity.Identity{ID: 1})
4782
assert.NoError(t, err)
48-
assert.Equal(t, testEntityId, entityIdn)
83+
assert.Equal(t, testEntityID, entityIdn)
4984
}
5085

5186
func Test_ConnectUpdate_ReturnsSameIDForSameFingerprint(t *testing.T) {
87+
metadataHarvester := identityapi.MetadataHarvesterMock{}
88+
defer metadataHarvester.AssertExpectations(t)
5289
harvester := &fingerprint.MockHarvestor{}
5390
mockFingerprint, _ := harvester.Harvest()
5491
// explicitly setting null client to make sure we're not calling it IF we have the same fingerprint
55-
service := NewIdentityConnectService(nil, harvester)
92+
service := NewIdentityConnectService(nil, harvester, &metadataHarvester)
5693
service.lastFingerprint = mockFingerprint
5794

5895
agentIdn := entity.Identity{ID: 1}
@@ -67,13 +104,18 @@ func Test_ConnectUpdate_ReturnsSameDifferentIDForDifferentFingerprint(t *testing
67104
mockFingerprint, _ := harvester.Harvest()
68105
mockFingerprint.Hostname = "someHostName"
69106

70-
service := NewIdentityConnectService(&MockIdentityConnectClient{}, harvester)
107+
metadataHarvester := identityapi.MetadataHarvesterMock{}
108+
defer metadataHarvester.AssertExpectations(t)
109+
110+
metadataHarvester.ShouldHarvest(identityapi.Metadata{})
111+
112+
service := NewIdentityConnectService(&MockIdentityConnectClient{}, harvester, &metadataHarvester)
71113
service.lastFingerprint = mockFingerprint
72114

73115
agentIdn := entity.Identity{ID: 1}
74116
entityID, err := service.ConnectUpdate(agentIdn)
75117

76118
assert.NoError(t, err)
77-
assert.Equal(t, testEntityId, entityID)
78-
assert.NotEqual(t, testEntityId, agentIdn)
119+
assert.Equal(t, testEntityID, entityID)
120+
assert.NotEqual(t, testEntityID, agentIdn)
79121
}

pkg/backend/identityapi/connect_client.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ var (
3333
var ilog = log.WithComponent("IdentityConnectClient")
3434

3535
type IdentityConnectClient interface {
36-
Connect(fingerprint fingerprint.Fingerprint) (entity.Identity, backendhttp.RetryPolicy, error)
37-
ConnectUpdate(entity.Identity, fingerprint.Fingerprint) (backendhttp.RetryPolicy, entity.Identity, error)
36+
Connect(fingerprint fingerprint.Fingerprint, metadata Metadata) (entity.Identity, backendhttp.RetryPolicy, error)
37+
ConnectUpdate(entityID entity.Identity, fingerprint fingerprint.Fingerprint, metadata Metadata) (backendhttp.RetryPolicy, entity.Identity, error)
3838
Disconnect(entityID entity.ID, reason DisconnectReason) error
3939
}
4040

@@ -49,6 +49,7 @@ type identityClient struct {
4949

5050
type postConnectBody struct {
5151
Fingerprint fingerprint.Fingerprint `json:"fingerprint"`
52+
Metadata Metadata `json:"metadata"`
5253
Type string `json:"type"`
5354
Protocol string `json:"protocol"`
5455
EntityID entity.ID `json:"entityId,omitempty"`
@@ -97,13 +98,15 @@ func NewIdentityConnectClient(
9798

9899
// Perform the Connect step. The Agent must supply a fingerprint for the host. Backend should reply
99100
// with a unique Entity ID across NR.
100-
func (ic *identityClient) Connect(fingerprint fingerprint.Fingerprint) (ids entity.Identity, retry backendhttp.RetryPolicy, err error) {
101+
//
102+
//nolint:cyclop
103+
func (ic *identityClient) Connect(fingerprint fingerprint.Fingerprint, metadata Metadata) (ids entity.Identity, retry backendhttp.RetryPolicy, err error) {
101104
buf, err := ic.marshal(postConnectBody{
102105
Fingerprint: fingerprint,
106+
Metadata: metadata,
103107
Type: ic.agentType(),
104108
Protocol: "v1",
105109
})
106-
107110
if err != nil {
108111
return
109112
}
@@ -161,9 +164,11 @@ func (ic *identityClient) Connect(fingerprint fingerprint.Fingerprint) (ids enti
161164
}
162165

163166
// ConnectUpdate is used to update the host fingerprint of the entityID to the backend.
164-
func (ic *identityClient) ConnectUpdate(entityIdn entity.Identity, fingerprint fingerprint.Fingerprint) (retry backendhttp.RetryPolicy, ids entity.Identity, err error) {
167+
// nolint:cyclop
168+
func (ic *identityClient) ConnectUpdate(entityIdn entity.Identity, fingerprint fingerprint.Fingerprint, metadata Metadata) (retry backendhttp.RetryPolicy, ids entity.Identity, err error) {
165169
buf, err := ic.marshal(postConnectBody{
166170
Fingerprint: fingerprint,
171+
Metadata: metadata,
167172
Type: ic.agentType(),
168173
Protocol: "v1",
169174
EntityID: entityIdn.ID,

0 commit comments

Comments
 (0)