From 802dcaf83c97edfe46fd6bb9c000007d929c1c35 Mon Sep 17 00:00:00 2001 From: rahulreddy15 Date: Fri, 24 Jan 2025 14:07:23 +0000 Subject: [PATCH 1/8] ohi inventory integrationVersion, integrationName, reportingAgent added custom attributes from infra agent config are added with prefix labels/ labels from ohi config file are added with prefix labels/ --- pkg/integrations/legacy/runner.go | 4 ++- pkg/integrations/legacy/runner_test.go | 24 +++++++++++--- pkg/integrations/legacy/utils.go | 44 ++++++++++++++++++++++---- pkg/integrations/v4/dm/emitter.go | 10 ++++-- pkg/integrations/v4/dm/emitter_test.go | 24 +++++++++++--- test/core/dummy_plugin_v4.go | 6 +++- 6 files changed, 91 insertions(+), 21 deletions(-) diff --git a/pkg/integrations/legacy/runner.go b/pkg/integrations/legacy/runner.go index 9e3ca69dc..9669f346a 100644 --- a/pkg/integrations/legacy/runner.go +++ b/pkg/integrations/legacy/runner.go @@ -707,9 +707,11 @@ func EmitDataSet( if err != nil { return fmt.Errorf("couldn't determine a unique entity Key: %s", err.Error()) } + // Custom attributes are from infra agent config + customAttr := ctx.Config().CustomAttributes.DataMap() if len(dataSet.Inventory) > 0 { - inventoryDataSet := BuildInventoryDataSet(elog, dataSet.Inventory, labels, integrationUser, pluginName, entityKey.String()) + inventoryDataSet := BuildInventoryDataSet(elog, dataSet.Inventory, labels, customAttr, integrationUser, pluginName, pluginVersion, agentIdentifier, entityKey.String()) emitter.EmitInventory(inventoryDataSet, entity.NewWithoutID(entityKey)) } diff --git a/pkg/integrations/legacy/runner_test.go b/pkg/integrations/legacy/runner_test.go index 3149e56c5..bfb347dcc 100644 --- a/pkg/integrations/legacy/runner_test.go +++ b/pkg/integrations/legacy/runner_test.go @@ -676,10 +676,10 @@ func (rs *RunnerSuite) TestPluginHandleOutputV1(c *C) { c.Assert(err, IsNil) c.Assert(rd, NotNil) - c.Assert(len(rd.Data), Equals, 4) + c.Assert(len(rd.Data), Equals, 8) c.Assert(rd.Data[0].SortKey(), Equals, "first") - invData := rd.Data[3].(protocol.InventoryData) + invData := rd.Data[5].(protocol.InventoryData) c.Assert(invData["id"], Equals, "integrationUser") c.Assert(invData["value"], Equals, "test") @@ -690,7 +690,7 @@ func (rs *RunnerSuite) TestPluginHandleOutputV1(c *C) { c.Assert(event["integrationUser"], Equals, "test") for _, labelKey := range labelKeys { - if rd.Data[1].SortKey() != labelKey && rd.Data[2].SortKey() != labelKey { + if rd.Data[3].SortKey() != labelKey && rd.Data[4].SortKey() != labelKey { c.Errorf("There isn't label '%s'' in the inventory", labelKey) } } @@ -831,7 +831,7 @@ func (rs *RunnerSuite) TestEventsPluginRunV1(c *C) { c.Assert(err, IsNil) c.Assert(rd, NotNil) - c.Assert(len(rd.Data), Equals, 3) + c.Assert(len(rd.Data), Equals, 7) c.Assert(rd.Data[0].SortKey(), Equals, "first") c.Assert(event, NotNil) @@ -1030,7 +1030,7 @@ func (rs *RunnerSuite) TestHandleOutputV1(c *C) { c.Assert(err, IsNil) // labels are added as inventory - c.Assert(len(rd.Data)-len(labelKeys), Equals, 3) + c.Assert(len(rd.Data)-len(labelKeys), Equals, 7) firstData := rd.Data[0] inv := firstData.(protocol.InventoryData) @@ -1596,6 +1596,13 @@ func TestEmitPayloadV2NoDisplayNameNoEntityName(t *testing.T) { assert.EqualValues(t, "my-agent-id", emitter.lastEventData["entityKey"]) } +func createMockConfigWithDataMap(attrs map[string]interface{}) *config.Config { + customAttrs := config.CustomAttributeMap(attrs) + return &config.Config{ + CustomAttributes: customAttrs, + } +} + func TestEmitDataSet_OnAddHostnameDecoratesWithHostname(t *testing.T) { evType := "foo" user := "user" @@ -1626,6 +1633,7 @@ func TestEmitDataSet_OnAddHostnameDecoratesWithHostname(t *testing.T) { ctx.On("EntityKey").Return(agentIdentifier) ctx.On("HostnameResolver").Return(newFixedHostnameResolver(hn, "short")) ctx.On("IDLookup").Return(newFixedIDLookup()) + ctx.On("Config").Return(createMockConfigWithDataMap(make(map[string]interface{}))) em := &fakeEmitter{} extraAnnotations := map[string]string{} @@ -1677,6 +1685,7 @@ func TestEmitDataSet_EntityNameLocalhostIsNotReplacedWithHostnameV2(t *testing.T ctx.On("EntityKey").Return(agID) ctx.On("HostnameResolver").Return(newFixedHostnameResolver("foo.bar", "short")) ctx.On("IDLookup").Return(newFixedIDLookup()) + ctx.On("Config").Return(createMockConfigWithDataMap(make(map[string]interface{}))) em := &fakeEmitter{} extraAnnotations := map[string]string{} @@ -1724,6 +1733,7 @@ func TestEmitDataSet_EntityNameLocalhostIsReplacedWithHostnameV3(t *testing.T) { ctx.On("EntityKey").Return(agID) ctx.On("HostnameResolver").Return(newFixedHostnameResolver("foo.bar", "short")) ctx.On("IDLookup").Return(newFixedIDLookup()) + ctx.On("Config").Return(createMockConfigWithDataMap(make(map[string]interface{}))) em := &fakeEmitter{} extraAnnotations := map[string]string{} @@ -1772,6 +1782,7 @@ func TestEmitDataSet_MetricHostnameIsReplacedIfLocalhostV3(t *testing.T) { ctx.On("EntityKey").Return(agID) ctx.On("HostnameResolver").Return(newFixedHostnameResolver("foo.bar", "short")) ctx.On("IDLookup").Return(newFixedIDLookup()) + ctx.On("Config").Return(createMockConfigWithDataMap(make(map[string]interface{}))) em := &fakeEmitter{} extraAnnotations := map[string]string{} @@ -1821,6 +1832,7 @@ func TestEmitDataSet_ReportingFieldsAreReplacedIfLocalhostV3(t *testing.T) { ctx.On("EntityKey").Return(agID) ctx.On("HostnameResolver").Return(newFixedHostnameResolver("foo.bar", "short")) ctx.On("IDLookup").Return(newFixedIDLookup()) + ctx.On("Config").Return(createMockConfigWithDataMap(make(map[string]interface{}))) em := &fakeEmitter{} extraAnnotations := map[string]string{} @@ -1869,6 +1881,7 @@ func TestEmitDataSet_LogsEntityViolationsOncePerEntity(t *testing.T) { ctx.On("EntityKey").Return(agID) ctx.On("HostnameResolver").Return(newFixedHostnameResolver("foo.bar", "short")) ctx.On("IDLookup").Return(newFixedIDLookup()) + ctx.On("Config").Return(createMockConfigWithDataMap(make(map[string]interface{}))) em := &fakeEmitter{} extraAnnotations := map[string]string{} @@ -1906,6 +1919,7 @@ func TestEmitDataSet_DoNotOverrideExistingMetrics(t *testing.T) { ctx.On("EntityKey").Return("agent-id") ctx.On("HostnameResolver").Return(newFixedHostnameResolver("long", "short")) ctx.On("IDLookup").Return(newFixedIDLookup()) + ctx.On("Config").Return(createMockConfigWithDataMap(make(map[string]interface{}))) em := &fakeEmitter{} extraAnnotations := map[string]string{ "cluster_name": "K8sDiscoveredCluster", diff --git a/pkg/integrations/legacy/utils.go b/pkg/integrations/legacy/utils.go index 512b61b88..052cd5139 100644 --- a/pkg/integrations/legacy/utils.go +++ b/pkg/integrations/legacy/utils.go @@ -4,6 +4,7 @@ package legacy import ( "fmt" + "github.com/newrelic/infrastructure-agent/internal/agent/types" event2 "github.com/newrelic/infrastructure-agent/pkg/event" @@ -12,12 +13,22 @@ import ( "github.com/sirupsen/logrus" ) +const ( + integrationUserID = "integrationUser" + integrationNameID = "integrationName" + integrationVersionID = "integrationVersion" + reportingAgentID = "reportingAgent" +) + func BuildInventoryDataSet( entryLog log.Entry, inventoryData map[string]protocol.InventoryData, labels map[string]string, + customAttr map[string]string, integrationUser string, pluginName string, + pluginVersion string, + reportingAgent string, entityKey string) types.PluginInventoryDataset { var inventoryDataSet types.PluginInventoryDataset @@ -31,20 +42,39 @@ func BuildInventoryDataSet( } } - for key, value := range labels { + addLabeledData := func(id string, value string) { inventoryDataSet = append(inventoryDataSet, protocol.InventoryData{ - "id": fmt.Sprintf("labels/%s", key), + "id": id, "value": value, "entityKey": entityKey, }) } + for key, value := range customAttr { + // Do not set in the case of duplicate key + if _, exists := labels[key]; !exists { + addLabeledData(fmt.Sprintf("labels/%s", key), value) + } + } + + for key, value := range labels { + addLabeledData(fmt.Sprintf("labels/%s", key), value) + } + if integrationUser != "" { - inventoryDataSet = append(inventoryDataSet, protocol.InventoryData{ - "id": "integrationUser", - "value": integrationUser, - "entityKey": entityKey, - }) + addLabeledData(integrationUserID, integrationUser) + } + + if pluginName != "" { + addLabeledData(integrationNameID, pluginName) + } + + if pluginVersion != "" { + addLabeledData(integrationVersionID, pluginVersion) + } + + if reportingAgent != "" { + addLabeledData(reportingAgentID, reportingAgent) } return inventoryDataSet diff --git a/pkg/integrations/v4/dm/emitter.go b/pkg/integrations/v4/dm/emitter.go index fec16742b..82347a20f 100644 --- a/pkg/integrations/v4/dm/emitter.go +++ b/pkg/integrations/v4/dm/emitter.go @@ -185,7 +185,9 @@ func (e *emitter) emitDataset(req fwrequest.EntityFwRequest) { plugin := agent.NewExternalPluginCommon(req.Definition.PluginID(req.Integration.Name), e.agentContext, req.Definition.Name) - emitInventory(&plugin, req.Definition, req.Integration, req.ID(), req.Data, labels) + customAttr := e.agentContext.Config().CustomAttributes.DataMap() + agentIdentifier := e.agentContext.EntityKey() + emitInventory(&plugin, req.Definition, req.Integration, req.ID(), req.Data, labels, customAttr, agentIdentifier) emitEvent(&plugin, req.Definition, req.Data, labels, annos, req.ID()) @@ -216,6 +218,8 @@ func emitInventory( entityID entity.ID, dataSet protocol.Dataset, labels map[string]string, + customAttr map[string]string, + reportingAgent string, ) { logEntry := elog.WithField("action", "EmitV4DataSet") @@ -223,8 +227,8 @@ func emitInventory( if len(dataSet.Inventory) > 0 { inventoryDataSet := legacy.BuildInventoryDataSet( - logEntry, dataSet.Inventory, labels, integrationUser, integrationMetadata.Name, - dataSet.Entity.Name) + logEntry, dataSet.Inventory, labels, customAttr, integrationUser, integrationMetadata.Name, + integrationMetadata.Version, reportingAgent, dataSet.Entity.Name) entityKey := entity.Key(dataSet.Entity.Name) emitter.EmitInventory(inventoryDataSet, entity.New(entityKey, entityID)) } diff --git a/pkg/integrations/v4/dm/emitter_test.go b/pkg/integrations/v4/dm/emitter_test.go index 463a20714..60511da58 100644 --- a/pkg/integrations/v4/dm/emitter_test.go +++ b/pkg/integrations/v4/dm/emitter_test.go @@ -167,11 +167,21 @@ func TestEmitter_Send_usingIDCache(t *testing.T) { firstEntity := entity.Entity{Key: entity.Key(data.DataSets[0].Entity.Name), ID: entity.ID(1)} secondEntity := entity.Entity{Key: entity.Key(data.DataSets[1].Entity.Name), ID: entity.ID(2)} - aCtx := getAgentContext("TestEmitter_Send_usingIDCache") + aCtx := getAgentContext("mock_reporting_agent") aCtx.On("SendEvent", mock.Anything, mock.Anything) - aCtx.On("SendData", types.PluginOutput{Id: ids.PluginID{Category: "integration", Term: "Sample"}, Entity: firstEntity, Data: types.PluginInventoryDataset{protocol.InventoryData{"id": "inventory_payload_one", "value": "foo-one"}}, NotApplicable: false}) - aCtx.On("SendData", types.PluginOutput{Id: ids.PluginID{Category: "integration", Term: "Sample"}, Entity: secondEntity, Data: types.PluginInventoryDataset{protocol.InventoryData{"id": "inventory_payload_two", "value": "bar-two"}}, NotApplicable: false}) + aCtx.On("SendData", types.PluginOutput{ + Id: ids.PluginID{Category: "integration", Term: "Sample"}, + Entity: firstEntity, + Data: types.PluginInventoryDataset{protocol.InventoryData{"id": "inventory_payload_one", "value": "foo-one"}, protocol.InventoryData{"entityKey": "entity.name", "id": "integrationName", "value": "Sample"}, protocol.InventoryData{"entityKey": "entity.name", "id": "integrationVersion", "value": "1.2.3"}, protocol.InventoryData{"entityKey": "entity.name", "id": "reportingAgent", "value": "mock_reporting_agent"}}, + NotApplicable: false, + }) + aCtx.On("SendData", types.PluginOutput{ + Id: ids.PluginID{Category: "integration", Term: "Sample"}, + Entity: secondEntity, + Data: types.PluginInventoryDataset{protocol.InventoryData{"id": "inventory_payload_two", "value": "bar-two"}, protocol.InventoryData{"entityKey": "entity.name", "id": "integrationName", "value": "Sample"}, protocol.InventoryData{"entityKey": "entity.name", "id": "integrationVersion", "value": "1.2.3"}, protocol.InventoryData{"entityKey": "entity.name", "id": "reportingAgent", "value": "mock_reporting_agent"}}, + NotApplicable: false, + }) dmSender := &mockedMetricsSender{ wg: sync.WaitGroup{}, @@ -220,6 +230,7 @@ func TestEmitter_Send_ignoreEntity(t *testing.T) { aCtx := &mocks.AgentContext{} aCtx.On("Config").Return(config.NewConfig()) aCtx.On("Version").Return("dev") + aCtx.On("EntityKey").Return("mock_reporting_agent") dmSender := &mockedMetricsSender{ wg: sync.WaitGroup{}, @@ -286,7 +297,7 @@ func TestEmitter_Send(t *testing.T) { agentEntityID := entity.ID(321) aCtx := &mocks.AgentContext{} if testCase.register { - aCtx = getAgentContext("TestEmitter_Send") + aCtx = getAgentContext("mock_reporting_agent") aCtx.On("SendData", types.PluginOutput{ Id: ids.PluginID{Category: "integration", Term: "integration name"}, @@ -294,15 +305,20 @@ func TestEmitter_Send(t *testing.T) { Data: types.PluginInventoryDataset{ protocol.InventoryData{"id": "inventory_foo", "value": "bar"}, protocol.InventoryData{"entityKey": "unique name", "id": "integrationUser", "value": "root"}, + protocol.InventoryData{"entityKey": "unique name", "id": "integrationName", "value": "integration name"}, + protocol.InventoryData{"entityKey": "unique name", "id": "integrationVersion", "value": "integration version"}, + protocol.InventoryData{"entityKey": "unique name", "id": "reportingAgent", "value": "mock_reporting_agent"}, }, NotApplicable: false, }, ) aCtx.On("SendEvent", mock.Anything, entity.Key("unique name")).Run(assertEventData(t)) aCtx.On("Identity").Return(entity.Identity{ID: agentEntityID}) + aCtx.On("EntityKey").Return("mock_reporting_agent") } else { aCtx.On("Config").Return(config.NewConfig()) aCtx.On("Version").Return("dev") + aCtx.On("EntityKey").Return("mock_reporting_agent") } metricSender := &mockedMetricsSender{ diff --git a/test/core/dummy_plugin_v4.go b/test/core/dummy_plugin_v4.go index 288ad5604..14e7a24f5 100644 --- a/test/core/dummy_plugin_v4.go +++ b/test/core/dummy_plugin_v4.go @@ -4,9 +4,10 @@ package core import ( - "github.com/newrelic/infrastructure-agent/internal/agent/types" "testing" + "github.com/newrelic/infrastructure-agent/internal/agent/types" + "github.com/newrelic/infrastructure-agent/internal/agent" "github.com/newrelic/infrastructure-agent/internal/feature_flags/test" "github.com/newrelic/infrastructure-agent/internal/integrations/v4/fixtures" @@ -78,8 +79,11 @@ func InventoryDatasetsForPayload(t *testing.T, payload []byte) (dss []types.Plug log.WithComponent("test"), ds.Inventory, nil, + nil, "integrationUser", dataV4.Integration.Name, + dataV4.Integration.Version, + "agent_id", ds.Entity.Name, ) From 3748d6da171570b5a31fa610b910a5d7e9b92e8f Mon Sep 17 00:00:00 2001 From: rahulreddy15 Date: Fri, 24 Jan 2025 14:20:32 +0000 Subject: [PATCH 2/8] Linting and type checks --- pkg/integrations/legacy/runner_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/integrations/legacy/runner_test.go b/pkg/integrations/legacy/runner_test.go index bfb347dcc..14f20ef9b 100644 --- a/pkg/integrations/legacy/runner_test.go +++ b/pkg/integrations/legacy/runner_test.go @@ -679,7 +679,9 @@ func (rs *RunnerSuite) TestPluginHandleOutputV1(c *C) { c.Assert(len(rd.Data), Equals, 8) c.Assert(rd.Data[0].SortKey(), Equals, "first") - invData := rd.Data[5].(protocol.InventoryData) + invData, success := rd.Data[5].(protocol.InventoryData) + c.Assert(success, Equals, true) // checking successful type conversion + c.Assert(invData["id"], Equals, "integrationUser") c.Assert(invData["value"], Equals, "test") @@ -1598,9 +1600,7 @@ func TestEmitPayloadV2NoDisplayNameNoEntityName(t *testing.T) { func createMockConfigWithDataMap(attrs map[string]interface{}) *config.Config { customAttrs := config.CustomAttributeMap(attrs) - return &config.Config{ - CustomAttributes: customAttrs, - } + return &config.Config{CustomAttributes: customAttrs} } func TestEmitDataSet_OnAddHostnameDecoratesWithHostname(t *testing.T) { From 41c9d83ffd804a2d42dfc711599b64051304ae4e Mon Sep 17 00:00:00 2001 From: rahulreddy15 Date: Fri, 24 Jan 2025 14:25:48 +0000 Subject: [PATCH 3/8] Linting issue --- pkg/integrations/legacy/runner_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/integrations/legacy/runner_test.go b/pkg/integrations/legacy/runner_test.go index 14f20ef9b..1abb9691a 100644 --- a/pkg/integrations/legacy/runner_test.go +++ b/pkg/integrations/legacy/runner_test.go @@ -1600,7 +1600,7 @@ func TestEmitPayloadV2NoDisplayNameNoEntityName(t *testing.T) { func createMockConfigWithDataMap(attrs map[string]interface{}) *config.Config { customAttrs := config.CustomAttributeMap(attrs) - return &config.Config{CustomAttributes: customAttrs} + return &config.Config{CustomAttributes: customAttrs} //nolint:exhaustruct } func TestEmitDataSet_OnAddHostnameDecoratesWithHostname(t *testing.T) { From 3baac8b97c0b51e8731884adeb7c8345d9929a2a Mon Sep 17 00:00:00 2001 From: rahulreddy15 Date: Wed, 5 Feb 2025 08:00:32 +0000 Subject: [PATCH 4/8] Added unit tests for the function building inventory --- pkg/integrations/legacy/utils_test.go | 196 ++++++++++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 pkg/integrations/legacy/utils_test.go diff --git a/pkg/integrations/legacy/utils_test.go b/pkg/integrations/legacy/utils_test.go new file mode 100644 index 000000000..aacb98590 --- /dev/null +++ b/pkg/integrations/legacy/utils_test.go @@ -0,0 +1,196 @@ +package legacy + +import ( + "testing" + + "github.com/newrelic/infrastructure-agent/internal/agent/types" + "github.com/newrelic/infrastructure-agent/pkg/integrations/v4/protocol" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestBuildInventoryDataSet(t *testing.T) { + elog := rlog.WithField("action", "EmitDataSet") + + tests := []struct { + name string + inventoryData map[string]protocol.InventoryData + labels map[string]string + customAttr map[string]string + integrationUser string + pluginName string + pluginVersion string + reportingAgent string + entityKey string + want int + wantLabelValues map[string]string + }{ + { + name: "empty input data", + inventoryData: map[string]protocol.InventoryData{}, + labels: map[string]string{}, + customAttr: map[string]string{}, + entityKey: "entity1", + want: 0, + wantLabelValues: map[string]string{}, + }, + { + name: "basic inventory data", + inventoryData: map[string]protocol.InventoryData{ + "test1": {"name": "item1", "value": "value1"}, + "test2": {"name": "item2", "value": "value2"}, + }, + labels: map[string]string{}, + customAttr: map[string]string{}, + entityKey: "entity1", + want: 2, + wantLabelValues: map[string]string{}, + }, + { + name: "with labels", + inventoryData: map[string]protocol.InventoryData{}, + labels: map[string]string{ + "env": "prod", + "region": "us-east", + }, + customAttr: map[string]string{}, + entityKey: "entity1", + want: 2, + wantLabelValues: map[string]string{ + "labels/env": "prod", + "labels/region": "us-east", + }, + }, + { + name: "with custom attributes", + inventoryData: map[string]protocol.InventoryData{}, + labels: map[string]string{}, + customAttr: map[string]string{ + "team": "ohai", + "owner": "david", + }, + entityKey: "entity1", + want: 2, + wantLabelValues: map[string]string{ + "labels/team": "ohai", + "labels/owner": "david", + }, + }, + { + name: "custom attributes with duplicate labels", + inventoryData: map[string]protocol.InventoryData{}, + labels: map[string]string{ + "team": "infra", + }, + customAttr: map[string]string{ + "team": "ohai", + "owner": "david", + }, + entityKey: "entity1", + want: 2, + wantLabelValues: map[string]string{ + "labels/team": "infra", + "labels/owner": "david", + }, + }, + { + name: "verify labels precedence over custom attributes", + inventoryData: map[string]protocol.InventoryData{}, + labels: map[string]string{ + "team": "infra", + "env": "production", + }, + customAttr: map[string]string{ + "team": "ohai", + "env": "staging", + }, + entityKey: "entity1", + want: 2, + wantLabelValues: map[string]string{ + "labels/team": "infra", + "labels/env": "production", + }, + }, + { + name: "with all integration metadata", + inventoryData: map[string]protocol.InventoryData{ + "test1": {"name": "item1", "value": "value1"}, + }, + labels: map[string]string{"env": "prod"}, + customAttr: map[string]string{"owner": "john"}, + integrationUser: "serviceAccount", + pluginName: "test-plugin", + pluginVersion: "1.0.0", + reportingAgent: "agent1", + entityKey: "entity1", + want: 7, + wantLabelValues: map[string]string{ + "labels/env": "prod", + "labels/owner": "john", + integrationUserID: "serviceAccount", + integrationNameID: "test-plugin", + integrationVersionID: "1.0.0", + reportingAgentID: "agent1", + }, + }, + { + name: "partial integration metadata", + inventoryData: map[string]protocol.InventoryData{}, + labels: map[string]string{}, + customAttr: map[string]string{}, + integrationUser: "serviceAccount", + pluginName: "test-plugin", + entityKey: "entity1", + want: 2, + wantLabelValues: map[string]string{ + integrationUserID: "serviceAccount", + integrationNameID: "test-plugin", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := BuildInventoryDataSet( + elog, + tt.inventoryData, + tt.labels, + tt.customAttr, + tt.integrationUser, + tt.pluginName, + tt.pluginVersion, + tt.reportingAgent, + tt.entityKey, + ) + + assert.Len(t, result, tt.want, "unexpected number of items in result") + + // Check for expected labels and their values + for labelID, expectedValue := range tt.wantLabelValues { + exists, actualValue := findItemAndValueInPluginInventoryDataset(result, labelID) + require.True(t, exists, "missing expected label %s", labelID) + assert.Equal(t, expectedValue, actualValue, "unexpected value for label %s", labelID) + } + + // Verify inventory data items exist + for key := range tt.inventoryData { + exists, _ := findItemAndValueInPluginInventoryDataset(result, key) + require.True(t, exists, "missing inventory item with ID %s", key) + } + }) + } +} + +func findItemAndValueInPluginInventoryDataset(items types.PluginInventoryDataset, id string) (bool, string) { + for _, item := range items { + if item.SortKey() == id { + if invData, ok := item.(protocol.InventoryData); ok { + if value, ok := invData["value"].(string); ok { + return true, value + } + } + return true, "" + } + } + return false, "" +} From b76d60ad671b152d688244f187ce5ada93516a89 Mon Sep 17 00:00:00 2001 From: rahulreddy15 Date: Wed, 5 Feb 2025 08:48:51 +0000 Subject: [PATCH 5/8] Testing at the Plugin output level as well --- pkg/integrations/legacy/runner_test.go | 81 +++++++++++++++++++++----- 1 file changed, 65 insertions(+), 16 deletions(-) diff --git a/pkg/integrations/legacy/runner_test.go b/pkg/integrations/legacy/runner_test.go index 1abb9691a..ccfeaa917 100644 --- a/pkg/integrations/legacy/runner_test.go +++ b/pkg/integrations/legacy/runner_test.go @@ -509,7 +509,7 @@ func newFakePlugin(ctx customContext, pluginVersion int) externalPlugin { Context: ctx, }, pluginInstance: &PluginV1Instance{ - Labels: map[string]string{"role": "fileserver", "environment": "development"}, + Labels: map[string]string{"role": "fileserver", "environment": "development", "agent_role": "overwrite agent role"}, plugin: &Plugin{ Name: "new-plugin", ProtocolVersion: pluginVersion, @@ -573,7 +573,7 @@ func newFakePluginWithEnvVars(pluginVersion int) externalPlugin { Context: ctx, }, pluginInstance: &PluginV1Instance{ - Labels: map[string]string{"role": "fileserver", "environment": "development"}, + Labels: map[string]string{"role": "fileserver", "environment": "development", "agent_role": "overwrite agent role"}, plugin: &Plugin{ Name: "new-plugin", ProtocolVersion: pluginVersion, @@ -676,14 +676,6 @@ func (rs *RunnerSuite) TestPluginHandleOutputV1(c *C) { c.Assert(err, IsNil) c.Assert(rd, NotNil) - c.Assert(len(rd.Data), Equals, 8) - c.Assert(rd.Data[0].SortKey(), Equals, "first") - - invData, success := rd.Data[5].(protocol.InventoryData) - c.Assert(success, Equals, true) // checking successful type conversion - - c.Assert(invData["id"], Equals, "integrationUser") - c.Assert(invData["value"], Equals, "test") c.Assert(event, NotNil) c.Assert(event["event_type"], Equals, "LoadBalancerSample") @@ -691,9 +683,27 @@ func (rs *RunnerSuite) TestPluginHandleOutputV1(c *C) { c.Assert(event["value"], Equals, "random") c.Assert(event["integrationUser"], Equals, "test") - for _, labelKey := range labelKeys { - if rd.Data[3].SortKey() != labelKey && rd.Data[4].SortKey() != labelKey { - c.Errorf("There isn't label '%s'' in the inventory", labelKey) + expectedLabelValues := map[string]string{ + "first": "fake", + "labels/my_group": "test group", + "labels/role": "fileserver", + "labels/environment": "development", + "labels/agent_role": "overwrite agent role", + "integrationUser": "test", + "integrationName": "test", + "integrationVersion": "1.0.0", + } + + c.Assert(len(rd.Data), Equals, 8) + + for _, item := range rd.Data { + if invData, ok := item.(protocol.InventoryData); ok { + id, _ := invData["id"].(string) + value, _ := invData["value"].(string) + + if expectedValue, exists := expectedLabelValues[id]; exists { + c.Assert(value, Equals, expectedValue) + } } } } @@ -728,6 +738,7 @@ func (rs *RunnerSuite) TestPluginHandleOutputEventsV1(c *C) { // labels from pluginInstance c.Assert(event["label.environment"], Equals, "development") c.Assert(event["label.role"], Equals, "fileserver") + c.Assert(event["label.agent_role"], Equals, "overwrite agent role") // labels from databind c.Assert(event["label.expected"], Equals, "extra label") @@ -1547,12 +1558,16 @@ func TestParsePayloadV3(t *testing.T) { type fakeEmitter struct { lastEventData map[string]interface{} lastEntityKey string + inventory types.PluginInventoryDataset } func (f *fakeEmitter) EmitInventoryWithPluginId(data types.PluginInventoryDataset, entityKey string, pluginId ids.PluginID) { + f.inventory = data } -func (f *fakeEmitter) EmitInventory(data types.PluginInventoryDataset, entity entity.Entity) {} +func (f *fakeEmitter) EmitInventory(data types.PluginInventoryDataset, entity entity.Entity) { + f.inventory = data +} func (f *fakeEmitter) EmitEvent(eventData map[string]interface{}, entityKey entity.Key) { f.lastEventData = eventData @@ -1588,6 +1603,27 @@ func TestEmitPayloadV2NoDisplayNameNoEntityName(t *testing.T) { assert.EqualValues(t, "Motorbike", emitter.lastEventData["entityName"]) assert.EqualValues(t, "motorbike:street_hawk", emitter.lastEventData["entityKey"]) + expectedLabelValues := map[string]string{ + "integrationUser": "testuser", + "integrationName": "test/test", + "integrationVersion": "x.y.z", + "reportingAgent": "my-agent-id", + "motor": "", // we are type converting to string so motor does not have a value + } + + assert.EqualValues(t, len(emitter.inventory), 5) + + for _, item := range emitter.inventory { + if invData, ok := item.(protocol.InventoryData); ok { + id, _ := invData["id"].(string) + value, _ := invData["value"].(string) + + if expectedValue, exists := expectedLabelValues[id]; exists { + assert.EqualValues(t, value, expectedValue) + } + } + } + // Local entity, no displayName, no entityName assert.NoError(t, EmitDataSet(ctx, &emitter, "test/test", "x.y.z", "testuser", rd.DataSets[2], extraAnnotations, labels, entityRewrite, version)) _, ok := emitter.lastEventData["displayName"] @@ -1596,6 +1632,20 @@ func TestEmitPayloadV2NoDisplayNameNoEntityName(t *testing.T) { assert.False(t, ok) // but entityKey is the agent key assert.EqualValues(t, "my-agent-id", emitter.lastEventData["entityKey"]) + + // Check inventory data + assert.EqualValues(t, len(emitter.inventory), 5) + + for _, item := range emitter.inventory { + if invData, ok := item.(protocol.InventoryData); ok { + id, _ := invData["id"].(string) + value, _ := invData["value"].(string) + + if expectedValue, exists := expectedLabelValues[id]; exists { + assert.EqualValues(t, value, expectedValue) + } + } + } } func createMockConfigWithDataMap(attrs map[string]interface{}) *config.Config { @@ -2221,8 +2271,7 @@ func TestLogFields(t *testing.T) { "TEMP_DIR": "a/path", }) assert.Equal(t, fields["labels"], map[string]string{ - "role": "fileserver", - "environment": "development", + "agent_role": "overwrite agent role", "environment": "development", "role": "fileserver", }) if runtime.GOOS == "windows" { From f2e25b42f343db01f0e3205fc7f238b99eee150c Mon Sep 17 00:00:00 2001 From: rahulreddy15 Date: Wed, 5 Feb 2025 08:57:34 +0000 Subject: [PATCH 6/8] license and only using assert --- pkg/integrations/legacy/utils_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/integrations/legacy/utils_test.go b/pkg/integrations/legacy/utils_test.go index aacb98590..77f9ee89d 100644 --- a/pkg/integrations/legacy/utils_test.go +++ b/pkg/integrations/legacy/utils_test.go @@ -1,3 +1,5 @@ +// Copyright 2020 New Relic Corporation. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 package legacy import ( @@ -6,7 +8,6 @@ import ( "github.com/newrelic/infrastructure-agent/internal/agent/types" "github.com/newrelic/infrastructure-agent/pkg/integrations/v4/protocol" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestBuildInventoryDataSet(t *testing.T) { @@ -168,14 +169,14 @@ func TestBuildInventoryDataSet(t *testing.T) { // Check for expected labels and their values for labelID, expectedValue := range tt.wantLabelValues { exists, actualValue := findItemAndValueInPluginInventoryDataset(result, labelID) - require.True(t, exists, "missing expected label %s", labelID) + assert.Truef(t, exists, "missing expected label %s", labelID) assert.Equal(t, expectedValue, actualValue, "unexpected value for label %s", labelID) } // Verify inventory data items exist for key := range tt.inventoryData { exists, _ := findItemAndValueInPluginInventoryDataset(result, key) - require.True(t, exists, "missing inventory item with ID %s", key) + assert.Truef(t, exists, "missing inventory item with ID %s", key) } }) } From 549bfb69270b5466c2862e0d24b5e05df33dab99 Mon Sep 17 00:00:00 2001 From: rahulreddy15 Date: Wed, 5 Feb 2025 09:00:09 +0000 Subject: [PATCH 7/8] linting issues --- pkg/integrations/legacy/runner_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/integrations/legacy/runner_test.go b/pkg/integrations/legacy/runner_test.go index ccfeaa917..4b8b7a318 100644 --- a/pkg/integrations/legacy/runner_test.go +++ b/pkg/integrations/legacy/runner_test.go @@ -1650,7 +1650,7 @@ func TestEmitPayloadV2NoDisplayNameNoEntityName(t *testing.T) { func createMockConfigWithDataMap(attrs map[string]interface{}) *config.Config { customAttrs := config.CustomAttributeMap(attrs) - return &config.Config{CustomAttributes: customAttrs} //nolint:exhaustruct + return &config.Config{CustomAttributes: customAttrs} //nolint:all } func TestEmitDataSet_OnAddHostnameDecoratesWithHostname(t *testing.T) { From 116b27d4363b9478b8c558141ee37b7d93dc5b71 Mon Sep 17 00:00:00 2001 From: rahulreddy15 Date: Thu, 6 Feb 2025 09:31:37 +0000 Subject: [PATCH 8/8] Tests fail when label is not found --- pkg/integrations/legacy/runner_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/integrations/legacy/runner_test.go b/pkg/integrations/legacy/runner_test.go index 4b8b7a318..d27a61fed 100644 --- a/pkg/integrations/legacy/runner_test.go +++ b/pkg/integrations/legacy/runner_test.go @@ -703,6 +703,8 @@ func (rs *RunnerSuite) TestPluginHandleOutputV1(c *C) { if expectedValue, exists := expectedLabelValues[id]; exists { c.Assert(value, Equals, expectedValue) + } else { + c.Fatalf("Expected label: %v not found in Inventory", id) } } } @@ -1620,6 +1622,8 @@ func TestEmitPayloadV2NoDisplayNameNoEntityName(t *testing.T) { if expectedValue, exists := expectedLabelValues[id]; exists { assert.EqualValues(t, value, expectedValue) + } else { + assert.Fail(t, "Expected label not found in Inventory", "Label: %s not found in inventory", id) } } } @@ -1643,6 +1647,8 @@ func TestEmitPayloadV2NoDisplayNameNoEntityName(t *testing.T) { if expectedValue, exists := expectedLabelValues[id]; exists { assert.EqualValues(t, value, expectedValue) + } else { + assert.Fail(t, "Expected label not found in Inventory", "Label: %s not found in inventory", id) } } }