Skip to content

Commit b056433

Browse files
authoredJan 11, 2023
[new-component] Operator OpAMP Bridge Service (#1339)
* Begin the journey * Some hacks to functionality * Fix create bug, getting prepped for refactor * Some reorganization and cleaning, added tests * Add deletion and dockerfile * Add logic for specifying allowed components * Linting, CI, header * Makefile CI * Added comments, created an object for use by the applied keys map * Fix makefile, update to use require * IMports * updated from comments * Improved logging, working dockerfile * Rename the whole thing * Change service name * Update from feedback
1 parent 0bdaa9e commit b056433

29 files changed

+3013
-3
lines changed
 
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
2+
change_type: new_component
3+
4+
# The name of the component, or a single word describing the area of concern, (e.g. operator, target allocator, github action)
5+
component: Operator OpAMP Bridge
6+
7+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
8+
note: Introducing the Operator OpAMP Bridge service, which allows a user to manage OpenTelemetry Collector CRDs via OpAMP
9+
10+
# One or more tracking issues related to the change
11+
issues: [1318]
12+
13+
# (Optional) One or more lines of additional information to render under the primary note.
14+
# These lines will be padded with 2 spaces and then inserted directly into the document.
15+
# Use pipe (|) for multiline entries.
16+
subtext:

‎.github/workflows/continuous-integration.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
runs-on: ubuntu-20.04
3434
strategy:
3535
matrix:
36-
workdir: [".", "./cmd/otel-allocator"]
36+
workdir: [".", "./cmd/otel-allocator", "./cmd/operator-opamp-bridge"]
3737
steps:
3838
- name: Set up Go
3939
uses: actions/setup-go@v3

‎.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*.dylib
88
bin
99
vendor
10+
.DS_Store
1011

1112
# Test binary, build with `go test -c`
1213
*.test

‎Makefile

+15-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ BUNDLE_IMG ?= ${IMG_PREFIX}/${IMG_REPO}-bundle:${VERSION}
2121
TARGETALLOCATOR_IMG_REPO ?= target-allocator
2222
TARGETALLOCATOR_IMG ?= ${IMG_PREFIX}/${TARGETALLOCATOR_IMG_REPO}:$(addprefix v,${VERSION})
2323

24+
OPERATOROPAMPBRIDGE_IMG_REPO ?= operator-opamp-bridge
25+
OPERATOROPAMPBRIDGE_IMG ?= ${IMG_PREFIX}/${OPERATOROPAMPBRIDGE_IMG_REPO}:$(addprefix v,${VERSION})
26+
2427
# Options for 'bundle-build'
2528
ifneq ($(origin CHANNELS), undefined)
2629
BUNDLE_CHANNELS := --channels=$(CHANNELS)
@@ -89,6 +92,7 @@ ci: test
8992
test: generate fmt vet ensure-generate-is-noop envtest
9093
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(KUBE_VERSION) -p path)" go test ${GOTEST_OPTS} ./...
9194
cd cmd/otel-allocator && KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(KUBE_VERSION) -p path)" go test ${GOTEST_OPTS} ./...
95+
cd cmd/operator-opamp-bridge && KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(KUBE_VERSION) -p path)" go test ${GOTEST_OPTS} ./...
9296

9397
# Build manager binary
9498
.PHONY: manager
@@ -152,6 +156,7 @@ vet:
152156
lint:
153157
golangci-lint run
154158
cd cmd/otel-allocator && golangci-lint run
159+
cd cmd/operator-opamp-bridge && golangci-lint run
155160

156161
# Generate code
157162
.PHONY: generate
@@ -174,7 +179,7 @@ e2e-log-operator:
174179
kubectl get deploy -A
175180

176181
.PHONY: prepare-e2e
177-
prepare-e2e: kuttl set-image-controller container container-target-allocator start-kind cert-manager install-metrics-server install-openshift-routes load-image-all deploy
182+
prepare-e2e: kuttl set-image-controller container container-target-allocator container-operator-opamp-bridge start-kind cert-manager install-metrics-server install-openshift-routes load-image-all deploy
178183
TARGETALLOCATOR_IMG=$(TARGETALLOCATOR_IMG) ./hack/modify-test-images.sh
179184

180185
.PHONY: scorecard-tests
@@ -201,6 +206,10 @@ container-target-allocator-push:
201206
container-target-allocator:
202207
docker buildx build --load --platform linux/${ARCH} -t ${TARGETALLOCATOR_IMG} cmd/otel-allocator
203208

209+
.PHONY: container-operator-opamp-bridge
210+
container-operator-opamp-bridge:
211+
docker buildx build --platform linux/${ARCH} -t ${OPERATOROPAMPBRIDGE_IMG} cmd/operator-opamp-bridge
212+
204213
.PHONY: start-kind
205214
start-kind:
206215
ifeq (true,$(START_KIND_CLUSTER))
@@ -216,7 +225,7 @@ install-openshift-routes:
216225
./hack/install-openshift-routes.sh
217226

218227
.PHONY: load-image-all
219-
load-image-all: load-image-operator load-image-target-allocator
228+
load-image-all: load-image-operator load-image-target-allocator load-image-operator-opamp-bridge
220229

221230
.PHONY: load-image-operator
222231
load-image-operator: container
@@ -236,6 +245,10 @@ else
236245
endif
237246

238247

248+
.PHONY: load-image-operator-opamp-bridge
249+
load-image-operator-opamp-bridge:
250+
kind load docker-image ${OPERATOROPAMPBRIDGE_IMG}
251+
239252
.PHONY: cert-manager
240253
cert-manager: cmctl
241254
# Consider using cmctl to install the cert-manager once install command is not experimental

