Skip to content

Commit a7b0226

Browse files
committed
Move logic for creating a session into the LocalDistributor
We originally lifted this logic up to the `Distributor` class when working on a version of the Distributor that stored state in either a database or redis. However, we never implemented those, and there is restructuring work to do. Moving the logic back into the main implementation makes this easier.
1 parent 97379a0 commit a7b0226

File tree

5 files changed

+232
-187
lines changed

5 files changed

+232
-187
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Licensed to the Software Freedom Conservancy (SFC) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The SFC licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.openqa.selenium.grid.distributor;
19+
20+
import org.openqa.selenium.SessionNotCreatedException;
21+
import org.openqa.selenium.grid.data.CreateSessionResponse;
22+
import org.openqa.selenium.grid.data.SessionRequest;
23+
import org.openqa.selenium.internal.Either;
24+
import org.openqa.selenium.internal.Require;
25+
import org.openqa.selenium.remote.http.Contents;
26+
import org.openqa.selenium.remote.http.HttpHandler;
27+
import org.openqa.selenium.remote.http.HttpRequest;
28+
import org.openqa.selenium.remote.http.HttpResponse;
29+
30+
import java.io.UncheckedIOException;
31+
import java.util.Map;
32+
33+
import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR;
34+
import static java.util.Collections.singletonMap;
35+
36+
class CreateSession implements HttpHandler {
37+
38+
private final Distributor distributor;
39+
40+
CreateSession(Distributor distributor) {
41+
this.distributor = Require.nonNull("Distributor", distributor);
42+
}
43+
44+
@Override
45+
public HttpResponse execute(HttpRequest req) throws UncheckedIOException {
46+
SessionRequest request = Contents.fromJson(req, SessionRequest.class);
47+
48+
Either<SessionNotCreatedException, CreateSessionResponse> result = distributor.newSession(request);
49+
50+
HttpResponse res = new HttpResponse();
51+
Map<String, Object> value;
52+
if (result.isLeft()) {
53+
res.setStatus(HTTP_INTERNAL_ERROR);
54+
value = singletonMap("value", result.left());
55+
} else {
56+
value = singletonMap("value", result.right());
57+
}
58+
59+
res.setContent(Contents.asJson(value));
60+
61+
return res;
62+
}
63+
}

java/server/src/org/openqa/selenium/grid/distributor/Distributor.java

Lines changed: 7 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -17,63 +17,42 @@
1717

1818
package org.openqa.selenium.grid.distributor;
1919

20-
import com.google.common.collect.ImmutableMap;
21-
import com.google.common.collect.ImmutableSet;
22-
import org.openqa.selenium.Capabilities;
23-
import org.openqa.selenium.RetrySessionRequestException;
2420
import org.openqa.selenium.SessionNotCreatedException;
25-
import org.openqa.selenium.grid.data.CreateSessionRequest;
2621
import org.openqa.selenium.grid.data.CreateSessionResponse;
2722
import org.openqa.selenium.grid.data.DistributorStatus;
2823
import org.openqa.selenium.grid.data.NodeId;
2924
import org.openqa.selenium.grid.data.NodeStatus;
3025
import org.openqa.selenium.grid.data.Session;
31-
import org.openqa.selenium.grid.data.SlotId;
26+
import org.openqa.selenium.grid.data.SessionRequest;
3227
import org.openqa.selenium.grid.distributor.selector.SlotSelector;
3328
import org.openqa.selenium.grid.node.Node;
3429
import org.openqa.selenium.grid.security.RequiresSecretFilter;
3530
import org.openqa.selenium.grid.security.Secret;
3631
import org.openqa.selenium.grid.sessionmap.SessionMap;
37-
import org.openqa.selenium.grid.data.SessionRequest;
3832
import org.openqa.selenium.internal.Either;
3933
import org.openqa.selenium.internal.Require;
4034
import org.openqa.selenium.json.Json;
41-
import org.openqa.selenium.remote.SessionId;
4235
import org.openqa.selenium.remote.http.HttpClient;
4336
import org.openqa.selenium.remote.http.HttpRequest;
4437
import org.openqa.selenium.remote.http.HttpResponse;
4538
import org.openqa.selenium.remote.http.Routable;
4639
import org.openqa.selenium.remote.http.Route;
47-
import org.openqa.selenium.remote.tracing.AttributeKey;
48-
import org.openqa.selenium.remote.tracing.EventAttribute;
49-
import org.openqa.selenium.remote.tracing.EventAttributeValue;
50-
import org.openqa.selenium.remote.tracing.Span;
5140
import org.openqa.selenium.remote.tracing.SpanDecorator;
52-
import org.openqa.selenium.remote.tracing.Status;
5341
import org.openqa.selenium.remote.tracing.Tracer;
5442
import org.openqa.selenium.status.HasReadyState;
5543

