From 255bab7c13d68c4c39b2387c0b8dc203c54e391c Mon Sep 17 00:00:00 2001 From: Emily Stolfo Date: Mon, 27 Oct 2025 11:24:17 +0100 Subject: [PATCH 01/39] Support wait_for_status query param on root endpoint --- .../lib/logstash/api/modules/root.rb | 25 ++++ .../spec/logstash/api/modules/root_spec.rb | 109 ++++++++++++++++++ 2 files changed, 134 insertions(+) diff --git a/logstash-core/lib/logstash/api/modules/root.rb b/logstash-core/lib/logstash/api/modules/root.rb index 260319e78b1..f646d588c59 100644 --- a/logstash-core/lib/logstash/api/modules/root.rb +++ b/logstash-core/lib/logstash/api/modules/root.rb @@ -19,10 +19,35 @@ module LogStash module Api module Modules class Root < ::LogStash::Api::Modules::Base + + HEALTH_STATUS = [ + 'GREEN', + 'YELLOW', + 'RED' + ] + get "/" do + target_status = params[:wait_for_status]&.upcase + + if target_status && HEALTH_STATUS.include?(target_status) + wait_for_status(params[:timeout], target_status) if params[:timeout] + end + command = factory.build(:system_basic_info) respond_with command.run end + + private + def wait_for_status(timeout, target_status) + end_time = Time.now + timeout.to_i + wait_interval_seconds = 1 + + while Time.now < end_time + break if target_status == agent.health_observer.status.to_s + sleep(wait_interval_seconds) + wait_interval_seconds = wait_interval_seconds * 2 + end + end end end end diff --git a/logstash-core/spec/logstash/api/modules/root_spec.rb b/logstash-core/spec/logstash/api/modules/root_spec.rb index 22fbfe44de5..043bb1ddd8b 100644 --- a/logstash-core/spec/logstash/api/modules/root_spec.rb +++ b/logstash-core/spec/logstash/api/modules/root_spec.rb @@ -29,4 +29,113 @@ end include_examples "not found" + + describe 'wait_for_status query param' do + + let(:margin_of_error) { 0.1 } + + context 'no timeout is provided' do + + let(:return_time) { 0.1 } + + it 'returns immediately' do + start_time = Time.now + get "/?wait_for_status=red" + end_time = Time.now + expect(end_time - start_time).to be_within(margin_of_error).of(return_time) + end + end + + context "timeout is provided" do + + let(:timeout) { 1 } + + context "the status doesn't change" do + + let(:return_time) { timeout + 0.1 } + + it 'checks the status until the timeout is reached' do + start_time = Time.now + get "/?wait_for_status=red&timeout=#{timeout}" + end_time = Time.now + expect(end_time - start_time).to be_within(margin_of_error).of(return_time) + end + end + + context 'the status changes within the timeout' do + + let(:timeout) { 2 } + + before do + allow(@agent.health_observer.status).to receive(:to_s).and_return('GREEN', 'YELLOW') + end + + it 'returns when the target status is reached' do + start_time = Time.now + get "/?wait_for_status=yellow&timeout=#{timeout}" + end_time = Time.now + expect(end_time - start_time).to be < timeout + end + end + end + + described_class::HEALTH_STATUS.each do |status| + + context "#{status} is provided" do + + let(:timeout) do + # Two statuses are checked before the target is reached. The first wait time is 1 second, + # the second wait time is 2 seconds. So it takes at least 3 seconds to reach target status. + 3.1 + end + + let(:return_statues) do + # Make the target status last in the returned values + statuses = described_class::HEALTH_STATUS.dup + statuses.delete(status) + statuses << status + end + + it 'checks for the status until it changes' do + allow(@agent.health_observer.status).to receive(:to_s).and_return(*return_statues) + start_time = Time.now + get "/?wait_for_status=#{status}&timeout=#{timeout}" + end_time = Time.now + expect(end_time - start_time).to be < timeout + end + end + end + + context 'status string is formatted differently' do + + let(:timeout) { 2 } + let(:return_time) do + # We wait 1 second to check the status a second time. The target status + # is reached on the second check. + 1.1 + end + + before { allow(@agent.health_observer.status).to receive(:to_s).and_return('GREEN', 'YELLOW') } + + it 'normalizes the format and checks the status' do + start_time = Time.now + get "/?wait_for_status=yElLoW&timeout=#{timeout}" + end_time = Time.now + expect(end_time - start_time).to be_within(margin_of_error).of(return_time) + end + end + + context "invalid status is provided" do + + let(:timeout) { 2 } + let(:return_time) { 0.1 } + + it 'returns immediately' do + start_time = Time.now + get "/?wait_for_status=invalid&timeout=#{timeout}" + end_time = Time.now + expect(end_time - start_time).to be_within(margin_of_error).of(return_time) + end + end + end end From a73fe16b8bc2587e3eb6004f56257ef492165413 Mon Sep 17 00:00:00 2001 From: Emily Stolfo Date: Tue, 28 Oct 2025 12:40:14 +0100 Subject: [PATCH 02/39] Use Java::OrgLogstashHealth::Status enum values for status constant --- logstash-core/lib/logstash/api/modules/root.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/logstash-core/lib/logstash/api/modules/root.rb b/logstash-core/lib/logstash/api/modules/root.rb index f646d588c59..8e531bf5c07 100644 --- a/logstash-core/lib/logstash/api/modules/root.rb +++ b/logstash-core/lib/logstash/api/modules/root.rb @@ -21,9 +21,9 @@ module Modules class Root < ::LogStash::Api::Modules::Base HEALTH_STATUS = [ - 'GREEN', - 'YELLOW', - 'RED' + Java::OrgLogstashHealth::Status::GREEN.to_s, + Java::OrgLogstashHealth::Status::YELLOW.to_s, + Java::OrgLogstashHealth::Status::RED.to_s ] get "/" do From 131d6698ce8e909894a585bdd3608791382a603a Mon Sep 17 00:00:00 2001 From: Emily Stolfo Date: Tue, 28 Oct 2025 14:20:06 +0100 Subject: [PATCH 03/39] Add docs for root api including new query params --- docs/static/spec/openapi/logstash-api.yaml | 71 ++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/docs/static/spec/openapi/logstash-api.yaml b/docs/static/spec/openapi/logstash-api.yaml index 82d96862e6b..54b3b5bb15c 100644 --- a/docs/static/spec/openapi/logstash-api.yaml +++ b/docs/static/spec/openapi/logstash-api.yaml @@ -66,6 +66,77 @@ tags: # description: # url: paths: + /: + get: + summary: Gets basic metadata + description: | + Shows basic metadata about the running logstash service. This includes build info, pipeline info and the service's status. + operationId: root + tags: + - root + parameters: + - name: wait_for_status + in: query + required: false + schema: + type: string + description: One of green, yellow or red. Will wait (until the timeout provided) until the status of the service changes to the one provided or better, i.e. green > yellow > red. By default, will not wait for any status. + - name: timeout + in: query + required: false + schema: + type: string + description: Period to wait for the status to reach the requested target status. If the target status is not reached before the timeout expires, the request returns as normal. + - $ref: "#/components/parameters/pretty" + responses: + '200': + description: Indicates a successful call + content: + application/json: + schema: + - type: object + properties: + host: + type: string + version: + type: string + http_address: + type: string + id: + type: string + name: + type: string + ephemeral_id: + type: string + snapshot: + type: string + status: + type: string + pipeline: + type: object + properties: + workers: + type: integer + batch_size: + type: integer + batch_delay: + type: integer + example: + host: "" + version: "9.3.0" + http_address: "127.0.0.1:9600" + id: "58df6f7c-eb5c-5d42-bc20-c7b22779aa12" + name: "" + ephermeral_id: "59df6f6c-eb5c-4d42-bc20-c7b44779aa12" + snapshot: + status: "green" + pipeline: + workers: 10 + batch_size: 125 + batch_delay: 50 + x-metaTags: + - content: Logstash + name: product_name /_node/jvm: get: summary: Gets node-level JVM info From 61a138a9a6e86633fec8a5cc9579c4b6da8fdfa6 Mon Sep 17 00:00:00 2001 From: Emily Stolfo Date: Tue, 28 Oct 2025 14:38:40 +0100 Subject: [PATCH 04/39] Be consistent with return statuses definition in specs --- .../spec/logstash/api/modules/root_spec.rb | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/logstash-core/spec/logstash/api/modules/root_spec.rb b/logstash-core/spec/logstash/api/modules/root_spec.rb index 043bb1ddd8b..67c1d1d6fb9 100644 --- a/logstash-core/spec/logstash/api/modules/root_spec.rb +++ b/logstash-core/spec/logstash/api/modules/root_spec.rb @@ -66,8 +66,10 @@ let(:timeout) { 2 } + let(:return_statuses) { ['GREEN', 'YELLOW'] } + before do - allow(@agent.health_observer.status).to receive(:to_s).and_return('GREEN', 'YELLOW') + allow(@agent.health_observer.status).to receive(:to_s).and_return(*return_statuses) end it 'returns when the target status is reached' do @@ -96,8 +98,11 @@ statuses << status end - it 'checks for the status until it changes' do + before do allow(@agent.health_observer.status).to receive(:to_s).and_return(*return_statues) + end + + it 'checks for the status until it changes' do start_time = Time.now get "/?wait_for_status=#{status}&timeout=#{timeout}" end_time = Time.now @@ -109,13 +114,18 @@ context 'status string is formatted differently' do let(:timeout) { 2 } + let(:return_time) do # We wait 1 second to check the status a second time. The target status # is reached on the second check. 1.1 end - before { allow(@agent.health_observer.status).to receive(:to_s).and_return('GREEN', 'YELLOW') } + let(:return_statuses) { ['GREEN', 'YELLOW'] } + + before do + allow(@agent.health_observer.status).to receive(:to_s).and_return(*return_statuses) + end it 'normalizes the format and checks the status' do start_time = Time.now From aa51212ac9b2420b1414ddfe48846e9c26da2113 Mon Sep 17 00:00:00 2001 From: Emily Stolfo Date: Wed, 29 Oct 2025 15:01:22 +0100 Subject: [PATCH 05/39] Use org.logstash.health.Status for constant and in tests --- .../lib/logstash/api/modules/root.rb | 10 +++----- .../spec/logstash/api/modules/root_spec.rb | 24 +++++++++++++------ 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/logstash-core/lib/logstash/api/modules/root.rb b/logstash-core/lib/logstash/api/modules/root.rb index 8e531bf5c07..10a27b645b6 100644 --- a/logstash-core/lib/logstash/api/modules/root.rb +++ b/logstash-core/lib/logstash/api/modules/root.rb @@ -20,14 +20,10 @@ module Api module Modules class Root < ::LogStash::Api::Modules::Base - HEALTH_STATUS = [ - Java::OrgLogstashHealth::Status::GREEN.to_s, - Java::OrgLogstashHealth::Status::YELLOW.to_s, - Java::OrgLogstashHealth::Status::RED.to_s - ] + HEALTH_STATUS = java.util.EnumSet.allOf(org.logstash.health.Status).map(&:external_value) get "/" do - target_status = params[:wait_for_status]&.upcase + target_status = params[:wait_for_status]&.downcase if target_status && HEALTH_STATUS.include?(target_status) wait_for_status(params[:timeout], target_status) if params[:timeout] @@ -43,7 +39,7 @@ def wait_for_status(timeout, target_status) wait_interval_seconds = 1 while Time.now < end_time - break if target_status == agent.health_observer.status.to_s + break if target_status == agent.health_observer.status.external_value sleep(wait_interval_seconds) wait_interval_seconds = wait_interval_seconds * 2 end diff --git a/logstash-core/spec/logstash/api/modules/root_spec.rb b/logstash-core/spec/logstash/api/modules/root_spec.rb index 67c1d1d6fb9..24ea3fa0e09 100644 --- a/logstash-core/spec/logstash/api/modules/root_spec.rb +++ b/logstash-core/spec/logstash/api/modules/root_spec.rb @@ -66,10 +66,15 @@ let(:timeout) { 2 } - let(:return_statuses) { ['GREEN', 'YELLOW'] } + let(:return_statuses) do + [ + org.logstash.health.Status::GREEN, + org.logstash.health.Status::YELLOW + ] + end before do - allow(@agent.health_observer.status).to receive(:to_s).and_return(*return_statuses) + allow(@agent.health_observer).to receive(:status).and_return(*return_statuses) end it 'returns when the target status is reached' do @@ -81,7 +86,7 @@ end end - described_class::HEALTH_STATUS.each do |status| + java.util.EnumSet.allOf(org.logstash.health.Status).each do |status| context "#{status} is provided" do @@ -93,13 +98,13 @@ let(:return_statues) do # Make the target status last in the returned values - statuses = described_class::HEALTH_STATUS.dup + statuses = java.util.EnumSet.allOf(org.logstash.health.Status).to_a statuses.delete(status) statuses << status end before do - allow(@agent.health_observer.status).to receive(:to_s).and_return(*return_statues) + allow(@agent.health_observer).to receive(:status).and_return(*return_statues) end it 'checks for the status until it changes' do @@ -121,10 +126,15 @@ 1.1 end - let(:return_statuses) { ['GREEN', 'YELLOW'] } + let(:return_statuses) do + [ + org.logstash.health.Status::GREEN, + org.logstash.health.Status::YELLOW + ] + end before do - allow(@agent.health_observer.status).to receive(:to_s).and_return(*return_statuses) + allow(@agent.health_observer).to receive(:status).and_return(*return_statuses) end it 'normalizes the format and checks the status' do From 2bb4c7981a7f09ad087367552c583ce606247ac2 Mon Sep 17 00:00:00 2001 From: Emily Stolfo Date: Wed, 29 Oct 2025 16:54:28 +0100 Subject: [PATCH 06/39] Return http status code 503 when timeout and support target status or better --- logstash-core/lib/logstash/api/app_helpers.rb | 3 +- .../lib/logstash/api/modules/root.rb | 11 +- .../spec/logstash/api/modules/root_spec.rb | 134 +++++++++++++----- 3 files changed, 107 insertions(+), 41 deletions(-) diff --git a/logstash-core/lib/logstash/api/app_helpers.rb b/logstash-core/lib/logstash/api/app_helpers.rb index 6df4cc70650..1777fd4824c 100644 --- a/logstash-core/lib/logstash/api/app_helpers.rb +++ b/logstash-core/lib/logstash/api/app_helpers.rb @@ -29,7 +29,8 @@ def respond_with(data, options = {}) as = options.fetch(:as, :json) filter = options.fetch(:filter, "") - status data.respond_to?(:status_code) ? data.status_code : 200 + status data.respond_to?(:status_code) ? + data.status_code : options.fetch(:status_code, 200) if as == :json if api_error?(data) diff --git a/logstash-core/lib/logstash/api/modules/root.rb b/logstash-core/lib/logstash/api/modules/root.rb index 10a27b645b6..1560390ddbc 100644 --- a/logstash-core/lib/logstash/api/modules/root.rb +++ b/logstash-core/lib/logstash/api/modules/root.rb @@ -24,13 +24,14 @@ class Root < ::LogStash::Api::Modules::Base get "/" do target_status = params[:wait_for_status]&.downcase + status = 200 - if target_status && HEALTH_STATUS.include?(target_status) - wait_for_status(params[:timeout], target_status) if params[:timeout] + if HEALTH_STATUS.include?(target_status) && params[:timeout] + status = 503 unless wait_for_status(params[:timeout], target_status) end command = factory.build(:system_basic_info) - respond_with command.run + respond_with(command.run, status_code: status) end private @@ -39,7 +40,9 @@ def wait_for_status(timeout, target_status) wait_interval_seconds = 1 while Time.now < end_time - break if target_status == agent.health_observer.status.external_value + if HEALTH_STATUS.index(agent.health_observer.status.external_value) <= HEALTH_STATUS.index(target_status) + return true + end sleep(wait_interval_seconds) wait_interval_seconds = wait_interval_seconds * 2 end diff --git a/logstash-core/spec/logstash/api/modules/root_spec.rb b/logstash-core/spec/logstash/api/modules/root_spec.rb index 24ea3fa0e09..1fa0ee6275f 100644 --- a/logstash-core/spec/logstash/api/modules/root_spec.rb +++ b/logstash-core/spec/logstash/api/modules/root_spec.rb @@ -32,17 +32,15 @@ describe 'wait_for_status query param' do - let(:margin_of_error) { 0.1 } + let(:margin_of_error) { 0.2 } context 'no timeout is provided' do - let(:return_time) { 0.1 } - it 'returns immediately' do start_time = Time.now get "/?wait_for_status=red" end_time = Time.now - expect(end_time - start_time).to be_within(margin_of_error).of(return_time) + expect(end_time - start_time).to be < 1 end end @@ -54,12 +52,27 @@ let(:return_time) { timeout + 0.1 } + let(:return_statuses) do + [ + org.logstash.health.Status::RED + ] + end + + before do + allow(@agent.health_observer).to receive(:status).and_return(*return_statuses) + end + it 'checks the status until the timeout is reached' do start_time = Time.now - get "/?wait_for_status=red&timeout=#{timeout}" + get "/?wait_for_status=green&timeout=#{timeout}" end_time = Time.now expect(end_time - start_time).to be_within(margin_of_error).of(return_time) end + + it 'returns status code 503' do + response = get "/?wait_for_status=green&timeout=#{timeout}" + expect(response.status).to eq 503 + end end context 'the status changes within the timeout' do @@ -68,7 +81,7 @@ let(:return_statuses) do [ - org.logstash.health.Status::GREEN, + org.logstash.health.Status::RED, org.logstash.health.Status::YELLOW ] end @@ -86,49 +99,98 @@ end end - java.util.EnumSet.allOf(org.logstash.health.Status).each do |status| + context "green is provided" do - context "#{status} is provided" do + let(:timeout) do + # Two statuses are checked before the target is reached. The first wait time is 1 second, + # the second wait time is 2 seconds. So it takes at least 3 seconds to reach target status. + 4 + end - let(:timeout) do - # Two statuses are checked before the target is reached. The first wait time is 1 second, - # the second wait time is 2 seconds. So it takes at least 3 seconds to reach target status. - 3.1 - end + let(:return_time) { 3.1 } - let(:return_statues) do - # Make the target status last in the returned values - statuses = java.util.EnumSet.allOf(org.logstash.health.Status).to_a - statuses.delete(status) - statuses << status - end + let(:return_statues) do + [ + org.logstash.health.Status::RED, + org.logstash.health.Status::YELLOW, + org.logstash.health.Status::GREEN, + ] + end - before do - allow(@agent.health_observer).to receive(:status).and_return(*return_statues) - end + before do + allow(@agent.health_observer).to receive(:status).and_return(*return_statues) + end - it 'checks for the status until it changes' do - start_time = Time.now - get "/?wait_for_status=#{status}&timeout=#{timeout}" - end_time = Time.now - expect(end_time - start_time).to be < timeout - end + it 'checks for the status until it changes' do + start_time = Time.now + get "/?wait_for_status=green&timeout=#{timeout}" + end_time = Time.now + expect(end_time - start_time).to be_within(margin_of_error).of(return_time) end end - context 'status string is formatted differently' do + context "yellow is provided" do - let(:timeout) { 2 } + let(:timeout) do + # Two statuses are checked before the target is reached. The first wait time is 1 second, + # the second wait time is 2 seconds. So it takes at least 3 seconds to reach target status. + 2 + end - let(:return_time) do - # We wait 1 second to check the status a second time. The target status - # is reached on the second check. - 1.1 + let(:return_statues) do + [ + org.logstash.health.Status::RED, + org.logstash.health.Status::YELLOW + ] + end + + before do + allow(@agent.health_observer).to receive(:status).and_return(*return_statues) + end + + it 'checks for the status until it changes' do + start_time = Time.now + get "/?wait_for_status=yellow&timeout=#{timeout}" + end_time = Time.now + expect(end_time - start_time).to be < timeout + end + end + + context "red is provided" do + + let(:timeout) do + # Two statuses are checked before the target is reached. The first wait time is 1 second, + # the second wait time is 2 seconds. So it takes at least 3 seconds to reach target status. + 2 + end + + let(:return_statues) do + [ + org.logstash.health.Status::RED + ] + end + + let(:return_time) { 0.2 } + + before do + allow(@agent.health_observer).to receive(:status).and_return(*return_statues) end + it 'checks for the status until it changes to the target' do + start_time = Time.now + get "/?wait_for_status=red&timeout=#{timeout}" + end_time = Time.now + expect(end_time - start_time).to be_within(margin_of_error).of(return_time) + end + end + + context 'status string is formatted differently' do + + let(:timeout) { 2 } + let(:return_statuses) do [ - org.logstash.health.Status::GREEN, + org.logstash.health.Status::RED, org.logstash.health.Status::YELLOW ] end @@ -141,7 +203,7 @@ start_time = Time.now get "/?wait_for_status=yElLoW&timeout=#{timeout}" end_time = Time.now - expect(end_time - start_time).to be_within(margin_of_error).of(return_time) + expect(end_time - start_time).to be < timeout end end From 7fbd94cfa2ec4f7b4a2f1013d6348df5c646c79e Mon Sep 17 00:00:00 2001 From: Emily Stolfo Date: Wed, 29 Oct 2025 16:56:19 +0100 Subject: [PATCH 07/39] Update docs with note about returning status code 503 --- docs/static/spec/openapi/logstash-api.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/static/spec/openapi/logstash-api.yaml b/docs/static/spec/openapi/logstash-api.yaml index 54b3b5bb15c..b68d010ef76 100644 --- a/docs/static/spec/openapi/logstash-api.yaml +++ b/docs/static/spec/openapi/logstash-api.yaml @@ -86,7 +86,7 @@ paths: required: false schema: type: string - description: Period to wait for the status to reach the requested target status. If the target status is not reached before the timeout expires, the request returns as normal. + description: Period to wait for the status to reach the requested target status. If the target status is not reached before the timeout expires, the request returns status code 503. - $ref: "#/components/parameters/pretty" responses: '200': From 75030775b104fa56cbd1ac8d7b00030733f7de13 Mon Sep 17 00:00:00 2001 From: Emily Stolfo Date: Thu, 30 Oct 2025 10:00:04 +0100 Subject: [PATCH 08/39] Use Timeout instead while waiting for the status --- .../lib/logstash/api/modules/root.rb | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/logstash-core/lib/logstash/api/modules/root.rb b/logstash-core/lib/logstash/api/modules/root.rb index 1560390ddbc..ccf66738193 100644 --- a/logstash-core/lib/logstash/api/modules/root.rb +++ b/logstash-core/lib/logstash/api/modules/root.rb @@ -15,6 +15,8 @@ # specific language governing permissions and limitations # under the License. +require 'timeout' + module LogStash module Api module Modules @@ -27,7 +29,11 @@ class Root < ::LogStash::Api::Modules::Base status = 200 if HEALTH_STATUS.include?(target_status) && params[:timeout] - status = 503 unless wait_for_status(params[:timeout], target_status) + begin + wait_for_status(params[:timeout], target_status) + rescue Timeout::Error + status = 503 + end end command = factory.build(:system_basic_info) @@ -35,14 +41,13 @@ class Root < ::LogStash::Api::Modules::Base end private - def wait_for_status(timeout, target_status) - end_time = Time.now + timeout.to_i + def wait_for_status(timeout_seconds, target_status) wait_interval_seconds = 1 - while Time.now < end_time - if HEALTH_STATUS.index(agent.health_observer.status.external_value) <= HEALTH_STATUS.index(target_status) - return true - end + Timeout.timeout(timeout_seconds.to_i) do + current_status = HEALTH_STATUS.index(agent.health_observer.status.external_value) + break if current_status <= HEALTH_STATUS.index(target_status) + sleep(wait_interval_seconds) wait_interval_seconds = wait_interval_seconds * 2 end From 0affd82f984e226a0d9857b298d502bebcf76b22 Mon Sep 17 00:00:00 2001 From: Emily Stolfo Date: Thu, 30 Oct 2025 10:32:59 +0100 Subject: [PATCH 09/39] Only use green, yellow, and red statuses in constant (exclude unknown) --- logstash-core/lib/logstash/api/modules/root.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/logstash-core/lib/logstash/api/modules/root.rb b/logstash-core/lib/logstash/api/modules/root.rb index ccf66738193..c5b0faa17b1 100644 --- a/logstash-core/lib/logstash/api/modules/root.rb +++ b/logstash-core/lib/logstash/api/modules/root.rb @@ -22,7 +22,11 @@ module Api module Modules class Root < ::LogStash::Api::Modules::Base - HEALTH_STATUS = java.util.EnumSet.allOf(org.logstash.health.Status).map(&:external_value) + HEALTH_STATUS = [ + Java::OrgLogstashHealth::Status::GREEN, + Java::OrgLogstashHealth::Status::YELLOW, + Java::OrgLogstashHealth::Status::RED + ].map(&:external_value) get "/" do target_status = params[:wait_for_status]&.downcase From 242f0305bbcbaabebbd2a9428d90e546bfe0278a Mon Sep 17 00:00:00 2001 From: Emily Stolfo Date: Thu, 30 Oct 2025 10:33:16 +0100 Subject: [PATCH 10/39] Handle non integer timeout string --- logstash-core/lib/logstash/api/modules/root.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/logstash-core/lib/logstash/api/modules/root.rb b/logstash-core/lib/logstash/api/modules/root.rb index c5b0faa17b1..d83e6665d79 100644 --- a/logstash-core/lib/logstash/api/modules/root.rb +++ b/logstash-core/lib/logstash/api/modules/root.rb @@ -30,9 +30,10 @@ class Root < ::LogStash::Api::Modules::Base get "/" do target_status = params[:wait_for_status]&.downcase + timeout = params[:timeout].to_i status = 200 - if HEALTH_STATUS.include?(target_status) && params[:timeout] + if HEALTH_STATUS.include?(target_status) && timeout > 0 begin wait_for_status(params[:timeout], target_status) rescue Timeout::Error From 042bf1ccf8d0458d5556a7e6c791843813f79bdc Mon Sep 17 00:00:00 2001 From: Emily Stolfo Date: Thu, 30 Oct 2025 10:33:34 +0100 Subject: [PATCH 11/39] Add the loop back in to timeout block --- logstash-core/lib/logstash/api/modules/root.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/logstash-core/lib/logstash/api/modules/root.rb b/logstash-core/lib/logstash/api/modules/root.rb index d83e6665d79..ccaa80dba38 100644 --- a/logstash-core/lib/logstash/api/modules/root.rb +++ b/logstash-core/lib/logstash/api/modules/root.rb @@ -50,11 +50,13 @@ def wait_for_status(timeout_seconds, target_status) wait_interval_seconds = 1 Timeout.timeout(timeout_seconds.to_i) do - current_status = HEALTH_STATUS.index(agent.health_observer.status.external_value) - break if current_status <= HEALTH_STATUS.index(target_status) + loop do + current_status = HEALTH_STATUS.index(agent.health_observer.status.external_value) + break if current_status <= HEALTH_STATUS.index(target_status) - sleep(wait_interval_seconds) - wait_interval_seconds = wait_interval_seconds * 2 + sleep(wait_interval_seconds) + wait_interval_seconds = wait_interval_seconds * 2 + end end end end From d0c064ed09ea55655fa32ea078006297f159c268 Mon Sep 17 00:00:00 2001 From: Emily Stolfo Date: Thu, 30 Oct 2025 10:43:59 +0100 Subject: [PATCH 12/39] Update tests --- .../spec/logstash/api/modules/root_spec.rb | 329 ++++++++++++------ 1 file changed, 231 insertions(+), 98 deletions(-) diff --git a/logstash-core/spec/logstash/api/modules/root_spec.rb b/logstash-core/spec/logstash/api/modules/root_spec.rb index 1fa0ee6275f..b7d1d6e45ee 100644 --- a/logstash-core/spec/logstash/api/modules/root_spec.rb +++ b/logstash-core/spec/logstash/api/modules/root_spec.rb @@ -31,26 +31,34 @@ include_examples "not found" describe 'wait_for_status query param' do - - let(:margin_of_error) { 0.2 } - + context 'no timeout is provided' do it 'returns immediately' do start_time = Time.now get "/?wait_for_status=red" end_time = Time.now - expect(end_time - start_time).to be < 1 + expect(end_time - start_time).to be < 0.5 end end context "timeout is provided" do - let(:timeout) { 1 } + context "the timeout value is not a valid integer" do + + let(:timeout) { "invalid" } + + it 'returns immediately' do + start_time = Time.now + get "/?wait_for_status=red&timeout=#{timeout}" + end_time = Time.now + expect(end_time - start_time).to be < 0.5 + end + end context "the status doesn't change" do - let(:return_time) { timeout + 0.1 } + let(:timeout) { 1 } let(:return_statuses) do [ @@ -66,7 +74,7 @@ start_time = Time.now get "/?wait_for_status=green&timeout=#{timeout}" end_time = Time.now - expect(end_time - start_time).to be_within(margin_of_error).of(return_time) + expect(end_time - start_time).to be >= timeout end it 'returns status code 503' do @@ -75,9 +83,12 @@ end end - context 'the status changes within the timeout' do + context 'the status changes to the target status within the timeout' do - let(:timeout) { 2 } + let(:timeout) do + # The first wait interval is 1 second + 2 + end let(:return_statuses) do [ @@ -99,124 +110,246 @@ end end - context "green is provided" do + context 'status is provided' do - let(:timeout) do - # Two statuses are checked before the target is reached. The first wait time is 1 second, - # the second wait time is 2 seconds. So it takes at least 3 seconds to reach target status. - 4 - end + context 'no timeout' do - let(:return_time) { 3.1 } + described_class::HEALTH_STATUS.each do |status| - let(:return_statues) do - [ - org.logstash.health.Status::RED, - org.logstash.health.Status::YELLOW, - org.logstash.health.Status::GREEN, - ] - end + context "with status \"#{status}\"" do - before do - allow(@agent.health_observer).to receive(:status).and_return(*return_statues) + it 'returns immediately' do + start_time = Time.now + get "/?wait_for_status=#{status}" + end_time = Time.now + expect(end_time - start_time).to be < 0.5 + end + end + end end - it 'checks for the status until it changes' do - start_time = Time.now - get "/?wait_for_status=green&timeout=#{timeout}" - end_time = Time.now - expect(end_time - start_time).to be_within(margin_of_error).of(return_time) + context "invalid status" do + + it "pending", :skip do + # start_time = Time.now + # get "/?wait_for_status=invalid" + # end_time = Time.now + # expect(end_time - start_time).to be < 0.5 + end end - end - context "yellow is provided" do + context 'status is formatted differently' do - let(:timeout) do - # Two statuses are checked before the target is reached. The first wait time is 1 second, - # the second wait time is 2 seconds. So it takes at least 3 seconds to reach target status. - 2 - end + let(:timeout) { 2 } - let(:return_statues) do - [ - org.logstash.health.Status::RED, - org.logstash.health.Status::YELLOW - ] - end + let(:return_statuses) do + [ + org.logstash.health.Status::RED, + org.logstash.health.Status::YELLOW + ] + end - before do - allow(@agent.health_observer).to receive(:status).and_return(*return_statues) - end + before do + allow(@agent.health_observer).to receive(:status).and_return(*return_statuses) + end - it 'checks for the status until it changes' do - start_time = Time.now - get "/?wait_for_status=yellow&timeout=#{timeout}" - end_time = Time.now - expect(end_time - start_time).to be < timeout + it 'normalizes the format and checks the status' do + start_time = Time.now + get "/?wait_for_status=yElLoW&timeout=#{timeout}" + end_time = Time.now + expect(end_time - start_time).to be < timeout + end end - end - context "red is provided" do + context 'target status is green' do - let(:timeout) do - # Two statuses are checked before the target is reached. The first wait time is 1 second, - # the second wait time is 2 seconds. So it takes at least 3 seconds to reach target status. - 2 - end + let(:timeout) { 2 } - let(:return_statues) do - [ - org.logstash.health.Status::RED - ] - end + context 'the status does not change' do + + let(:return_statuses) do + [ + org.logstash.health.Status::RED, + org.logstash.health.Status::YELLOW + ] + end + + before do + allow(@agent.health_observer).to receive(:status).and_return(*return_statuses) + end + + it 'checks for the status until the timeout is reached' do + start_time = Time.now + get "/?wait_for_status=green&timeout=#{timeout}" + end_time = Time.now + expect(end_time - start_time).to be >= timeout + end + end - let(:return_time) { 0.2 } + context 'the status changes to green' do - before do - allow(@agent.health_observer).to receive(:status).and_return(*return_statues) - end + let(:timeout) { 2 } - it 'checks for the status until it changes to the target' do - start_time = Time.now - get "/?wait_for_status=red&timeout=#{timeout}" - end_time = Time.now - expect(end_time - start_time).to be_within(margin_of_error).of(return_time) + let(:return_statuses) do + [ + org.logstash.health.Status::RED, + org.logstash.health.Status::GREEN + ] + end + + before do + allow(@agent.health_observer).to receive(:status).and_return(*return_statuses) + end + + it 'checks for the status until the timeout is reached' do + start_time = Time.now + get "/?wait_for_status=green&timeout=#{timeout}" + end_time = Time.now + expect(end_time - start_time).to be < timeout + end + end end - end - context 'status string is formatted differently' do + context 'target status is yellow' do - let(:timeout) { 2 } + let(:timeout) { 2 } - let(:return_statuses) do - [ - org.logstash.health.Status::RED, - org.logstash.health.Status::YELLOW - ] - end + context 'the status does not change' do - before do - allow(@agent.health_observer).to receive(:status).and_return(*return_statuses) - end + let(:return_statuses) do + [ + org.logstash.health.Status::RED + ] + end - it 'normalizes the format and checks the status' do - start_time = Time.now - get "/?wait_for_status=yElLoW&timeout=#{timeout}" - end_time = Time.now - expect(end_time - start_time).to be < timeout + before do + allow(@agent.health_observer).to receive(:status).and_return(*return_statuses) + end + + it 'checks for the status until the timeout is reached' do + start_time = Time.now + get "/?wait_for_status=yellow&timeout=#{timeout}" + end_time = Time.now + expect(end_time - start_time).to be >= timeout + end + end + + context 'the status changes to yellow' do + + let(:timeout) { 2 } + + let(:return_statuses) do + [ + org.logstash.health.Status::RED, + org.logstash.health.Status::YELLOW + ] + end + + before do + allow(@agent.health_observer).to receive(:status).and_return(*return_statuses) + end + + it 'checks for the status until the yellow status is reached' do + start_time = Time.now + get "/?wait_for_status=yellow&timeout=#{timeout}" + end_time = Time.now + expect(end_time - start_time).to be < timeout + end + end + + context 'the status changes to green' do + + let(:timeout) { 2 } + + let(:return_statuses) do + [ + org.logstash.health.Status::RED, + org.logstash.health.Status::GREEN + ] + end + + before do + allow(@agent.health_observer).to receive(:status).and_return(*return_statuses) + end + + it 'checks for the status until a status that is better (green) is reached' do + start_time = Time.now + get "/?wait_for_status=yellow&timeout=#{timeout}" + end_time = Time.now + expect(end_time - start_time).to be < timeout + end + end end - end - context "invalid status is provided" do + context 'target status is red' do - let(:timeout) { 2 } - let(:return_time) { 0.1 } + let(:timeout) { 2 } - it 'returns immediately' do - start_time = Time.now - get "/?wait_for_status=invalid&timeout=#{timeout}" - end_time = Time.now - expect(end_time - start_time).to be_within(margin_of_error).of(return_time) + context 'the status does not change' do + + let(:return_statuses) do + [ + org.logstash.health.Status::RED + ] + end + + before do + allow(@agent.health_observer).to receive(:status).and_return(*return_statuses) + end + + it 'returns immediately' do + start_time = Time.now + get "/?wait_for_status=red&timeout=#{timeout}" + end_time = Time.now + expect(end_time - start_time).to be < 0.5 + end + end + + context 'the status changes to yellow' do + + let(:timeout) { 2 } + + let(:return_statuses) do + [ + org.logstash.health.Status::RED, + org.logstash.health.Status::YELLOW + ] + end + + before do + allow(@agent.health_observer).to receive(:status).and_return(*return_statuses) + end + + it 'returns immediately' do + start_time = Time.now + get "/?wait_for_status=red&timeout=#{timeout}" + end_time = Time.now + expect(end_time - start_time).to be < 0.5 + end + end + + context 'the status changes to green' do + + let(:timeout) { 2 } + + let(:return_statuses) do + [ + org.logstash.health.Status::RED, + org.logstash.health.Status::GREEN + ] + end + + before do + allow(@agent.health_observer).to receive(:status).and_return(*return_statuses) + end + + it 'returns immediately' do + start_time = Time.now + get "/?wait_for_status=red&timeout=#{timeout}" + end_time = Time.now + expect(end_time - start_time).to be < 0.5 + end + end end end end From 9c507604c574fb9ccf2bc29e3dbcba22ac3874f7 Mon Sep 17 00:00:00 2001 From: Emily Stolfo Date: Fri, 31 Oct 2025 13:34:15 +0100 Subject: [PATCH 13/39] Refactor and fix tests --- logstash-core/lib/logstash/api/errors.rb | 20 ++ .../lib/logstash/api/modules/root.rb | 64 +++- .../spec/logstash/api/modules/root_spec.rb | 301 ++++++++---------- logstash-core/spec/support/shared_examples.rb | 10 + 4 files changed, 217 insertions(+), 178 deletions(-) diff --git a/logstash-core/lib/logstash/api/errors.rb b/logstash-core/lib/logstash/api/errors.rb index ad783c63aa5..f5bcdd67abe 100644 --- a/logstash-core/lib/logstash/api/errors.rb +++ b/logstash-core/lib/logstash/api/errors.rb @@ -40,5 +40,25 @@ def status_code 404 end end + + class BadRequest < ApiError + def initialize(message = nil) + super(message || "Bad Request") + end + + def status_code + 400 + end + end + + class TimedOut < ApiError + def initialize(message = nil) + super(message || "Timed out") + end + + def status_code + 503 + end + end end end diff --git a/logstash-core/lib/logstash/api/modules/root.rb b/logstash-core/lib/logstash/api/modules/root.rb index ccaa80dba38..02c8afb3bc5 100644 --- a/logstash-core/lib/logstash/api/modules/root.rb +++ b/logstash-core/lib/logstash/api/modules/root.rb @@ -28,36 +28,66 @@ class Root < ::LogStash::Api::Modules::Base Java::OrgLogstashHealth::Status::RED ].map(&:external_value) + INVALID_HEALTH_STATUS_MESSAGE = "Invalid status '%s' provided. The valid statuses are: green, yellow, red." + INVALID_TIMEOUT_MESSAGE = "Invalid timeout '%s' provided." + TIMED_OUT_WAITING_FOR_STATUS_MESSAGE = "Timed out waiting for status '%s'." + get "/" do - target_status = params[:wait_for_status]&.downcase - timeout = params[:timeout].to_i - status = 200 - - if HEALTH_STATUS.include?(target_status) && timeout > 0 - begin - wait_for_status(params[:timeout], target_status) - rescue Timeout::Error - status = 503 - end + input_status = params[:wait_for_status] + input_timeout = params[:timeout] + + if input_status + return status_error_response(input_status) unless target_status = parse_status(input_status) + end + + if input_timeout + return timeout_error_response(input_timeout) unless timeout_f = parse_timeout_f(input_timeout) end - command = factory.build(:system_basic_info) - respond_with(command.run, status_code: status) + if target_status && timeout_f + wait_for_status_and_respond(target_status, timeout_f) + else + command = factory.build(:system_basic_info) + respond_with(command.run) + end end private - def wait_for_status(timeout_seconds, target_status) - wait_interval_seconds = 1 + def parse_timeout_f(timeout) + LogStash::Util::TimeValue.from_value(timeout).to_nanos/1e9 + rescue ArgumentError + end - Timeout.timeout(timeout_seconds.to_i) do + def parse_status(target_status) + target_status = target_status&.downcase + target_status if HEALTH_STATUS.include?(target_status) + end + + def timeout_error_response(timeout) + respond_with(BadRequest.new(INVALID_TIMEOUT_MESSAGE % [timeout])) + end + + def status_error_response(target_status) + respond_with(BadRequest.new(INVALID_HEALTH_STATUS_MESSAGE % [target_status])) + end + + def wait_for_status_and_respond(target_status, timeout) + wait_interval_s = 1 + + Timeout.timeout(timeout) do loop do current_status = HEALTH_STATUS.index(agent.health_observer.status.external_value) break if current_status <= HEALTH_STATUS.index(target_status) - sleep(wait_interval_seconds) - wait_interval_seconds = wait_interval_seconds * 2 + sleep(wait_interval_s) + wait_interval_s = wait_interval_s * 2 end + + command = factory.build(:system_basic_info) + respond_with(command.run) end + rescue Timeout::Error + respond_with(TimedOut.new(TIMED_OUT_WAITING_FOR_STATUS_MESSAGE % [target_status])) end end end diff --git a/logstash-core/spec/logstash/api/modules/root_spec.rb b/logstash-core/spec/logstash/api/modules/root_spec.rb index b7d1d6e45ee..c27e29bdbcb 100644 --- a/logstash-core/spec/logstash/api/modules/root_spec.rb +++ b/logstash-core/spec/logstash/api/modules/root_spec.rb @@ -30,140 +30,176 @@ include_examples "not found" - describe 'wait_for_status query param' do - - context 'no timeout is provided' do - - it 'returns immediately' do - start_time = Time.now - get "/?wait_for_status=red" - end_time = Time.now - expect(end_time - start_time).to be < 0.5 - end - end + describe 'wait_for_status' do - context "timeout is provided" do + let(:response) { get request } - context "the timeout value is not a valid integer" do + context 'timeout' do - let(:timeout) { "invalid" } + context 'no timeout provided' do - it 'returns immediately' do - start_time = Time.now - get "/?wait_for_status=red&timeout=#{timeout}" - end_time = Time.now - expect(end_time - start_time).to be < 0.5 - end + let(:request) { "/" } + + include_examples "returns without waiting" end - context "the status doesn't change" do + context 'timeout is provided' do - let(:timeout) { 1 } + let(:request) { "/?timeout=#{timeout}" } - let(:return_statuses) do - [ - org.logstash.health.Status::RED - ] - end + context 'timeout does not have units' do - before do - allow(@agent.health_observer).to receive(:status).and_return(*return_statuses) - end + let(:timeout) { '1' } - it 'checks the status until the timeout is reached' do - start_time = Time.now - get "/?wait_for_status=green&timeout=#{timeout}" - end_time = Time.now - expect(end_time - start_time).to be >= timeout - end + it 'returns an error response' do + expect(response.body).to include(described_class::INVALID_TIMEOUT_MESSAGE % [timeout]) + end - it 'returns status code 503' do - response = get "/?wait_for_status=green&timeout=#{timeout}" - expect(response.status).to eq 503 + it 'returns a 400 status' do + expect(response.status).to be 400 + end end - end - context 'the status changes to the target status within the timeout' do + context 'timeout number is not an integer' do - let(:timeout) do - # The first wait interval is 1 second - 2 - end + let(:timeout) { '1.0s' } - let(:return_statuses) do - [ - org.logstash.health.Status::RED, - org.logstash.health.Status::YELLOW - ] + it 'returns an error response' do + expect(response.body).to include(described_class::INVALID_TIMEOUT_MESSAGE % [timeout]) + end + + it 'returns an 400 status' do + expect(response.status).to be 400 + end end - before do - allow(@agent.health_observer).to receive(:status).and_return(*return_statuses) + context 'timeout is not in the accepted format' do + + let(:timeout) { 'invalid' } + + it 'returns an error response' do + expect(response.body).to include(described_class::INVALID_TIMEOUT_MESSAGE % [timeout]) + end + + it 'returns an 400 status' do + expect(response.status).to be 400 + end end - it 'returns when the target status is reached' do - start_time = Time.now - get "/?wait_for_status=yellow&timeout=#{timeout}" - end_time = Time.now - expect(end_time - start_time).to be < timeout + context 'valid timeout is provided' do + + context 'no status is provided' do + + let(:timeout) { '1s' } + + include_examples "returns without waiting" + end + + context 'status is provided' do + + let(:timeout) { '1s' } + let(:status) { 'green' } + let(:request) { "/?status=#{status}timeout=#{timeout}" } + + it 'returns status code 200' do + expect(response.status).to be 200 + end + + include_examples "returns without waiting" + end end end end - context 'status is provided' do + context 'status' do - context 'no timeout' do + context 'no status provided' do - described_class::HEALTH_STATUS.each do |status| + let(:request) { '/'} - context "with status \"#{status}\"" do + include_examples "returns without waiting" + end - it 'returns immediately' do - start_time = Time.now - get "/?wait_for_status=#{status}" - end_time = Time.now - expect(end_time - start_time).to be < 0.5 - end + context 'status is provided' do + + let(:request) { "/?wait_for_status=#{status}" } + + context 'status is not valid' do + + let(:status) { 'invalid' } + + it 'returns an error response' do + expect(response.body).to include(described_class::INVALID_HEALTH_STATUS_MESSAGE % [status]) + end + + it 'returns an 400 status' do + expect(response.status).to be 400 end end - end - context "invalid status" do + context 'status is valid' do - it "pending", :skip do - # start_time = Time.now - # get "/?wait_for_status=invalid" - # end_time = Time.now - # expect(end_time - start_time).to be < 0.5 + let(:status) { 'red' } + + context 'no timeout is provided' do + + + end + + context 'timeout is provided' do + + let(:timeout) { '1s' } + let(:status) { 'green' } + let(:request) { "/?wait_for_status=#{status}&timeout=#{timeout}" } + + it 'returns status code 200' do + expect(response.status).to be 200 + end + + include_examples "returns without waiting" + end end end + end - context 'status is formatted differently' do + context 'timeout and status provided' do + + let(:timeout_num) { 2 } + let(:timeout_string) { "#{timeout_num}s"} + let(:status) { 'green' } + let(:request) { "/?wait_for_status=#{status}&timeout=#{timeout_string}" } + + before do + allow(@agent.health_observer).to receive(:status).and_return(*return_statuses) + end - let(:timeout) { 2 } + context "the status doesn't change before the timeout" do let(:return_statuses) do [ - org.logstash.health.Status::RED, - org.logstash.health.Status::YELLOW + org.logstash.health.Status::RED ] end - before do - allow(@agent.health_observer).to receive(:status).and_return(*return_statuses) - end - - it 'normalizes the format and checks the status' do + it 'checks the status until timeout' do start_time = Time.now - get "/?wait_for_status=yElLoW&timeout=#{timeout}" + response end_time = Time.now - expect(end_time - start_time).to be < timeout + expect(end_time - start_time).to be >= timeout_num + end + + it 'returns status code 503' do + expect(response.status).to eq 503 + end + + it 'returns a message saying the request timed out' do + expect(response.body).to include(described_class::TIMED_OUT_WAITING_FOR_STATUS_MESSAGE % [status]) end end context 'target status is green' do - let(:timeout) { 2 } + let(:status) { 'green' } context 'the status does not change' do @@ -174,22 +210,16 @@ ] end - before do - allow(@agent.health_observer).to receive(:status).and_return(*return_statuses) - end - - it 'checks for the status until the timeout is reached' do + it 'checks for the status until timeout' do start_time = Time.now - get "/?wait_for_status=green&timeout=#{timeout}" + response end_time = Time.now - expect(end_time - start_time).to be >= timeout + expect(end_time - start_time).to be >= timeout_num end end context 'the status changes to green' do - let(:timeout) { 2 } - let(:return_statuses) do [ org.logstash.health.Status::RED, @@ -197,22 +227,18 @@ ] end - before do - allow(@agent.health_observer).to receive(:status).and_return(*return_statuses) - end - - it 'checks for the status until the timeout is reached' do + it 'checks for the status until the target status is reached' do start_time = Time.now - get "/?wait_for_status=green&timeout=#{timeout}" + response end_time = Time.now - expect(end_time - start_time).to be < timeout + expect(end_time - start_time).to be < timeout_num end end end context 'target status is yellow' do - let(:timeout) { 2 } + let(:status) { 'yellow' } context 'the status does not change' do @@ -222,22 +248,16 @@ ] end - before do - allow(@agent.health_observer).to receive(:status).and_return(*return_statuses) - end - - it 'checks for the status until the timeout is reached' do + it 'checks for the status until timeout' do start_time = Time.now - get "/?wait_for_status=yellow&timeout=#{timeout}" + response end_time = Time.now - expect(end_time - start_time).to be >= timeout + expect(end_time - start_time).to be >= timeout_num end end context 'the status changes to yellow' do - let(:timeout) { 2 } - let(:return_statuses) do [ org.logstash.health.Status::RED, @@ -245,22 +265,16 @@ ] end - before do - allow(@agent.health_observer).to receive(:status).and_return(*return_statuses) - end - it 'checks for the status until the yellow status is reached' do start_time = Time.now - get "/?wait_for_status=yellow&timeout=#{timeout}" + response end_time = Time.now - expect(end_time - start_time).to be < timeout + expect(end_time - start_time).to be < timeout_num end end context 'the status changes to green' do - let(:timeout) { 2 } - let(:return_statuses) do [ org.logstash.health.Status::RED, @@ -268,22 +282,18 @@ ] end - before do - allow(@agent.health_observer).to receive(:status).and_return(*return_statuses) - end - it 'checks for the status until a status that is better (green) is reached' do start_time = Time.now - get "/?wait_for_status=yellow&timeout=#{timeout}" + response end_time = Time.now - expect(end_time - start_time).to be < timeout + expect(end_time - start_time).to be < timeout_num end end end context 'target status is red' do - let(:timeout) { 2 } + let(:status) { 'red' } context 'the status does not change' do @@ -293,22 +303,11 @@ ] end - before do - allow(@agent.health_observer).to receive(:status).and_return(*return_statuses) - end - - it 'returns immediately' do - start_time = Time.now - get "/?wait_for_status=red&timeout=#{timeout}" - end_time = Time.now - expect(end_time - start_time).to be < 0.5 - end + include_examples "returns without waiting" end context 'the status changes to yellow' do - let(:timeout) { 2 } - let(:return_statuses) do [ org.logstash.health.Status::RED, @@ -316,22 +315,11 @@ ] end - before do - allow(@agent.health_observer).to receive(:status).and_return(*return_statuses) - end - - it 'returns immediately' do - start_time = Time.now - get "/?wait_for_status=red&timeout=#{timeout}" - end_time = Time.now - expect(end_time - start_time).to be < 0.5 - end + include_examples "returns without waiting" end context 'the status changes to green' do - let(:timeout) { 2 } - let(:return_statuses) do [ org.logstash.health.Status::RED, @@ -339,16 +327,7 @@ ] end - before do - allow(@agent.health_observer).to receive(:status).and_return(*return_statuses) - end - - it 'returns immediately' do - start_time = Time.now - get "/?wait_for_status=red&timeout=#{timeout}" - end_time = Time.now - expect(end_time - start_time).to be < 0.5 - end + include_examples "returns without waiting" end end end diff --git a/logstash-core/spec/support/shared_examples.rb b/logstash-core/spec/support/shared_examples.rb index 29eb4953b19..13bafce862a 100644 --- a/logstash-core/spec/support/shared_examples.rb +++ b/logstash-core/spec/support/shared_examples.rb @@ -119,3 +119,13 @@ expect(LogStash::Json.load(last_response.body)["path"]).not_to be_nil end end + +shared_examples "returns without waiting" do + + it 'returns immediately' do + start_time = Time.now + response + end_time = Time.now + expect(end_time - start_time).to be < 0.5 + end +end From a5e25f440324246699c6d999fa77c6a6efdaf2f1 Mon Sep 17 00:00:00 2001 From: Emily Stolfo Date: Fri, 31 Oct 2025 13:39:15 +0100 Subject: [PATCH 14/39] Not necessary to handle status code in app helpers anymore --- logstash-core/lib/logstash/api/app_helpers.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/logstash-core/lib/logstash/api/app_helpers.rb b/logstash-core/lib/logstash/api/app_helpers.rb index 1777fd4824c..6df4cc70650 100644 --- a/logstash-core/lib/logstash/api/app_helpers.rb +++ b/logstash-core/lib/logstash/api/app_helpers.rb @@ -29,8 +29,7 @@ def respond_with(data, options = {}) as = options.fetch(:as, :json) filter = options.fetch(:filter, "") - status data.respond_to?(:status_code) ? - data.status_code : options.fetch(:status_code, 200) + status data.respond_to?(:status_code) ? data.status_code : 200 if as == :json if api_error?(data) From dfdf2246ef9bc9f51bf49a32ce27e08588b14deb Mon Sep 17 00:00:00 2001 From: Emily Stolfo Date: Fri, 31 Oct 2025 13:50:51 +0100 Subject: [PATCH 15/39] Rename method to reflect time units --- logstash-core/lib/logstash/api/modules/root.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/logstash-core/lib/logstash/api/modules/root.rb b/logstash-core/lib/logstash/api/modules/root.rb index 02c8afb3bc5..62093ef81c8 100644 --- a/logstash-core/lib/logstash/api/modules/root.rb +++ b/logstash-core/lib/logstash/api/modules/root.rb @@ -41,11 +41,11 @@ class Root < ::LogStash::Api::Modules::Base end if input_timeout - return timeout_error_response(input_timeout) unless timeout_f = parse_timeout_f(input_timeout) + return timeout_error_response(input_timeout) unless timeout_s = parse_timeout_s(input_timeout) end - if target_status && timeout_f - wait_for_status_and_respond(target_status, timeout_f) + if target_status && timeout_s + wait_for_status_and_respond(target_status, timeout_s) else command = factory.build(:system_basic_info) respond_with(command.run) @@ -53,7 +53,9 @@ class Root < ::LogStash::Api::Modules::Base end private - def parse_timeout_f(timeout) + def parse_timeout_s(timeout) + # If we call #to_seconds directly, the value will be rounded. So call to_nanos, then convert + # to seconds, represented as a float. LogStash::Util::TimeValue.from_value(timeout).to_nanos/1e9 rescue ArgumentError end From 8f9a031c05b15dfa68a0bc71bdc6d99fcb04943c Mon Sep 17 00:00:00 2001 From: Emily Stolfo Date: Fri, 31 Oct 2025 14:12:33 +0100 Subject: [PATCH 16/39] Use shared examples in specs --- .../spec/logstash/api/modules/root_spec.rb | 127 ++++++------------ logstash-core/spec/support/shared_examples.rb | 47 ++++++- 2 files changed, 88 insertions(+), 86 deletions(-) diff --git a/logstash-core/spec/logstash/api/modules/root_spec.rb b/logstash-core/spec/logstash/api/modules/root_spec.rb index c27e29bdbcb..85c6ff9dc72 100644 --- a/logstash-core/spec/logstash/api/modules/root_spec.rb +++ b/logstash-core/spec/logstash/api/modules/root_spec.rb @@ -40,7 +40,7 @@ let(:request) { "/" } - include_examples "returns without waiting" + include_examples "returns successfully without waiting" end context 'timeout is provided' do @@ -50,40 +50,25 @@ context 'timeout does not have units' do let(:timeout) { '1' } + let(:error_message) { described_class::INVALID_TIMEOUT_MESSAGE % [timeout] } - it 'returns an error response' do - expect(response.body).to include(described_class::INVALID_TIMEOUT_MESSAGE % [timeout]) - end - - it 'returns a 400 status' do - expect(response.status).to be 400 - end + include_examples 'timed out response' end context 'timeout number is not an integer' do let(:timeout) { '1.0s' } + let(:error_message) { described_class::INVALID_TIMEOUT_MESSAGE % [timeout] } - it 'returns an error response' do - expect(response.body).to include(described_class::INVALID_TIMEOUT_MESSAGE % [timeout]) - end - - it 'returns an 400 status' do - expect(response.status).to be 400 - end + include_examples 'timed out response' end context 'timeout is not in the accepted format' do let(:timeout) { 'invalid' } + let(:error_message) { described_class::INVALID_TIMEOUT_MESSAGE % [timeout] } - it 'returns an error response' do - expect(response.body).to include(described_class::INVALID_TIMEOUT_MESSAGE % [timeout]) - end - - it 'returns an 400 status' do - expect(response.status).to be 400 - end + include_examples 'timed out response' end context 'valid timeout is provided' do @@ -92,7 +77,7 @@ let(:timeout) { '1s' } - include_examples "returns without waiting" + include_examples "returns successfully without waiting" end context 'status is provided' do @@ -105,7 +90,7 @@ expect(response.status).to be 200 end - include_examples "returns without waiting" + include_examples "returns successfully without waiting" end end end @@ -117,7 +102,7 @@ let(:request) { '/'} - include_examples "returns without waiting" + include_examples "returns successfully without waiting" end context 'status is provided' do @@ -127,36 +112,46 @@ context 'status is not valid' do let(:status) { 'invalid' } + let(:error_message) { described_class::INVALID_HEALTH_STATUS_MESSAGE % [status] } - it 'returns an error response' do - expect(response.body).to include(described_class::INVALID_HEALTH_STATUS_MESSAGE % [status]) - end - - it 'returns an 400 status' do - expect(response.status).to be 400 - end + include_examples 'timed out response' end context 'status is valid' do - let(:status) { 'red' } + let(:return_statuses) do + [ + org.logstash.health.Status::RED + ] + end + + before do + allow(@agent.health_observer).to receive(:status).and_return(*return_statuses) + end context 'no timeout is provided' do + let(:request) { "/?wait_for_status=green" } + include_examples "returns successfully without waiting" end context 'timeout is provided' do - let(:timeout) { '1s' } + let(:timeout_num) { 2 } + let(:timeout_string) { "#{timeout_num}s"} let(:status) { 'green' } - let(:request) { "/?wait_for_status=#{status}&timeout=#{timeout}" } + let(:request) { "/?wait_for_status=#{status}&timeout=#{timeout_string}" } - it 'returns status code 200' do - expect(response.status).to be 200 + let(:return_statuses) do + [ + org.logstash.health.Status::RED, + org.logstash.health.Status::GREEN + + ] end - include_examples "returns without waiting" + include_examples 'waits until the target status (or better) is reached and returns successfully' end end end @@ -181,20 +176,7 @@ ] end - it 'checks the status until timeout' do - start_time = Time.now - response - end_time = Time.now - expect(end_time - start_time).to be >= timeout_num - end - - it 'returns status code 503' do - expect(response.status).to eq 503 - end - - it 'returns a message saying the request timed out' do - expect(response.body).to include(described_class::TIMED_OUT_WAITING_FOR_STATUS_MESSAGE % [status]) - end + include_examples 'times out waiting for target status (or better)' end context 'target status is green' do @@ -210,12 +192,7 @@ ] end - it 'checks for the status until timeout' do - start_time = Time.now - response - end_time = Time.now - expect(end_time - start_time).to be >= timeout_num - end + include_examples 'times out waiting for target status (or better)' end context 'the status changes to green' do @@ -227,12 +204,7 @@ ] end - it 'checks for the status until the target status is reached' do - start_time = Time.now - response - end_time = Time.now - expect(end_time - start_time).to be < timeout_num - end + include_examples 'waits until the target status (or better) is reached and returns successfully' end end @@ -248,12 +220,7 @@ ] end - it 'checks for the status until timeout' do - start_time = Time.now - response - end_time = Time.now - expect(end_time - start_time).to be >= timeout_num - end + include_examples 'times out waiting for target status (or better)' end context 'the status changes to yellow' do @@ -265,12 +232,7 @@ ] end - it 'checks for the status until the yellow status is reached' do - start_time = Time.now - response - end_time = Time.now - expect(end_time - start_time).to be < timeout_num - end + include_examples 'waits until the target status (or better) is reached and returns successfully' end context 'the status changes to green' do @@ -282,12 +244,7 @@ ] end - it 'checks for the status until a status that is better (green) is reached' do - start_time = Time.now - response - end_time = Time.now - expect(end_time - start_time).to be < timeout_num - end + include_examples 'waits until the target status (or better) is reached and returns successfully' end end @@ -303,7 +260,7 @@ ] end - include_examples "returns without waiting" + include_examples "returns successfully without waiting" end context 'the status changes to yellow' do @@ -315,7 +272,7 @@ ] end - include_examples "returns without waiting" + include_examples "returns successfully without waiting" end context 'the status changes to green' do @@ -327,7 +284,7 @@ ] end - include_examples "returns without waiting" + include_examples "returns successfully without waiting" end end end diff --git a/logstash-core/spec/support/shared_examples.rb b/logstash-core/spec/support/shared_examples.rb index 13bafce862a..05270c06896 100644 --- a/logstash-core/spec/support/shared_examples.rb +++ b/logstash-core/spec/support/shared_examples.rb @@ -120,7 +120,11 @@ end end -shared_examples "returns without waiting" do +shared_examples "returns successfully without waiting" do + + it 'returns status code 200' do + expect(response.status).to be 200 + end it 'returns immediately' do start_time = Time.now @@ -129,3 +133,44 @@ expect(end_time - start_time).to be < 0.5 end end + +shared_examples "timed out response" do + + it 'returns an error response' do + expect(response.body).to include(error_message) + end + + it 'returns an 400 status' do + expect(response.status).to be 400 + end +end + +shared_examples 'waits until the target status (or better) is reached and returns successfully' do + it 'returns status code 200' do + expect(response.status).to be 200 + end + + it 'checks for the status until the target status (or better) is reached' do + start_time = Time.now + response + end_time = Time.now + expect(end_time - start_time).to be < timeout_num + end +end + +shared_examples 'times out waiting for target status (or better)' do + it 'times out waiting for target status (or better)' do + start_time = Time.now + response + end_time = Time.now + expect(end_time - start_time).to be >= timeout_num + end + + it 'returns status code 503' do + expect(response.status).to eq 503 + end + + it 'returns a message saying the request timed out' do + expect(response.body).to include(described_class::TIMED_OUT_WAITING_FOR_STATUS_MESSAGE % [status]) + end +end From a1cb3f254f1376cb394107fc358f58f9b6c7a8af Mon Sep 17 00:00:00 2001 From: Emily Stolfo Date: Fri, 31 Oct 2025 14:14:22 +0100 Subject: [PATCH 17/39] Small update to comment --- logstash-core/lib/logstash/api/modules/root.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logstash-core/lib/logstash/api/modules/root.rb b/logstash-core/lib/logstash/api/modules/root.rb index 62093ef81c8..2d3b90c73d7 100644 --- a/logstash-core/lib/logstash/api/modules/root.rb +++ b/logstash-core/lib/logstash/api/modules/root.rb @@ -55,7 +55,7 @@ class Root < ::LogStash::Api::Modules::Base private def parse_timeout_s(timeout) # If we call #to_seconds directly, the value will be rounded. So call to_nanos, then convert - # to seconds, represented as a float. + # to seconds, so we get a float. LogStash::Util::TimeValue.from_value(timeout).to_nanos/1e9 rescue ArgumentError end From 6c6d18f8aa07e680f04610fdb6c5f4567eb891ac Mon Sep 17 00:00:00 2001 From: Emily Stolfo Date: Fri, 31 Oct 2025 16:18:30 +0100 Subject: [PATCH 18/39] Change variables names for clarity --- logstash-core/lib/logstash/api/modules/root.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/logstash-core/lib/logstash/api/modules/root.rb b/logstash-core/lib/logstash/api/modules/root.rb index 2d3b90c73d7..ef47bc26961 100644 --- a/logstash-core/lib/logstash/api/modules/root.rb +++ b/logstash-core/lib/logstash/api/modules/root.rb @@ -60,8 +60,8 @@ def parse_timeout_s(timeout) rescue ArgumentError end - def parse_status(target_status) - target_status = target_status&.downcase + def parse_status(input_status) + target_status = input_status&.downcase target_status if HEALTH_STATUS.include?(target_status) end From 805114f3b994914359de9c5886aae3274bcf3497 Mon Sep 17 00:00:00 2001 From: Emily Stolfo Date: Fri, 31 Oct 2025 16:20:20 +0100 Subject: [PATCH 19/39] No need to check for nil, the method is called only if the variable is not nil --- logstash-core/lib/logstash/api/modules/root.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logstash-core/lib/logstash/api/modules/root.rb b/logstash-core/lib/logstash/api/modules/root.rb index ef47bc26961..1e785c56749 100644 --- a/logstash-core/lib/logstash/api/modules/root.rb +++ b/logstash-core/lib/logstash/api/modules/root.rb @@ -61,7 +61,7 @@ def parse_timeout_s(timeout) end def parse_status(input_status) - target_status = input_status&.downcase + target_status = input_status.downcase target_status if HEALTH_STATUS.include?(target_status) end From a9b53f0f46d2edecb26b06dfceeaa37bb81bdebf Mon Sep 17 00:00:00 2001 From: Emily Stolfo Date: Tue, 4 Nov 2025 09:47:45 +0100 Subject: [PATCH 20/39] Fix shared examples name --- .../spec/logstash/api/modules/root_spec.rb | 15 ++++++++------- logstash-core/spec/support/shared_examples.rb | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/logstash-core/spec/logstash/api/modules/root_spec.rb b/logstash-core/spec/logstash/api/modules/root_spec.rb index 85c6ff9dc72..53c73a4a759 100644 --- a/logstash-core/spec/logstash/api/modules/root_spec.rb +++ b/logstash-core/spec/logstash/api/modules/root_spec.rb @@ -52,7 +52,7 @@ let(:timeout) { '1' } let(:error_message) { described_class::INVALID_TIMEOUT_MESSAGE % [timeout] } - include_examples 'timed out response' + include_examples 'bad request response' end context 'timeout number is not an integer' do @@ -60,7 +60,7 @@ let(:timeout) { '1.0s' } let(:error_message) { described_class::INVALID_TIMEOUT_MESSAGE % [timeout] } - include_examples 'timed out response' + include_examples 'bad request response' end context 'timeout is not in the accepted format' do @@ -68,7 +68,7 @@ let(:timeout) { 'invalid' } let(:error_message) { described_class::INVALID_TIMEOUT_MESSAGE % [timeout] } - include_examples 'timed out response' + include_examples 'bad request response' end context 'valid timeout is provided' do @@ -82,15 +82,16 @@ context 'status is provided' do - let(:timeout) { '1s' } + let(:timeout_num) { 2 } + let(:timeout_string) { "#{timeout_num}s"} let(:status) { 'green' } - let(:request) { "/?status=#{status}timeout=#{timeout}" } + let(:request) { "/?wait_for_status=#{status}&timeout=#{timeout_string}" } it 'returns status code 200' do expect(response.status).to be 200 end - include_examples "returns successfully without waiting" + include_examples "waits until the target status (or better) is reached and returns successfully" end end end @@ -114,7 +115,7 @@ let(:status) { 'invalid' } let(:error_message) { described_class::INVALID_HEALTH_STATUS_MESSAGE % [status] } - include_examples 'timed out response' + include_examples 'bad request response' end context 'status is valid' do diff --git a/logstash-core/spec/support/shared_examples.rb b/logstash-core/spec/support/shared_examples.rb index 05270c06896..66f8e6e3b53 100644 --- a/logstash-core/spec/support/shared_examples.rb +++ b/logstash-core/spec/support/shared_examples.rb @@ -134,7 +134,7 @@ end end -shared_examples "timed out response" do +shared_examples "bad request response" do it 'returns an error response' do expect(response.body).to include(error_message) From 174934dcb8da021c156b5afe9f8a9de3c1799df8 Mon Sep 17 00:00:00 2001 From: Emily S Date: Tue, 4 Nov 2025 09:51:02 +0100 Subject: [PATCH 21/39] Update docs/static/spec/openapi/logstash-api.yaml Co-authored-by: Rye Biesemeyer --- docs/static/spec/openapi/logstash-api.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/static/spec/openapi/logstash-api.yaml b/docs/static/spec/openapi/logstash-api.yaml index b68d010ef76..479bd3fece8 100644 --- a/docs/static/spec/openapi/logstash-api.yaml +++ b/docs/static/spec/openapi/logstash-api.yaml @@ -122,7 +122,7 @@ paths: batch_delay: type: integer example: - host: "" + host: "logstash-pipelines.example.com" version: "9.3.0" http_address: "127.0.0.1:9600" id: "58df6f7c-eb5c-5d42-bc20-c7b22779aa12" From 79a44a818f0cacc9e4b955ebf4e55678b094e5af Mon Sep 17 00:00:00 2001 From: Emily S Date: Tue, 4 Nov 2025 09:51:30 +0100 Subject: [PATCH 22/39] Update docs/static/spec/openapi/logstash-api.yaml Co-authored-by: Rye Biesemeyer --- docs/static/spec/openapi/logstash-api.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/static/spec/openapi/logstash-api.yaml b/docs/static/spec/openapi/logstash-api.yaml index 479bd3fece8..867288cfca0 100644 --- a/docs/static/spec/openapi/logstash-api.yaml +++ b/docs/static/spec/openapi/logstash-api.yaml @@ -126,7 +126,7 @@ paths: version: "9.3.0" http_address: "127.0.0.1:9600" id: "58df6f7c-eb5c-5d42-bc20-c7b22779aa12" - name: "" + name: "logstash-pipelines" ephermeral_id: "59df6f6c-eb5c-4d42-bc20-c7b44779aa12" snapshot: status: "green" From 2e28f387399191e9f999e73209cb75e8802a8e67 Mon Sep 17 00:00:00 2001 From: Emily Stolfo Date: Tue, 4 Nov 2025 11:53:27 +0100 Subject: [PATCH 23/39] Use deadline instead of Timeout --- .../lib/logstash/api/modules/root.rb | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/logstash-core/lib/logstash/api/modules/root.rb b/logstash-core/lib/logstash/api/modules/root.rb index 1e785c56749..3b5666b4952 100644 --- a/logstash-core/lib/logstash/api/modules/root.rb +++ b/logstash-core/lib/logstash/api/modules/root.rb @@ -74,22 +74,23 @@ def status_error_response(target_status) end def wait_for_status_and_respond(target_status, timeout) - wait_interval_s = 1 + wait_interval = 0.2 # seconds + deadline = Time.now + timeout - Timeout.timeout(timeout) do - loop do - current_status = HEALTH_STATUS.index(agent.health_observer.status.external_value) - break if current_status <= HEALTH_STATUS.index(target_status) + loop do + current_status = HEALTH_STATUS.index(agent.health_observer.status.external_value) + break if current_status <= HEALTH_STATUS.index(target_status) - sleep(wait_interval_s) - wait_interval_s = wait_interval_s * 2 + if Time.now > deadline + return respond_with(TimedOut.new(TIMED_OUT_WAITING_FOR_STATUS_MESSAGE % [target_status])) end - command = factory.build(:system_basic_info) - respond_with(command.run) + sleep(wait_interval) + wait_interval = wait_interval * 2 end - rescue Timeout::Error - respond_with(TimedOut.new(TIMED_OUT_WAITING_FOR_STATUS_MESSAGE % [target_status])) + + command = factory.build(:system_basic_info) + respond_with(command.run) end end end From bab9243008ec1a19944f8f83e7963d0075f9cc11 Mon Sep 17 00:00:00 2001 From: Emily Stolfo Date: Tue, 4 Nov 2025 11:53:43 +0100 Subject: [PATCH 24/39] Be more explicit about class being Float --- logstash-core/lib/logstash/api/modules/root.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logstash-core/lib/logstash/api/modules/root.rb b/logstash-core/lib/logstash/api/modules/root.rb index 3b5666b4952..13d5193d145 100644 --- a/logstash-core/lib/logstash/api/modules/root.rb +++ b/logstash-core/lib/logstash/api/modules/root.rb @@ -56,7 +56,7 @@ class Root < ::LogStash::Api::Modules::Base def parse_timeout_s(timeout) # If we call #to_seconds directly, the value will be rounded. So call to_nanos, then convert # to seconds, so we get a float. - LogStash::Util::TimeValue.from_value(timeout).to_nanos/1e9 + LogStash::Util::TimeValue.from_value(timeout).to_nanos.to_f/1_000_000_000 rescue ArgumentError end From b53c9d7556935b2b8657111626d696b0f74f81a6 Mon Sep 17 00:00:00 2001 From: Emily Stolfo Date: Tue, 4 Nov 2025 11:54:43 +0100 Subject: [PATCH 25/39] No need for timeout --- logstash-core/lib/logstash/api/modules/root.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/logstash-core/lib/logstash/api/modules/root.rb b/logstash-core/lib/logstash/api/modules/root.rb index 13d5193d145..d6340e0862f 100644 --- a/logstash-core/lib/logstash/api/modules/root.rb +++ b/logstash-core/lib/logstash/api/modules/root.rb @@ -15,8 +15,6 @@ # specific language governing permissions and limitations # under the License. -require 'timeout' - module LogStash module Api module Modules From f9e50e9b70a708ef502243aa3e1a9b80b8fb888d Mon Sep 17 00:00:00 2001 From: Emily Stolfo Date: Tue, 4 Nov 2025 12:05:31 +0100 Subject: [PATCH 26/39] Test timeout units in ms --- .../spec/logstash/api/modules/root_spec.rb | 19 ++++++++++++++++++- logstash-core/spec/support/shared_examples.rb | 7 ++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/logstash-core/spec/logstash/api/modules/root_spec.rb b/logstash-core/spec/logstash/api/modules/root_spec.rb index 53c73a4a759..bef7f46c436 100644 --- a/logstash-core/spec/logstash/api/modules/root_spec.rb +++ b/logstash-core/spec/logstash/api/modules/root_spec.rb @@ -161,7 +161,8 @@ context 'timeout and status provided' do let(:timeout_num) { 2 } - let(:timeout_string) { "#{timeout_num}s"} + let(:timeout_unit) { 's' } + let(:timeout_string) { "#{timeout_num}#{timeout_unit}"} let(:status) { 'green' } let(:request) { "/?wait_for_status=#{status}&timeout=#{timeout_string}" } @@ -288,6 +289,22 @@ include_examples "returns successfully without waiting" end end + + context 'timeout units is ms' do + + let(:timeout_unit) { 'ms' } + + context "the status doesn't change before the timeout" do + + let(:return_statuses) do + [ + org.logstash.health.Status::RED + ] + end + + include_examples 'times out waiting for target status (or better)' + end + end end end end diff --git a/logstash-core/spec/support/shared_examples.rb b/logstash-core/spec/support/shared_examples.rb index 66f8e6e3b53..fe19ced9368 100644 --- a/logstash-core/spec/support/shared_examples.rb +++ b/logstash-core/spec/support/shared_examples.rb @@ -159,11 +159,16 @@ end shared_examples 'times out waiting for target status (or better)' do + + let(:expected_time_elapsed) do + LogStash::Util::TimeValue.from_value(timeout_string).to_nanos.to_f/1_000_000_000 + end + it 'times out waiting for target status (or better)' do start_time = Time.now response end_time = Time.now - expect(end_time - start_time).to be >= timeout_num + expect(end_time - start_time).to be >= expected_time_elapsed end it 'returns status code 503' do From 2084dc1cf23d2e82c378a622a1e9def3b96cd2fd Mon Sep 17 00:00:00 2001 From: Emily Stolfo Date: Tue, 4 Nov 2025 12:12:40 +0100 Subject: [PATCH 27/39] Minor updates to specs --- logstash-core/spec/logstash/api/modules/root_spec.rb | 6 +++--- logstash-core/spec/support/shared_examples.rb | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/logstash-core/spec/logstash/api/modules/root_spec.rb b/logstash-core/spec/logstash/api/modules/root_spec.rb index bef7f46c436..a978fe41bd8 100644 --- a/logstash-core/spec/logstash/api/modules/root_spec.rb +++ b/logstash-core/spec/logstash/api/modules/root_spec.rb @@ -161,8 +161,8 @@ context 'timeout and status provided' do let(:timeout_num) { 2 } - let(:timeout_unit) { 's' } - let(:timeout_string) { "#{timeout_num}#{timeout_unit}"} + let(:timeout_units) { 's' } + let(:timeout_string) { "#{timeout_num}#{timeout_units}"} let(:status) { 'green' } let(:request) { "/?wait_for_status=#{status}&timeout=#{timeout_string}" } @@ -292,7 +292,7 @@ context 'timeout units is ms' do - let(:timeout_unit) { 'ms' } + let(:timeout_units) { 'ms' } context "the status doesn't change before the timeout" do diff --git a/logstash-core/spec/support/shared_examples.rb b/logstash-core/spec/support/shared_examples.rb index fe19ced9368..d33993feed5 100644 --- a/logstash-core/spec/support/shared_examples.rb +++ b/logstash-core/spec/support/shared_examples.rb @@ -154,7 +154,7 @@ start_time = Time.now response end_time = Time.now - expect(end_time - start_time).to be < timeout_num + expect(end_time - start_time).to be <= timeout_num end end From 3d5695fc8f4502aa97a18af1bec05be38e55a224 Mon Sep 17 00:00:00 2001 From: Emily Stolfo Date: Tue, 4 Nov 2025 12:14:46 +0100 Subject: [PATCH 28/39] Update comment --- logstash-core/lib/logstash/api/modules/root.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logstash-core/lib/logstash/api/modules/root.rb b/logstash-core/lib/logstash/api/modules/root.rb index d6340e0862f..a1aa084f17b 100644 --- a/logstash-core/lib/logstash/api/modules/root.rb +++ b/logstash-core/lib/logstash/api/modules/root.rb @@ -53,7 +53,7 @@ class Root < ::LogStash::Api::Modules::Base private def parse_timeout_s(timeout) # If we call #to_seconds directly, the value will be rounded. So call to_nanos, then convert - # to seconds, so we get a float. + # to a float and divide by 1e9 to get the value in seconds. LogStash::Util::TimeValue.from_value(timeout).to_nanos.to_f/1_000_000_000 rescue ArgumentError end From 131eaa69a9e9092c7e2c6b88ade2d93ab84a7a7c Mon Sep 17 00:00:00 2001 From: Emily Stolfo Date: Tue, 4 Nov 2025 15:36:15 +0100 Subject: [PATCH 29/39] Use status code 408 when the request times out --- logstash-core/lib/logstash/api/errors.rb | 6 +++--- logstash-core/lib/logstash/api/modules/root.rb | 2 +- logstash-core/spec/support/shared_examples.rb | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/logstash-core/lib/logstash/api/errors.rb b/logstash-core/lib/logstash/api/errors.rb index f5bcdd67abe..f15f77c7999 100644 --- a/logstash-core/lib/logstash/api/errors.rb +++ b/logstash-core/lib/logstash/api/errors.rb @@ -51,13 +51,13 @@ def status_code end end - class TimedOut < ApiError + class RequestTimeout < ApiError def initialize(message = nil) - super(message || "Timed out") + super(message || "Request Timeout") end def status_code - 503 + 408 end end end diff --git a/logstash-core/lib/logstash/api/modules/root.rb b/logstash-core/lib/logstash/api/modules/root.rb index a1aa084f17b..b4a8b06acea 100644 --- a/logstash-core/lib/logstash/api/modules/root.rb +++ b/logstash-core/lib/logstash/api/modules/root.rb @@ -80,7 +80,7 @@ def wait_for_status_and_respond(target_status, timeout) break if current_status <= HEALTH_STATUS.index(target_status) if Time.now > deadline - return respond_with(TimedOut.new(TIMED_OUT_WAITING_FOR_STATUS_MESSAGE % [target_status])) + return respond_with(RequestTimeout.new(TIMED_OUT_WAITING_FOR_STATUS_MESSAGE % [target_status])) end sleep(wait_interval) diff --git a/logstash-core/spec/support/shared_examples.rb b/logstash-core/spec/support/shared_examples.rb index d33993feed5..3a1f3cda4a1 100644 --- a/logstash-core/spec/support/shared_examples.rb +++ b/logstash-core/spec/support/shared_examples.rb @@ -171,8 +171,8 @@ expect(end_time - start_time).to be >= expected_time_elapsed end - it 'returns status code 503' do - expect(response.status).to eq 503 + it 'returns status code 408' do + expect(response.status).to eq 408 end it 'returns a message saying the request timed out' do From 9f7d2564e957e0ad3f03e8446f3f7c437fc30293 Mon Sep 17 00:00:00 2001 From: Emily Stolfo Date: Wed, 5 Nov 2025 10:44:29 +0100 Subject: [PATCH 30/39] Require timeout along with wait_for_status --- docs/static/spec/openapi/logstash-api.yaml | 2 +- logstash-core/lib/logstash/api/modules/root.rb | 8 +++++++- logstash-core/spec/logstash/api/modules/root_spec.rb | 6 +++--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/docs/static/spec/openapi/logstash-api.yaml b/docs/static/spec/openapi/logstash-api.yaml index 867288cfca0..6febf385afc 100644 --- a/docs/static/spec/openapi/logstash-api.yaml +++ b/docs/static/spec/openapi/logstash-api.yaml @@ -80,7 +80,7 @@ paths: required: false schema: type: string - description: One of green, yellow or red. Will wait (until the timeout provided) until the status of the service changes to the one provided or better, i.e. green > yellow > red. By default, will not wait for any status. + description: One of green, yellow or red. Will wait (until the timeout provided) until the status of the service changes to the one provided or better, i.e. green > yellow > red. Timeout is required when this query param is provided. - name: timeout in: query required: false diff --git a/logstash-core/lib/logstash/api/modules/root.rb b/logstash-core/lib/logstash/api/modules/root.rb index b4a8b06acea..38325fd9159 100644 --- a/logstash-core/lib/logstash/api/modules/root.rb +++ b/logstash-core/lib/logstash/api/modules/root.rb @@ -28,6 +28,7 @@ class Root < ::LogStash::Api::Modules::Base INVALID_HEALTH_STATUS_MESSAGE = "Invalid status '%s' provided. The valid statuses are: green, yellow, red." INVALID_TIMEOUT_MESSAGE = "Invalid timeout '%s' provided." + TIMEOUT_REQUIRED_WITH_STATUS_MESSAGE = "A timeout must be provided along with a status." TIMED_OUT_WAITING_FOR_STATUS_MESSAGE = "Timed out waiting for status '%s'." get "/" do @@ -42,7 +43,8 @@ class Root < ::LogStash::Api::Modules::Base return timeout_error_response(input_timeout) unless timeout_s = parse_timeout_s(input_timeout) end - if target_status && timeout_s + if target_status + return timeout_required_with_status_response unless timeout_s wait_for_status_and_respond(target_status, timeout_s) else command = factory.build(:system_basic_info) @@ -71,6 +73,10 @@ def status_error_response(target_status) respond_with(BadRequest.new(INVALID_HEALTH_STATUS_MESSAGE % [target_status])) end + def timeout_required_with_status_response + respond_with(BadRequest.new(TIMEOUT_REQUIRED_WITH_STATUS_MESSAGE)) + end + def wait_for_status_and_respond(target_status, timeout) wait_interval = 0.2 # seconds deadline = Time.now + timeout diff --git a/logstash-core/spec/logstash/api/modules/root_spec.rb b/logstash-core/spec/logstash/api/modules/root_spec.rb index a978fe41bd8..8b9cdd4c012 100644 --- a/logstash-core/spec/logstash/api/modules/root_spec.rb +++ b/logstash-core/spec/logstash/api/modules/root_spec.rb @@ -108,12 +108,11 @@ context 'status is provided' do - let(:request) { "/?wait_for_status=#{status}" } - context 'status is not valid' do let(:status) { 'invalid' } let(:error_message) { described_class::INVALID_HEALTH_STATUS_MESSAGE % [status] } + let(:request) { "/?wait_for_status=#{status}&timeout=1s" } include_examples 'bad request response' end @@ -133,8 +132,9 @@ context 'no timeout is provided' do let(:request) { "/?wait_for_status=green" } + let(:error_message) { described_class::TIMEOUT_REQUIRED_WITH_STATUS_MESSAGE } - include_examples "returns successfully without waiting" + include_examples "bad request response" end context 'timeout is provided' do From 2f4244e9831ae3fb8e0b8bdd9bf1898a965d996d Mon Sep 17 00:00:00 2001 From: Emily Stolfo Date: Wed, 5 Nov 2025 11:21:09 +0100 Subject: [PATCH 31/39] Update docs to be clearer about timeout being required with wait_for_status --- docs/static/spec/openapi/logstash-api.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/static/spec/openapi/logstash-api.yaml b/docs/static/spec/openapi/logstash-api.yaml index 6febf385afc..ab09c55b459 100644 --- a/docs/static/spec/openapi/logstash-api.yaml +++ b/docs/static/spec/openapi/logstash-api.yaml @@ -80,7 +80,7 @@ paths: required: false schema: type: string - description: One of green, yellow or red. Will wait (until the timeout provided) until the status of the service changes to the one provided or better, i.e. green > yellow > red. Timeout is required when this query param is provided. + description: One of green, yellow or red. Will wait (until the timeout provided) until the status of the service changes to the one provided or better, i.e. green > yellow > red. The query param, timeout, is required when this query param is provided. - name: timeout in: query required: false From 770cba8204abd778c9df1be3a0b4ee0a5086d3af Mon Sep 17 00:00:00 2001 From: Emily Stolfo Date: Wed, 5 Nov 2025 11:36:15 +0100 Subject: [PATCH 32/39] Move before block to shared examples --- .../spec/logstash/api/modules/root_spec.rb | 22 +++++++------------ logstash-core/spec/support/shared_examples.rb | 9 ++++++++ 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/logstash-core/spec/logstash/api/modules/root_spec.rb b/logstash-core/spec/logstash/api/modules/root_spec.rb index 8b9cdd4c012..703277c3a11 100644 --- a/logstash-core/spec/logstash/api/modules/root_spec.rb +++ b/logstash-core/spec/logstash/api/modules/root_spec.rb @@ -87,6 +87,14 @@ let(:status) { 'green' } let(:request) { "/?wait_for_status=#{status}&timeout=#{timeout_string}" } + let(:return_statuses) do + [ + org.logstash.health.Status::RED, + org.logstash.health.Status::GREEN + + ] + end + it 'returns status code 200' do expect(response.status).to be 200 end @@ -119,16 +127,6 @@ context 'status is valid' do - let(:return_statuses) do - [ - org.logstash.health.Status::RED - ] - end - - before do - allow(@agent.health_observer).to receive(:status).and_return(*return_statuses) - end - context 'no timeout is provided' do let(:request) { "/?wait_for_status=green" } @@ -166,10 +164,6 @@ let(:status) { 'green' } let(:request) { "/?wait_for_status=#{status}&timeout=#{timeout_string}" } - before do - allow(@agent.health_observer).to receive(:status).and_return(*return_statuses) - end - context "the status doesn't change before the timeout" do let(:return_statuses) do diff --git a/logstash-core/spec/support/shared_examples.rb b/logstash-core/spec/support/shared_examples.rb index 3a1f3cda4a1..0706148590a 100644 --- a/logstash-core/spec/support/shared_examples.rb +++ b/logstash-core/spec/support/shared_examples.rb @@ -146,6 +146,11 @@ end shared_examples 'waits until the target status (or better) is reached and returns successfully' do + + before do + allow(@agent.health_observer).to receive(:status).and_return(*return_statuses) + end + it 'returns status code 200' do expect(response.status).to be 200 end @@ -164,6 +169,10 @@ LogStash::Util::TimeValue.from_value(timeout_string).to_nanos.to_f/1_000_000_000 end + before do + allow(@agent.health_observer).to receive(:status).and_return(*return_statuses) + end + it 'times out waiting for target status (or better)' do start_time = Time.now response From 9311b98b1782fbd5977df387512970604b2a400f Mon Sep 17 00:00:00 2001 From: Emily S Date: Mon, 24 Nov 2025 09:32:11 +0100 Subject: [PATCH 33/39] Update logstash-core/lib/logstash/api/modules/root.rb Co-authored-by: Rye Biesemeyer --- logstash-core/lib/logstash/api/modules/root.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logstash-core/lib/logstash/api/modules/root.rb b/logstash-core/lib/logstash/api/modules/root.rb index 38325fd9159..17ec8a49978 100644 --- a/logstash-core/lib/logstash/api/modules/root.rb +++ b/logstash-core/lib/logstash/api/modules/root.rb @@ -85,7 +85,7 @@ def wait_for_status_and_respond(target_status, timeout) current_status = HEALTH_STATUS.index(agent.health_observer.status.external_value) break if current_status <= HEALTH_STATUS.index(target_status) - if Time.now > deadline + if Time.now >= deadline return respond_with(RequestTimeout.new(TIMED_OUT_WAITING_FOR_STATUS_MESSAGE % [target_status])) end From 6164f5d8806ca98eda81c8111bbfc6054f6b644d Mon Sep 17 00:00:00 2001 From: Emily Stolfo Date: Mon, 24 Nov 2025 13:05:17 +0100 Subject: [PATCH 34/39] Limit the sleep time to max timeout --- logstash-core/lib/logstash/api/modules/root.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/logstash-core/lib/logstash/api/modules/root.rb b/logstash-core/lib/logstash/api/modules/root.rb index 17ec8a49978..a68df824698 100644 --- a/logstash-core/lib/logstash/api/modules/root.rb +++ b/logstash-core/lib/logstash/api/modules/root.rb @@ -85,11 +85,11 @@ def wait_for_status_and_respond(target_status, timeout) current_status = HEALTH_STATUS.index(agent.health_observer.status.external_value) break if current_status <= HEALTH_STATUS.index(target_status) - if Time.now >= deadline + time_remaining = deadline - Time.now + if time_remaining <= 0 return respond_with(RequestTimeout.new(TIMED_OUT_WAITING_FOR_STATUS_MESSAGE % [target_status])) end - - sleep(wait_interval) + sleep((time_remaining <= wait_interval) ? time_remaining : wait_interval) wait_interval = wait_interval * 2 end From b6d013fdd460a9d96c69528c7e8568e8909ec5f3 Mon Sep 17 00:00:00 2001 From: Emily S Date: Mon, 24 Nov 2025 13:09:17 +0100 Subject: [PATCH 35/39] Update logstash-core/lib/logstash/api/modules/root.rb Co-authored-by: Rye Biesemeyer --- logstash-core/lib/logstash/api/modules/root.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/logstash-core/lib/logstash/api/modules/root.rb b/logstash-core/lib/logstash/api/modules/root.rb index a68df824698..e6fe62fcc64 100644 --- a/logstash-core/lib/logstash/api/modules/root.rb +++ b/logstash-core/lib/logstash/api/modules/root.rb @@ -35,12 +35,12 @@ class Root < ::LogStash::Api::Modules::Base input_status = params[:wait_for_status] input_timeout = params[:timeout] - if input_status - return status_error_response(input_status) unless target_status = parse_status(input_status) + if input_status && !(target_status = parse_status(input_status)) + return status_error_response(input_status) end - if input_timeout - return timeout_error_response(input_timeout) unless timeout_s = parse_timeout_s(input_timeout) + if input_timeout && !(timeout_s = parse_timeout_s(input_timeout)) + return timeout_error_response(input_timeout) end if target_status From 15900be9b0336d76d079d2dac9bcaeed694a12f3 Mon Sep 17 00:00:00 2001 From: Emily Stolfo Date: Mon, 1 Dec 2025 10:33:00 +0100 Subject: [PATCH 36/39] Updates to docs --- docs/static/spec/openapi/logstash-api.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/static/spec/openapi/logstash-api.yaml b/docs/static/spec/openapi/logstash-api.yaml index ab09c55b459..fd9555f9e9d 100644 --- a/docs/static/spec/openapi/logstash-api.yaml +++ b/docs/static/spec/openapi/logstash-api.yaml @@ -86,7 +86,7 @@ paths: required: false schema: type: string - description: Period to wait for the status to reach the requested target status. If the target status is not reached before the timeout expires, the request returns status code 503. + description: Period to wait for the status to reach the requested target status. Must be an integer with units, for example `3s`. If the target status is not reached before the timeout expires, the request returns status code 503. - $ref: "#/components/parameters/pretty" responses: '200': @@ -109,7 +109,7 @@ paths: ephemeral_id: type: string snapshot: - type: string + type: boolean status: type: string pipeline: @@ -128,7 +128,7 @@ paths: id: "58df6f7c-eb5c-5d42-bc20-c7b22779aa12" name: "logstash-pipelines" ephermeral_id: "59df6f6c-eb5c-4d42-bc20-c7b44779aa12" - snapshot: + snapshot: false status: "green" pipeline: workers: 10 From 283dc4bc2510129f46eed2543bed9df60d1dcb6e Mon Sep 17 00:00:00 2001 From: Emily Stolfo Date: Mon, 1 Dec 2025 10:57:29 +0100 Subject: [PATCH 37/39] Add build_date, build_sha, build_snapshot to docs --- docs/static/spec/openapi/logstash-api.yaml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/static/spec/openapi/logstash-api.yaml b/docs/static/spec/openapi/logstash-api.yaml index fd9555f9e9d..2984ccb6be1 100644 --- a/docs/static/spec/openapi/logstash-api.yaml +++ b/docs/static/spec/openapi/logstash-api.yaml @@ -112,6 +112,12 @@ paths: type: boolean status: type: string + build_date: + type: string + build_sha: + type: string + build_snapshot: + type: boolean pipeline: type: object properties: @@ -123,13 +129,16 @@ paths: type: integer example: host: "logstash-pipelines.example.com" - version: "9.3.0" + version: "9.2.1" http_address: "127.0.0.1:9600" id: "58df6f7c-eb5c-5d42-bc20-c7b22779aa12" name: "logstash-pipelines" ephermeral_id: "59df6f6c-eb5c-4d42-bc20-c7b44779aa12" - snapshot: false + snapshot: true status: "green" + build_date: "2025-11-20T01:18:55+00:00" + build_sha: "ff3e87d66f10c05a74d0cef7bc2911d60cee1ebc" + build_snapshot: true pipeline: workers: 10 batch_size: 125 From 31ccb9fdc2b109357fc928ed5841cc40819f5292 Mon Sep 17 00:00:00 2001 From: Emily Stolfo Date: Tue, 2 Dec 2025 11:27:51 +0100 Subject: [PATCH 38/39] Update docs to say timeout status code is 408 --- docs/static/spec/openapi/logstash-api.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/static/spec/openapi/logstash-api.yaml b/docs/static/spec/openapi/logstash-api.yaml index 2984ccb6be1..102577451b8 100644 --- a/docs/static/spec/openapi/logstash-api.yaml +++ b/docs/static/spec/openapi/logstash-api.yaml @@ -86,7 +86,7 @@ paths: required: false schema: type: string - description: Period to wait for the status to reach the requested target status. Must be an integer with units, for example `3s`. If the target status is not reached before the timeout expires, the request returns status code 503. + description: Period to wait for the status to reach the requested target status. Must be an integer with units, for example `3s`. If the target status is not reached before the timeout expires, the request returns status code 408. - $ref: "#/components/parameters/pretty" responses: '200': From b3c73eb8cddd286fbdce7b97196bd0efbf5a1068 Mon Sep 17 00:00:00 2001 From: lcawl Date: Wed, 3 Dec 2025 12:07:44 -0800 Subject: [PATCH 39/39] Fix linting errors in logstash-api.yaml --- docs/static/spec/openapi/logstash-api.yaml | 108 +++++++++++---------- 1 file changed, 55 insertions(+), 53 deletions(-) diff --git a/docs/static/spec/openapi/logstash-api.yaml b/docs/static/spec/openapi/logstash-api.yaml index 102577451b8..977567bd912 100644 --- a/docs/static/spec/openapi/logstash-api.yaml +++ b/docs/static/spec/openapi/logstash-api.yaml @@ -82,11 +82,11 @@ paths: type: string description: One of green, yellow or red. Will wait (until the timeout provided) until the status of the service changes to the one provided or better, i.e. green > yellow > red. The query param, timeout, is required when this query param is provided. - name: timeout - in: query - required: false - schema: - type: string - description: Period to wait for the status to reach the requested target status. Must be an integer with units, for example `3s`. If the target status is not reached before the timeout expires, the request returns status code 408. + in: query + required: false + schema: + type: string + description: Period to wait for the status to reach the requested target status. Must be an integer with units, for example `3s`. If the target status is not reached before the timeout expires, the request returns status code 408. - $ref: "#/components/parameters/pretty" responses: '200': @@ -94,55 +94,57 @@ paths: content: application/json: schema: - - type: object - properties: - host: - type: string - version: - type: string - http_address: - type: string - id: - type: string - name: - type: string - ephemeral_id: - type: string - snapshot: - type: boolean - status: - type: string - build_date: - type: string - build_sha: - type: string - build_snapshot: - type: boolean + type: object + properties: + host: + type: string + version: + type: string + http_address: + type: string + id: + type: string + name: + type: string + ephemeral_id: + type: string + snapshot: + type: boolean + status: + type: string + build_date: + type: string + build_sha: + type: string + build_snapshot: + type: boolean + pipeline: + type: object + properties: + workers: + type: integer + batch_size: + type: integer + batch_delay: + type: integer + examples: + basicMetadataExample1: + value: + host: "logstash-pipelines.example.com" + version: "9.2.1" + http_address: "127.0.0.1:9600" + id: "58df6f7c-eb5c-5d42-bc20-c7b22779aa12" + name: "logstash-pipelines" + ephemeral_id: "59df6f6c-eb5c-4d42-bc20-c7b44779aa12" + snapshot: true + status: "green" + build_date: "2025-11-20T01:18:55+00:00" + build_sha: "ff3e87d66f10c05a74d0cef7bc2911d60cee1ebc" + build_snapshot: true pipeline: - type: object - properties: - workers: - type: integer - batch_size: - type: integer - batch_delay: - type: integer - example: - host: "logstash-pipelines.example.com" - version: "9.2.1" - http_address: "127.0.0.1:9600" - id: "58df6f7c-eb5c-5d42-bc20-c7b22779aa12" - name: "logstash-pipelines" - ephermeral_id: "59df6f6c-eb5c-4d42-bc20-c7b44779aa12" - snapshot: true - status: "green" - build_date: "2025-11-20T01:18:55+00:00" - build_sha: "ff3e87d66f10c05a74d0cef7bc2911d60cee1ebc" - build_snapshot: true - pipeline: - workers: 10 - batch_size: 125 - batch_delay: 50 + workers: 10 + batch_size: 125 + batch_delay: 50 x-metaTags: - content: Logstash name: product_name