Skip to content

Commit

Permalink
Merge pull request #210 from DataDog/anmarchenko/remote_configuration…
Browse files Browse the repository at this point in the history
…_component

[SDTEST-172] Remote configuration component
  • Loading branch information
anmarchenko authored Jul 30, 2024
2 parents 03fe65e + 1ea8eff commit 0e24d28
Show file tree
Hide file tree
Showing 19 changed files with 415 additions and 223 deletions.
22 changes: 13 additions & 9 deletions lib/datadog/ci/configuration/components.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

require_relative "../ext/settings"
require_relative "../git/tree_uploader"
require_relative "../remote/component"
require_relative "../remote/library_settings_client"
require_relative "../test_optimisation/component"
require_relative "../test_optimisation/coverage/transport"
require_relative "../test_optimisation/coverage/writer"
Expand All @@ -15,7 +17,6 @@
require_relative "../test_visibility/transport"
require_relative "../transport/adapters/telemetry_webmock_safe_adapter"
require_relative "../transport/api/builder"
require_relative "../transport/remote_settings_api"
require_relative "../utils/identity"
require_relative "../utils/parsing"
require_relative "../utils/test_run"
Expand All @@ -26,11 +27,13 @@ module CI
module Configuration
# Adds CI behavior to Datadog trace components
module Components
attr_reader :test_visibility, :test_optimisation
attr_reader :test_visibility, :test_optimisation, :git_tree_upload_worker, :ci_remote

def initialize(settings)
@test_optimisation = nil
@test_visibility = TestVisibility::NullComponent.new
@git_tree_upload_worker = DummyWorker.new
@ci_remote = nil

# Activate CI mode if enabled
if settings.ci.enabled
Expand All @@ -45,6 +48,7 @@ def shutdown!(replacement = nil)

@test_visibility&.shutdown!
@test_optimisation&.shutdown!
@git_tree_upload_worker&.stop
end

def activate_ci!(settings)
Expand Down Expand Up @@ -102,14 +106,14 @@ def activate_ci!(settings)

settings.tracing.test_mode.writer_options = trace_writer_options

@git_tree_upload_worker = build_git_upload_worker(settings, test_visibility_api)
@ci_remote = Remote::Component.new(
library_settings_client: build_library_settings_client(settings, test_visibility_api)
)
# @type ivar @test_optimisation: Datadog::CI::TestOptimisation::Component
@test_optimisation = build_test_optimisation(settings, test_visibility_api)

@test_visibility = TestVisibility::Component.new(
test_optimisation: @test_optimisation,
test_suite_level_visibility_enabled: !settings.ci.force_test_level_visibility,
remote_settings_api: build_remote_settings_client(settings, test_visibility_api),
git_tree_upload_worker: build_git_upload_worker(settings, test_visibility_api)
test_suite_level_visibility_enabled: !settings.ci.force_test_level_visibility
)
end

Expand Down Expand Up @@ -216,8 +220,8 @@ def build_git_upload_worker(settings, api)
end
end