‎cmd/operator-opamp-bridge/Dockerfile

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Build the operator-opamp-bridge binary
2+
FROM golang:1.19-alpine as builder
3+
4+
WORKDIR /app
5+
6+
RUN apk --no-cache add ca-certificates
7+
8+
# Copy go mod and sum files
9+
COPY go.mod go.sum ./
10+
11+
RUN go mod download
12+
13+
COPY . .
14+
15+
# Build the Go app
16+
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
17+
18+
######## Start a new stage from scratch #######
19+
FROM scratch
20+
21+
WORKDIR /root/
22+
23+
# Copy the certs from the builder
24+
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
25+
26+
# Copy the pre-built binary file from the previous stage
27+
COPY --from=builder /app/main .
28+
29+
ENTRYPOINT ["./main"]
+295
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package agent
16+
17+
import (
18+
"bytes"
19+
"context"
20+
"time"
21+
22+
"gopkg.in/yaml.v3"
23+
24+
"github.com/open-telemetry/opentelemetry-operator/cmd/operator-opamp-bridge/metrics"
25+
"github.com/open-telemetry/opentelemetry-operator/cmd/operator-opamp-bridge/operator"
26+
27+
"github.com/open-telemetry/opentelemetry-operator/cmd/operator-opamp-bridge/config"
28+
29+
"github.com/oklog/ulid/v2"
30+
"go.uber.org/multierr"
31+
32+
"github.com/open-telemetry/opamp-go/client"
33+
"github.com/open-telemetry/opamp-go/client/types"
34+
"github.com/open-telemetry/opamp-go/protobufs"
35+
)
36+
37+
type Agent struct {
38+
logger types.Logger
39+
40+
appliedKeys map[collectorKey]bool
41+
startTime uint64
42+
lastHash []byte
43+
44+
instanceId ulid.ULID
45+
agentDescription *protobufs.AgentDescription
46+
remoteConfigStatus *protobufs.RemoteConfigStatus
47+
48+
opampClient client.OpAMPClient
49+
metricReporter *metrics.MetricReporter
50+
config config.Config
51+
applier operator.ConfigApplier
52+
remoteConfigEnabled bool
53+
}
54+
55+
func NewAgent(logger types.Logger, applier operator.ConfigApplier, config config.Config, opampClient client.OpAMPClient) *Agent {
56+
agent := &Agent{
57+
config: config,
58+
applier: applier,
59+
logger: logger,
60+
appliedKeys: map[collectorKey]bool{},
61+
instanceId: config.GetNewInstanceId(),
62+
agentDescription: config.GetDescription(),
63+
remoteConfigEnabled: config.RemoteConfigEnabled(),
64+
opampClient: opampClient,
65+
}
66+
67+
agent.logger.Debugf("Agent created, id=%v, type=%s, version=%s.",
68+
agent.instanceId.String(), config.GetAgentType(), config.GetAgentVersion())
69+
70+
return agent
71+
}
72+
73+
// TODO: Something should run on a schedule to set the health of the OpAMP client.
74+
func (agent *Agent) getHealth() *protobufs.AgentHealth {
75+
return &protobufs.AgentHealth{
76+
Healthy: true,
77+
StartTimeUnixNano: agent.startTime,
78+
LastError: "",
79+
}
80+
}
81+
82+
// onConnect is called when an agent is successfully connected to a server.
83+
func (agent *Agent) onConnect() {
84+
agent.logger.Debugf("Connected to the server.")
85+
}
86+
87+
// onConnectFailed is called when an agent was unable to connect to a server.
88+
func (agent *Agent) onConnectFailed(err error) {
89+
agent.logger.Errorf("Failed to connect to the server: %v", err)
90+
}
91+
92+
// onError is called when an agent receives an error response from the server.
93+
func (agent *Agent) onError(err *protobufs.ServerErrorResponse) {
94+
agent.logger.Errorf("Server returned an error response: %v", err.ErrorMessage)
95+
}
96+
97+
// saveRemoteConfigStatus receives a status from the server when the server sets a remote configuration.
98+
func (agent *Agent) saveRemoteConfigStatus(_ context.Context, status *protobufs.RemoteConfigStatus) {
99+
agent.remoteConfigStatus = status
100+
}
101+
102+
// Start sets up the callbacks for the OpAMP client and begins the client's connection to the server.
103+
func (agent *Agent) Start() error {
104+
agent.startTime = uint64(time.Now().UnixNano())
105+
settings := types.StartSettings{
106+
OpAMPServerURL: agent.config.Endpoint,
107+
InstanceUid: agent.instanceId.String(),
108+
Callbacks: types.CallbacksStruct{
109+
OnConnectFunc: agent.onConnect,
110+
OnConnectFailedFunc: agent.onConnectFailed,
111+
OnErrorFunc: agent.onError,
112+
SaveRemoteConfigStatusFunc: agent.saveRemoteConfigStatus,
113+
GetEffectiveConfigFunc: agent.getEffectiveConfig,
114+
OnMessageFunc: agent.onMessage,
115+
},
116+
RemoteConfigStatus: agent.remoteConfigStatus,
117+
PackagesStateProvider: nil,
118+
Capabilities: agent.config.GetCapabilities(),
119+
}
120+
err := agent.opampClient.SetAgentDescription(agent.agentDescription)
121+
if err != nil {
122+
return err
123+
}
124+
err = agent.opampClient.SetHealth(agent.getHealth())
125+
if err != nil {
126+
return err
127+
}
128+
129+
agent.logger.Debugf("Starting OpAMP client...")
130+
131+
err = agent.opampClient.Start(context.Background(), settings)
132+
if err != nil {
133+
return err
134+
}
135+
136+
agent.logger.Debugf("OpAMP Client started.")
137+
138+
return nil
139+
}
140+
141+
// updateAgentIdentity receives a new instanced Id from the remote server and updates the agent's instanceID field.
142+
// The meter will be reinitialized by the onMessage function.
143+
func (agent *Agent) updateAgentIdentity(instanceId ulid.ULID) {
144+
agent.logger.Debugf("Agent identity is being changed from id=%v to id=%v",
145+
agent.instanceId.String(),
146+
instanceId.String())
147+
agent.instanceId = instanceId
148+
}
149+
150+
// getEffectiveConfig is called when a remote server needs to learn of the current effective configuration of each
151+
// collector the agent is managing.
152+
func (agent *Agent) getEffectiveConfig(ctx context.Context) (*protobufs.EffectiveConfig, error) {
153+
instances, err := agent.applier.ListInstances()
154+
if err != nil {
155+
agent.logger.Errorf("couldn't list instances", err)
156+
return nil, err
157+
}
158+
instanceMap := map[string]*protobufs.AgentConfigFile{}
159+
for _, instance := range instances {
160+
marshaled, err := yaml.Marshal(instance)
161+
if err != nil {
162+
agent.logger.Errorf("couldn't marshal collector configuration", err)
163+
return nil, err
164+
}
165+
mapKey := newCollectorKey(instance.GetName(), instance.GetNamespace())
166+
instanceMap[mapKey.String()] = &protobufs.AgentConfigFile{
167+
Body: marshaled,
168+
ContentType: "yaml",
169+
}
170+
}
171+
return &protobufs.EffectiveConfig{
172+
ConfigMap: &protobufs.AgentConfigMap{
173+
ConfigMap: instanceMap,
174+
},
175+
}, nil
176+
}
177+
178+
// initMeter initializes a metric reporter instance for the agent to report runtime metrics to the
179+
// configured destination. The settings received will be used to initialize a reporter, shutting down any previously
180+
// running metrics reporting instances.
181+
func (agent *Agent) initMeter(settings *protobufs.TelemetryConnectionSettings) {
182+
reporter, err := metrics.NewMetricReporter(agent.logger, settings, agent.config.GetAgentType(), agent.config.GetAgentVersion(), agent.instanceId)
183+
if err != nil {
184+
agent.logger.Errorf("Cannot collect metrics: %v", err)
185+
return
186+
}
187+
188+
if agent.metricReporter != nil {
189+
agent.metricReporter.Shutdown()
190+
}
191+
agent.metricReporter = reporter
192+
}
193+
194+
// applyRemoteConfig receives a remote configuration from a remote server of the following form:
195+
//
196+
// map[name/namespace] -> collector CRD spec
197+
//
198+
// For every key in the received remote configuration, the agent attempts to apply it to the connected
199+
// Kubernetes cluster. If an agent fails to apply a collector CRD, it will continue to the next entry. The agent will
200+
// store the received configuration hash regardless of application status as per the OpAMP spec.
201+
//
202+
// INVARIANT: The caller must verify that config isn't nil _and_ the configuration has changed between calls.
203+
func (agent *Agent) applyRemoteConfig(config *protobufs.AgentRemoteConfig) (*protobufs.RemoteConfigStatus, error) {
204+
var multiErr error
205+
// Apply changes from the received config map
206+
for key, file := range config.Config.GetConfigMap() {
207+
if len(key) == 0 || len(file.Body) == 0 {
208+
continue
209+
}
210+
colKey, err := collectorKeyFromKey(key)
211+
if err != nil {
212+
multiErr = multierr.Append(multiErr, err)
213+
continue
214+
}
215+
err = agent.applier.Apply(colKey.name, colKey.namespace, file)
216+
if err != nil {
217+
multiErr = multierr.Append(multiErr, err)
218+
continue
219+
}
220+
agent.appliedKeys[colKey] = true
221+
}
222+
// Check if anything was deleted
223+
for collectorKey := range agent.appliedKeys {
224+
if _, ok := config.Config.GetConfigMap()[collectorKey.String()]; !ok {
225+
err := agent.applier.Delete(collectorKey.name, collectorKey.namespace)
226+
if err != nil {
227+
multiErr = multierr.Append(multiErr, err)
228+
}
229+
}
230+
}
231+
agent.lastHash = config.GetConfigHash()
232+
if multiErr != nil {
233+
return &protobufs.RemoteConfigStatus{
234+
LastRemoteConfigHash: agent.lastHash,
235+
Status: protobufs.RemoteConfigStatuses_RemoteConfigStatuses_FAILED,
236+
ErrorMessage: multiErr.Error(),
237+
}, multiErr
238+
}
239+
return &protobufs.RemoteConfigStatus{
240+
LastRemoteConfigHash: agent.lastHash,
241+
Status: protobufs.RemoteConfigStatuses_RemoteConfigStatuses_APPLIED,
242+
}, nil
243+
}
244+
245+
// Shutdown will stop the OpAMP client gracefully.
246+
func (agent *Agent) Shutdown() {
247+
agent.logger.Debugf("Agent shutting down...")
248+
if agent.opampClient != nil {
249+
err := agent.opampClient.Stop(context.Background())
250+
if err != nil {
251+
agent.logger.Errorf(err.Error())
252+
}
253+
}
254+
if agent.metricReporter != nil {
255+
agent.metricReporter.Shutdown()
256+
}
257+
}
258+
259+
// onMessage is called when the client receives a new message from the connected OpAMP server. The agent is responsible
260+
// for checking if it should apply a new remote configuration. The agent will also initialize metrics based on the
261+
// settings received from the server. The agent is also able to update its identifier if it needs to.
262+
func (agent *Agent) onMessage(ctx context.Context, msg *types.MessageData) {
263+
// If we received remote configuration, and it's not the same as the previously applied one
264+
if agent.remoteConfigEnabled && msg.RemoteConfig != nil && !bytes.Equal(agent.lastHash, msg.RemoteConfig.GetConfigHash()) {
265+
var err error
266+
status, err := agent.applyRemoteConfig(msg.RemoteConfig)
267+
if err != nil {
268+
agent.logger.Errorf(err.Error())
269+
}
270+
err = agent.opampClient.SetRemoteConfigStatus(status)
271+
if err != nil {
272+
agent.logger.Errorf(err.Error())
273+
return
274+
}
275+
err = agent.opampClient.UpdateEffectiveConfig(ctx)
276+
if err != nil {
277+
agent.logger.Errorf(err.Error())
278+
}
279+
}
280+
281+
// The instance id is updated prior to the meter initialization so that the new meter will report using the updated
282+
// instanceId.
283+
if msg.AgentIdentification != nil {
284+
newInstanceId, err := ulid.Parse(msg.AgentIdentification.NewInstanceUid)
285+
if err != nil {
286+
agent.logger.Errorf(err.Error())
287+
return
288+
}
289+
agent.updateAgentIdentity(newInstanceId)
290+
}
291+
292+
if msg.OwnMetricsConnSettings != nil {
293+
agent.initMeter(msg.OwnMetricsConnSettings)
294+
}
295+
}

