Skip to content

Commit dc83bda

Browse files
estolfoyaauielcawl
authored
Support wait_for_status and timeout query params on root endpoint (#18377)
* Support wait_for_status query param on root endpoint * Use Java::OrgLogstashHealth::Status enum values for status constant * Add docs for root api including new query params * Be consistent with return statuses definition in specs * Use org.logstash.health.Status for constant and in tests * Return http status code 503 when timeout and support target status or better * Update docs with note about returning status code 503 * Use Timeout instead while waiting for the status * Only use green, yellow, and red statuses in constant (exclude unknown) * Handle non integer timeout string * Add the loop back in to timeout block * Update tests * Refactor and fix tests * Not necessary to handle status code in app helpers anymore * Rename method to reflect time units * Use shared examples in specs * Small update to comment * Change variables names for clarity * No need to check for nil, the method is called only if the variable is not nil * Fix shared examples name * Update docs/static/spec/openapi/logstash-api.yaml Co-authored-by: Rye Biesemeyer <[email protected]> * Update docs/static/spec/openapi/logstash-api.yaml Co-authored-by: Rye Biesemeyer <[email protected]> * Use deadline instead of Timeout * Be more explicit about class being Float * No need for timeout * Test timeout units in ms * Minor updates to specs * Update comment * Use status code 408 when the request times out * Require timeout along with wait_for_status * Update docs to be clearer about timeout being required with wait_for_status * Move before block to shared examples * Update logstash-core/lib/logstash/api/modules/root.rb Co-authored-by: Rye Biesemeyer <[email protected]> * Limit the sleep time to max timeout * Update logstash-core/lib/logstash/api/modules/root.rb Co-authored-by: Rye Biesemeyer <[email protected]> * Updates to docs * Add build_date, build_sha, build_snapshot to docs * Update docs to say timeout status code is 408 * Fix linting errors in logstash-api.yaml * Use common schema for root response descriptions * Sort tags by displayname * Handle the case when the current status is unknown * Update docs/static/spec/openapi/logstash-api.yaml Co-authored-by: Lisa Cawley <[email protected]> --------- Co-authored-by: Rye Biesemeyer <[email protected]> Co-authored-by: lcawl <[email protected]>
1 parent e0acfe7 commit dc83bda

File tree

5 files changed

+529
-1
lines changed

5 files changed

+529
-1
lines changed

docs/static/spec/openapi/logstash-api.yaml

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ tags:
4444
# externalDocs:
4545
# description:
4646
# url:
47+
- name: root
48+
x-displayName: Metadata
49+
description: |
50+
Build and service metadata, including build info, pipeline info and the service's status.
51+
# externalDocs:
52+
# description:
53+
# url:
4754
- name: node info
4855
x-displayName: Node info
4956
description: |
@@ -66,6 +73,80 @@ tags:
6673
# description:
6774
# url:
6875
paths:
76+
/:
77+
get:
78+
summary: Get build and service metadata
79+
description: |
80+
Shows basic metadata about the running logstash service. This includes build info, pipeline info and the service's status.
81+
operationId: root
82+
tags:
83+
- root
84+
parameters:
85+
- name: wait_for_status
86+
in: query
87+
required: false
88+
schema:
89+
type: string
90+
description: Wait (until the timeout expires) for the service changes to a specific health status (or a better status). A green status is better than yellow and yellow is better than red. The `timeout` query parameter is required when you use this parameter.
91+
- name: timeout
92+
in: query
93+
required: false
94+
schema:
95+
type: string
96+
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.
97+
- $ref: "#/components/parameters/pretty"
98+
responses:
99+
'200':
100+
description: Indicates a successful call
101+
content:
102+
application/json:
103+
schema:
104+
allOf:
105+
- $ref: '#/components/schemas/Common'
106+
- type: object
107+
properties:
108+
build_date:
109+
type: string
110+
description: The timestamp when this Logstash build was created.
111+
build_sha:
112+
type: string
113+
description: The SHA-1 hash of the Logstash build.
114+
build_snapshot:
115+
type: boolean
116+
description: Whether this Logstash build is a snapshot build.
117+
pipeline:
118+
type: object
119+
properties:
120+
workers:
121+
type: integer
122+
description: The number of workers in the pipeline.
123+
batch_size:
124+
type: integer
125+
description: The batch size for the pipeline.
126+
batch_delay:
127+
type: integer
128+
description: The batch delay for the pipeline.
129+
examples:
130+
basicMetadataExample1:
131+
value:
132+
host: "logstash-pipelines.example.com"
133+
version: "9.2.1"
134+
http_address: "127.0.0.1:9600"
135+
id: "58df6f7c-eb5c-5d42-bc20-c7b22779aa12"
136+
name: "logstash-pipelines"
137+
ephemeral_id: "59df6f6c-eb5c-4d42-bc20-c7b44779aa12"
138+
snapshot: true
139+
status: "green"
140+
build_date: "2025-11-20T01:18:55+00:00"
141+
build_sha: "ff3e87d66f10c05a74d0cef7bc2911d60cee1ebc"
142+
build_snapshot: true
143+
pipeline:
144+
workers: 10
145+
batch_size: 125
146+
batch_delay: 50
147+
x-metaTags:
148+
- content: Logstash
149+
name: product_name
69150
/_node/jvm:
70151
get:
71152
summary: Gets node-level JVM info

logstash-core/lib/logstash/api/errors.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,25 @@ def status_code
4040
404
4141
end
4242
end
43+
44+
class BadRequest < ApiError
45+
def initialize(message = nil)
46+
super(message || "Bad Request")
47+
end
48+
49+
def status_code
50+
400
51+
end
52+
end
53+
54+
class RequestTimeout < ApiError
55+
def initialize(message = nil)
56+
super(message || "Request Timeout")
57+
end
58+
59+
def status_code
60+
408
61+
end
62+
end
4363
end
4464
end

logstash-core/lib/logstash/api/modules/root.rb

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,83 @@ module LogStash
1919
module Api
2020
module Modules
2121
class Root < ::LogStash::Api::Modules::Base
22+
23+
HEALTH_STATUS = [
24+
Java::OrgLogstashHealth::Status::GREEN,
25+
Java::OrgLogstashHealth::Status::YELLOW,
26+
Java::OrgLogstashHealth::Status::RED
27+
].map(&:external_value)
28+
29+
INVALID_HEALTH_STATUS_MESSAGE = "Invalid status '%s' provided. The valid statuses are: green, yellow, red."
30+
INVALID_TIMEOUT_MESSAGE = "Invalid timeout '%s' provided."
31+
TIMEOUT_REQUIRED_WITH_STATUS_MESSAGE = "A timeout must be provided along with a status."
32+
TIMED_OUT_WAITING_FOR_STATUS_MESSAGE = "Timed out waiting for status '%s'."
33+
2234
get "/" do
35+
input_status = params[:wait_for_status]
36+
input_timeout = params[:timeout]
37+
38+
if input_status && !(target_status = parse_status(input_status))
39+
return status_error_response(input_status)
40+
end
41+
42+
if input_timeout && !(timeout_s = parse_timeout_s(input_timeout))
43+
return timeout_error_response(input_timeout)
44+
end
45+
46+
if target_status
47+
return timeout_required_with_status_response unless timeout_s
48+
wait_for_status_and_respond(target_status, timeout_s)
49+
else
50+
command = factory.build(:system_basic_info)
51+
respond_with(command.run)
52+
end
53+
end
54+
55+
private
56+
def parse_timeout_s(timeout)
57+
# If we call #to_seconds directly, the value will be rounded. So call to_nanos, then convert
58+
# to a float and divide by 1e9 to get the value in seconds.
59+
LogStash::Util::TimeValue.from_value(timeout).to_nanos.to_f/1_000_000_000
60+
rescue ArgumentError
61+
end
62+
63+
def parse_status(input_status)
64+
target_status = input_status.downcase
65+
target_status if HEALTH_STATUS.include?(target_status)
66+
end
67+
68+
def timeout_error_response(timeout)
69+
respond_with(BadRequest.new(INVALID_TIMEOUT_MESSAGE % [timeout]))
70+
end
71+
72+
def status_error_response(target_status)
73+
respond_with(BadRequest.new(INVALID_HEALTH_STATUS_MESSAGE % [target_status]))
74+
end
75+
76+
def timeout_required_with_status_response
77+
respond_with(BadRequest.new(TIMEOUT_REQUIRED_WITH_STATUS_MESSAGE))
78+
end
79+
80+
def wait_for_status_and_respond(target_status, timeout)
81+
wait_interval = 0.2 # seconds
82+
deadline = Time.now + timeout
83+
84+
loop do
85+
current_status = HEALTH_STATUS.index(agent.health_observer.status.external_value)
86+
# The current status could be "unknown", which isn't in the HEALTH_STATUS array.
87+
break if current_status && current_status <= HEALTH_STATUS.index(target_status)
88+
89+
time_remaining = deadline - Time.now
90+
if time_remaining <= 0
91+
return respond_with(RequestTimeout.new(TIMED_OUT_WAITING_FOR_STATUS_MESSAGE % [target_status]))
92+
end
93+
sleep((time_remaining <= wait_interval) ? time_remaining : wait_interval)
94+
wait_interval = wait_interval * 2
95+
end
96+
2397
command = factory.build(:system_basic_info)
24-
respond_with command.run
98+
respond_with(command.run)
2599
end
26100
end
27101
end

0 commit comments

Comments
 (0)