def build_remote_settings_client(settings, api)
Transport::RemoteSettingsApi.new(
def build_library_settings_client(settings, api)
Remote::LibrarySettingsClient.new(
api: api,
dd_env: settings.env,
config_tags: custom_configuration(settings)
Expand Down
45 changes: 45 additions & 0 deletions lib/datadog/ci/remote/component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# frozen_string_literal: true

module Datadog
module CI
module Remote
# Remote configuration component.
# Responsible for fetching library settings and configuring the library accordingly.
class Component
def initialize(library_settings_client:)
@library_settings_client = library_settings_client
end

# called on test session start, uses test session info to send configuration request to the backend
def configure(test_session)
library_configuration = @library_settings_client.fetch(test_session)
# sometimes we can skip code coverage for default branch if there are no changes in the repository
# backend needs git metadata uploaded for this test session to check if we can skip code coverage
if library_configuration.require_git?
Datadog.logger.debug { "Library configuration endpoint requires git upload to be finished, waiting..." }
git_tree_upload_worker.wait_until_done

Datadog.logger.debug { "Requesting library configuration again..." }
library_configuration = @library_settings_client.fetch(test_session)

if library_configuration.require_git?
Datadog.logger.debug { "git metadata upload did not complete in time when configuring library" }
end
end

test_optimisation.configure(library_configuration, test_session)
end

private

def test_optimisation
Datadog.send(:components).test_optimisation
end

def git_tree_upload_worker
Datadog.send(:components).git_tree_upload_worker
end
end
end
end
end
85 changes: 85 additions & 0 deletions lib/datadog/ci/remote/library_settings.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# frozen_string_literal: true

require "json"

require_relative "../ext/telemetry"
require_relative "../ext/transport"
require_relative "../transport/telemetry"
require_relative "../utils/parsing"

module Datadog
module CI
module Remote
# Wrapper around the settings HTTP response
class LibrarySettings
def initialize(http_response)
@http_response = http_response
@json = nil
end

def ok?
resp = @http_response
!resp.nil? && resp.ok?
end

def payload
cached = @json
return cached unless cached.nil?

resp = @http_response
return @json = default_payload if resp.nil? || !ok?

begin
@json = JSON.parse(resp.payload).dig(*Ext::Transport::DD_API_SETTINGS_RESPONSE_DIG_KEYS) ||
default_payload
rescue JSON::ParserError => e
Datadog.logger.error("Failed to parse settings response payload: #{e}. Payload was: #{resp.payload}")

Transport::Telemetry.api_requests_errors(
Ext::Telemetry::METRIC_GIT_REQUESTS_SETTINGS_ERRORS,
1,
error_type: "invalid_json",
status_code: nil
)

@json = default_payload
end
end

def require_git?
return @require_git if defined?(@require_git)

@require_git = bool(Ext::Transport::DD_API_SETTINGS_RESPONSE_REQUIRE_GIT_KEY)
end

def itr_enabled?
return @itr_enabled if defined?(@itr_enabled)

@itr_enabled = bool(Ext::Transport::DD_API_SETTINGS_RESPONSE_ITR_ENABLED_KEY)
end

def code_coverage_enabled?
return @code_coverage_enabled if defined?(@code_coverage_enabled)

@code_coverage_enabled = bool(Ext::Transport::DD_API_SETTINGS_RESPONSE_CODE_COVERAGE_KEY)
end

def tests_skipping_enabled?
return @tests_skipping_enabled if defined?(@tests_skipping_enabled)

@tests_skipping_enabled = bool(Ext::Transport::DD_API_SETTINGS_RESPONSE_TESTS_SKIPPING_KEY)
end

private

def bool(key)
Utils::Parsing.convert_to_bool(payload.fetch(key, false))
end

def default_payload
Ext::Transport::DD_API_SETTINGS_RESPONSE_DEFAULT
end
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -4,73 +4,28 @@

require "datadog/core/environment/identity"

require_relative "library_settings"

require_relative "../ext/test"
require_relative "../ext/telemetry"
require_relative "../ext/transport"
require_relative "../transport/telemetry"
require_relative "../utils/parsing"
require_relative "../utils/telemetry"

module Datadog
module CI
module Transport
# Datadog API client
module Remote
# Calls settings endpoint to fetch library settings for given service and env
class RemoteSettingsApi
class Response
def initialize(http_response)
@http_response = http_response
@json = nil
end

def ok?
resp = @http_response
!resp.nil? && resp.ok?
end

def payload
cached = @json
return cached unless cached.nil?

resp = @http_response
return @json = default_payload if resp.nil? || !ok?

begin
@json = JSON.parse(resp.payload).dig(*Ext::Transport::DD_API_SETTINGS_RESPONSE_DIG_KEYS) ||
default_payload
rescue JSON::ParserError => e
Datadog.logger.error("Failed to parse settings response payload: #{e}. Payload was: #{resp.payload}")

Transport::Telemetry.api_requests_errors(
Ext::Telemetry::METRIC_GIT_REQUESTS_SETTINGS_ERRORS,
1,
error_type: "invalid_json",
status_code: nil
)

@json = default_payload
end
end

def require_git?
Utils::Parsing.convert_to_bool(payload[Ext::Transport::DD_API_SETTINGS_RESPONSE_REQUIRE_GIT_KEY])
end

private

def default_payload
Ext::Transport::DD_API_SETTINGS_RESPONSE_DEFAULT
end
end

class LibrarySettingsClient
def initialize(dd_env:, api: nil, config_tags: {})
@api = api
@dd_env = dd_env
@config_tags = config_tags || {}
end

def fetch_library_settings(test_session)
def fetch(test_session)
api = @api
return Response.new(nil) unless api
return LibrarySettings.new(nil) unless api

request_payload = payload(test_session)
Datadog.logger.debug("Fetching library settings with request: #{request_payload}")
Expand All @@ -96,18 +51,18 @@ def fetch_library_settings(test_session)
)
end

response = Response.new(http_response)
library_settings = LibrarySettings.new(http_response)

Utils::Telemetry.inc(
Ext::Telemetry::METRIC_GIT_REQUESTS_SETTINGS_RESPONSE,
1,
{
Ext::Telemetry::TAG_COVERAGE_ENABLED => response.payload[Ext::Transport::DD_API_SETTINGS_RESPONSE_CODE_COVERAGE_KEY],
Ext::Telemetry::TAG_ITR_SKIP_ENABLED => response.payload[Ext::Transport::DD_API_SETTINGS_RESPONSE_TESTS_SKIPPING_KEY]
Ext::Telemetry::TAG_COVERAGE_ENABLED => library_settings.code_coverage_enabled?.to_s,
Ext::Telemetry::TAG_ITR_SKIP_ENABLED => library_settings.tests_skipping_enabled?.to_s
}
)

response
library_settings
end

private
Expand Down
28 changes: 12 additions & 16 deletions lib/datadog/ci/test_optimisation/component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

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

require_relative "../git/local_repository"

Expand Down Expand Up @@ -65,32 +64,25 @@ def initialize(
Datadog.logger.debug("TestOptimisation initialized with enabled: #{@enabled}")
end

def configure(remote_configuration, test_session:, git_tree_upload_worker:)
def configure(remote_configuration, test_session)
return unless enabled?

Datadog.logger.debug("Configuring TestOptimisation with remote configuration: #{remote_configuration}")

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

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)

load_datadog_cov! if @code_coverage_enabled

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

fetch_skippable_tests(test_session: test_session, git_tree_upload_worker: git_tree_upload_worker)
fetch_skippable_tests(test_session)
end

def enabled?
Expand Down Expand Up @@ -225,7 +217,7 @@ def ensure_test_source_covered(test_source_file, coverage)
coverage[absolute_test_source_file_path] = true
end

def fetch_skippable_tests(test_session:, git_tree_upload_worker:)
def fetch_skippable_tests(test_session)
return unless skipping_tests?

# we can only request skippable tests if git metadata is already uploaded
Expand All @@ -248,6 +240,10 @@ def fetch_skippable_tests(test_session:, git_tree_upload_worker:)
def code_coverage_mode
@use_single_threaded_coverage ? :single : :multi
end

def git_tree_upload_worker
Datadog.send(:components).git_tree_upload_worker
end
end
end
end
Expand Down
Loading

0 comments on commit 0e24d28

Please sign in to comment.