Skip to content

Commit 1f5ba9c

Browse files
authored
[health] bootstrap HealthObserver from agent to API (#16141)
* [health] bootstrap HealthObserver from agent to API * specs: mocked agent needs health observer * add license headers
1 parent 9e452d2 commit 1f5ba9c

File tree

6 files changed

+208
-2
lines changed

6 files changed

+208
-2
lines changed

logstash-core/lib/logstash/agent.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ class LogStash::Agent
4040
attr_reader :metric, :name, :settings, :dispatcher, :ephemeral_id, :pipeline_bus
4141
attr_accessor :logger
4242

43+
attr_reader :health_observer
44+
4345
# initialize method for LogStash::Agent
4446
# @param params [Hash] potential parameters are:
4547
# :name [String] - identifier for the agent
@@ -51,6 +53,9 @@ def initialize(settings = LogStash::SETTINGS, source_loader = nil)
5153
@auto_reload = setting("config.reload.automatic")
5254
@ephemeral_id = SecureRandom.uuid
5355

56+
java_import("org.logstash.health.HealthObserver")
57+
@health_observer = HealthObserver.new
58+
5459
# Mutex to synchronize in the exclusive method
5560
# Initial usage for the Ruby pipeline initialization which is not thread safe
5661
@webserver_control_lock = Mutex.new

logstash-core/lib/logstash/api/commands/default_metadata.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def all
2828
:id => service.agent.id,
2929
:name => service.agent.name,
3030
:ephemeral_id => service.agent.ephemeral_id,
31-
:status => "green", # This is hard-coded to mirror x-pack behavior
31+
:status => service.agent.health_observer.status,
3232
:snapshot => ::BUILD_INFO["build_snapshot"],
3333
:pipeline => {
3434
:workers => LogStash::SETTINGS.get("pipeline.workers"),

logstash-core/spec/logstash/webserver_spec.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,13 @@ def free_ports(servers)
5555
end
5656

5757
let(:logger) { LogStash::Logging::Logger.new("testing") }
58-
let(:agent) { OpenStruct.new({:webserver => webserver_block, :http_address => "127.0.0.1", :id => "myid", :name => "myname"}) }
58+
let(:agent) { OpenStruct.new({
59+
webserver: webserver_block,
60+
http_address: "127.0.0.1",
61+
id: "myid",
62+
name: "myname",
63+
health_observer: org.logstash.health.HealthObserver.new,
64+
}) }
5965
let(:webserver_block) { OpenStruct.new({}) }
6066

6167
subject(:webserver) { LogStash::WebServer.new(logger, agent, webserver_options) }
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.logstash.health;
20+
21+
import com.google.common.collect.Iterables;
22+
23+
import java.util.EnumSet;
24+
25+
public class HealthObserver {
26+
public final Status getStatus() {
27+
// INTERNAL-ONLY Proof-of-concept to show flow-through to API results
28+
switch (System.getProperty("logstash.apiStatus", "green")) {
29+
case "green": return Status.GREEN;
30+
case "yellow": return Status.YELLOW;
31+
case "red": return Status.RED;
32+
case "random":
33+
final EnumSet<Status> statuses = EnumSet.allOf(Status.class);
34+
return Iterables.get(statuses, new java.util.Random().nextInt(statuses.size()));
35+
default:
36+
return Status.UNKNOWN;
37+
}
38+
}
39+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.logstash.health;
20+
21+
import com.fasterxml.jackson.annotation.JsonValue;
22+
23+
public enum Status {
24+
UNKNOWN,
25+
GREEN,
26+
YELLOW,
27+
RED,
28+
;
29+
30+
private final String externalValue = name().toLowerCase();
31+
32+
@JsonValue
33+
public String externalValue() {
34+
return externalValue;
35+
}
36+
37+
/**
38+
* Combine this status with another status.
39+
* This method is commutative.
40+
* @param status the other status
41+
* @return the more-degraded of the two statuses.
42+
*/
43+
public Status reduce(Status status) {
44+
if (compareTo(status) >= 0) {
45+
return this;
46+
} else {
47+
return status;
48+
}
49+
}
50+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package org.logstash.health;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import org.junit.Test;
5+
import org.junit.experimental.runners.Enclosed;
6+
import org.junit.runner.RunWith;
7+
import org.junit.runners.Parameterized;
8+
import org.junit.runners.Parameterized.Parameter;
9+
import org.junit.runners.Parameterized.Parameters;
10+
11+
import java.util.ArrayList;
12+
import java.util.Collection;
13+
import java.util.Collections;
14+
import java.util.EnumSet;
15+
import java.util.List;
16+
import java.util.stream.Collectors;
17+
18+
import static com.google.common.collect.Collections2.orderedPermutations;
19+
import static org.hamcrest.CoreMatchers.is;
20+
import static org.hamcrest.CoreMatchers.equalTo;
21+
import static org.hamcrest.MatcherAssert.assertThat;
22+
import static org.logstash.health.Status.*;
23+
24+
@RunWith(Enclosed.class)
25+
public class StatusTest {
26+
27+
public static class Tests {
28+
@Test
29+
public void testReduceUnknown() {
30+
assertThat(UNKNOWN.reduce(UNKNOWN), is(UNKNOWN));
31+
assertThat(UNKNOWN.reduce(GREEN), is(GREEN));
32+
assertThat(UNKNOWN.reduce(YELLOW), is(YELLOW));
33+
assertThat(UNKNOWN.reduce(RED), is(RED));
34+
}
35+
36+
@Test
37+
public void testReduceGreen() {
38+
assertThat(GREEN.reduce(UNKNOWN), is(GREEN));
39+
assertThat(GREEN.reduce(GREEN), is(GREEN));
40+
assertThat(GREEN.reduce(YELLOW), is(YELLOW));
41+
assertThat(GREEN.reduce(RED), is(RED));
42+
}
43+
44+
@Test
45+
public void testReduceYellow() {
46+
assertThat(YELLOW.reduce(UNKNOWN), is(YELLOW));
47+
assertThat(YELLOW.reduce(GREEN), is(YELLOW));
48+
assertThat(YELLOW.reduce(YELLOW), is(YELLOW));
49+
assertThat(YELLOW.reduce(RED), is(RED));
50+
}
51+
52+
@Test
53+
public void testReduceRed() {
54+
assertThat(RED.reduce(UNKNOWN), is(RED));
55+
assertThat(RED.reduce(GREEN), is(RED));
56+
assertThat(RED.reduce(YELLOW), is(RED));
57+
assertThat(RED.reduce(RED), is(RED));
58+
}
59+
}
60+
61+
@RunWith(Parameterized.class)
62+
public static class JacksonSerialization {
63+
@Parameters(name = "{0}")
64+
public static Iterable<?> data() {
65+
return EnumSet.allOf(Status.class);
66+
}
67+
68+
@Parameter
69+
public Status status;
70+
71+
private final ObjectMapper mapper = new ObjectMapper();
72+
73+
@Test
74+
public void testSerialization() throws Exception {
75+
assertThat(mapper.writeValueAsString(status), is(equalTo('"' + status.name().toLowerCase() + '"')));
76+
}
77+
}
78+
79+
@RunWith(Parameterized.class)
80+
public static class ReduceCommutativeSpecification {
81+
@Parameters(name = "{0}<=>{1}")
82+
public static Collection<Object[]> data() {
83+
return getCombinations(EnumSet.allOf(Status.class), 2);
84+
}
85+
86+
@Parameter(0)
87+
public Status statusA;
88+
@Parameter(1)
89+
public Status statusB;
90+
91+
@Test
92+
public void testReduceCommutative() {
93+
assertThat(statusA.reduce(statusB), is(statusB.reduce(statusA)));
94+
}
95+
96+
private static <T extends Comparable<T>> List<Object[]> getCombinations(Collection<T> source, int count) {
97+
return orderedPermutations(source).stream()
98+
.map((l) -> l.subList(0, count))
99+
.map(ArrayList::new).peek(Collections::sort)
100+
.collect(Collectors.toSet())
101+
.stream()
102+
.map(List::toArray)
103+
.collect(Collectors.toList());
104+
}
105+
}
106+
}

0 commit comments

Comments
 (0)