5644
import java.io.UncheckedIOException;
57-
import java.util.HashMap;
58-
import java.util.Iterator;
5945
import java.util.Map;
6046
import java.util.Set;
6147
import java.util.UUID;
62-
import java.util.concurrent.locks.Lock;
6348
import java.util.concurrent.locks.ReadWriteLock;
6449
import java.util.concurrent.locks.ReentrantReadWriteLock;
6550
import java.util.function.Predicate;
6651
import java.util.logging.Logger;
67-
import java.util.stream.Collectors;
6852

69-
import static org.openqa.selenium.remote.RemoteTags.CAPABILITIES;
70-
import static org.openqa.selenium.remote.RemoteTags.CAPABILITIES_EVENT;
71-
import static org.openqa.selenium.remote.RemoteTags.SESSION_ID;
72-
import static org.openqa.selenium.remote.RemoteTags.SESSION_ID_EVENT;
7353
import static org.openqa.selenium.remote.http.Route.delete;
7454
import static org.openqa.selenium.remote.http.Route.get;
7555
import static org.openqa.selenium.remote.http.Route.post;
76-
import static org.openqa.selenium.remote.tracing.Tags.EXCEPTION;
7756

7857
/**
7958
* Responsible for being the central place where the {@link Node}s
@@ -117,20 +96,14 @@ public abstract class Distributor implements HasReadyState, Predicate<HttpReques
11796

11897
private final Route routes;
11998
protected final Tracer tracer;
120-
private final SlotSelector slotSelector;
121-
private final SessionMap sessions;
12299
private final ReadWriteLock lock = new ReentrantReadWriteLock(true);
123100

124101
protected Distributor(
125102
Tracer tracer,
126103
HttpClient.Factory httpClientFactory,
127-
SlotSelector slotSelector,
128-
SessionMap sessions,
129104
Secret registrationSecret) {
130105
this.tracer = Require.nonNull("Tracer", tracer);
131106
Require.nonNull("HTTP client factory", httpClientFactory);
132-
this.slotSelector = Require.nonNull("Slot selector", slotSelector);
133-
this.sessions = Require.nonNull("Session map", sessions);
134107

135108
Require.nonNull("Registration secret", registrationSecret);
136109

@@ -150,132 +123,16 @@ protected Distributor(
150123
.to(params ->
151124
new RemoveNode(this, new NodeId(UUID.fromString(params.get("nodeId")))))
152125
.with(requiresSecret),
153-
get("/se/grid/distributor/status")
126+
post("/se/grid/distributor/session")
127+
.to(() -> new CreateSession(this))
128+
.with(requiresSecret),
129+
get("/se/grid/distributor/status")
154130
.to(() -> new GetDistributorStatus(this))
155131
.with(new SpanDecorator(tracer, req -> "distributor.status")));
156132
}
157133

158-
public Either<SessionNotCreatedException, CreateSessionResponse> newSession(SessionRequest request)
159-
throws SessionNotCreatedException {
160-
Require.nonNull("Requests to process", request);
161-
162-
Span span = tracer.getCurrentContext().createSpan("distributor.create_session_response");
163-
Map<String, EventAttributeValue> attributeMap = new HashMap<>();
164-
try {
165-
attributeMap.put(AttributeKey.LOGGER_CLASS.getKey(),
166-
EventAttribute.setValue(getClass().getName()));
167-
168-
Iterator<Capabilities> iterator = request.getDesiredCapabilities().iterator();
169-
attributeMap.put("request.payload", EventAttribute.setValue(request.getDesiredCapabilities().toString()));
170-
String sessionReceivedMessage = "Session request received by the distributor";
171-
span.addEvent(sessionReceivedMessage, attributeMap);
172-
LOG.info(String.format("%s: \n %s", sessionReceivedMessage, request.getDesiredCapabilities()));
173-
174-
if (!iterator.hasNext()) {
175-
SessionNotCreatedException exception =
176-
new SessionNotCreatedException("No capabilities found in session request payload");
177-
EXCEPTION.accept(attributeMap, exception);
178-
attributeMap.put(AttributeKey.EXCEPTION_MESSAGE.getKey(),
179-
EventAttribute.setValue("Unable to create session. No capabilities found: " +
180-
exception.getMessage()));
181-
span.addEvent(AttributeKey.EXCEPTION_EVENT.getKey(), attributeMap);
182-
return Either.left(exception);
183-
}
184-
185-
Either<SessionNotCreatedException, CreateSessionResponse> selected;
186-
CreateSessionRequest firstRequest = new CreateSessionRequest(
187-
request.getDownstreamDialects(),
188-
iterator.next(),
189-
ImmutableMap.of("span", span));
190-
191-
Lock writeLock = this.lock.writeLock();
192-
writeLock.lock();
193-
try {
194-
Set<NodeStatus> model = ImmutableSet.copyOf(getAvailableNodes());
195-
196-
// Reject new session immediately if no node has the required capabilities
197-
boolean hostsWithCaps = model.stream()
198-
.anyMatch(nodeStatus -> nodeStatus.hasCapability(firstRequest.getDesiredCapabilities()));
199-
200-
if (!hostsWithCaps) {
201-
String errorMessage = String.format(
202-
"No Node supports the required capabilities: %s",
203-
request.getDesiredCapabilities().stream().map(Capabilities::toString)
204-
.collect(Collectors.joining(", ")));
205-
SessionNotCreatedException exception = new SessionNotCreatedException(errorMessage);
206-
span.setAttribute(AttributeKey.ERROR.getKey(), true);
207-
span.setStatus(Status.ABORTED);
208-
209-
EXCEPTION.accept(attributeMap, exception);
210-
attributeMap.put(AttributeKey.EXCEPTION_MESSAGE.getKey(),
211-
EventAttribute.setValue("Unable to create session: " + exception.getMessage()));
212-
span.addEvent(AttributeKey.EXCEPTION_EVENT.getKey(), attributeMap);
213-
return Either.left(exception);
214-
}
215-
216-
// Find a Node that supports the capabilities present in the new session
217-
Set<SlotId> slotIds = slotSelector.selectSlot(firstRequest.getDesiredCapabilities(), model);
218-
if (!slotIds.isEmpty()) {
219-
selected = reserve(slotIds.iterator().next(), firstRequest);
220-
} else {
221-
String errorMessage =
222-
String.format(
223-
"Unable to find provider for session: %s",
224-
request.getDesiredCapabilities().stream().map(Capabilities::toString)
225-
.collect(Collectors.joining(", ")));
226-
SessionNotCreatedException exception = new RetrySessionRequestException(errorMessage);
227-
selected = Either.left(exception);
228-
}
229-
} finally {
230-
writeLock.unlock();
231-
}
232-
233-
if (selected.isRight()) {
234-
CreateSessionResponse sessionResponse = selected.right();
235-
236-
sessions.add(sessionResponse.getSession());
237-
SessionId sessionId = sessionResponse.getSession().getId();
238-
Capabilities caps = sessionResponse.getSession().getCapabilities();
239-
String sessionUri = sessionResponse.getSession().getUri().toString();
240-
SESSION_ID.accept(span, sessionId);
241-
CAPABILITIES.accept(span, caps);
242-
SESSION_ID_EVENT.accept(attributeMap, sessionId);
243-
CAPABILITIES_EVENT.accept(attributeMap, caps);
244-
span.setAttribute(AttributeKey.SESSION_URI.getKey(), sessionUri);
245-
attributeMap.put(AttributeKey.SESSION_URI.getKey(), EventAttribute.setValue(sessionUri));
246-
247-
String sessionCreatedMessage = "Session created by the distributor";
248-
span.addEvent(sessionCreatedMessage, attributeMap);
249-
LOG.info(String.format("%s. Id: %s, Caps: %s", sessionCreatedMessage, sessionId, caps));
250-
return Either.right(sessionResponse);
251-
252-
} else {
253-
return selected;
254-
}
255-
} catch (SessionNotCreatedException e) {
256-
span.setAttribute(AttributeKey.ERROR.getKey(), true);
257-
span.setStatus(Status.ABORTED);
258-
259-
EXCEPTION.accept(attributeMap, e);
260-
attributeMap.put(AttributeKey.EXCEPTION_MESSAGE.getKey(),
261-
EventAttribute.setValue("Unable to create session: " + e.getMessage()));
262-
span.addEvent(AttributeKey.EXCEPTION_EVENT.getKey(), attributeMap);
263-
264-
return Either.left(e);
265-
} catch (UncheckedIOException e) {
266-
span.setAttribute(AttributeKey.ERROR.getKey(), true);
267-
span.setStatus(Status.UNKNOWN);
268-
269-
EXCEPTION.accept(attributeMap, e);
270-
attributeMap.put(AttributeKey.EXCEPTION_MESSAGE.getKey(),
271-
EventAttribute.setValue("Unknown error in LocalDistributor while creating session: " + e.getMessage()));
272-
span.addEvent(AttributeKey.EXCEPTION_EVENT.getKey(), attributeMap);
273-
274-
return Either.left(new SessionNotCreatedException(e.getMessage(), e));
275-
} finally {
276-
span.close();
277-
}
278-
}
134+
public abstract Either<SessionNotCreatedException, CreateSessionResponse> newSession(SessionRequest request)
135+
throws SessionNotCreatedException;
279136

280137
public abstract Distributor add(Node node);
281138

@@ -285,12 +142,6 @@ public Either<SessionNotCreatedException, CreateSessionResponse> newSession(Sess
285142

286143
public abstract DistributorStatus getStatus();
287144

288-
protected abstract Set<NodeStatus> getAvailableNodes();
289-
290-
protected abstract Either<SessionNotCreatedException, CreateSessionResponse> reserve(
291-
SlotId slot,
292-
CreateSessionRequest request);
293-
294145
@Override
295146
public boolean test(HttpRequest httpRequest) {
296147
return matches(httpRequest);

0 commit comments

Comments
 (0)