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

Remote library configuration support and ITR::Runner #129

Merged
merged 20 commits into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
45d5d79
add DD_CIVISIBILITY_ITR_ENABLED setting
anmarchenko Feb 29, 2024
1169f66
add ITR::Runner class
anmarchenko Feb 29, 2024
7d5bb6f
configure ITR runner when instantiating the CI component
anmarchenko Feb 29, 2024
5000886
add ITR client to communicate with CI vis backend
anmarchenko Mar 1, 2024
b90c6d1
support headers override for test visibility API
anmarchenko Mar 1, 2024
1f6d3cb
API layer now supports both api.datadoghq.com and citestcycle-intake.…
anmarchenko Mar 4, 2024
286ed0c
decouple API client that requests library settings from ITR
anmarchenko Mar 4, 2024
b5c0aff
send correct runtime id in the library settings request
anmarchenko Mar 4, 2024
c99bd95
add DD_ENV to library settings request
anmarchenko Mar 4, 2024
e67b976
typechecking fix
anmarchenko Mar 4, 2024
4842bba
pass TestSession object to the ApiClient together with runtime inform…
anmarchenko Mar 4, 2024
688f02a
send correct settings request payload
anmarchenko Mar 5, 2024
a5bc756
error handling for settings api
anmarchenko Mar 5, 2024
c52453e
configure ITR::Runner according to remote configuration
anmarchenko Mar 5, 2024
04be43d
rename ApiClient to RemoteSettingsApi
anmarchenko Mar 5, 2024
fb76b51
add ITR tags to TestSession object when configuring ITR
anmarchenko Mar 5, 2024
4651472
debug logs for ITR configs and change readme for webmock and vcr
anmarchenko Mar 5, 2024
86932a8
add test_level attribute to remote configuration request
anmarchenko Mar 6, 2024
309d07a
Debug log http client response for all requests
anmarchenko Mar 6, 2024
06f6752
fix settings api path
anmarchenko Mar 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ Webmock accordingly.
```ruby
# when using agentless mode
# note to use the correct datadog site (e.g. datadoghq.eu, etc)
WebMock.disable_net_connect!(:allow => "citestcycle-intake.datadoghq.com")
WebMock.disable_net_connect!(:allow => /datadoghq.com/)

# when using agent
WebMock.disable_net_connect!(:allow_localhost => true)
Expand All @@ -199,7 +199,7 @@ VCR.configure do |config|

# when using agentless mode
# note to use the correct datadog site (e.g. datadoghq.eu, etc)
config.ignore_hosts "citestcycle-intake.datadoghq.com"
config.ignore_hosts "citestcycle-intake.datadoghq.com", "api.datadoghq.com"
end
```

Expand Down
24 changes: 20 additions & 4 deletions lib/datadog/ci/configuration/components.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
# frozen_string_literal: true

require_relative "../ext/settings"
require_relative "../itr/runner"
require_relative "../test_visibility/flush"
require_relative "../test_visibility/recorder"
require_relative "../test_visibility/null_recorder"
require_relative "../test_visibility/serializers/factories/test_level"
require_relative "../test_visibility/serializers/factories/test_suite_level"
require_relative "../test_visibility/transport"
require_relative "../transport/api/builder"
require_relative "../transport/remote_settings_api"

module Datadog
module CI
Expand Down Expand Up @@ -59,13 +61,30 @@ def activate_ci!(settings)
writer_options[:buffer_size] = 10_000

settings.tracing.test_mode.async = true
else
# only legacy APM protocol is supported, so no test suite level visibility
settings.ci.force_test_level_visibility = true

# ITR is not supported with APM protocol
settings.ci.itr_enabled = false
end

settings.tracing.test_mode.writer_options = writer_options

itr = Datadog::CI::ITR::Runner.new(
enabled: settings.ci.enabled && settings.ci.itr_enabled
)

remote_settings_api = Transport::RemoteSettingsApi.new(
api: test_visibility_api,
dd_env: settings.env
)

