Skip to content

Commit

Permalink
[grid] Getting the Grid status from the Model
Browse files Browse the repository at this point in the history
To get the status we were usually querying via
HTTP all nodes and then rendering that status
update. This could lead to a more precise status
but it will flood the Grid with HTTP requests
everytime the status endpoint is queried (which
some users do to monitor the Grid).

On the other hand, we keep the Node
status updated with a small delta
from the "real time" status. This
commit leverages that update process
and uses the data collected there
to show it to the user as the
overall Grid status.
  • Loading branch information
diemol committed Mar 18, 2021
1 parent 8c4050f commit 159b80e
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 115 deletions.
135 changes: 23 additions & 112 deletions java/server/src/org/openqa/selenium/grid/router/GridStatusHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,61 +18,40 @@
package org.openqa.selenium.grid.router;

import com.google.common.collect.ImmutableMap;

import org.openqa.selenium.grid.data.DistributorStatus;
import org.openqa.selenium.grid.distributor.Distributor;
import org.openqa.selenium.internal.Require;
import org.openqa.selenium.json.Json;
import org.openqa.selenium.remote.http.HttpClient;
import org.openqa.selenium.remote.http.HttpHandler;
import org.openqa.selenium.remote.http.HttpRequest;
import org.openqa.selenium.remote.http.HttpResponse;
import org.openqa.selenium.remote.tracing.AttributeKey;
import org.openqa.selenium.remote.tracing.EventAttribute;
import org.openqa.selenium.remote.tracing.EventAttributeValue;
import org.openqa.selenium.remote.tracing.HttpTracing;
import org.openqa.selenium.remote.tracing.Span;
import org.openqa.selenium.remote.tracing.Status;
import org.openqa.selenium.remote.tracing.Tracer;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeoutException;

import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.stream.Collectors.toList;
import static org.openqa.selenium.json.Json.MAP_TYPE;
import static org.openqa.selenium.remote.http.Contents.asJson;
import static org.openqa.selenium.remote.http.Contents.string;
import static org.openqa.selenium.remote.http.HttpMethod.GET;
import static org.openqa.selenium.remote.tracing.HttpTracing.newSpanAsChildOf;
import static org.openqa.selenium.remote.tracing.Tags.EXCEPTION;
import static org.openqa.selenium.remote.tracing.Tags.HTTP_RESPONSE;
import static org.openqa.selenium.remote.tracing.Tags.HTTP_RESPONSE_EVENT;
import static org.openqa.selenium.remote.tracing.Tags.HTTP_REQUEST;
import static org.openqa.selenium.remote.tracing.Tags.HTTP_REQUEST_EVENT;
import static org.openqa.selenium.remote.tracing.Tags.HTTP_RESPONSE;
import static org.openqa.selenium.remote.tracing.Tags.HTTP_RESPONSE_EVENT;

