Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[jaeger-v2] Add kafka exporter and receiver configuration #4971

Open
wants to merge 18 commits into
base: main
Choose a base branch
from

Conversation

james-ryans
Copy link
Contributor

@james-ryans james-ryans commented Nov 27, 2023

Which problem is this PR solving?

Description of the changes

  • Create collector-with-kafka.yaml and ingester.yaml, to mimic what jaeger-collector with kafka span storage type and jaeger-ingester already have.
  • ingester.yaml configuration exposes prometheus metrics at 8889 port to avoid conflict with collector-with-kafka.yaml.
  • Execute the below command to run the collector and ingester components.
$ go run -tags=ui ./cmd/jaeger --config ./cmd/jaeger/collector-with-kafka.yaml
$ go run -tags=ui ./cmd/jaeger --config ./cmd/jaeger/ingester.yaml

How was this change tested?

  • Execute ./scripts/otel-kafka-integration-test.sh to test the overall pipelines.
  • The testbed module is used to implement the integration test without data race detection because it has goroutines that read/write the same variable.
  • The testbed validator checks if the kafka message and memory remote storage have the same spans from the load generator.

Checklist

@james-ryans james-ryans requested a review from a team as a code owner November 27, 2023 19:02
Copy link

codecov bot commented Nov 27, 2023

Codecov Report

Attention: 1 lines in your changes are missing coverage. Please review.

Comparison is base (e08f576) 95.64% compared to head (fb44ba5) 95.63%.

Files Patch % Lines
plugin/storage/grpc/factory.go 87.50% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4971      +/-   ##
==========================================
- Coverage   95.64%   95.63%   -0.01%     
==========================================
  Files         325      325              
  Lines       18619    18640      +21     
==========================================
+ Hits        17808    17827      +19     
- Misses        651      652       +1     
- Partials      160      161       +1     
Flag Coverage Δ
cassandra-3.x 25.58% <ø> (ø)
cassandra-4.x 25.58% <ø> (ø)
elasticsearch-5.x 19.86% <ø> (ø)
elasticsearch-6.x 19.86% <ø> (+0.01%) ⬆️
elasticsearch-7.x 20.00% <ø> (ø)
elasticsearch-8.x 20.08% <ø> (ø)
grpc-badger 19.47% <0.00%> (-0.02%) ⬇️
kafka 14.09% <ø> (ø)
opensearch-1.x 19.98% <ø> (-0.02%) ⬇️
opensearch-2.x 19.98% <ø> (-0.02%) ⬇️
unittests 93.34% <95.65%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Member

@yurishkuro yurishkuro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great start! But what we really need is an e2e integration test utilizing these configs.

cmd/jaeger/collector-with-kafka.yaml Outdated Show resolved Hide resolved
cmd/jaeger/collector-with-kafka.yaml Outdated Show resolved Hide resolved
cmd/jaeger/collector-with-kafka.yaml Outdated Show resolved Hide resolved
cmd/jaeger/ingester.yaml Outdated Show resolved Hide resolved
@james-ryans
Copy link
Contributor Author

HI! I am currently halfway through implementing the e2e test. I've discoveered testbed pkg from opentelemetry-collector-contrib repo, designed for Otel collector integration tests. I've successfully test e2e from load generator (otlp) -> collector-with-kafka -> kafka -> mock backend (kafka receiver), and now I'm stuck to test load generator -> collector-with-kafka -> kafka -> ingester -> mock backend because ingester doesn't actually export the traces instead it stores them to memory. Do you have any suggestions on how should the test be done?

Here's the diagram of testbed and my e2e test structure.
testbed
Testbed structure

kafka-receiver
The successful tested collector with kafka

ingester-receiver
The actual e2e collector needs to be tested

@yurishkuro
Copy link
Member

because ingester doesn't actually export the traces instead it stores them to memory. Do you have any suggestions on how should the test be done?

This is the first time I see the testbed framework, I will need to understand it better. What is the expectation of the mock backend - is it expected to "push" the data somewhere?

If it can be pulled for data by the framework, then you could utilize a remote memory storage (i.e. running as a separate process), similar to how our existing integration test works:

jaeger-remote-storage:

@james-ryans
Copy link
Contributor Author

What is the expectation of the mock backend - is it expected to "push" the data somewhere?

Nope, it can be a "pull" strategy too! The mock backend uses the receiver component to retrieve the traces, and there are some receivers in a "pull" strategy, such as kafka_receiver.

If it can be pulled for data by the framework, then you could utilize a remote memory storage ... similar to how our existing integration test works.

Ah, okay, I think this can do. I'll try to read it.