# CI visibility recorder global instance
@ci_recorder = TestVisibility::Recorder.new(
test_suite_level_visibility_enabled: !settings.ci.force_test_level_visibility
test_suite_level_visibility_enabled: !settings.ci.force_test_level_visibility,
itr: itr,
remote_settings_api: remote_settings_api
)
end

Expand Down Expand Up @@ -95,9 +114,6 @@ def build_test_visibility_api(settings)
Datadog.logger.debug(
"Old agent version detected, no evp_proxy support. Forcing test level visibility mode"
)

# CI visibility is still enabled but in legacy test level visibility mode
settings.ci.force_test_level_visibility = true
end
end

Expand Down
6 changes: 6 additions & 0 deletions lib/datadog/ci/configuration/settings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ def self.add_settings!(base)
end
end

option :itr_enabled do |o|
o.type :bool
o.env CI::Ext::Settings::ENV_ITR_ENABLED
o.default false
end

define_method(:instrument) do |integration_name, options = {}, &block|
return unless enabled

Expand Down
1 change: 1 addition & 0 deletions lib/datadog/ci/ext/settings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module Settings
ENV_AGENTLESS_URL = "DD_CIVISIBILITY_AGENTLESS_URL"
ENV_EXPERIMENTAL_TEST_SUITE_LEVEL_VISIBILITY_ENABLED = "DD_CIVISIBILITY_EXPERIMENTAL_TEST_SUITE_LEVEL_VISIBILITY_ENABLED"
ENV_FORCE_TEST_LEVEL_VISIBILITY = "DD_CIVISIBILITY_FORCE_TEST_LEVEL_VISIBILITY"
ENV_ITR_ENABLED = "DD_CIVISIBILITY_ITR_ENABLED"

