From 7c46b1d316b963b3bd5f55ca6b52657866ca4ed1 Mon Sep 17 00:00:00 2001 From: Jason Plumb Date: Mon, 19 Sep 2022 15:57:04 -0700 Subject: [PATCH 1/4] Introduce the splunk-otel-java-agent framework. This framework will wire up the splunk distribution of opentelemetry java for auto-instrumentation and direct ingest. It leverages VCAP_SERVICES and the existence of a service binding with a specific name (splunk-o11y). Signed-off-by: Jason Plumb jplumb@splunk.com Resolves issue #825 --- config/components.yml | 1 + config/splunk_otel_java_agent.yml | 21 +++++ docs/framework-splunk_otel_java_agent.md | 51 ++++++++++++ .../framework/splunk_otel_java_agent.rb | 58 ++++++++++++++ spec/fixtures/stub-splunk-otel-javaagent.jar | Bin 0 -> 341 bytes .../framework/splunk_otel_java_agent_spec.rb | 74 ++++++++++++++++++ 6 files changed, 205 insertions(+) create mode 100644 config/splunk_otel_java_agent.yml create mode 100644 docs/framework-splunk_otel_java_agent.md create mode 100644 lib/java_buildpack/framework/splunk_otel_java_agent.rb create mode 100644 spec/fixtures/stub-splunk-otel-javaagent.jar create mode 100644 spec/java_buildpack/framework/splunk_otel_java_agent_spec.rb diff --git a/config/components.yml b/config/components.yml index 9011950635..933bf1a413 100644 --- a/config/components.yml +++ b/config/components.yml @@ -71,6 +71,7 @@ frameworks: - "JavaBuildpack::Framework::SealightsAgent" - "JavaBuildpack::Framework::SeekerSecurityProvider" - "JavaBuildpack::Framework::SpringAutoReconfiguration" + - "JavaBuildpack::Framework::SplunkOtelJavaAgent" - "JavaBuildpack::Framework::SpringInsight" - "JavaBuildpack::Framework::SkyWalkingAgent" - "JavaBuildpack::Framework::YourKitProfiler" diff --git a/config/splunk_otel_java_agent.yml b/config/splunk_otel_java_agent.yml new file mode 100644 index 0000000000..7a3824cf67 --- /dev/null +++ b/config/splunk_otel_java_agent.yml @@ -0,0 +1,21 @@ +# Cloud Foundry Java Buildpack +# Copyright 2013-2020 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Configuration for the Splunk Distribution of OpenTelemetry Java Instrumentation +# See https://github.com/signalfx/splunk-otel-java for more information +--- +version: + +repository_root: https://raw.githubusercontent.com/signalfx/splunk-otel-java/main/deployments/cloudfoundry/ + \ No newline at end of file diff --git a/docs/framework-splunk_otel_java_agent.md b/docs/framework-splunk_otel_java_agent.md new file mode 100644 index 0000000000..5724c8aec1 --- /dev/null +++ b/docs/framework-splunk_otel_java_agent.md @@ -0,0 +1,51 @@ +# Splunk Distribution of OpenTelemetry Java Instrumentation + +The Splunk OpenTelemetry Java Agent buildpack framework will cause an application to be automatically instrumented +with the [Splunk distribution of OpenTelemetry Java Instrumentation](https://github.com/signalfx/splunk-otel-java). + +Trace data will be sent directly to Splunk Observability Cloud. + +* **Detection criteria**: Existence of a bound service with the name `splunk-o11y`. +* **Tags**: `splunk-otel-java-agent=` to control which version of the instrumentation agent is used. + * Default = latest available version + +## User-Provided Service + +Users are currently expected to provide their own "custom user provided service" (cups) +instance and bind it to their application. The service MUST be named `splunk-o11y`. + +For example, to create a service named `splunk-o11y` that represents Observability Cloud +realm `us0` and represents a user environment named `cf-demo`, you could use the following +commands: + +``` +$ cf cups splunk-o11y -p \ + '{"splunk.realm": "us0", "splunk.access.token": "", "otel.resource.attributes": "deployment.environment=cf-demo"}' +$ cf bind-service myApp splunk-o11y +$ cf restage myApp +``` + +The `credential` field of the service should provide these entries: + +| Name | Required? | Description +|------------------------|-----------| ----------- +| `splunk.access.token` | Yes | The Splunk [org access token](https://docs.splunk.com/observability/admin/authentication-tokens/org-tokens.html). +| `splunk.realm` | Yes | The Splunk realm where data will be sent. This is commonly `us0` or `eu0` etc. +| `otel.*` or `splunk.*` | Optional | All additional credentials starting with these prefixes will be appended to the application's JVM arguments as system properties. + +### Choosing a version + +Most users should skip this and simply use the latest version of the agent available (the default). +To override the default and choose a specific version, you can use the `JBP_CONFIG_*` mechanism +and set the `JBP_CONFIG_SPLUNK_OTEL_JAVA_AGENT` environment variable for your application. + +For example, to use version 1.16.0 of the Splunk OpenTelemetry Java Instrumentation, you +could run: +``` +$ cf set-env testapp JBP_CONFIG_SPLUNK_OTEL_JAVA_AGENT '{version: 1.16.0}' +``` + +# Additional Resources + +* [Splunk Observability](https://www.splunk.com/en_us/products/observability.html) +* [Splunk Distribution of OpenTelemetry Java](https://github.com/signalfx/splunk-otel-java) on GitHub diff --git a/lib/java_buildpack/framework/splunk_otel_java_agent.rb b/lib/java_buildpack/framework/splunk_otel_java_agent.rb new file mode 100644 index 0000000000..bb18e59913 --- /dev/null +++ b/lib/java_buildpack/framework/splunk_otel_java_agent.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +# Cloud Foundry Java Buildpack +# Copyright 2013-2020 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'java_buildpack/component/versioned_dependency_component' +require 'java_buildpack/framework' + +module JavaBuildpack + module Framework + + # Main class for adding the Splunk OpenTelemetry instrumentation agent + class SplunkOtelJavaAgent < JavaBuildpack::Component::VersionedDependencyComponent + + # (see JavaBuildpack::Component::BaseComponent#compile) + def compile + download_jar + end + + # (see JavaBuildpack::Component::BaseComponent#release) + def release + java_opts = @droplet.java_opts + java_opts.add_javaagent(@droplet.sandbox + jar_name) + + credentials = @application.services.find_service(REQUIRED_SERVICE_NAME_FILTER)['credentials'] + # Add all otel.* and splunk.* credentials from the service bind as jvm system properties + credentials&.each do |key, value| + java_opts.add_system_property(key, value) if key.start_with?('splunk.') || key.start_with?('otel.') + end + + app_name = @application.details['application_name'] + java_opts.add_system_property('otel.service.name', app_name) + end + + protected + + # (see JavaBuildpack::Component::VersionedDependencyComponent#supports?) + def supports? + @application.services.one_service? REQUIRED_SERVICE_NAME_FILTER + end + + REQUIRED_SERVICE_NAME_FILTER = /^splunk-o11y$/.freeze + + end + end +end diff --git a/spec/fixtures/stub-splunk-otel-javaagent.jar b/spec/fixtures/stub-splunk-otel-javaagent.jar new file mode 100644 index 0000000000000000000000000000000000000000..0878c3ccb50e0696f38ae356e9854c75199158f3 GIT binary patch literal 341 zcmWIWW@Zs#;Nak3$ZLM$#DD}i8CV#6T|*poJ^kGD|D9rBU}gyLX6FE@V1gx0>v$BCyF#%yMkUj+BFaQAGLPY%l literal 0 HcmV?d00001 diff --git a/spec/java_buildpack/framework/splunk_otel_java_agent_spec.rb b/spec/java_buildpack/framework/splunk_otel_java_agent_spec.rb new file mode 100644 index 0000000000..476f78ad52 --- /dev/null +++ b/spec/java_buildpack/framework/splunk_otel_java_agent_spec.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +# Cloud Foundry Java Buildpack +# Copyright 2013-2020 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'spec_helper' +require 'component_helper' +require 'java_buildpack/framework/splunk_otel_java_agent' +require 'java_buildpack/util/tokenized_version' + +describe JavaBuildpack::Framework::SplunkOtelJavaAgent do + include_context 'with component help' + + let(:configuration) { { 'version' => '1.16.0' } } + let(:vcap_application) { { 'application_name' => 'GreatServiceTM' } } + + it 'does not detect without splunk-o11y service bind' do + expect(component.detect).to be_nil + end + + context 'when detected' do + + before do + allow(services).to receive(:one_service?).with(/^splunk-o11y$/).and_return(true) + end + + it 'detects with splunk-otel-java' do + expect(component.detect).to eq("splunk-otel-java-agent=#{version}") + end + + it 'downloads the splunk otel javaagent jar', cache_fixture: 'stub-splunk-otel-javaagent.jar' do + + component.compile + + expect(sandbox + "splunk_otel_java_agent-#{version}.jar").to exist + end + + it 'updates JAVA_OPTS' do + allow(services).to receive(:find_service).and_return('credentials' => { 'splunk.access.token' => 'sekret', + 'ignored' => 'not used', + 'otel.foo' => 'bar' }) + component.release + + expect(java_opts).to include( + "-javaagent:$PWD/.java-buildpack/splunk_otel_java_agent/splunk_otel_java_agent-#{version}.jar" + ) + expect(java_opts).to include('-Dsplunk.access.token=sekret') + expect(java_opts).to include('-Dotel.foo=bar') + end + + it 'sets the service name from the application name' do + allow(services).to receive(:find_service).and_return('credentials' => { 'splunk.access.token' => 'sekret' }) + # allow(details).to be( { 'application_name' => 'dick' }) + + component.release + + expect(java_opts).to include('-Dotel.service.name=GreatServiceTM') + end + + end + +end From 52a6183baa0444b5d930e37bb8a23efd5ae9e3d3 Mon Sep 17 00:00:00 2001 From: Jason Plumb Date: Wed, 14 Sep 2022 10:53:16 -0700 Subject: [PATCH 2/4] Use consistent doc table format --- docs/framework-splunk_otel_java_agent.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/docs/framework-splunk_otel_java_agent.md b/docs/framework-splunk_otel_java_agent.md index 5724c8aec1..0ba30e97d5 100644 --- a/docs/framework-splunk_otel_java_agent.md +++ b/docs/framework-splunk_otel_java_agent.md @@ -5,14 +5,23 @@ with the [Splunk distribution of OpenTelemetry Java Instrumentation](https://git Trace data will be sent directly to Splunk Observability Cloud. -* **Detection criteria**: Existence of a bound service with the name `splunk-o11y`. -* **Tags**: `splunk-otel-java-agent=` to control which version of the instrumentation agent is used. - * Default = latest available version + + + + + + + + + +
Detection CriterionExistence of a bound service containing the string splunk-o11y
Tagssplunk-otel-java-agent=<version>
+ +Tags are printed to standard output by the buildpack detect script ## User-Provided Service Users are currently expected to provide their own "custom user provided service" (cups) -instance and bind it to their application. The service MUST be named `splunk-o11y`. +instance and bind it to their application. The service MUST contain the string `splunk-o11y`. For example, to create a service named `splunk-o11y` that represents Observability Cloud realm `us0` and represents a user environment named `cf-demo`, you could use the following From 8e0e951cf1d13e663d22dc445e5e48392aa6f584 Mon Sep 17 00:00:00 2001 From: Jason Plumb Date: Thu, 29 Sep 2022 13:07:15 -0700 Subject: [PATCH 3/4] relax to substring match --- lib/java_buildpack/framework/splunk_otel_java_agent.rb | 2 +- spec/java_buildpack/framework/splunk_otel_java_agent_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/java_buildpack/framework/splunk_otel_java_agent.rb b/lib/java_buildpack/framework/splunk_otel_java_agent.rb index bb18e59913..160605eb7f 100644 --- a/lib/java_buildpack/framework/splunk_otel_java_agent.rb +++ b/lib/java_buildpack/framework/splunk_otel_java_agent.rb @@ -51,7 +51,7 @@ def supports? @application.services.one_service? REQUIRED_SERVICE_NAME_FILTER end - REQUIRED_SERVICE_NAME_FILTER = /^splunk-o11y$/.freeze + REQUIRED_SERVICE_NAME_FILTER = /splunk-o11y/.freeze end end diff --git a/spec/java_buildpack/framework/splunk_otel_java_agent_spec.rb b/spec/java_buildpack/framework/splunk_otel_java_agent_spec.rb index 476f78ad52..96797b96f5 100644 --- a/spec/java_buildpack/framework/splunk_otel_java_agent_spec.rb +++ b/spec/java_buildpack/framework/splunk_otel_java_agent_spec.rb @@ -33,7 +33,7 @@ context 'when detected' do before do - allow(services).to receive(:one_service?).with(/^splunk-o11y$/).and_return(true) + allow(services).to receive(:one_service?).with(/splunk-o11y/).and_return(true) end it 'detects with splunk-otel-java' do From 71b5948c7c0870d86d153fa5856b37dd9b8775b8 Mon Sep 17 00:00:00 2001 From: Jason Plumb Date: Thu, 29 Sep 2022 14:02:02 -0700 Subject: [PATCH 4/4] only set otel.service.name from application_name if not set in credentials --- lib/java_buildpack/framework/splunk_otel_java_agent.rb | 4 ++++ .../framework/splunk_otel_java_agent_spec.rb | 10 +++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/java_buildpack/framework/splunk_otel_java_agent.rb b/lib/java_buildpack/framework/splunk_otel_java_agent.rb index 160605eb7f..f3361a869d 100644 --- a/lib/java_buildpack/framework/splunk_otel_java_agent.rb +++ b/lib/java_buildpack/framework/splunk_otel_java_agent.rb @@ -40,6 +40,9 @@ def release java_opts.add_system_property(key, value) if key.start_with?('splunk.') || key.start_with?('otel.') end + # Set the otel.service.name to the application_name if not specified in credentials + return if credentials.key? 'otel.service.name' + app_name = @application.details['application_name'] java_opts.add_system_property('otel.service.name', app_name) end @@ -51,6 +54,7 @@ def supports? @application.services.one_service? REQUIRED_SERVICE_NAME_FILTER end + # bound service must contain the string `splunk-o11y` REQUIRED_SERVICE_NAME_FILTER = /splunk-o11y/.freeze end diff --git a/spec/java_buildpack/framework/splunk_otel_java_agent_spec.rb b/spec/java_buildpack/framework/splunk_otel_java_agent_spec.rb index 96797b96f5..eb75ada8e5 100644 --- a/spec/java_buildpack/framework/splunk_otel_java_agent_spec.rb +++ b/spec/java_buildpack/framework/splunk_otel_java_agent_spec.rb @@ -62,13 +62,21 @@ it 'sets the service name from the application name' do allow(services).to receive(:find_service).and_return('credentials' => { 'splunk.access.token' => 'sekret' }) - # allow(details).to be( { 'application_name' => 'dick' }) component.release expect(java_opts).to include('-Dotel.service.name=GreatServiceTM') end + it 'prefers credentials over application_name for service name' do + creds = { 'credentials' => { 'otel.service.name' => 'sweet', 'splunk.access.token' => 'sekret' } } + allow(services).to receive(:find_service).and_return(creds) + + component.release + + expect(java_opts).to include('-Dotel.service.name=sweet') + end + end end