Skip to content

Commit 95160f9

Browse files
authored
Merge pull request #171 from newrelic/IHOST-1997_identifier_attributes
Protocol V3: Added identifier attributes
2 parents 542fcc1 + 84b901c commit 95160f9

File tree

6 files changed

+113
-57
lines changed

6 files changed

+113
-57
lines changed

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
2323

2424
### Added
2525

26-
- Added metadata optional decoration for entity metrics (`hostname`,
27-
`clusterName` and `serviceName`), [check doc](/docs/entity-definition.md)
26+
- Added metadata optional decoration for entity metrics (`hostname`), [check doc](/docs/entity-definition.md)
27+
- Added `identifier attributes` for entity metadata.
2828

2929

3030
## 3.0.3

integration/entity.go

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,14 @@ type Entity struct {
2323
customAttributes []metric.Attribute
2424
}
2525

26+
//IDAttributes used for the entity key uniqueness
27+
type IDAttributes []IDAttribute
28+
2629
// EntityMetadata stores entity Metadata
2730
type EntityMetadata struct {
28-
Name string `json:"name"`
29-
Namespace string `json:"type"` // For compatibility reasons we keep the type.
31+
Name string `json:"name"`
32+
Namespace string `json:"type"` // For compatibility reasons we keep the type.
33+
IDAttrs IDAttributes `json:"id_attributes"` // For entity Key uniqueness
3034
}
3135

3236
// newLocalEntity creates unique default entity without identifier (name & type)
@@ -42,9 +46,15 @@ func newLocalEntity(storer persist.Storer, addHostnameToMetadata bool) *Entity {
4246
}
4347
}
4448