class GridStatusHandler implements HttpHandler {

private static final ScheduledExecutorService
SCHEDULED_SERVICE =
Executors.newScheduledThreadPool(
1,
r -> {
Thread thread = new Thread(r, "Scheduled grid status executor");
thread.setDaemon(true);
return thread;
});


private static final ExecutorService EXECUTOR_SERVICE = Executors.newCachedThreadPool(
r -> {
Thread thread = new Thread(r, "Grid status executor");
Expand All @@ -81,21 +60,16 @@ class GridStatusHandler implements HttpHandler {
});


private final Json json;
private final Tracer tracer;
private final HttpClient.Factory clientFactory;
private final Distributor distributor;

GridStatusHandler(Json json, Tracer tracer, HttpClient.Factory clientFactory, Distributor distributor) {
this.json = Require.nonNull("JSON encoder", json);
GridStatusHandler(Tracer tracer, Distributor distributor) {
this.tracer = Require.nonNull("Tracer", tracer);
this.clientFactory = Require.nonNull("HTTP client factory", clientFactory);
this.distributor = Require.nonNull("Distributor", distributor);
}

@Override
public HttpResponse execute(HttpRequest req) {
long start = System.currentTimeMillis();

try (Span span = newSpanAsChildOf(tracer, req, "grid.status")) {
Map<String, EventAttributeValue> attributeMap = new HashMap<>();
Expand All @@ -113,7 +87,8 @@ public HttpResponse execute(HttpRequest req) {
span.setStatus(Status.CANCELLED);
EXCEPTION.accept(attributeMap, e);
attributeMap.put(AttributeKey.EXCEPTION_MESSAGE.getKey(),
EventAttribute.setValue("Unable to get distributor status due to execution error or timeout: " + e.getMessage()));
EventAttribute.setValue("Error or timeout while getting Distributor "
+ "status: " + e.getMessage()));
HttpResponse response = new HttpResponse().setContent(asJson(
ImmutableMap.of("value", ImmutableMap.of(
"ready", false,
Expand All @@ -129,7 +104,8 @@ public HttpResponse execute(HttpRequest req) {
span.setStatus(Status.ABORTED);
EXCEPTION.accept(attributeMap, e);
attributeMap.put(AttributeKey.EXCEPTION_MESSAGE.getKey(),
EventAttribute.setValue("Interruption while getting distributor status: " + e.getMessage()));
EventAttribute.setValue("Interruption while getting distributor status: "
+ e.getMessage()));

HttpResponse response = new HttpResponse().setContent(asJson(
ImmutableMap.of("value", ImmutableMap.of(
Expand All @@ -141,82 +117,31 @@ public HttpResponse execute(HttpRequest req) {
span.addEvent(AttributeKey.EXCEPTION_EVENT.getKey(), attributeMap);

Thread.currentThread().interrupt();
return response;
return response;
}

boolean ready = status.hasCapacity();

long remaining = System.currentTimeMillis() + 2000 - start;
List<Future<Map<String, Object>>> nodeResults = status.getNodes().stream()
.map(node -> {
ImmutableMap<String, Object> defaultResponse = ImmutableMap.of(
"id", node.getId(),
"uri", node.getUri(),
"maxSessions", node.getMaxSessionCount(),
"slots", node.getSlots(),
"warning", "Unable to read data from node.");

CompletableFuture<Map<String, Object>> toReturn = new CompletableFuture<>();

Future<?> future = EXECUTOR_SERVICE.submit(
() -> {
try (HttpClient client = clientFactory.createClient(node.getUri().toURL())) {
HttpRequest nodeStatusReq = new HttpRequest(GET, "/se/grid/node/status");
HttpTracing.inject(tracer, span, nodeStatusReq);
HttpResponse res = client.execute(nodeStatusReq);

toReturn.complete(res.getStatus() == 200
? json.toType(string(res), MAP_TYPE)
: defaultResponse);
} catch (IOException e) {
toReturn.complete(defaultResponse);
}
});

SCHEDULED_SERVICE.schedule(
() -> {
if (!toReturn.isDone()) {
toReturn.complete(defaultResponse);
future.cancel(true);
}
},
remaining,
MILLISECONDS);

return toReturn;
})
List<Map<String, Object>> nodeResults = status.getNodes().stream()
.map(node -> new ImmutableMap.Builder<String, Object>()
.put("id", node.getId())
.put("uri", node.getUri())
.put("maxSessions", node.getMaxSessionCount())
.put("osInfo", node.getOsInfo())
.put("heartbeatPeriod", node.heartbeatPeriod().toMillis())
.put("availability", node.getAvailability())
.put("version", node.getVersion())
.put("slots", node.getSlots())
.build())
.collect(toList());

ImmutableMap.Builder<String, Object> value = ImmutableMap.builder();
value.put("ready", ready);
value.put("message", ready ? "Selenium Grid ready." : "Selenium Grid not ready.");
value.put("nodes", nodeResults);

value.put("nodes", nodeResults.stream()
.map(summary -> {
try {
return summary.get();
} catch (ExecutionException e) {
span.setAttribute("error", true);
span.setStatus(Status.NOT_FOUND);
EXCEPTION.accept(attributeMap, e);
attributeMap.put(AttributeKey.EXCEPTION_MESSAGE.getKey(),
EventAttribute.setValue("Unable to get Node information: " + e.getMessage()));
span.addEvent(AttributeKey.EXCEPTION_EVENT.getKey(), attributeMap);
throw wrap(e);
} catch (InterruptedException e) {
span.setAttribute("error", true);
span.setStatus(Status.NOT_FOUND);
EXCEPTION.accept(attributeMap, e);
attributeMap.put(AttributeKey.EXCEPTION_MESSAGE.getKey(),
EventAttribute.setValue("Unable to get Node information: " + e.getMessage()));
span.addEvent(AttributeKey.EXCEPTION_EVENT.getKey(), attributeMap);
Thread.currentThread().interrupt();
throw wrap(e);
}
})
.collect(toList()));

HttpResponse res = new HttpResponse().setContent(asJson(ImmutableMap.of("value", value.build())));
HttpResponse res = new HttpResponse()
.setContent(asJson(ImmutableMap.of("value", value.build())));
HTTP_RESPONSE.accept(span, res);
HTTP_RESPONSE_EVENT.accept(attributeMap, res);
attributeMap.put("grid.status", EventAttribute.setValue(ready));
Expand All @@ -225,18 +150,4 @@ public HttpResponse execute(HttpRequest req) {
return res;
}
}

private RuntimeException wrap(Exception e) {
if (e instanceof InterruptedException) {
Thread.currentThread().interrupt();
return new RuntimeException(e);
}

Throwable cause = e.getCause();
if (cause == null) {
return e instanceof RuntimeException ? (RuntimeException) e : new RuntimeException(e);
}
return cause instanceof RuntimeException ? (RuntimeException) cause
: new RuntimeException(cause);
}
}
5 changes: 2 additions & 3 deletions java/server/src/org/openqa/selenium/grid/router/Router.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@
package org.openqa.selenium.grid.router;

import com.google.common.collect.ImmutableSet;

import org.openqa.selenium.grid.distributor.Distributor;
import org.openqa.selenium.grid.sessionmap.SessionMap;
import org.openqa.selenium.grid.sessionqueue.NewSessionQueuer;
import org.openqa.selenium.internal.Require;
import org.openqa.selenium.json.Json;
import org.openqa.selenium.remote.http.HttpClient;
import org.openqa.selenium.remote.http.HttpRequest;
import org.openqa.selenium.remote.http.HttpResponse;
Expand Down Expand Up @@ -62,8 +62,7 @@ public Router(

routes =
combine(
get("/status")
.to(() -> new GridStatusHandler(new Json(), tracer, clientFactory, distributor)),
get("/status").to(() -> new GridStatusHandler(tracer, distributor)),
sessions.with(new SpanDecorator(tracer, req -> "session_map")),
queuer.with(new SpanDecorator(tracer, req -> "session_queuer")),
distributor.with(new SpanDecorator(tracer, req -> "distributor")),
Expand Down

0 comments on commit 159b80e

Please sign in to comment.