This fixes two closely related problems.
1. While fanoutconsumers do not themselves mutate data, they should
expose whether or not they are handing data off to consumers which may
do so. Otherwise, the service cannot correctly determine how to fan out
after a receiver. e.g. a receiver shared between two pipelines, one of
which contains an exporter or connector which mutates data.
2. Connectors can themselves mutate data but we were not taking this
into account when building the graph.
A follow-up optimization after merging
https://github.com/open-telemetry/opentelemetry-collector/pull/8634.
There is no need to create a fanout consumer for only one read-only
consumer. This introduces a behavior that closely resembles its previous
state, before the introduction of the readonly/mutable states.
This change enables the runtime assertions to catch unintentional pdata
mutations in components claiming as non-mutating pdata. Without these
assertions, runtime errors may still occur, but thrown by unrelated
components, making it very difficult to troubleshoot.
This required introducing extra API to get the pdata mutability state:
- p[metric|trace|log].[Metrics|Traces|Logs].IsReadOnly()
Resolves:
https://github.com/open-telemetry/opentelemetry-collector/issues/6794
This PR adds helpers to `connectortest` to aid the construction of
`connector.*Router`s for testing connectors. This was implemented based
on @djaglowski's [comment
here](https://github.com/open-telemetry/opentelemetry-collector-contrib/pull/21498#issuecomment-1542682841),
with some slight modifications. I found it more ergonomic to pass the
sink into the `WithTracesSink` (and similar) options. You usually want a
handle on the sink after creating the router. While it's possible to get
the sink out of the router after the fact, it's a little cumbersome.
These helpers will be useful for
https://github.com/open-telemetry/opentelemetry-collector-contrib/pull/21498
and future connectors that need use of routers.
For example, here is a test with the consumer passed in:
```go
func TestFanoutTracesWithSink(t *testing.T) {
var sink0, sink1 consumertest.TracesSink
tr, err := NewTracesRouterSink(
WithTracesSink(component.NewIDWithName(component.DataTypeTraces, "0"), &sink0),
WithTracesSink(component.NewIDWithName(component.DataTypeTraces, "1"), &sink1),
)
require.NoError(t, err)
require.Equal(t, 0, sink0.SpanCount())
require.Equal(t, 0, sink1.SpanCount())
td := testdata.GenerateTraces(1)
err = tr.(consumer.Traces).ConsumeTraces(context.Background(), td)
require.NoError(t, err)
require.Equal(t, 1, sink0.SpanCount())
require.Equal(t, 1, sink1.SpanCount())
}
```
The same test having to extract the consumer out after the fact:
```go
func TestFanoutTracesWithSink(t *testing.T) {
traces0 := component.NewIDWithName(component.DataTypeTraces, "0")
traces1 := component.NewIDWithName(component.DataTypeTraces, "1")
tr, err := NewTracesRouterSink(
WithTracesSink(traces0),
WithTracesSink(traces1),
)
require.NoError(t, err)
cons0, _ := tr.Consumer(traces0)
sink0 := cons0.(*consumertest.TracesSink)
cons1, _ := tr.Consumer(traces1)
sink1 := cons1.(*consumertest.TracesSink)
require.Equal(t, 0, sink0.SpanCount())
require.Equal(t, 0, sink1.SpanCount())
td := testdata.GenerateTraces(1)
err = tr.(consumer.Traces).ConsumeTraces(context.Background(), td)
require.NoError(t, err)
require.Equal(t, 1, sink0.SpanCount())
require.Equal(t, 1, sink1.SpanCount())}
}
```
**Link to tracking Issue:**
#7672
**Testing:**
Unit tests
**Documentation:**
Source code comments
---------
Co-authored-by: Daniel Jaglowski <jaglows3@gmail.com>