45-
// newEntity creates a new remote-entity.
46-
func newEntity(name, namespace string, storer persist.Storer, addHostnameToMetadata bool) (*Entity, error) {
47-
// If one of the attributes is defined, both Name and Namespace are needed.
49+
// newEntity creates a new remote-entity with entity attributes.
50+
func newEntity(
51+
name,
52+
namespace string,
53+
storer persist.Storer,
54+
addHostnameToMetadata bool,
55+
idAttrs ...IDAttribute,
56+
) (*Entity, error) {
57+
4858
if name == "" || namespace == "" {
4959
return nil, errors.New("entity name and type are required when defining one")
5060
}
@@ -57,19 +67,28 @@ func newEntity(name, namespace string, storer persist.Storer, addHostnameToMetad
5767
AddHostname: addHostnameToMetadata,
5868
storer: storer,
5969
lock: &sync.Mutex{},
60-
}
61-
62-
// Entity data is optional. When not specified, data from the integration is reported for the agent's own entity.
63-
if name != "" && namespace != "" {
64-
d.Metadata = &EntityMetadata{
70+
Metadata: &EntityMetadata{
6571
Name: name,
6672
Namespace: namespace,
67-
}
73+
IDAttrs: idAttributes(idAttrs...),
74+
},
6875
}
6976

7077
return &d, nil
7178
}
7279

80+
func idAttributes(idAttrs ...IDAttribute) IDAttributes {
81+
attrs := make(IDAttributes, len(idAttrs))
82+
if len(attrs) == 0 {
83+
return attrs
84+
}
85+
for i, attr := range idAttrs {
86+
attrs[i] = attr
87+
}
88+
89+
return attrs
90+
}
91+
7392
// isLocalEntity returns true if entity is the default one (has no identifier: name & type)
7493
func (e *Entity) isLocalEntity() bool {
7594
return e.Metadata == nil || e.Metadata.Name == ""

integration/entity_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,43 @@ func TestNewEntity(t *testing.T) {
1919
assert.NoError(t, err)
2020
assert.Equal(t, "name", e.Metadata.Name)
2121
assert.Equal(t, "type", e.Metadata.Namespace)
22+
assert.False(t, e.AddHostname)
23+
assert.Empty(t, e.Metadata.IDAttrs)
24+
}
25+
26+
func TestNewEntityWithAttributes(t *testing.T) {
27+
attr1 := NewIDAttribute("env", "prod")
28+
attr2 := NewIDAttribute("srv", "auth")
29+
e, err := newEntity(
30+
"name",
31+
"type",
32+
persist.NewInMemoryStore(),
33+
true,
34+
attr1,
35+
attr2,
36+
)
37+
38+
assert.NoError(t, err)
39+
assert.True(t, e.AddHostname)
40+
assert.Len(t, e.Metadata.IDAttrs, 2)
41+
assert.Equal(t, e.Metadata.IDAttrs[0], attr1)
42+
assert.Equal(t, e.Metadata.IDAttrs[1], attr2)
43+
}
44+
45+
func TestNewEntityWithOneAttribute(t *testing.T) {
46+
attr1 := NewIDAttribute("env", "prod")
47+
e, err := newEntity(
48+
"name",
49+
"type",
50+
persist.NewInMemoryStore(),
51+
true,
52+
attr1,
53+
)
54+
55+
assert.NoError(t, err)
56+
assert.True(t, e.AddHostname)
57+
assert.Len(t, e.Metadata.IDAttrs, 1)
58+
assert.Equal(t, e.Metadata.IDAttrs[0], attr1)
2259
}
2360

2461
func TestEntitiesRequireNameAndType(t *testing.T) {

integration/integration.go

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const (
2525

2626
// NR infrastructure agent protocol version
2727
const (
28-
protocolVersion = "2"
28+
protocolVersion = "3"
2929
)
3030

3131
// Integration defines the format of the output JSON that integrations will return for protocol 2.
@@ -43,6 +43,20 @@ type Integration struct {
4343
args interface{}
4444
}
4545

46+
// IDAttribute is a Key Value struct which is used to have uniqueness during the entity Key resolution.
47+
type IDAttribute struct {
48+
Key string
49+
Value string
50+
}
51+
52+
// NewIDAttribute creates new identifier attribute.
53+
func NewIDAttribute(key, value string) IDAttribute {
54+
return IDAttribute{
55+
Key: key,
56+
Value: value,
57+
}
58+
}
59+
4660
// New creates new integration with sane default values.
4761
func New(name, version string, opts ...Option) (i *Integration, err error) {
4862

@@ -118,7 +132,7 @@ func (i *Integration) LocalEntity() *Entity {
118132
}
119133

120134
// Entity method creates or retrieves an already created entity.
121-
func (i *Integration) Entity(name, namespace string) (e *Entity, err error) {
135+
func (i *Integration) Entity(name, namespace string, idAttributes ...IDAttribute) (e *Entity, err error) {
122136
i.locker.Lock()
123137
defer i.locker.Unlock()
124138

@@ -129,7 +143,7 @@ func (i *Integration) Entity(name, namespace string) (e *Entity, err error) {
129143
}
130144
}
131145

132-
e, err = newEntity(name, namespace, i.storer, i.addHostnameToMeta)
146+
e, err = newEntity(name, namespace, i.storer, i.addHostnameToMeta, idAttributes...)
133147
if err != nil {
134148
return nil, err
135149
}

integration/integration_test.go

Lines changed: 26 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ import (
88
"strings"
99
"testing"
1010

11-
sdk_args "github.com/newrelic/infra-integrations-sdk/args"
1211
"github.com/newrelic/infra-integrations-sdk/data/event"
12+
13+
sdk_args "github.com/newrelic/infra-integrations-sdk/args"
1314
"github.com/newrelic/infra-integrations-sdk/data/metric"
1415
"github.com/newrelic/infra-integrations-sdk/log"
1516
"github.com/stretchr/testify/assert"
@@ -23,18 +24,10 @@ var (
2324
func TestCreation(t *testing.T) {
2425
i := newTestIntegration(t)
2526

26-
if i.Name != "TestIntegration" {
27-
t.Error()
28-
}
29-
if i.IntegrationVersion != "1.0" {
30-
t.Error()
31-
}
32-
if i.ProtocolVersion != "2" {
33-
t.Error()
34-
}
35-
if len(i.Entities) != 0 {
36-
t.Error()
37-
}
27+
assert.Equal(t, "TestIntegration", i.Name)
28+
assert.Equal(t, "1.0", i.IntegrationVersion)
29+
assert.Equal(t, "3", i.ProtocolVersion)
30+
assert.Len(t, i.Entities, 0)
3831
}
3932

4033
func TestDefaultIntegrationWritesToStdout(t *testing.T) {
@@ -52,7 +45,7 @@ func TestDefaultIntegrationWritesToStdout(t *testing.T) {
5245
assert.NoError(t, err)
5346
assert.Equal(t, "integration", i.Name)
5447
assert.Equal(t, "4.0", i.IntegrationVersion)
55-
assert.Equal(t, "2", i.ProtocolVersion)
48+
assert.Equal(t, "3", i.ProtocolVersion)
5649
assert.Equal(t, 0, len(i.Entities))
5750

5851
assert.NoError(t, i.Publish())
@@ -61,7 +54,7 @@ func TestDefaultIntegrationWritesToStdout(t *testing.T) {
6154
f.Close()
6255
payload, err := ioutil.ReadFile(f.Name())
6356
assert.NoError(t, err)
64-
assert.Equal(t, `{"name":"integration","protocol_version":"2","integration_version":"4.0","data":[]}`+"\n", string(payload))
57+
assert.Equal(t, `{"name":"integration","protocol_version":"3","integration_version":"4.0","data":[]}`+"\n", string(payload))
6558
}
6659

6760
func TestIntegration_DefaultEntity(t *testing.T) {
@@ -78,27 +71,13 @@ func TestDefaultArguments(t *testing.T) {
7871
i, err := New("TestIntegration", "1.0", Logger(log.Discard), Writer(ioutil.Discard), Args(&al))
7972
assert.NoError(t, err)
8073

81-
if i.Name != "TestIntegration" {
82-
t.Error()
83-
}
84-
if i.IntegrationVersion != "1.0" {
85-
t.Error()
86-
}
87-
if i.ProtocolVersion != "2" {
88-
t.Error()
89-
}
90-
if len(i.Entities) != 0 {
91-
t.Error()
92-
}
93-
if !al.All() {
94-
t.Error()
95-
}
96-
if al.Pretty {
97-
t.Error()
98-
}
99-
if al.Verbose {
100-
t.Error()
101-
}
74+
assert.Equal(t, "TestIntegration", i.Name)
75+
assert.Equal(t, "1.0", i.IntegrationVersion)
76+
assert.Equal(t, "3", i.ProtocolVersion)
77+
assert.Len(t, i.Entities, 0)
78+
assert.True(t, al.All())
79+
assert.False(t, al.Pretty)
80+
assert.False(t, al.Verbose)
10281
}
10382

10483
func TestClusterAndServiceArgumentsAreAddedToMetadata(t *testing.T) {
@@ -244,13 +223,19 @@ func TestIntegration_Publish(t *testing.T) {
244223
expectedOutputRaw := []byte(`
245224
{
246225
"name": "TestIntegration",
247-
"protocol_version": "2",
226+
"protocol_version": "3",
248227
"integration_version": "1.0",
249228
"data": [
250229
{
251230
"entity": {
252231
"name": "EntityOne",
253-
"type": "test"
232+
"type": "test",
233+
"id_attributes": [
234+
{
235+
"Key":"env",
236+
"Value":"prod"
237+
}
238+
]
254239
},
255240
"metrics": [
256241
{
@@ -275,7 +260,8 @@ func TestIntegration_Publish(t *testing.T) {
275260
{
276261
"entity": {
277262
"name": "EntityTwo",
278-
"type": "test"
263+
"type": "test",
264+
"id_attributes": []
279265
},
280266
"metrics": [
281267
{
@@ -304,7 +290,7 @@ func TestIntegration_Publish(t *testing.T) {
304290
i, err := New("TestIntegration", "1.0", Logger(log.Discard), Writer(w))
305291
assert.NoError(t, err)
306292

307-
e, err := i.Entity("EntityOne", "test")
293+
e, err := i.Entity("EntityOne", "test", NewIDAttribute("env", "prod"))
308294
assert.NoError(t, err)
309295
ms := e.NewMetricSet("EventTypeForEntityOne")
310296
assert.NoError(t, ms.SetMetric("metricOne", 1, metric.GAUGE))

integration/options_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func TestWriter(t *testing.T) {
2525

2626
assert.NoError(t, i.Publish())
2727

28-
assert.Equal(t, `{"name":"integration","protocol_version":"2","integration_version":"7.0","data":[]}`+"\n", w.String())
28+
assert.Equal(t, `{"name":"integration","protocol_version":"3","integration_version":"7.0","data":[]}`+"\n", w.String())
2929
}
3030

3131
func TestArgs(t *testing.T) {

0 commit comments

Comments
 (0)