@james-ryans
Copy link
Contributor Author

Hi, I've almost finished the integration test. Currently, I'm blocked on a mismatch model between OTLP Span Link (here) and Jaeger Span Reference, OTLP Span Link has attributes but Jaeger Span Reference doesn't thus the data sent with span link attributes will be discarded at the Jaeger storage.

2024/01/01 21:55:23 Sent and received data counters match.
    validator.go:92: 
        	Error Trace:	/Users/james_ryans/go/src/github.com/james-ryans/jaeger/vendor/github.com/open-telemetry/opentelemetry-collector-contrib/testbed/testbed/validator.go:92
        	            				/Users/james_ryans/go/src/github.com/james-ryans/jaeger/vendor/github.com/open-telemetry/opentelemetry-collector-contrib/testbed/testbed/test_case.go:242
        	            				/Users/james_ryans/go/src/github.com/james-ryans/jaeger/cmd/jaeger/integration/kafka_test.go:96
        	Error:      	Not equal: 
        	            	expected: 0
        	            	actual  : 11824
        	Test:       	TestKafkaStorage
        	Messages:   	There are span data mismatches.
2024-01-01T21:55:23.870+0700	info	otelcol/collector.go:266	Received shutdown request
2024-01-01T21:55:23.870+0700	info	service/service.go:185	Starting shutdown...
2024-01-01T21:55:24.034+0700	info	kafkareceiver/kafka_receiver.go:179	Consumer stopped	{"kind": "receiver", "name": "kafka", "data_type": "traces", "error": "context canceled"}
2024-01-01T21:55:24.039+0700	info	extensions/extensions.go:61	Stopping extensions...
2024-01-01T21:55:24.039+0700	info	service/service.go:199	Shutdown complete.
2024/01/01 21:55:24 assertion failures "Link.Attributes[messaging.system]": expected="kafka" actual=null
2024/01/01 21:55:24 assertion failures "Link.Attributes[messaging.destination]": expected="infrastructure-events-zone1" actual=null
2024/01/01 21:55:24 assertion failures "Link.Attributes[messaging.operation]": expected="receive" actual=null
2024/01/01 21:55:24 assertion failures "Link.Attributes[net.peer.ip]": expected="2600:1700:1f00:11c0:4de0:c223:a800:4e87" actual=null
2024/01/01 21:55:24 assertion failures "Link.Attributes[enduser.id]": expected="unittest" actual=null
2024/01/01 21:55:24 assertion failures "Link.Attributes[app.inretry]": expected=true actual=null
2024/01/01 21:55:24 assertion failures "Link.Attributes[app.progress]": expected=0.6 actual=null
2024/01/01 21:55:24 assertion failures "Link.Attributes[app.statemap]": expected="14|5|202" actual=null
2024/01/01 21:55:24 assertion failure count: {"Link.Attributes[app.inretry]":658,"Link.Attributes[app.progress]":658,"Link.Attributes[app.statemap]":658,"Link.Attributes[enduser.id]":1970,"Link.Attributes[messaging.destination]":1970,"Link.Attributes[messaging.operation]":1970,"Link.Attributes[messaging.system]":1970,"Link.Attributes[net.peer.ip]":1970}

How should we solve this? Does it make sense if we add attributes to our Span Reference model?

@yurishkuro
Copy link
Member

It's going to be pretty significant change to add attributes to SpanRefs. Is it possible to configure the test controller not to generate attributes for the links?

@james-ryans
Copy link
Contributor Author

It's going to be pretty significant change to add attributes to SpanRefs. Is it possible to configure the test controller not to generate attributes for the links?

The only way to not generate attributes is not to generate a span link entirely. Are you okay with that?

@yurishkuro
Copy link
Member

Yes, that's fine - we're not testing model conversion here, but the ingestion pipeline integration, it doesn't matter much what is in the spans.

Copy link

github-actions bot commented Jan 10, 2024

Test Results

2 081 tests  +5   2 070 ✅ +4   1m 10s ⏱️ -1s
  219 suites +3      11 💤 +1 
    1 files   ±0       0 ❌ ±0 

Results for commit 51b71d2. ± Comparison against base commit adbdb2d.