# Source: https://docs.datadoghq.com/getting_started/site/
DD_SITE_ALLOWLIST = [
Expand Down
9 changes: 9 additions & 0 deletions lib/datadog/ci/ext/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ module Test
TAG_CODEOWNERS = "test.codeowners"
TAG_PARAMETERS = "test.parameters"

# ITR tags
TAG_ITR_TEST_SKIPPING_ENABLED = "test.itr.tests_skipping.enabled"
TAG_ITR_TEST_SKIPPING_TYPE = "test.itr.tests_skipping.type"

# Code coverage tags
TAG_CODE_COVERAGE_ENABLED = "test.code_coverage.enabled"

# those tags are special and used to correlate tests with the test sessions, suites, and modules
# they are transient and not sent to the backend
TAG_TEST_SESSION_ID = "_test.session_id"
Expand All @@ -43,6 +50,8 @@ module Test
TAG_SPAN_KIND = "span.kind"
SPAN_KIND_TEST = "test"

ITR_TEST_SKIPPING_MODE = "test"

# test status as recognized by Datadog
module Status
PASS = "pass"
Expand Down
11 changes: 11 additions & 0 deletions lib/datadog/ci/ext/transport.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,18 @@ module Transport
TEST_VISIBILITY_INTAKE_HOST_PREFIX = "citestcycle-intake"
TEST_VISIBILITY_INTAKE_PATH = "/api/v2/citestcycle"

DD_API_HOST_PREFIX = "api"
DD_API_SETTINGS_PATH = "/api/v2/ci/libraries/tests/services/setting"
DD_API_SETTINGS_TYPE = "ci_app_test_service_libraries_settings"
DD_API_SETTINGS_RESPONSE_DIG_KEYS = %w[data attributes].freeze
DD_API_SETTINGS_RESPONSE_ITR_ENABLED_KEY = "itr_enabled"
DD_API_SETTINGS_RESPONSE_CODE_COVERAGE_KEY = "code_coverage"
DD_API_SETTINGS_RESPONSE_TESTS_SKIPPING_KEY = "tests_skipping"
DD_API_SETTINGS_RESPONSE_REQUIRE_GIT_KEY = "require_git"
DD_API_SETTINGS_RESPONSE_DEFAULT = {DD_API_SETTINGS_RESPONSE_ITR_ENABLED_KEY => false}.freeze

CONTENT_TYPE_MESSAGEPACK = "application/msgpack"
CONTENT_TYPE_JSON = "application/json"
CONTENT_ENCODING_GZIP = "gzip"
end
end
Expand Down
67 changes: 67 additions & 0 deletions lib/datadog/ci/itr/runner.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# frozen_string_literal: true

require_relative "../ext/test"
require_relative "../ext/transport"

module Datadog
module CI
module ITR
# Intelligent test runner implementation
# Integrates with backend to provide test impact analysis data and
# skip tests that are not impacted by the changes
class Runner
def initialize(
enabled: false
)
@enabled = enabled
@test_skipping_enabled = false
@code_coverage_enabled = false

Datadog.logger.debug("ITR Runner initialized with enabled: #{@enabled}")
end

def configure(remote_configuration, test_session)
Datadog.logger.debug("Configuring ITR Runner with remote configuration: #{remote_configuration}")

@enabled = convert_to_bool(
remote_configuration.fetch(Ext::Transport::DD_API_SETTINGS_RESPONSE_ITR_ENABLED_KEY, false)
)
@test_skipping_enabled = @enabled && convert_to_bool(
remote_configuration.fetch(Ext::Transport::DD_API_SETTINGS_RESPONSE_TESTS_SKIPPING_KEY, false)
)
@code_coverage_enabled = @enabled && convert_to_bool(
remote_configuration.fetch(Ext::Transport::DD_API_SETTINGS_RESPONSE_CODE_COVERAGE_KEY, false)
)

test_session.set_tag(Ext::Test::TAG_ITR_TEST_SKIPPING_ENABLED, @test_skipping_enabled)
# currently we set this tag when ITR requires collecting code coverage
# this will change as soon as we implement total code coverage support in this library
test_session.set_tag(Ext::Test::TAG_CODE_COVERAGE_ENABLED, @code_coverage_enabled)

# we skip tests, not suites
test_session.set_tag(Ext::Test::TAG_ITR_TEST_SKIPPING_TYPE, Ext::Test::ITR_TEST_SKIPPING_MODE)

Datadog.logger.debug("Configured ITR Runner with enabled: #{@enabled}, skipping_tests: #{@test_skipping_enabled}, code_coverage: #{@code_coverage_enabled}")
end

def enabled?
@enabled
end

def skipping_tests?
@test_skipping_enabled
end

def code_coverage?
@code_coverage_enabled
end

private

def convert_to_bool(value)
value.to_s == "true"
end
end
end
end
end
42 changes: 42 additions & 0 deletions lib/datadog/ci/span.rb
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,48 @@ def set_tags(tags)
tracer_span.set_tags(tags)
end

# Returns the git repository URL extracted from the environment.
# @return [String] the repository URL.
def git_repository_url
tracer_span.get_tag(Ext::Git::TAG_REPOSITORY_URL)
end

# Returns the latest commit SHA extracted from the environment.
# @return [String] the commit SHA of the last commit.
def git_commit_sha
tracer_span.get_tag(Ext::Git::TAG_COMMIT_SHA)
end

# Returns the git branch name extracted from the environment.
# @return [String] the branch.
def git_branch
tracer_span.get_tag(Ext::Git::TAG_BRANCH)
end

# Returns the OS architecture extracted from the environment.
# @return [String] OS arch.
def os_architecture
tracer_span.get_tag(Ext::Test::TAG_OS_ARCHITECTURE)
end

# Returns the OS platform extracted from the environment.
# @return [String] OS platform.
def os_platform
tracer_span.get_tag(Ext::Test::TAG_OS_PLATFORM)
end

# Returns the runtime name extracted from the environment.
# @return [String] runtime name.
def runtime_name
tracer_span.get_tag(Ext::Test::TAG_RUNTIME_NAME)
end

# Returns the runtime version extracted from the environment.
# @return [String] runtime version.
def runtime_version
tracer_span.get_tag(Ext::Test::TAG_RUNTIME_VERSION)
end

def set_environment_runtime_tags
tracer_span.set_tag(Ext::Test::TAG_OS_ARCHITECTURE, ::RbConfig::CONFIG["host_cpu"])
tracer_span.set_tag(Ext::Test::TAG_OS_PLATFORM, ::RbConfig::CONFIG["host_os"])
Expand Down
8 changes: 8 additions & 0 deletions lib/datadog/ci/test_session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ def name
get_tag(Ext::Test::TAG_COMMAND)
end

def skipping_tests?
get_tag(Ext::Test::TAG_ITR_TEST_SKIPPING_ENABLED) == "true"
end

def code_coverage?
get_tag(Ext::Test::TAG_CODE_COVERAGE_ENABLED) == "true"
end

# Return the test session tags that could be inherited by sub-spans
# @return [Hash] the tags to be inherited by sub-spans.
def inheritable_tags
Expand Down
24 changes: 22 additions & 2 deletions lib/datadog/ci/test_visibility/recorder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,19 @@ class Recorder
attr_reader :environment_tags, :test_suite_level_visibility_enabled

def initialize(
test_suite_level_visibility_enabled: false,
itr:, remote_settings_api:, test_suite_level_visibility_enabled: false,
codeowners: Codeowners::Parser.new(Utils::Git.root).parse
)
@test_suite_level_visibility_enabled = test_suite_level_visibility_enabled

@environment_tags = Ext::Environment.tags(ENV).freeze
@local_context = Context::Local.new
@global_context = Context::Global.new

@codeowners = codeowners

@itr = itr
@remote_settings_api = remote_settings_api
end

def start_test_session(service: nil, tags: {})
Expand All @@ -49,7 +53,11 @@ def start_test_session(service: nil, tags: {})
)
set_session_context(tags, tracer_span)