‎cmd/operator-opamp-bridge/agent/agent_test.go

+555
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package agent
16+
17+
import (
18+
"errors"
19+
"fmt"
20+
"strings"
21+
)
22+
23+
type collectorKey struct {
24+
name string
25+
namespace string
26+
}
27+
28+
func newCollectorKey(name string, namespace string) collectorKey {
29+
return collectorKey{name: name, namespace: namespace}
30+
}
31+
32+
func collectorKeyFromKey(key string) (collectorKey, error) {
33+
s := strings.Split(key, "/")
34+
// We expect map keys to be of the form name/namespace
35+
if len(s) != 2 {
36+
return collectorKey{}, errors.New("invalid key")
37+
}
38+
return newCollectorKey(s[0], s[1]), nil
39+
}
40+
41+
func (k collectorKey) String() string {
42+
return fmt.Sprintf("%s/%s", k.name, k.namespace)
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package agent
16+
17+
import (
18+
"fmt"
19+
"testing"
20+
21+
"github.com/stretchr/testify/assert"
22+
)
23+
24+
func Test_collectorKeyFromKey(t *testing.T) {
25+
type args struct {
26+
key string
27+
}
28+
tests := []struct {
29+
name string
30+
args args
31+
want collectorKey
32+
wantErr assert.ErrorAssertionFunc
33+
}{
34+
{
35+
name: "base case",
36+
args: args{
37+
key: "good/namespace",
38+
},
39+
want: collectorKey{
40+
name: "good",
41+
namespace: "namespace",
42+
},
43+
wantErr: assert.NoError,
44+
},
45+
{
46+
name: "unable to get key",
47+
args: args{
48+
key: "badnamespace",
49+
},
50+
want: collectorKey{},
51+
wantErr: assert.Error,
52+
},
53+
{
54+
name: "too many slashes",
55+
args: args{
56+
key: "too/many/slashes",
57+
},
58+
want: collectorKey{},
59+
wantErr: assert.Error,
60+
},
61+
}
62+
for _, tt := range tests {
63+
t.Run(tt.name, func(t *testing.T) {
64+
got, err := collectorKeyFromKey(tt.args.key)
65+
if !tt.wantErr(t, err, fmt.Sprintf("collectorKeyFromKey(%v)", tt.args.key)) {
66+
return
67+
}
68+
assert.Equalf(t, tt.want, got, "collectorKeyFromKey(%v)", tt.args.key)
69+
})
70+
}
71+
}
72+
73+
func Test_collectorKey_String(t *testing.T) {
74+
type fields struct {
75+
name string
76+
namespace string
77+
}
78+
tests := []struct {
79+
name string
80+
fields fields
81+
want string
82+
}{
83+
{
84+
name: "can make a key",
85+
fields: fields{
86+
name: "good",
87+
namespace: "namespace",
88+
},
89+
want: "good/namespace",
90+
},
91+
}
92+
for _, tt := range tests {
93+
t.Run(tt.name, func(t *testing.T) {
94+
k := newCollectorKey(tt.fields.name, tt.fields.namespace)
95+
assert.Equalf(t, tt.want, k.String(), "String()")
96+
})
97+
}
98+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
endpoint: ws://127.0.0.1:4320/v1/opamp
2+
protocol: wss
3+
capabilities:
4+
- AcceptsRemoteConfig
5+
- ReportsEffectiveConfig
6+
# - AcceptsPackages
7+
# - ReportsPackageStatuses
8+
- ReportsOwnTraces
9+
- ReportsOwnMetrics
10+
- ReportsOwnLogs
11+
- AcceptsOpAMPConnectionSettings
12+
- AcceptsOtherConnectionSettings
13+
- AcceptsRestartCommand
14+
- ReportsHealth
15+
- ReportsRemoteConfig
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
endpoint: ws://127.0.0.1:4320/v1/opamp
2+
protocol: wss
3+
capabilities:
4+
- AcceptsRemoteConfig
5+
- ReportsEffectiveConfig
6+
# - AcceptsPackages
7+
# - ReportsPackageStatuses
8+
- ReportsOwnTraces
9+
- ReportsOwnMetrics
10+
- ReportsOwnLogs
11+
- AcceptsOpAMPConnectionSettings
12+
- AcceptsOtherConnectionSettings
13+
- AcceptsRestartCommand
14+
- ReportsHealth
15+
- ReportsRemoteConfig
16+
components_allowed:
17+
receivers:
18+
- otlp
19+
processors:
20+
- memory_limiter
21+
- batch
22+
exporters:
23+
- logging
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
endpoint: ws://127.0.0.1:4320/v1/opamp
2+
protocol: wss
3+
capabilities:
4+
- AcceptsRemoteConfig
5+
- ReportsEffectiveConfig
6+
# - AcceptsPackages
7+
# - ReportsPackageStatuses
8+
- ReportsOwnTraces
9+
- ReportsOwnMetrics
10+
- ReportsOwnLogs
11+
- AcceptsOpAMPConnectionSettings
12+
- AcceptsOtherConnectionSettings
13+
- AcceptsRestartCommand
14+
- ReportsHealth
15+
- ReportsRemoteConfig
16+
components_allowed:
17+
receivers:
18+
- otlp
19+
processors:
20+
- memory_limiter
21+
exporters:
22+
- logging
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
endpoint: ws://127.0.0.1:4320/v1/opamp
2+
protocol: wss
3+
capabilities:
4+
- AcceptsRemoteConfig
5+
- ReportsEffectiveConfig
6+
# - AcceptsPackages
7+
# - ReportsPackageStatuses
8+
- ReportsOwnTraces
9+
- ReportsOwnMetrics
10+
- ReportsOwnLogs
11+
- AcceptsOpAMPConnectionSettings
12+
- AcceptsOtherConnectionSettings
13+
- AcceptsRestartCommand
14+
- ReportsHealth
15+
- ReportsRemoteConfig
16+
components_allowed:
17+
receivers:
18+
- otlp
19+
exporters:
20+
- logging
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
config: |
2+
receivers:
3+
otlp:
4+
protocols:
5+
grpc:
6+
http:
7+
processors:
8+
memory_limiter:
9+
check_interval: 1s
10+
limit_percentage: 75
11+
spike_limit_percentage: 15
12+
batch:
13+
send_batch_size: 10000
14+
timeout: 10s
15+
16+
exporters:
17+
logging:
18+
19+
service:
20+
pipelines:
21+
traces:
22+
receivers: [otlp]
23+
processors: []
24+
exporters: [logging]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
config: |
2+
receivers:
3+
otlp:
4+
protocols:
5+
grpc:
6+
http:
7+
processors:
8+
memory_limiter:
9+
check_interval: 1s
10+
limit_percentage: 75
11+
spike_limit_percentage: 15
12+
batch:
13+
send_batch_size: 10000
14+
timeout: 10s
15+
16+
GARBAGE
17+
exporters:
18+
logging:
19+
service:
20+
pipelines:
21+
traces:
22+
receivers: [otlp]
23+
processors: []
24+
exporters: [logging]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
config: |
2+
receivers:
3+
otlp:
4+
protocols:
5+
grpc:
6+
http:
7+
processors:
8+
memory_limiter:
9+
check_interval: 1s
10+
limit_percentage: 75
11+
spike_limit_percentage: 15
12+
batch:
13+
send_batch_size: 10000
14+
timeout: 10s
15+
16+
exporters:
17+
logging:
18+
19+
service:
20+
pipelines:
21+
traces:
22+
receivers: [otlp]
23+
processors: [memory_limiter, batch]
24+
exporters: [logging]
25+
replicas: 3
+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package config
16+
17+
import (
18+
"errors"
19+
"flag"
20+
"io/fs"
21+
"path/filepath"
22+
23+
"github.com/go-logr/logr"
24+
"github.com/spf13/pflag"
25+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26+
"k8s.io/apimachinery/pkg/runtime"
27+
"k8s.io/client-go/kubernetes/scheme"
28+
"k8s.io/client-go/rest"
29+
"k8s.io/client-go/tools/clientcmd"
30+
"k8s.io/client-go/util/homedir"
31+
"k8s.io/klog/v2"
32+
"sigs.k8s.io/controller-runtime/pkg/client"
33+
"sigs.k8s.io/controller-runtime/pkg/log/zap"
34+
35+
"github.com/open-telemetry/opentelemetry-operator/apis/v1alpha1"
36+
)
37+
38+
var (
39+
schemeBuilder = runtime.NewSchemeBuilder(registerKnownTypes)
40+
)
41+
42+
func registerKnownTypes(s *runtime.Scheme) error {
43+
s.AddKnownTypes(v1alpha1.GroupVersion, &v1alpha1.OpenTelemetryCollector{}, &v1alpha1.OpenTelemetryCollectorList{})
44+
metav1.AddToGroupVersion(s, v1alpha1.GroupVersion)
45+
return nil
46+
}
47+
48+
type CLIConfig struct {
49+
ListenAddr *string
50+
ConfigFilePath *string
51+
52+
ClusterConfig *rest.Config
53+
// KubeConfigFilePath empty if in cluster configuration is in use
54+
KubeConfigFilePath string
55+
RootLogger logr.Logger
56+
}
57+
58+
func GetLogger() logr.Logger {
59+
opts := zap.Options{}
60+
opts.BindFlags(flag.CommandLine)
61+
62+
return zap.New(zap.UseFlagOptions(&opts))
63+
}
64+
65+
func ParseCLI(logger logr.Logger) (CLIConfig, error) {
66+
cLIConf := CLIConfig{
67+
RootLogger: logger,
68+
ListenAddr: pflag.String("listen-addr", ":8080", "The address where this service serves."),
69+
ConfigFilePath: pflag.String("config-file", defaultConfigFilePath, "The path to the config file."),
70+
}
71+
kubeconfigPath := pflag.String("kubeconfig-path", filepath.Join(homedir.HomeDir(), ".kube", "config"), "absolute path to the KubeconfigPath file")
72+
pflag.Parse()
73+
74+
klog.SetLogger(logger)
75+
76+
clusterConfig, err := clientcmd.BuildConfigFromFlags("", *kubeconfigPath)
77+
cLIConf.KubeConfigFilePath = *kubeconfigPath
78+
if err != nil {
79+
pathError := &fs.PathError{}
80+
if ok := errors.As(err, &pathError); !ok {
81+
return CLIConfig{}, err
82+
}
83+
clusterConfig, err = rest.InClusterConfig()
84+
if err != nil {
85+
return CLIConfig{}, err
86+
}
87+
cLIConf.KubeConfigFilePath = "" // reset as we use in cluster configuration
88+
}
89+
cLIConf.ClusterConfig = clusterConfig
90+
return cLIConf, nil
91+
}
92+
93+
func (cli CLIConfig) GetKubernetesClient() (client.Client, error) {
94+
err := schemeBuilder.AddToScheme(scheme.Scheme)
95+
if err != nil {
96+
return nil, err
97+
}
98+
return client.New(cli.ClusterConfig, client.Options{
99+
Scheme: scheme.Scheme,
100+
})
101+
}
+142
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package config
16+
17+
import (
18+
"crypto/rand"
19+
"fmt"
20+
"os"
21+
"runtime"
22+
"time"
23+
24+
"github.com/oklog/ulid/v2"
25+
"github.com/open-telemetry/opamp-go/client"
26+
"github.com/open-telemetry/opamp-go/protobufs"
27+
"gopkg.in/yaml.v2"
28+
29+
"github.com/open-telemetry/opentelemetry-operator/cmd/operator-opamp-bridge/logger"
30+
)
31+
32+
const (
33+
agentType = "io.opentelemetry.operator-opamp-bridge"
34+
defaultConfigFilePath = "/conf/remoteconfiguration.yaml"
35+
)
36+
37+
var (
38+
agentVersion = os.Getenv("OPAMP_VERSION")
39+
hostname, _ = os.Hostname()
40+
)
41+
42+
type Config struct {
43+
Endpoint string `yaml:"endpoint"`
44+
Protocol string `yaml:"protocol"`
45+
Capabilities []string `yaml:"capabilities"`
46+
47+
// ComponentsAllowed is a list of allowed OpenTelemetry components for each pipeline type (receiver, processor, etc.)
48+
ComponentsAllowed map[string][]string `yaml:"components_allowed,omitempty"`
49+
}
50+
51+
func (c *Config) CreateClient(logger *logger.Logger) client.OpAMPClient {
52+
if c.Protocol == "http" {
53+
return client.NewHTTP(logger)
54+
}
55+
return client.NewWebSocket(logger)
56+
}
57+
58+
func (c *Config) GetComponentsAllowed() map[string]map[string]bool {
59+
m := make(map[string]map[string]bool)
60+
for component, componentSet := range c.ComponentsAllowed {
61+
if _, ok := m[component]; !ok {
62+
m[component] = make(map[string]bool)
63+
}
64+
for _, s := range componentSet {
65+
m[component][s] = true
66+
}
67+
}
68+
return m
69+
}
70+
71+
func (c *Config) GetCapabilities() protobufs.AgentCapabilities {
72+
var capabilities int32
73+
for _, capability := range c.Capabilities {
74+
// This is a helper so that we don't force consumers to prefix every agent capability
75+
formatted := fmt.Sprintf("AgentCapabilities_%s", capability)
76+
if v, ok := protobufs.AgentCapabilities_value[formatted]; ok {
77+
capabilities = v | capabilities
78+
}
79+
}
80+
return protobufs.AgentCapabilities(capabilities)
81+
}
82+
83+
func (c *Config) GetAgentType() string {
84+
return agentType
85+
}
86+
87+
func (c *Config) GetAgentVersion() string {
88+
return agentVersion
89+
}
90+
91+
func (c *Config) GetDescription() *protobufs.AgentDescription {
92+
return &protobufs.AgentDescription{
93+
IdentifyingAttributes: []*protobufs.KeyValue{
94+
keyValuePair("service.name", c.GetAgentType()),
95+
keyValuePair("service.version", c.GetAgentVersion()),
96+
},
97+
NonIdentifyingAttributes: []*protobufs.KeyValue{
98+
keyValuePair("os.family", runtime.GOOS),
99+
keyValuePair("host.name", hostname),
100+
},
101+
}
102+
}
103+
104+
func keyValuePair(key string, value string) *protobufs.KeyValue {
105+
return &protobufs.KeyValue{
106+
Key: key,
107+
Value: &protobufs.AnyValue{
108+
Value: &protobufs.AnyValue_StringValue{
109+
StringValue: value,
110+
},
111+
},
112+
}
113+
}
114+
115+
func (c *Config) GetNewInstanceId() ulid.ULID {
116+
entropy := ulid.Monotonic(rand.Reader, 0)
117+
return ulid.MustNew(ulid.Timestamp(time.Now()), entropy)
118+
}
119+
120+
func (c *Config) RemoteConfigEnabled() bool {
121+
capabilities := c.GetCapabilities()
122+
return capabilities&protobufs.AgentCapabilities_AgentCapabilities_AcceptsRemoteConfig != 0
123+
}
124+
125+
func Load(file string) (Config, error) {
126+
var cfg Config
127+
if err := unmarshal(&cfg, file); err != nil {
128+
return Config{}, err
129+
}
130+
return cfg, nil
131+
}
132+
133+
func unmarshal(cfg *Config, configFile string) error {
134+
yamlFile, err := os.ReadFile(configFile)
135+
if err != nil {
136+
return err
137+
}
138+
if err = yaml.UnmarshalStrict(yamlFile, cfg); err != nil {
139+
return fmt.Errorf("error unmarshaling YAML: %w", err)
140+
}
141+
return nil
142+
}

‎cmd/operator-opamp-bridge/go.mod

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
module github.com/open-telemetry/opentelemetry-operator/cmd/operator-opamp-bridge
2+
3+
go 1.19
4+
5+
require (
6+
github.com/go-logr/logr v1.2.3
7+
github.com/oklog/ulid/v2 v2.0.2
8+
github.com/open-telemetry/opamp-go v0.5.0
9+
github.com/open-telemetry/opentelemetry-operator v1.51.0
10+
github.com/shirou/gopsutil v3.21.11+incompatible
11+
github.com/spf13/pflag v1.0.5
12+
github.com/stretchr/testify v1.8.1
13+
go.opentelemetry.io/otel v1.11.2
14+
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.34.0
15+
go.opentelemetry.io/otel/metric v0.34.0
16+
go.opentelemetry.io/otel/sdk v1.11.2
17+
go.opentelemetry.io/otel/sdk/metric v0.34.0
18+
go.uber.org/multierr v1.6.0
19+
gopkg.in/yaml.v2 v2.4.0
20+
gopkg.in/yaml.v3 v3.0.1
21+
k8s.io/apimachinery v0.26.0
22+
k8s.io/client-go v0.26.0
23+
k8s.io/klog/v2 v2.80.1
24+
sigs.k8s.io/controller-runtime v0.14.0
25+
)
26+
27+
require (
28+
github.com/beorn7/perks v1.0.1 // indirect
29+
github.com/cenkalti/backoff/v4 v4.2.0 // indirect
30+
github.com/cespare/xxhash/v2 v2.1.2 // indirect
31+
github.com/davecgh/go-spew v1.1.1 // indirect
32+
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
33+
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
34+
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
35+
github.com/fsnotify/fsnotify v1.6.0 // indirect
36+
github.com/go-logr/stdr v1.2.2 // indirect
37+
github.com/go-logr/zapr v1.2.3 // indirect
38+
github.com/go-ole/go-ole v1.2.6 // indirect
39+
github.com/go-openapi/jsonpointer v0.19.5 // indirect
40+
github.com/go-openapi/jsonreference v0.20.0 // indirect
41+
github.com/go-openapi/swag v0.19.14 // indirect
42+
github.com/gogo/protobuf v1.3.2 // indirect
43+
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
44+
github.com/golang/protobuf v1.5.2 // indirect
45+
github.com/google/gnostic v0.5.7-v3refs // indirect
46+
github.com/google/go-cmp v0.5.9 // indirect
47+
github.com/google/gofuzz v1.1.0 // indirect
48+
github.com/google/uuid v1.1.2 // indirect
49+
github.com/gorilla/websocket v1.4.2 // indirect
50+
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
51+
github.com/imdario/mergo v0.3.12 // indirect
52+
github.com/josharian/intern v1.0.0 // indirect
53+
github.com/json-iterator/go v1.1.12 // indirect
54+
github.com/mailru/easyjson v0.7.6 // indirect
55+
github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect
56+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
57+
github.com/modern-go/reflect2 v1.0.2 // indirect
58+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
59+
github.com/pkg/errors v0.9.1 // indirect
60+
github.com/pmezard/go-difflib v1.0.0 // indirect
61+
github.com/prometheus/client_golang v1.14.0 // indirect
62+
github.com/prometheus/client_model v0.3.0 // indirect
63+
github.com/prometheus/common v0.37.0 // indirect
64+
github.com/prometheus/procfs v0.8.0 // indirect
65+
github.com/tklauser/go-sysconf v0.3.11 // indirect
66+
github.com/tklauser/numcpus v0.6.0 // indirect
67+
github.com/yusufpapurcu/wmi v1.2.2 // indirect
68+
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.2 // indirect
69+
go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.34.0 // indirect
70+
go.opentelemetry.io/otel/trace v1.11.2 // indirect
71+
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
72+
go.uber.org/atomic v1.8.0 // indirect
73+
go.uber.org/zap v1.24.0 // indirect
74+
golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10 // indirect
75+
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect
76+
golang.org/x/sys v0.3.0 // indirect
77+
golang.org/x/term v0.3.0 // indirect
78+
golang.org/x/text v0.5.0 // indirect
79+
golang.org/x/time v0.3.0 // indirect
80+
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
81+
google.golang.org/appengine v1.6.7 // indirect
82+
google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect
83+
google.golang.org/grpc v1.51.0 // indirect
84+
google.golang.org/protobuf v1.28.1 // indirect
85+
gopkg.in/inf.v0 v0.9.1 // indirect
86+
k8s.io/api v0.26.0 // indirect
87+
k8s.io/apiextensions-apiserver v0.26.0 // indirect
88+
k8s.io/component-base v0.26.0 // indirect
89+
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
90+
k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 // indirect
91+
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
92+
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
93+
sigs.k8s.io/yaml v1.3.0 // indirect
94+
)

‎cmd/operator-opamp-bridge/go.sum

+707
Large diffs are not rendered by default.

‎cmd/operator-opamp-bridge/header.txt

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Copyright The OpenTelemetry Authors
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.
+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package logger
16+
17+
import (
18+
"fmt"
19+
20+
"github.com/go-logr/logr"
21+
"github.com/open-telemetry/opamp-go/client/types"
22+
)
23+
24+
var _ types.Logger = &Logger{}
25+
26+
type Logger struct {
27+
Logger *logr.Logger
28+
}
29+
30+
func NewLogger(logger *logr.Logger) *Logger {
31+
return &Logger{Logger: logger}
32+
}
33+
34+
func (l *Logger) Debugf(format string, v ...interface{}) {
35+
l.Logger.V(4).Info(fmt.Sprintf(format, v...))
36+
}
37+
38+
func (l *Logger) Errorf(format string, v ...interface{}) {
39+
l.Logger.V(0).Error(nil, fmt.Sprintf(format, v...))
40+
}

‎cmd/operator-opamp-bridge/main.go

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package main
16+
17+
import (
18+
"os"
19+
"os/signal"
20+
21+
"github.com/open-telemetry/opentelemetry-operator/cmd/operator-opamp-bridge/agent"
22+
"github.com/open-telemetry/opentelemetry-operator/cmd/operator-opamp-bridge/config"
23+
"github.com/open-telemetry/opentelemetry-operator/cmd/operator-opamp-bridge/logger"
24+
"github.com/open-telemetry/opentelemetry-operator/cmd/operator-opamp-bridge/operator"
25+
)
26+
27+
func main() {
28+
l := config.GetLogger()
29+
cliConf, err := config.ParseCLI(l.WithName("cli-config"))
30+
if err != nil {
31+
l.Error(err, "unable to load ")
32+
os.Exit(1)
33+
}
34+
cfg, configLoadErr := config.Load(*cliConf.ConfigFilePath)
35+
if configLoadErr != nil {
36+
l.Error(configLoadErr, "Unable to load configuration")
37+
return
38+
}
39+
l.Info("Starting the Remote Configuration service")
40+
agentLogf := l.WithName("agent")
41+
agentLogger := logger.NewLogger(&agentLogf)
42+
43+
kubeClient, kubeErr := cliConf.GetKubernetesClient()
44+
if kubeErr != nil {
45+
l.Error(kubeErr, "Couldn't create kubernetes client")
46+
os.Exit(1)
47+
}
48+
operatorClient := operator.NewClient(l.WithName("operator-client"), kubeClient, cfg.GetComponentsAllowed())
49+
50+
opampClient := cfg.CreateClient(agentLogger)
51+
opampAgent := agent.NewAgent(agentLogger, operatorClient, cfg, opampClient)
52+
53+
if err := opampAgent.Start(); err != nil {
54+
l.Error(err, "Cannot start OpAMP client")
55+
os.Exit(1)
56+
}
57+
58+
interrupt := make(chan os.Signal, 1)
59+
signal.Notify(interrupt, os.Interrupt)
60+
<-interrupt
61+
opampAgent.Shutdown()
62+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package metrics
16+
17+
import (
18+
"context"
19+
"fmt"
20+
"net/url"
21+
"os"
22+
"time"
23+
24+
"github.com/oklog/ulid/v2"
25+
"github.com/shirou/gopsutil/process"
26+
"go.opentelemetry.io/otel/attribute"
27+
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
28+
"go.opentelemetry.io/otel/metric"
29+
"go.opentelemetry.io/otel/metric/instrument"
30+
"go.opentelemetry.io/otel/metric/instrument/asyncfloat64"
31+
"go.opentelemetry.io/otel/metric/instrument/asyncint64"
32+
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
33+
otelresource "go.opentelemetry.io/otel/sdk/resource"
34+
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
35+
36+
"github.com/open-telemetry/opamp-go/client/types"
37+
"github.com/open-telemetry/opamp-go/protobufs"
38+
)
39+
40+
// MetricReporter is a metric reporter that collects Agent metrics and sends them to an
41+
// OTLP/HTTP destination.
42+
type MetricReporter struct {
43+
logger types.Logger
44+
45+
meter metric.Meter
46+
meterShutdowner func()
47+
done chan struct{}
48+
49+
// The Agent's process.
50+
process *process.Process
51+
52+
// Some example metrics to report.
53+
processMemoryPhysical asyncint64.Gauge
54+
processCpuTime asyncfloat64.Counter
55+
}
56+
57+
// NewMetricReporter creates an OTLP/HTTP client to the destination address supplied by the server.
58+
// TODO: do more validation on the endpoint, allow for gRPC.
59+
// TODO: set global provider and add more metrics to be reported.
60+
func NewMetricReporter(
61+
logger types.Logger,
62+
dest *protobufs.TelemetryConnectionSettings,
63+
agentType string,
64+
agentVersion string,
65+
instanceId ulid.ULID,
66+
) (*MetricReporter, error) {
67+
68+
if dest.DestinationEndpoint == "" {
69+
return nil, fmt.Errorf("metric destination must specify DestinationEndpoint")
70+
}
71+
72+
u, err := url.Parse(dest.DestinationEndpoint)
73+
if err != nil {
74+
return nil, fmt.Errorf("invalid DestinationEndpoint: %w", err)
75+
}
76+
77+
// Create OTLP/HTTP metric exporter.
78+
opts := []otlpmetrichttp.Option{
79+
otlpmetrichttp.WithEndpoint(u.Host),
80+
otlpmetrichttp.WithURLPath(u.Path),
81+
}
82+
83+
headers := map[string]string{}
84+
for _, header := range dest.Headers.GetHeaders() {
85+
headers[header.GetKey()] = header.GetValue()
86+
}
87+
opts = append(opts, otlpmetrichttp.WithHeaders(headers))
88+
89+
client, err := otlpmetrichttp.New(context.Background(), opts...)
90+
if err != nil {
91+
return nil, fmt.Errorf("failed to initialize otlp metric http client: %w", err)
92+
}
93+
94+
// Define the Resource to be exported with all metrics. Use OpenTelemetry semantic
95+
// conventions as the OpAMP spec requires:
96+
// https://github.com/open-telemetry/opamp-spec/blob/main/specification.md#own-telemetry-reporting
97+
resource, resourceErr := otelresource.New(context.Background(),
98+
otelresource.WithAttributes(
99+
semconv.ServiceNameKey.String(agentType),
100+
semconv.ServiceVersionKey.String(agentVersion),
101+
semconv.ServiceInstanceIDKey.String(instanceId.String()),
102+
),
103+
)
104+
if resourceErr != nil {
105+
return nil, resourceErr
106+
}
107+
108+
provider := sdkmetric.NewMeterProvider(
109+
sdkmetric.WithResource(resource),
110+
sdkmetric.WithReader(sdkmetric.NewPeriodicReader(client, sdkmetric.WithInterval(5*time.Second))))
111+
112+
reporter := &MetricReporter{
113+
logger: logger,
114+
}
115+
116+
reporter.done = make(chan struct{})
117+
118+
reporter.meter = provider.Meter("opamp")
119+
120+
reporter.process, err = process.NewProcess(int32(os.Getpid()))
121+
if err != nil {
122+
return nil, fmt.Errorf("cannot query own process: %w", err)
123+
}
124+
125+
// Create some metrics that will be reported according to OpenTelemetry semantic
126+
// conventions for process metrics (conventions are TBD for now).
127+
reporter.processCpuTime, err = reporter.meter.AsyncFloat64().Counter(
128+
"process.cpu.time",
129+
)
130+
if err != nil {
131+
return nil, fmt.Errorf("can't create process time metric: %w", err)
132+
}
133+
err = reporter.meter.RegisterCallback([]instrument.Asynchronous{reporter.processCpuTime}, reporter.processCpuTimeFunc)
134+
if err != nil {
135+
return nil, fmt.Errorf("can't create register callback: %w", err)
136+
}
137+
reporter.processMemoryPhysical, err = reporter.meter.AsyncInt64().Gauge(
138+
"process.memory.physical_usage",
139+
)
140+
if err != nil {
141+
return nil, fmt.Errorf("can't create memory metric: %w", err)
142+
}
143+
err = reporter.meter.RegisterCallback([]instrument.Asynchronous{reporter.processMemoryPhysical}, reporter.processMemoryPhysicalFunc)
144+
if err != nil {
145+
return nil, fmt.Errorf("can't register callback: %w", err)
146+
}
147+
148+
reporter.meterShutdowner = func() { _ = provider.Shutdown(context.Background()) }
149+
150+
return reporter, nil
151+
}
152+
153+
func (reporter *MetricReporter) processCpuTimeFunc(c context.Context) {
154+
times, err := reporter.process.Times()
155+
if err != nil {
156+
reporter.logger.Errorf("Cannot get process CPU times: %w", err)
157+
}
158+
reporter.processCpuTime.Observe(c, times.User, attribute.String("state", "user"))
159+
reporter.processCpuTime.Observe(c, times.System, attribute.String("state", "system"))
160+
reporter.processCpuTime.Observe(c, times.Iowait, attribute.String("state", "wait"))
161+
}
162+
163+
func (reporter *MetricReporter) processMemoryPhysicalFunc(ctx context.Context) {
164+
memory, err := reporter.process.MemoryInfo()
165+
if err != nil {
166+
reporter.logger.Errorf("Cannot get process memory information: %w", err)
167+
return
168+
}
169+
reporter.processMemoryPhysical.Observe(ctx, int64(memory.RSS))
170+
}
171+
172+
func (reporter *MetricReporter) Shutdown() {
173+
if reporter.done != nil {
174+
close(reporter.done)
175+
}
176+
177+
if reporter.meterShutdowner != nil {
178+
reporter.meterShutdowner()
179+
}
180+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package operator
16+
17+
import (
18+
"context"
19+
"fmt"
20+
21+
"github.com/go-logr/logr"
22+
"github.com/open-telemetry/opamp-go/protobufs"
23+
"gopkg.in/yaml.v3"
24+
"k8s.io/apimachinery/pkg/api/errors"
25+
"sigs.k8s.io/controller-runtime/pkg/client"
26+
27+
"github.com/open-telemetry/opentelemetry-operator/apis/v1alpha1"
28+
)
29+
30+
const (
31+
CollectorResource = "OpenTelemetryCollector"
32+
ResourceIdentifierKey = "created-by"
33+
ResourceIdentifierValue = "operator-opamp-bridge"
34+
)
35+
36+
type ConfigApplier interface {
37+
// Apply receives a name and namespace to apply an OpenTelemetryCollector CRD that is contained in the configmap.
38+
Apply(name string, namespace string, configmap *protobufs.AgentConfigFile) error
39+
40+
// GetInstance retrieves an OpenTelemetryCollector CRD given a name and namespace.
41+
GetInstance(name string, namespace string) (*v1alpha1.OpenTelemetryCollector, error)
42+
43+
// ListInstances retrieves all OpenTelemetryCollector CRDs created by the operator-opamp-bridge agent.
44+
ListInstances() ([]v1alpha1.OpenTelemetryCollector, error)
45+
46+
// Delete attempts to delete an OpenTelemetryCollector object given a name and namespace.
47+
Delete(name string, namespace string) error
48+
}
49+
50+
type Client struct {
51+
log logr.Logger
52+
componentsAllowed map[string]map[string]bool
53+
k8sClient client.Client
54+
close chan bool
55+
}
56+
57+
var _ ConfigApplier = &Client{}
58+
59+
func NewClient(log logr.Logger, c client.Client, componentsAllowed map[string]map[string]bool) *Client {
60+
return &Client{
61+
log: log,
62+
componentsAllowed: componentsAllowed,
63+
k8sClient: c,
64+
close: make(chan bool, 1),
65+
}
66+
}
67+
68+
func (c Client) create(ctx context.Context, name string, namespace string, collector *v1alpha1.OpenTelemetryCollector) error {
69+
// Set the defaults
70+
collector.Default()
71+
collector.TypeMeta.Kind = CollectorResource
72+
collector.TypeMeta.APIVersion = v1alpha1.GroupVersion.String()
73+
collector.ObjectMeta.Name = name
74+
collector.ObjectMeta.Namespace = namespace
75+
76+
if collector.ObjectMeta.Labels == nil {
77+
collector.ObjectMeta.Labels = map[string]string{}
78+
}
79+
collector.ObjectMeta.Labels[ResourceIdentifierKey] = ResourceIdentifierValue
80+
err := collector.ValidateCreate()
81+
if err != nil {
82+
return err
83+
}
84+
c.log.Info("Creating collector")
85+
return c.k8sClient.Create(ctx, collector)
86+
}
87+
88+
func (c Client) update(ctx context.Context, old *v1alpha1.OpenTelemetryCollector, new *v1alpha1.OpenTelemetryCollector) error {
89+
new.ObjectMeta = old.ObjectMeta
90+
new.TypeMeta = old.TypeMeta
91+
err := new.ValidateUpdate(old)
92+
if err != nil {
93+
return err
94+
}
95+
c.log.Info("Updating collector")
96+
return c.k8sClient.Update(ctx, new)
97+
}
98+
99+
func (c Client) Apply(name string, namespace string, configmap *protobufs.AgentConfigFile) error {
100+
c.log.Info("Received new config", "name", name, "namespace", namespace)
101+
var collectorSpec v1alpha1.OpenTelemetryCollectorSpec
102+
err := yaml.Unmarshal(configmap.Body, &collectorSpec)
103+
if err != nil {
104+
return err
105+
}
106+
if len(collectorSpec.Config) == 0 {
107+
return errors.NewBadRequest("Must supply valid configuration")
108+
}
109+
reasons, validateErr := c.validate(collectorSpec)
110+
if validateErr != nil {
111+
return validateErr
112+
}
113+
if len(reasons) > 0 {
114+
return errors.NewBadRequest(fmt.Sprintf("Items in config are not allowed: %v", reasons))
115+
}
116+
collector := &v1alpha1.OpenTelemetryCollector{Spec: collectorSpec}
117+
ctx := context.Background()
118+
instance, err := c.GetInstance(name, namespace)
119+
if err != nil {
120+
return err
121+
}
122+
if instance != nil {
123+
return c.update(ctx, instance, collector)
124+
}
125+
return c.create(ctx, name, namespace, collector)
126+
}
127+
128+
func (c Client) Delete(name string, namespace string) error {
129+
ctx := context.Background()
130+
result := v1alpha1.OpenTelemetryCollector{}
131+
err := c.k8sClient.Get(ctx, client.ObjectKey{
132+
Namespace: namespace,
133+
Name: name,
134+
}, &result)
135+
if err != nil {
136+
if errors.IsNotFound(err) {
137+
return nil
138+
}
139+
return err
140+
}
141+
return c.k8sClient.Delete(ctx, &result)
142+
}
143+
144+
func (c Client) ListInstances() ([]v1alpha1.OpenTelemetryCollector, error) {
145+
ctx := context.Background()
146+
result := v1alpha1.OpenTelemetryCollectorList{}
147+
err := c.k8sClient.List(ctx, &result, client.MatchingLabels{
148+
ResourceIdentifierKey: ResourceIdentifierValue,
149+
})
150+
if err != nil {
151+
return nil, err
152+
}
153+
return result.Items, nil
154+
}
155+
156+
func (c Client) GetInstance(name string, namespace string) (*v1alpha1.OpenTelemetryCollector, error) {
157+
ctx := context.Background()
158+
result := v1alpha1.OpenTelemetryCollector{}
159+
err := c.k8sClient.Get(ctx, client.ObjectKey{
160+
Namespace: namespace,
161+
Name: name,
162+
}, &result)
163+
if err != nil {
164+
if errors.IsNotFound(err) {
165+
return nil, nil
166+
}
167+
return nil, err
168+
}
169+
return &result, nil
170+
}
171+
172+
func (c Client) validate(spec v1alpha1.OpenTelemetryCollectorSpec) ([]string, error) {
173+
// Do not use this feature if it's not specified
174+
if c.componentsAllowed == nil || len(c.componentsAllowed) == 0 {
175+
return nil, nil
176+
}
177+
collectorConfig := make(map[string]map[string]interface{})
178+
err := yaml.Unmarshal([]byte(spec.Config), &collectorConfig)
179+
if err != nil {
180+
return nil, err
181+
}
182+
var invalidComponents []string
183+
for component, componentMap := range collectorConfig {
184+
if component == "service" {
185+
// We don't care about what's in the service pipelines.
186+
// Only components declared in the configuration can be used in the service pipeline.
187+
continue
188+
}
189+
if _, ok := c.componentsAllowed[component]; !ok {
190+
invalidComponents = append(invalidComponents, component)
191+
continue
192+
}
193+
for componentName := range componentMap {
194+
if _, ok := c.componentsAllowed[component][componentName]; !ok {
195+
invalidComponents = append(invalidComponents, fmt.Sprintf("%s.%s", component, componentName))
196+
}
197+
}
198+
}
199+
return invalidComponents, nil
200+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package operator
16+
17+
import (
18+
"os"
19+
"testing"
20+
21+
"github.com/stretchr/testify/require"
22+
23+
"github.com/open-telemetry/opamp-go/protobufs"
24+
"github.com/stretchr/testify/assert"
25+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26+
"k8s.io/apimachinery/pkg/runtime"
27+
"sigs.k8s.io/controller-runtime/pkg/client"
28+
"sigs.k8s.io/controller-runtime/pkg/client/fake"
29+
logf "sigs.k8s.io/controller-runtime/pkg/log"
30+
31+
"github.com/open-telemetry/opentelemetry-operator/apis/v1alpha1"
32+
)
33+
34+
var (
35+
clientLogger = logf.Log.WithName("client-tests")
36+
)
37+
38+
func getFakeClient(t *testing.T) client.WithWatch {
39+
schemeBuilder := runtime.NewSchemeBuilder(func(s *runtime.Scheme) error {
40+
s.AddKnownTypes(v1alpha1.GroupVersion, &v1alpha1.OpenTelemetryCollector{}, &v1alpha1.OpenTelemetryCollectorList{})
41+
metav1.AddToGroupVersion(s, v1alpha1.GroupVersion)
42+
return nil
43+
})
44+
scheme := runtime.NewScheme()
45+
err := schemeBuilder.AddToScheme(scheme)
46+
require.NoError(t, err, "Should be able to add custom types")
47+
c := fake.NewClientBuilder().WithScheme(scheme)
48+
return c.Build()
49+
}
50+
51+
func TestClient_Apply(t *testing.T) {
52+
type args struct {
53+
name string
54+
namespace string
55+
file string
56+
config string
57+
}
58+
tests := []struct {
59+
name string
60+
args args
61+
wantErr bool
62+
}{
63+
{
64+
name: "base case",
65+
args: args{
66+
name: "test",
67+
namespace: "opentelemetry",
68+
file: "testdata/collector.yaml",
69+
},
70+
wantErr: false,
71+
},
72+
{
73+
name: "invalid config",
74+
args: args{
75+
name: "test",
76+
namespace: "opentelemetry",
77+
file: "testdata/invalid-collector.yaml",
78+
},
79+
wantErr: true,
80+
},
81+
{
82+
name: "empty config",
83+
args: args{
84+
name: "test",
85+
namespace: "opentelemetry",
86+
config: "",
87+
},
88+
wantErr: true,
89+
},
90+
}
91+
for _, tt := range tests {
92+
t.Run(tt.name, func(t *testing.T) {
93+
fakeClient := getFakeClient(t)
94+
c := NewClient(clientLogger, fakeClient, nil)
95+
var colConfig []byte
96+
var err error
97+
if len(tt.args.file) > 0 {
98+
colConfig, err = loadConfig(tt.args.file)
99+
require.NoError(t, err, "Should be no error on loading test configuration")
100+
} else {
101+
colConfig = []byte(tt.args.config)
102+
}
103+
configmap := &protobufs.AgentConfigFile{
104+
Body: colConfig,
105+
ContentType: "yaml",
106+
}
107+
if err := c.Apply(tt.args.name, tt.args.namespace, configmap); (err != nil) != tt.wantErr {
108+
t.Errorf("Apply() error = %v, wantErr %v", err, tt.wantErr)
109+
}
110+
})
111+
}
112+
}
113+
114+
func Test_collectorUpdate(t *testing.T) {
115+
name := "test"
116+
namespace := "testing"
117+
fakeClient := getFakeClient(t)
118+
c := NewClient(clientLogger, fakeClient, nil)
119+
colConfig, err := loadConfig("testdata/collector.yaml")
120+
require.NoError(t, err, "Should be no error on loading test configuration")
121+
configmap := &protobufs.AgentConfigFile{
122+
Body: colConfig,
123+
ContentType: "yaml",
124+
}
125+
// Apply a valid initial configuration
126+
err = c.Apply(name, namespace, configmap)
127+
require.NoError(t, err, "Should apply base config")
128+
129+
// Get the newly created collector
130+
instance, err := c.GetInstance(name, namespace)
131+
require.NoError(t, err, "Should be able to get the newly created instance")
132+
assert.Contains(t, instance.Spec.Config, "processors: []")
133+
134+
// Try updating with an invalid one
135+
configmap.Body = []byte("empty, invalid!")
136+
err = c.Apply(name, namespace, configmap)
137+
assert.Error(t, err, "Should be unable to update")
138+
139+
// Update successfully with a valid configuration
140+
newColConfig, err := loadConfig("testdata/updated-collector.yaml")
141+
require.NoError(t, err, "Should be no error on loading test configuration")
142+
newConfigMap := &protobufs.AgentConfigFile{
143+
Body: newColConfig,
144+
ContentType: "yaml",
145+
}
146+
err = c.Apply(name, namespace, newConfigMap)
147+
require.NoError(t, err, "Should be able to update collector")
148+
149+
// Get the updated collector
150+
updatedInstance, err := c.GetInstance(name, namespace)
151+
require.NoError(t, err, "Should be able to get the updated instance")
152+
assert.Contains(t, updatedInstance.Spec.Config, "processors: [memory_limiter, batch]")
153+
154+
allInstances, err := c.ListInstances()
155+
require.NoError(t, err, "Should be able to list all collectors")
156+
assert.Len(t, allInstances, 1)
157+
assert.Equal(t, allInstances[0], *updatedInstance)
158+
}
159+
160+
func Test_collectorDelete(t *testing.T) {
161+
name := "test"
162+
namespace := "testing"
163+
fakeClient := getFakeClient(t)
164+
c := NewClient(clientLogger, fakeClient, nil)
165+
colConfig, err := loadConfig("testdata/collector.yaml")
166+
require.NoError(t, err, "Should be no error on loading test configuration")
167+
configmap := &protobufs.AgentConfigFile{
168+
Body: colConfig,
169+
ContentType: "yaml",
170+
}
171+
// Apply a valid initial configuration
172+
err = c.Apply(name, namespace, configmap)
173+
require.NoError(t, err, "Should apply base config")
174+
175+
// Get the newly created collector
176+
instance, err := c.GetInstance(name, namespace)
177+
require.NoError(t, err, "Should be able to get the newly created instance")
178+
assert.Contains(t, instance.Spec.Config, "processors: []")
179+
180+
// Delete it
181+
err = c.Delete(name, namespace)
182+
require.NoError(t, err, "Should be able to delete a collector")
183+
184+
// Check there's nothing left
185+
allInstances, err := c.ListInstances()
186+
require.NoError(t, err, "Should be able to list all collectors")
187+
assert.Len(t, allInstances, 0)
188+
}
189+
190+
func loadConfig(file string) ([]byte, error) {
191+
yamlFile, err := os.ReadFile(file)
192+
if err != nil {
193+
return []byte{}, err
194+
}
195+
return yamlFile, nil
196+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
config: |
2+
receivers:
3+
otlp:
4+
protocols:
5+
grpc:
6+
http:
7+
processors:
8+
memory_limiter:
9+
check_interval: 1s
10+
limit_percentage: 75
11+
spike_limit_percentage: 15
12+
batch:
13+
send_batch_size: 10000
14+
timeout: 10s
15+
16+
exporters:
17+
logging:
18+
19+
service:
20+
pipelines:
21+
traces:
22+
receivers: [otlp]
23+
processors: []
24+
exporters: [logging]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
config: |
2+
receivers:
3+
otlp:
4+
protocols:
5+
grpc:
6+
http:
7+
processors:
8+
memory_limiter:
9+
check_interval: 1s
10+
limit_percentage: 75
11+
spike_limit_percentage: 15
12+
batch:
13+
send_batch_size: 10000
14+
timeout: 10s
15+
GARBAGE
16+
exporters:
17+
logging:
18+
19+
service:
20+
pipelines:
21+
traces:
22+
receivers: [otlp]
23+
processors: []
24+
exporters: [logging]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
config: |
2+
receivers:
3+
otlp:
4+
protocols:
5+
grpc:
6+
http:
7+
processors:
8+
memory_limiter:
9+
check_interval: 1s
10+
limit_percentage: 75
11+
spike_limit_percentage: 15
12+
batch:
13+
send_batch_size: 10000
14+
timeout: 10s
15+
16+
exporters:
17+
logging:
18+
19+
service:
20+
pipelines:
21+
traces:
22+
receivers: [otlp]
23+
processors: [memory_limiter, batch]
24+
exporters: [logging]

0 commit comments

Comments
 (0)
Please sign in to comment.