This pull request removes 1 and adds 6 tests. Note that renamed tests count towards both.
github.com/jaegertracing/jaeger/cmd/jaeger/internal/extension/jaegerstorage ‑ TestStorageExtensionStartTwiceError
github.com/jaegertracing/jaeger/cmd/jaeger/integration ‑ TestKafkaStorage
github.com/jaegertracing/jaeger/cmd/jaeger/internal/extension/jaegerstorage ‑ TestGRPCStorageExtensionError
github.com/jaegertracing/jaeger/cmd/jaeger/internal/extension/jaegerstorage ‑ TestStorageExtensionDuplicateNameError
github.com/jaegertracing/jaeger/cmd/jaeger/internal/extension/jaegerstorage ‑ TestStorageExtensionDuplicateNameError/grpc
github.com/jaegertracing/jaeger/cmd/jaeger/internal/extension/jaegerstorage ‑ TestStorageExtensionDuplicateNameError/memory
github.com/jaegertracing/jaeger/plugin/storage/grpc ‑ TestGRPCStorageFactoryWithConfig

♻️ This comment has been updated with latest results.

Makefile Outdated Show resolved Hide resolved
Makefile Outdated Show resolved Hide resolved
cmd/jaeger/internal/receivers/storagereceiver/factory.go Outdated Show resolved Hide resolved
)

