From 82cb46bd37ef26d2505e1545ab11f4fd3d356b88 Mon Sep 17 00:00:00 2001 From: Loic Nageleisen Date: Thu, 16 Mar 2023 00:43:43 +0100 Subject: [PATCH] Add specs for config endpoint --- .env | 1 + docker-compose.yml | 1 + spec/datadog/core/transport/http_spec.rb | 173 ++++++++++++++++-- .../core/transport/integration_spec.rb | 75 ++++++++ 4 files changed, 231 insertions(+), 19 deletions(-) diff --git a/.env b/.env index f7432176544..65501d35dc1 100644 --- a/.env +++ b/.env @@ -4,6 +4,7 @@ DD_API_KEY=00000000000000000000000000000000 DD_METRIC_AGENT_PORT=8125 DD_TRACE_AGENT_PORT=8126 DD_INSTRUMENTATION_TELEMETRY_ENABLED=false +DD_REMOTE_CONFIGURATION_ENABLED=true TEST_DDAGENT_VAR_RUN=/var/run/datadog TEST_DDAGENT_UNIX_SOCKET=${TEST_DDAGENT_VAR_RUN}/apm.socket TEST_DDAGENT_API_KEY=invalid_key_but_this_is_fine diff --git a/docker-compose.yml b/docker-compose.yml index 9ff20920008..5c869843994 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -366,6 +366,7 @@ services: - "DD_API_KEY=${DD_API_KEY}" - DD_HOSTNAME=dd-trace-rb-ci - DD_APM_RECEIVER_SOCKET=/var/run/datadog/apm.socket + - DD_REMOTE_CONFIGURATION_ENABLED=true expose: - "8125/udp" - "8126" diff --git a/spec/datadog/core/transport/http_spec.rb b/spec/datadog/core/transport/http_spec.rb index a7406e2bdf3..7a52da6bc75 100644 --- a/spec/datadog/core/transport/http_spec.rb +++ b/spec/datadog/core/transport/http_spec.rb @@ -7,6 +7,31 @@ require 'datadog/core/transport/negotiation' RSpec.describe Datadog::Core::Transport::HTTP do + shared_context 'HTTP connection stub' do + before do + request_class = case request_verb + when :get then ::Net::HTTP::Get + when :post then ::Net::HTTP::Post + else raise "bad verb: #{request_verb.inspect}" + end + http_request = instance_double(request_class) + allow(http_request).to receive(:body=) + allow(request_class).to receive(:new).and_return(http_request) + + http_connection = instance_double(::Net::HTTP) + allow(::Net::HTTP).to receive(:new).and_return(http_connection) + + allow(http_connection).to receive(:open_timeout=) + allow(http_connection).to receive(:read_timeout=) + allow(http_connection).to receive(:use_ssl=) + + allow(http_connection).to receive(:start).and_yield(http_connection) + + http_response = instance_double(::Net::HTTPResponse, body: response_body, code: response_code) + allow(http_connection).to receive(:request).with(http_request).and_return(http_response) + end + end + describe '.root' do subject(:transport) { described_class.root(&client_options) } @@ -15,6 +40,8 @@ it { is_expected.to be_a(Datadog::Core::Transport::Negotiation::Transport) } describe '#send_info' do + include_context 'HTTP connection stub' + subject(:response) { transport.send_info } let(:request_verb) { :get } @@ -35,33 +62,141 @@ ) end - before do - request_class = case request_verb - when :get then ::Net::HTTP::Get - else raise "bad verb: #{request_verb.inspect}" - end - http_request = instance_double(request_class) - allow(request_class).to receive(:new).and_return(http_request) + it { is_expected.to be_a(Datadog::Core::Transport::HTTP::Negotiation::Response) } + + it { is_expected.to be_ok } + it { is_expected.to have_attributes(:version => '42') } + it { is_expected.to have_attributes(:endpoints => ['/info', '/v0/path']) } + it { is_expected.to have_attributes(:config => { max_request_bytes: '1234' }) } + end + end + + describe '.v7' do + subject(:transport) { described_class.v7(&client_options) } - http_connection = instance_double(::Net::HTTP) - allow(::Net::HTTP).to receive(:new).and_return(http_connection) + let(:client_options) { proc { |_client| } } - allow(http_connection).to receive(:open_timeout=) - allow(http_connection).to receive(:read_timeout=) - allow(http_connection).to receive(:use_ssl=) + it { is_expected.to be_a(Datadog::Core::Transport::Config::Transport) } - allow(http_connection).to receive(:start).and_yield(http_connection) + describe '#send_config' do + include_context 'HTTP connection stub' - http_response = instance_double(::Net::HTTPResponse, body: response_body, code: response_code) - allow(http_connection).to receive(:request).with(http_request).and_return(http_response) + let(:state) do + OpenStruct.new( + { + root_version: 1, # unverified mode, so 1 + targets_version: 0, # from scratch, so zero + config_states: [], # from scratch, so empty + has_error: false, # from scratch, so false + error: '', # from scratch, so blank + opaque_backend_state: '', # from scratch, so blank + } + ) end - it { is_expected.to be_a(Datadog::Core::Transport::HTTP::Negotiation::Response) } + let(:id) { SecureRandom.uuid } + + let(:products) { [] } + + let(:capabilities) { 0 } + + let(:capabilities_binary) do + capabilities + .to_s(16) + .tap { |s| s.size.odd? && s.prepend('0') } + .scan(/\h\h/) + .map { |e| e.to_i(16) } + .pack('C*') + end + + let(:payload) do + { + client: { + state: { + root_version: state.root_version, + targets_version: state.targets_version, + config_states: state.config_states, + has_error: state.has_error, + error: state.error, + backend_client_state: state.opaque_backend_state, + }, + id: id, + products: products, + is_tracer: true, + is_agent: false, + client_tracer: { + runtime_id: Datadog::Core::Environment::Identity.id, + language: Datadog::Core::Environment::Identity.lang, + tracer_version: Datadog::Core::Environment::Identity.tracer_version, + service: Datadog.configuration.service, + env: Datadog.configuration.env, + tags: [], + }, + capabilities: Base64.encode64(capabilities_binary).chomp, + }, + cached_target_files: [], + } + end + + subject(:response) { transport.send_config(payload) } + + let(:request_verb) { :post } + + let(:response_code) { 200 } + let(:response_body) do + encode = Proc.new do |obj| + Base64.strict_encode64(obj).chomp + end + + jencode = Proc.new do |obj| + Base64.strict_encode64(JSON.dump(obj)).chomp + end + + JSON.dump( + { + roots: [ + jencode.call({}), + jencode.call({}), + ], + targets: jencode.call({ + signed: { + expires: '2022-09-22T09:01:04Z', + targets: { + 'datadog/42/PRODUCT/foo/config' => { + hashes: { sha256: 'd0b425e00e15a0d36b9b361f02bab63563aed6cb4665083905386c55d5b679fa' }, + length: 8, + }, + 'employee/PRODUCT/bar/config' => { + hashes: { sha256: 'dab741b6289e7dccc1ed42330cae1accc2b755ce8079c2cd5d4b5366c9f769a6' }, + length: 8, + }, + } + } + }), + target_files: [ + { + path: 'datadog/42/PRODUCT/foo/config', + raw: encode.call('content1'), + }, + { + path: 'employee/PRODUCT/bar/config', + raw: encode.call('content2'), + }, + ], + client_configs: [ + 'datadog/42/PRODUCT/foo/config', + 'employee/PRODUCT/bar/config', + ], + } + ) + end + + it { is_expected.to be_a(Datadog::Core::Transport::HTTP::Config::Response) } it { is_expected.to be_ok } - it { is_expected.to have_attributes(:version => '42') } - it { is_expected.to have_attributes(:endpoints => ['/info', '/v0/path']) } - it { is_expected.to have_attributes(:config => { max_request_bytes: '1234' }) } + it { is_expected.to have_attributes(:roots => be_a(Array)) } + it { is_expected.to have_attributes(:targets => be_a(Hash)) } + it { is_expected.to have_attributes(:target_files => be_a(Array)) } end end end diff --git a/spec/datadog/core/transport/integration_spec.rb b/spec/datadog/core/transport/integration_spec.rb index ce6268020a3..8715d495b6f 100644 --- a/spec/datadog/core/transport/integration_spec.rb +++ b/spec/datadog/core/transport/integration_spec.rb @@ -27,4 +27,79 @@ it { is_expected.to_not have_attributes(:config => be_nil) } end end + + describe '.v7' do + subject(:transport) { described_class.v7(&client_options) } + + let(:client_options) { proc { |_client| } } + + it { is_expected.to be_a(Datadog::Core::Transport::Config::Transport) } + + describe '#send_config' do + let(:state) do + OpenStruct.new( + { + root_version: 1, # unverified mode, so 1 + targets_version: 0, # from scratch, so zero + config_states: [], # from scratch, so empty + has_error: false, # from scratch, so false + error: '', # from scratch, so blank + opaque_backend_state: '', # from scratch, so blank + } + ) + end + + let(:id) { SecureRandom.uuid } + + let(:products) { [] } + + let(:capabilities) { 0 } + + let(:capabilities_binary) do + capabilities + .to_s(16) + .tap { |s| s.size.odd? && s.prepend('0') } + .scan(/\h\h/) + .map { |e| e.to_i(16) } + .pack('C*') + end + + let(:payload) do + { + client: { + state: { + root_version: state.root_version, + targets_version: state.targets_version, + config_states: state.config_states, + has_error: state.has_error, + error: state.error, + backend_client_state: state.opaque_backend_state, + }, + id: id, + products: products, + is_tracer: true, + is_agent: false, + client_tracer: { + runtime_id: Datadog::Core::Environment::Identity.id, + language: Datadog::Core::Environment::Identity.lang, + tracer_version: Datadog::Core::Environment::Identity.tracer_version, + service: Datadog.configuration.service, + env: Datadog.configuration.env, + tags: [], + }, + capabilities: Base64.encode64(capabilities_binary).chomp, + }, + cached_target_files: [], + } + end + + subject(:response) { transport.send_config(payload) } + + it { is_expected.to be_a(Datadog::Core::Transport::HTTP::Config::Response) } + + it { is_expected.to be_ok } + it { is_expected.to_not have_attributes(:roots => be_nil) } + it { is_expected.to_not have_attributes(:targets => be_nil) } + end + end end