Skip to content

Commit

Permalink
[grid] Local session request queuer and queue (SeleniumHQ#8689)
Browse files Browse the repository at this point in the history
* [grid] Add sessionqueue package. Add NewSessionQueuer and Queuer interface and local implementation.

- Add NewSessionRequest and NewSessionRequestResponse events.

* [grid] Add tracing to LocalNewSessionQueue.

* [grid[ Add tracing to NewSessionQueuer handlers (AddToSessionQueue, RemoveFromSessionQueue, AddBackToSessionQueue)

* [grid] Lift Active up to a top-level class

* [json] Increase max depth of recursion for json output

* [grid] Expose slots from NodeStatus

* [grid] Remove duplicate information from the NodeStatus

* [issue-2070] Node drain feature. Still need to kill the process, but that should be all

This is 2 endpoints:
- One on the distributor (node/{nodeId}/drain)
Sets the distributor HOST to DRAINING, which in turn sets the node flag to "draining"

- One on the node (/drain)
Sends a new event (NODE_DRAINING_STARTED) to the Distributor via event bus

As long as the node is draining, it will not accept new sessions, and the Distributor will
filter it out of all new session requests.

Still need to terminate the Node process, but this does (arguably) the most important bit

Co-authored-by: Marcus Merrell <mmerrell@gmail.com>

* [grid] Add RequestId. Add endpoint to clear the queue. Fix NPE in GetNewSessionResponse. Cleanup validate() in NewSessionQueuer.

* [grid] Add unit and end-to-end tests for NewSessionQueuer and LocalNewSessionQueue.

* [grid] Add RemoteNewSessionQueuer. Add NewSessionQueuerServer. Add config files for queuer.

* [grid] Add response to span for AddBackToSessionQueue and RemoveFromSessionQueue. Remove EndToEndTests for queuer.

* Formatting [skip ci]

* 30s timeout is too short [skip ci]

A test can last 1 minute and if many tests are in the
queue, this could easily cause the timeout. Increased
to 300s (5min)

Also moved the method signature to Duration. The old
Grid had tons of problems with time units, milliseconds
in some places, seconds in other ones. Duration helps to
mitigate this.

* Formatting [skip ci]

* Using Duration instead of `int` primitive [skip ci]

* Refining test name and assertions [skip ci]

* Adding `sessionqueue` to grid build deps [skip ci]

Co-authored-by: David Burns <david.burns@theautomatedtester.co.uk>
Co-authored-by: Simon Stewart <simon.m.stewart@gmail.com>
Co-authored-by: Marcus Merrell <mmerrell@gmail.com>
Co-authored-by: Diego Molina <diemol@gmail.com>
Co-authored-by: Diego Molina <diemol@users.noreply.github.com>
  • Loading branch information
6 people committed Sep 28, 2020
1 parent 836bc67 commit b3f9cc7
Show file tree
Hide file tree
Showing 35 changed files with 2,509 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ public enum AttributeKey {
DATABASE_OPERATION ("db.operation"),
DATABASE_USER ("db.user"),
DATABASE_CONNECTION_STRING ("db.connection_string"),
DATABASE_SYSTEM("db.system");
DATABASE_SYSTEM("db.system"),

REQUEST_ID ("request.id");

private final String key;

Expand Down
1 change: 1 addition & 0 deletions java/server/src/org/openqa/selenium/grid/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ java_export(
"//java/server/src/org/openqa/selenium/grid/node/httpd",
"//java/server/src/org/openqa/selenium/grid/router/httpd",
"//java/server/src/org/openqa/selenium/grid/sessionmap/httpd",
"//java/server/src/org/openqa/selenium/grid/sessionqueue/httpd",
],
deps = [
":base-command",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ private StandardGridRoles() {
public static final Role NODE_ROLE = Role.of("grid-node");
public static final Role ROUTER_ROLE = Role.of("grid-router");
public static final Role SESSION_MAP_ROLE = Role.of("grid-session-map");
public static final Role SESSION_QUEUE_ROLE = Role.of("grid-new-session-queue");
public static final Role SESSION_QUEUER_ROLE = Role.of("grid-new-session-queuer");

public static final Set<Role> ALL_ROLES = Collections.unmodifiableSet(
new TreeSet<>(Arrays.asList(DISTRIBUTOR_ROLE, EVENT_BUS_ROLE, NODE_ROLE, ROUTER_ROLE, SESSION_MAP_ROLE)));
new TreeSet<>(
Arrays.asList(DISTRIBUTOR_ROLE, EVENT_BUS_ROLE, NODE_ROLE, ROUTER_ROLE, SESSION_MAP_ROLE, SESSION_QUEUER_ROLE, SESSION_QUEUE_ROLE)));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package org.openqa.selenium.grid.data;

import com.google.common.collect.ImmutableMap;

import org.openqa.selenium.internal.Require;
import org.openqa.selenium.json.JsonInput;

import java.util.Map;

public class NewSessionErrorResponse {

private final String message;
private final RequestId requestId;

public NewSessionErrorResponse(RequestId requestId, String message) {
this.requestId = Require.nonNull("Request Id", requestId);
this.message = Require.nonNull("Message", message);
}

public String getMessage() {
return message;
}

public RequestId getRequestId() {
return requestId;
}

private Map<String, Object> toJson() {
return ImmutableMap.of(
"message", message,
"requestId", requestId);
}

private static NewSessionErrorResponse fromJson(JsonInput input) {
String message = null;
RequestId requestId = null;

input.beginObject();
while (input.hasNext()) {
switch (input.nextName()) {
case "message":
message = input.read(String.class);
break;

case "requestId":
requestId = input.read(RequestId.class);
break;

default:
input.skipValue();
break;
}
}
input.endObject();

return new NewSessionErrorResponse(requestId, message);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package org.openqa.selenium.grid.data;

import org.openqa.selenium.events.Event;
import org.openqa.selenium.events.Type;

import java.util.UUID;

public class NewSessionRejectedEvent extends Event {

public static final Type NEW_SESSION_REJECTED = new Type("new-session-rejected");

public NewSessionRejectedEvent(NewSessionErrorResponse response) {
super(NEW_SESSION_REJECTED, response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.openqa.selenium.grid.data;

import org.openqa.selenium.remote.http.HttpResponse;

import java.util.UUID;
import java.util.concurrent.CountDownLatch;

public class NewSessionRequest {

private final RequestId requestId;
private final CountDownLatch latch;
private HttpResponse sessionResponse;

public NewSessionRequest(RequestId requestId, CountDownLatch latch) {
this.requestId = requestId;
this.latch = latch;
}

public CountDownLatch getLatch() {
return latch;
}

public void setSessionResponse(HttpResponse sessionResponse) {
this.sessionResponse = sessionResponse;
}

public HttpResponse getSessionResponse() {
return sessionResponse;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package org.openqa.selenium.grid.data;

import org.openqa.selenium.events.Event;
import org.openqa.selenium.events.Type;
import org.openqa.selenium.remote.SessionId;

import java.util.UUID;

public class NewSessionRequestEvent extends Event {

public static final Type NEW_SESSION_REQUEST = new Type("new-session-request");

public NewSessionRequestEvent(RequestId requestId) {
super(NEW_SESSION_REQUEST, requestId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package org.openqa.selenium.grid.data;

import com.google.common.collect.ImmutableMap;

import org.openqa.selenium.internal.Require;
import org.openqa.selenium.json.JsonInput;

import java.util.Base64;
import java.util.Map;

public class NewSessionResponse {

private final RequestId requestId;
private final Session session;
private final byte[] downstreamEncodedResponse;

public NewSessionResponse(RequestId requestId, Session session,
byte[] downstreamEncodedResponse) {
this.requestId = Require.nonNull("Request Id", requestId);
this.session = Require.nonNull("Session", session);
this.downstreamEncodedResponse = Require.nonNull
("Downstream encoded response", downstreamEncodedResponse);
}

public RequestId getRequestId() {
return requestId;
}

public Session getSession() {
return session;
}

public byte[] getDownstreamEncodedResponse() {
return downstreamEncodedResponse;
}

private Map<String, Object> toJson() {
return ImmutableMap.of(
"requestId", requestId,
"session", session,
"downstreamEncodedResponse", Base64.getEncoder().encodeToString(downstreamEncodedResponse)
);
}

private static NewSessionResponse fromJson(JsonInput input) {
RequestId requestId = null;
Session session = null;
byte[] downstreamResponse = null;

input.beginObject();
while (input.hasNext()) {
switch (input.nextName()) {
case "requestId":
requestId = input.read(RequestId.class);
break;

case "session":
session = input.read(Session.class);
break;

case "downstreamEncodedResponse":
downstreamResponse = Base64.getDecoder().decode(input.nextString());
break;

default:
input.skipValue();
break;
}
}
input.endObject();

return new NewSessionResponse(requestId, session, downstreamResponse);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package org.openqa.selenium.grid.data;

import org.openqa.selenium.events.Event;
import org.openqa.selenium.events.Type;

import java.util.UUID;

public class NewSessionResponseEvent extends Event {

public static final Type NEW_SESSION_RESPONSE = new Type("new-session-response");

public NewSessionResponseEvent(NewSessionResponse sessionResponse) {
super(NEW_SESSION_RESPONSE, sessionResponse);
}
}
65 changes: 65 additions & 0 deletions java/server/src/org/openqa/selenium/grid/data/RequestId.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package org.openqa.selenium.grid.data;

import org.openqa.selenium.internal.Require;

import java.util.Objects;
import java.util.UUID;

public class RequestId {

private final UUID uuid;

public RequestId(UUID uuid) {
this.uuid = Require.nonNull("Request id", uuid);
}

public UUID toUuid() {
return uuid;
}

@Override
public String toString() {
return uuid.toString();
}

@Override
public boolean equals(Object o) {
if (!(o instanceof RequestId)) {
return false;
}

RequestId that = (RequestId) o;
return Objects.equals(this.uuid, that.uuid);
}

@Override
public int hashCode() {
return Objects.hash(uuid);
}

private Object toJson() {
return uuid;
}

private static RequestId fromJson(UUID id) {
return new RequestId(id);
}

}
Loading

0 comments on commit b3f9cc7

Please sign in to comment.