// Config has the configuration for jaeger-query,
type Config struct {
Memory map[string]memoryCfg.Configuration `mapstructure:"memory"`
GRPC map[string]grpcCfg.Configuration `mapstructure:"grpc-plugin"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
GRPC map[string]grpcCfg.Configuration `mapstructure:"grpc-plugin"`
GRPC map[string]grpcCfg.Configuration `mapstructure:"grpc"`


func (r *storageReceiver) consumeLoop(ctx context.Context) error {
// golden data provider can produce one of these service names
services := []string{"", "customers", "OTLPResourceNoServiceName"}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be more robust if you just loaded services from SpanReader, in case the testbed changes its behavior

Copy link
Member

@yurishkuro yurishkuro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tagging as needing followup

@james-ryans
Copy link
Contributor Author

tagging as needing followup

Hi, I've raised a PR to fix the OTEL testbed data race and currently waiting for their review here open-telemetry/opentelemetry-collector-contrib#30549.

Signed-off-by: James Ryans <james.ryans2012@gmail.com>
@james-ryans
Copy link
Contributor Author

james-ryans commented Jan 16, 2024

Hi @yurishkuro, sorry, this PR became messy as I tried to solve the conflict merge with rebases, some failing CIs, an unsigned commit, and now I'm stuck with Build binaries CI. Can you help me find out what's wrong with my commits? I've successfully run make build-binaries-linux locally but somehow it is failing in CI.

Locally

+ git log --oneline --decorate=full -n 10
+ cat
53976a5e (HEAD, refs/remotes/origin/main, refs/remotes/origin/HEAD) Bump follow-redirects from 1.15.1 to 1.15.4 (#2108)
9be986e7 Bump actions/upload-artifact from 3.1.0 to 4.0.0 (#2106)
9bb97358 (tag: refs/tags/v1.37.0) Prepare release v1.37.0 (#2097)
a6fb5824 Setup OSSF Scorecard workflow  (#2096)
039d8d6d Bump classnames from 2.3.3 to 2.5.1 (#2085)
b574d7a9 Bump the eslint group with 2 updates (#2095)
9fadfff1 Fix broken Hot Reload of Plexus Package (#2089)
868bb058 Use fake/fixed date in unit tests (#2091)
911ccf54 feat: remove `is-promise` library (#2080)
cdd251ba feat: remove `ReactGA` and migrate to `GA4` for tracking (#2071)
++ git describe --tags --dirty
+ last_tag=v1.37.0-2-g53976a5e
+ [[ v1.37.0-2-g53976a5e =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]
+ yarn install --frozen-lockfile
yarn install v1.22.18
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
...

CI

+ git log --oneline --decorate=full -n 10
+ cat
53976a5e (grafted, HEAD, refs/remotes/origin/main, refs/remotes/origin/HEAD, refs/heads/main) Bump follow-redirects from 1.15.1 to 1.15.4 (#2108)
++ git describe --tags --dirty
+ last_tag=
make[2]: *** [Makefile:242: rebuild-ui] Error 128
make[2]: Leaving directory '/home/runner/work/jaeger/jaeger'
make[1]: *** [Makefile:238: jaeger-ui/packages/jaeger-ui/build/index.html] Error 2
make[1]: Leaving directory '/home/runner/work/jaeger/jaeger'
make: *** [Makefile:352: build-binaries-linux] Error 2

james-ryans and others added 3 commits January 18, 2024 19:21
Signed-off-by: James Ryans <james.ryans2012@gmail.com>
Signed-off-by: James Ryans <james.ryans2012@gmail.com>
@james-ryans
Copy link
Contributor Author

Hi, I've raised a PR to fix the OTEL testbed data race and currently waiting for their review here open-telemetry/opentelemetry-collector-contrib#30549.

I have got a good news that the OTEL testbed data race PR has been merged! And I've updated the OTEL testbed module dependency to that specific commit open-telemetry/opentelemetry-collector-contrib@95e673e because we don't know when is the next release.

Should I create a CI for this kafka integration test?

Signed-off-by: James Ryans <james.ryans2012@gmail.com>
@yurishkuro
Copy link
Member

The next collector release will be around Feb 11.

Should I create a CI for this kafka integration test?

Yes. Thanks.

@james-ryans
Copy link
Contributor Author

james-ryans commented Jan 24, 2024

we should be able to control it with .nocover files, but perhaps those only work for the main test script. Another alternative is to add the dir to .codecov.yaml as exclusion

Yes, I've added the .nocover files but it doesn't take any effect. I'll add an exclusion to that dir in .codecov.yaml.

@yurishkuro
Copy link
Member

does this release have what you need? #5143

Signed-off-by: James Ryans <james.ryans2012@gmail.com>
Signed-off-by: James Ryans <james.ryans2012@gmail.com>
@james-ryans
Copy link
Contributor Author

does this release have what you need? #5143

Wow, yes, it has! I've updated the go.mod but encountered an issue that the OTEL testcase is not closing.. I'll get back to you after I've found and fixed the issue. 😄

jkowall
jkowall previously approved these changes Jan 25, 2024
Copy link
Contributor

@jkowall jkowall left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, thanks for taking this on Yuri!

@yurishkuro
Copy link
Member

@jkowall I'm just reviewing, this is a community contribution from @james-ryans

@yurishkuro yurishkuro dismissed jkowall’s stale review January 25, 2024 15:39

not quite ready yet

@james-ryans
Copy link
Contributor Author

I think the issue in our CIT kafka v2 is because of OTEL kafka receiver v0.93.0 bug, the receiver is shutting down indefinitely. I've raised an issue on OTEL contrib repository here open-telemetry/opentelemetry-collector-contrib#30789. If it is actually the OTEL contrib bug, we'll need to wait until the next release again.

@yurishkuro
Copy link
Member

@james-ryans the ticket you referenced sounds like a deadlock issue during abnormal conditions, not when everything is running well, is that not true? Because if it's actually blocking normal operations it seems like a much bigger issue in OTEL.

@james-ryans
Copy link
Contributor Author

@james-ryans the ticket you referenced sounds like a deadlock issue during abnormal conditions, not when everything is running well, is that not true? Because if it's actually blocking normal operations it seems like a much bigger issue in OTEL.

I'm not sure, but I think this actually is a big issue? 🤔 They have labeled the ticket to priority:p1 since the deadlock always happens 100% when it's shutting down, there is no workaround to avoid this.

Or what you meant by "running well" is that when the collector never shut down? If yes, we can avoid this bug by not shutting them down after the test cases have been tested.

@yurishkuro
Copy link
Member

I didn't get into the meat of the issue, got the impression that deadlock happens when something first happens during startup. Otherwise how do any of their unit tests work, do they not test shutdown?

yurishkuro and others added 3 commits February 3, 2024 15:50
Signed-off-by: Yuri Shkuro <yurishkuro@users.noreply.github.com>
Signed-off-by: Yuri Shkuro <github@ysh.us>
Signed-off-by: Yuri Shkuro <github@ysh.us>
brokers:
- localhost:9092
encoding: otlp_proto # available encodings are otlp_proto, jaeger_proto, jaeger_json, zipkin_proto, zipkin_json, zipkin_thrift
initial_offset: earliest # consume messages from the beginning
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps we should parameterize this and default to latest checkpoint rather than earliest, since it would be very bad to run with earliest in production. The integration tests can override the value via env var.

extensions:
jaeger_storage:
grpc:
memstore:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we don't know it's a memstore in this config, let's call it 'external-storage'

return receiver.NewFactory(
componentType,
createDefaultConfig,
receiver.WithTraces(createTraces, component.StabilityLevelDevelopment),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
receiver.WithTraces(createTraces, component.StabilityLevelDevelopment),
receiver.WithTraces(tracesReceiver, component.StabilityLevelDevelopment),

return &Config{}
}

func createTraces(ctx context.Context, set receiver.CreateSettings, config component.Config, nextConsumer consumer.Traces) (receiver.Traces, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func createTraces(ctx context.Context, set receiver.CreateSettings, config component.Config, nextConsumer consumer.Traces) (receiver.Traces, error) {
func tracesReceiver(ctx context.Context, set receiver.CreateSettings, config component.Config, nextConsumer consumer.Traces) (receiver.Traces, error) {

if err != nil {
return nil, fmt.Errorf("failed to init storage factory: %w", err)
}
// TODO add support for other backends
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we use jaegerextension here, same as storageexporter?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think we can. I'll change the implementation in a new PR of this receiver.

Comment on lines +60 to +62
if err != nil {
t.Fatal(err)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

require.NoError(t, err)

t.Fatal(err)
}
configCleanup, err := runner.PrepareConfig(string(config))
require.NoError(t, err, "collector configuration resulted in: %v", err)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
require.NoError(t, err, "collector configuration resulted in: %v", err)
require.NoError(t, err)

load := i == 0
if load {
tc.StartLoad(testbed.LoadOptions{
DataItemsPerSecond: 16,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean there's no limit on the number of traces the generator creates? Seems like it would result in a random amount in each test. Not necessarily a problem as long as it doesn't cause flakiness.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It has a limit, it will stop once it has used all the combinations of traces and spans from the fixtures. It consistently stops at 3,524 traces every time I run the test.

Comment on lines +93 to +94
tc.WaitForN(func() bool { return tc.LoadGenerator.DataItemsSent() == tc.MockBackend.DataItemsReceived() },
10*time.Second, "all data items received")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
tc.WaitForN(func() bool { return tc.LoadGenerator.DataItemsSent() == tc.MockBackend.DataItemsReceived() },
10*time.Second, "all data items received")
tc.WaitForN(func() bool {
return tc.LoadGenerator.DataItemsSent() == tc.MockBackend.DataItemsReceived()
},
10*time.Second,
"all data items received")

@@ -0,0 +1 @@
FIXME
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

may want to pull storagereceiver into a separate PR as well, and add the tests to it (most other v2 packages already have tests)

Copy link
Member

@yurishkuro yurishkuro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please rebase on top of #5171

@james-ryans
Copy link
Contributor Author

Hi @yurishkuro, thank you for all of this invaluable feedback! I just recovered from dental implant post-OP fever, I'll get back to this issue as soon as I can. Sorry if the revision takes long enough. 😄

@yurishkuro
Copy link
Member

@james-ryans was the upstream issue in OTEL resolved in their latest release?

@james-ryans
Copy link
Contributor Author

@james-ryans was the upstream issue in OTEL resolved in their latest release?

Not yet. I've tried @v0.95.0, but I still encountered issues with terminating the Kafka receiver. I haven't fully understood the root cause yet. The issue they referenced occurs at startup, but our issue happens on shutdown, which is confusing. I plan to help pinpoint and possibly fix the problem or find out if there is a workaround after I finish refactoring this PR based on your feedbacks.

yurishkuro pushed a commit that referenced this pull request Feb 27, 2024
## Which problem is this PR solving?
- Part of #4843
- Separate GRPC storage PR from and will be used by jaeger-v2 Kafka PR
#4971

## Description of the changes
- Implement GRPC storage backend for Jaeger-V2 storage

## How was this change tested?
- Run two `jaegertracing/jaeger-remote-storage` at `17271` and `17281`
ports
- Execute `go run -tags=ui ./cmd/jaeger --config
./cmd/jaeger/grpc_config.yaml`

## Checklist
- [x] I have read
https://github.com/jaegertracing/jaeger/blob/master/CONTRIBUTING_GUIDELINES.md
- [x] I have signed all commits
- [x] I have added unit tests for the new functionality
- [x] I have run lint and test steps successfully
  - for `jaeger`: `make lint test`
  - for `jaeger-ui`: `yarn lint` and `yarn test`

---------

Signed-off-by: James Ryans <james.ryans2012@gmail.com>
yurishkuro pushed a commit that referenced this pull request Mar 4, 2024
## Which problem is this PR solving?
- Part of #4843
- Separate Jaeger storage receiver PR from and will be used by jaeger-v2
Kafka PR #4971

## Description of the changes
- Implement Jaeger storage receiver to be used by Jaeger-v2 Kafka
integration test.

## How was this change tested?
- Added some unit tests.

## Checklist
- [x] I have read
https://github.com/jaegertracing/jaeger/blob/master/CONTRIBUTING_GUIDELINES.md
- [x] I have signed all commits
- [x] I have added unit tests for the new functionality
- [x] I have run lint and test steps successfully
  - for `jaeger`: `make lint test`
  - for `jaeger-ui`: `yarn lint` and `yarn test`

---------

Signed-off-by: James Ryans <james.ryans2012@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
changelog:new-feature Change that should be called out as new feature in CHANGELOG
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants