Skip to content

Commit

Permalink
[extension/opamp] Add config options to specify extra non-identifying…
Browse files Browse the repository at this point in the history
… attributes (#32153)

**Description:** <Describe what has changed.>
* Adds a new `agent_description.non_identifying_attributes` config
option to allow setting user-defined non-identifying attributes

**Link to tracking Issue:** Closes #32107

**Testing:**
Added unit tests
Manually tested against an OpAMP server

**Documentation:**
Added new parameter to extension docs
  • Loading branch information
BinaryFissionGames authored Apr 16, 2024
1 parent e4cd403 commit c356157
Show file tree
Hide file tree
Showing 9 changed files with 149 additions and 16 deletions.
13 changes: 13 additions & 0 deletions .chloggen/feat_opamp-extension-user-defined-attributes.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Use this changelog template to create an entry for release notes.

# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: enhancement

# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
component: opampextension

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Added a new `agent_description.non_identifying_attributes` config option to allow setting user-defined non-identifying attributes

# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
issues: [32107]
2 changes: 1 addition & 1 deletion cmd/otelcontribcol/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -694,7 +694,7 @@ require (
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/crypto v0.22.0 // indirect
golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3 // indirect
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect
golang.org/x/mod v0.16.0 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/oauth2 v0.19.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions cmd/otelcontribcol/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions extension/opampextension/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ The following settings are optional:
instance UID remains constant across process restarts.
- `capabilities`: Keys with boolean true/false values that enable a particular OpAMP capability.
- `reports_effective_config`: Whether to enable the OpAMP ReportsEffectiveConfig capability. Default is `true`.
- `agent_description`: Setting that modifies the agent description reported to the OpAMP server.
- `non_identifying_attributes`: A map of key value pairs that will be added to the [non-identifying attributes](https://github.com/open-telemetry/opamp-spec/blob/main/specification.md#agentdescriptionnon_identifying_attributes) reported to the OpAMP server. If an attribute collides with the default non-identifying attributes that are automatically added, the ones specified here take precedence.

### Example

Expand Down
9 changes: 9 additions & 0 deletions extension/opampextension/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ type Config struct {

// Capabilities contains options to enable a particular OpAMP capability
Capabilities Capabilities `mapstructure:"capabilities"`

// Agent descriptions contains options to modify the AgentDescription message
AgentDescription AgentDescription `mapstructure:"agent_description"`
}

type AgentDescription struct {
// NonIdentifyingAttributes are a map of key-value pairs that may be specified to provide
// extra information about the agent to the OpAMP server.
NonIdentifyingAttributes map[string]string `mapstructure:"non_identifying_attributes"`
}

type Capabilities struct {
Expand Down
1 change: 1 addition & 0 deletions extension/opampextension/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ require (
go.opentelemetry.io/otel/trace v1.25.0
go.uber.org/goleak v1.3.0
go.uber.org/zap v1.27.0
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8
gopkg.in/yaml.v3 v3.0.1
)

Expand Down
2 changes: 2 additions & 0 deletions extension/opampextension/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 21 additions & 4 deletions extension/opampextension/opamp_agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net/http"
"os"
"runtime"
"sort"
"strings"
"sync"

Expand All @@ -21,6 +22,7 @@ import (
"go.opentelemetry.io/collector/pdata/pcommon"
semconv "go.opentelemetry.io/collector/semconv/v1.18.0"
"go.uber.org/zap"
"golang.org/x/exp/maps"
"gopkg.in/yaml.v3"
)

Expand Down Expand Up @@ -194,10 +196,25 @@ func (o *opampAgent) createAgentDescription() error {
stringKeyValue(semconv.AttributeServiceVersion, o.agentVersion),
}

nonIdent := []*protobufs.KeyValue{
stringKeyValue(semconv.AttributeOSType, runtime.GOOS),
stringKeyValue(semconv.AttributeHostArch, runtime.GOARCH),
stringKeyValue(semconv.AttributeHostName, hostname),
// Initially construct using a map to properly deduplicate any keys that
// are both automatically determined and defined in the config
nonIdentifyingAttributeMap := map[string]string{}
nonIdentifyingAttributeMap[semconv.AttributeOSType] = runtime.GOOS
nonIdentifyingAttributeMap[semconv.AttributeHostArch] = runtime.GOARCH
nonIdentifyingAttributeMap[semconv.AttributeHostName] = hostname

for k, v := range o.cfg.AgentDescription.NonIdentifyingAttributes {
nonIdentifyingAttributeMap[k] = v
}

// Sort the non identifying attributes to give them a stable order for tests
keys := maps.Keys(nonIdentifyingAttributeMap)
sort.Strings(keys)

nonIdent := make([]*protobufs.KeyValue, 0, len(nonIdentifyingAttributeMap))
for _, k := range keys {
v := nonIdentifyingAttributeMap[k]
nonIdent = append(nonIdent, stringKeyValue(k, v))
}

o.agentDescription = &protobufs.AgentDescription{
Expand Down
107 changes: 98 additions & 9 deletions extension/opampextension/opamp_agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ import (
"context"
"os"
"path/filepath"
"runtime"
"testing"

"github.com/oklog/ulid/v2"
"github.com/open-telemetry/opamp-go/protobufs"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/confmap/confmaptest"
Expand Down Expand Up @@ -47,15 +50,101 @@ func TestNewOpampAgentAttributes(t *testing.T) {
}

func TestCreateAgentDescription(t *testing.T) {
cfg := createDefaultConfig()
set := extensiontest.NewNopCreateSettings()
o, err := newOpampAgent(cfg.(*Config), set.Logger, set.BuildInfo, set.Resource)
assert.NoError(t, err)

assert.Nil(t, o.agentDescription)
err = o.createAgentDescription()
assert.NoError(t, err)
assert.NotNil(t, o.agentDescription)
hostname, err := os.Hostname()
require.NoError(t, err)

serviceName := "otelcol-distrot"
serviceVersion := "distro.0"
serviceInstanceUUID := "f8999bc1-4c9b-4619-9bae-7f009d2411ec"
serviceInstanceULID := "7RK6DW2K4V8RCSQBKZ02EJ84FC"

testCases := []struct {
name string
cfg func(*Config)

expected *protobufs.AgentDescription
}{
{
name: "No extra attributes",
cfg: func(_ *Config) {},
expected: &protobufs.AgentDescription{
IdentifyingAttributes: []*protobufs.KeyValue{
stringKeyValue(semconv.AttributeServiceInstanceID, serviceInstanceULID),
stringKeyValue(semconv.AttributeServiceName, serviceName),
stringKeyValue(semconv.AttributeServiceVersion, serviceVersion),
},
NonIdentifyingAttributes: []*protobufs.KeyValue{
stringKeyValue(semconv.AttributeHostArch, runtime.GOARCH),
stringKeyValue(semconv.AttributeHostName, hostname),
stringKeyValue(semconv.AttributeOSType, runtime.GOOS),
},
},
},
{
name: "Extra attributes specified",
cfg: func(c *Config) {
c.AgentDescription.NonIdentifyingAttributes = map[string]string{
"env": "prod",
semconv.AttributeK8SPodName: "my-very-cool-pod",
}
},
expected: &protobufs.AgentDescription{
IdentifyingAttributes: []*protobufs.KeyValue{
stringKeyValue(semconv.AttributeServiceInstanceID, serviceInstanceULID),
stringKeyValue(semconv.AttributeServiceName, serviceName),
stringKeyValue(semconv.AttributeServiceVersion, serviceVersion),
},
NonIdentifyingAttributes: []*protobufs.KeyValue{
stringKeyValue("env", "prod"),
stringKeyValue(semconv.AttributeHostArch, runtime.GOARCH),
stringKeyValue(semconv.AttributeHostName, hostname),
stringKeyValue(semconv.AttributeK8SPodName, "my-very-cool-pod"),
stringKeyValue(semconv.AttributeOSType, runtime.GOOS),
},
},
},
{
name: "Extra attributes override",
cfg: func(c *Config) {
c.AgentDescription.NonIdentifyingAttributes = map[string]string{
semconv.AttributeHostName: "override-host",
}
},
expected: &protobufs.AgentDescription{
IdentifyingAttributes: []*protobufs.KeyValue{
stringKeyValue(semconv.AttributeServiceInstanceID, serviceInstanceULID),
stringKeyValue(semconv.AttributeServiceName, serviceName),
stringKeyValue(semconv.AttributeServiceVersion, serviceVersion),
},
NonIdentifyingAttributes: []*protobufs.KeyValue{
stringKeyValue(semconv.AttributeHostArch, runtime.GOARCH),
stringKeyValue(semconv.AttributeHostName, "override-host"),
stringKeyValue(semconv.AttributeOSType, runtime.GOOS),
},
},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {

cfg := createDefaultConfig().(*Config)
tc.cfg(cfg)

set := extensiontest.NewNopCreateSettings()
set.Resource.Attributes().PutStr(semconv.AttributeServiceName, serviceName)
set.Resource.Attributes().PutStr(semconv.AttributeServiceVersion, serviceVersion)
set.Resource.Attributes().PutStr(semconv.AttributeServiceInstanceID, serviceInstanceUUID)

o, err := newOpampAgent(cfg, set.Logger, set.BuildInfo, set.Resource)
require.NoError(t, err)
assert.Nil(t, o.agentDescription)

err = o.createAgentDescription()
assert.NoError(t, err)
require.Equal(t, tc.expected, o.agentDescription)
})
}
}

func TestUpdateAgentIdentity(t *testing.T) {
Expand Down

0 comments on commit c356157

Please sign in to comment.