build_test_session(tracer_span, tags)
test_session = build_test_session(tracer_span, tags)

configure_library(test_session)

test_session
end
end

Expand Down Expand Up @@ -175,8 +183,20 @@ def deactivate_test_suite(test_suite_name)
@global_context.deactivate_test_suite!(test_suite_name)
end

def itr_enabled?
@itr.enabled?
end

private

def configure_library(test_session)
# this will change when EFD is implemented
return unless itr_enabled?

remote_configuration = @remote_settings_api.fetch_library_settings(test_session)
@itr.configure(remote_configuration.payload, test_session)
end

def skip_tracing(block = nil)
block.call(nil) if block
end
Expand Down
2 changes: 1 addition & 1 deletion lib/datadog/ci/test_visibility/transport.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def send_traces(traces)
private

def send_payload(encoded_payload)
api.request(
api.citestcycle_request(
path: Datadog::CI::Ext::Transport::TEST_VISIBILITY_INTAKE_PATH,
payload: encoded_payload
)
Expand Down
39 changes: 36 additions & 3 deletions lib/datadog/ci/transport/api/agentless.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,48 @@ module Api
class Agentless < Base
attr_reader :api_key

def initialize(api_key:, http:)
def initialize(api_key:, citestcycle_url:, api_url:)
@api_key = api_key
@citestcycle_http = build_http_client(citestcycle_url, compress: true)
@api_http = build_http_client(api_url, compress: false)
end

def citestcycle_request(path:, payload:, headers: {}, verb: "post")
super

perform_request(@citestcycle_http, path: path, payload: payload, headers: headers, verb: verb)
end

def api_request(path:, payload:, headers: {}, verb: "post")
super

super(http: http)
perform_request(@api_http, path: path, payload: payload, headers: headers, verb: verb)
end

private

def headers
def perform_request(http_client, path:, payload:, headers:, verb:)
http_client.request(
path: path,
payload: payload,
headers: headers_with_default(headers),
verb: verb
)
end

def build_http_client(url, compress:)
uri = URI.parse(url)
raise "Invalid agentless mode URL: #{url}" if uri.host.nil?

Datadog::CI::Transport::HTTP.new(
host: uri.host,
port: uri.port,
ssl: uri.scheme == "https" || uri.port == 443,
compress: compress
)
end

def default_headers
headers = super
headers[Ext::Transport::HEADER_DD_API_KEY] = api_key
headers
Expand Down
Loading
Loading