diff --git a/java/server/src/org/openqa/grid/web/Hub.java b/java/server/src/org/openqa/grid/web/Hub.java index 98c3d701ee75e..9983f47c03574 100644 --- a/java/server/src/org/openqa/grid/web/Hub.java +++ b/java/server/src/org/openqa/grid/web/Hub.java @@ -27,6 +27,7 @@ import org.openqa.grid.web.servlet.HubStatusServlet; import org.openqa.grid.web.servlet.HubW3CStatusServlet; import org.openqa.grid.web.servlet.LifecycleServlet; +import org.openqa.grid.web.servlet.NodeSessionsServlet; import org.openqa.grid.web.servlet.ProxyStatusServlet; import org.openqa.grid.web.servlet.RegistrationServlet; import org.openqa.grid.web.servlet.ResourceServlet; @@ -130,6 +131,7 @@ private void addDefaultServlets(ServletContextHandler handler) { handler.addServlet(DriverServlet.class.getName(), "/selenium-server/driver/*"); handler.addServlet(ProxyStatusServlet.class.getName(), "/grid/api/proxy/*"); + handler.addServlet(NodeSessionsServlet.class.getName(), "/grid/api/sessions/*"); handler.addServlet(HubStatusServlet.class.getName(), "/grid/api/hub/*"); diff --git a/java/server/src/org/openqa/grid/web/servlet/NodeSessionsServlet.java b/java/server/src/org/openqa/grid/web/servlet/NodeSessionsServlet.java new file mode 100644 index 0000000000000..4089271e97186 --- /dev/null +++ b/java/server/src/org/openqa/grid/web/servlet/NodeSessionsServlet.java @@ -0,0 +1,119 @@ +// 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.grid.web.servlet; + +import org.openqa.grid.common.exception.GridException; +import org.openqa.grid.internal.GridRegistry; +import org.openqa.grid.internal.RemoteProxy; +import org.openqa.selenium.json.Json; +import org.openqa.selenium.json.JsonException; +import org.openqa.selenium.json.JsonInput; +import org.openqa.selenium.json.JsonOutput; +import org.openqa.selenium.remote.http.HttpMethod; +import org.openqa.selenium.remote.http.HttpRequest; +import org.openqa.selenium.remote.http.HttpResponse; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.Writer; +import java.net.URL; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * API to query all the sessions that are currently running in the hub. + */ +public class NodeSessionsServlet extends RegistryBasedServlet { + + private final Json json = new Json(); + + public NodeSessionsServlet() { + this(null); + } + + public NodeSessionsServlet(GridRegistry registry) { + super(registry); + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse rsp) throws IOException { + process(rsp); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { + process(resp); + } + + protected void process(HttpServletResponse response) throws IOException { + response.setContentType("application/json"); + response.setCharacterEncoding("UTF-8"); + response.setStatus(200); + Map proxies = new TreeMap<>(); + try (Writer writer = response.getWriter(); + JsonOutput out = json.newOutput(writer)) { + proxies.put("success", true); + proxies.put("proxies", extractSessionsFromAllProxies()); + out.write(proxies); + } + } + + private List> extractSessionsFromAllProxies() { + List> results = new LinkedList<>(); + List proxies = getRegistry().getAllProxies().getBusyProxies(); + for (RemoteProxy proxy : proxies) { + Map res = new TreeMap<>(); + res.put("id", proxy.getId()); + res.put("remoteHost", proxy.getRemoteHost().toString()); + Map sessionsInProxy = new TreeMap<>(extractSessionInfo(proxy)); + if (sessionsInProxy.isEmpty()) { + sessionsInProxy.put("success", false); + } + res.put("sessions", sessionsInProxy); + results.add(res); + } + return results; + } + + private Map extractSessionInfo(RemoteProxy proxy) { + try { + URL url = proxy.getRemoteHost(); + HttpRequest req = new HttpRequest(HttpMethod.GET, "/wd/hub/sessions"); + HttpResponse rsp = proxy.getHttpClient(url).execute(req); + + try (InputStream in = new ByteArrayInputStream(rsp.getContent()); + Reader reader = new InputStreamReader(in, rsp.getContentEncoding()); + JsonInput jsonReader = json.newInput(reader)){ + return jsonReader.read(Json.MAP_TYPE); + } catch (JsonException e) { + // Nothing to do --- poorly formed payload. + } + } catch (IOException e) { + throw new GridException(e.getMessage()); + } + return new TreeMap<>(); + } +} diff --git a/java/server/test/org/openqa/grid/e2e/GridE2ETests.java b/java/server/test/org/openqa/grid/e2e/GridE2ETests.java index 88b53219218ac..b67e0ea08e626 100644 --- a/java/server/test/org/openqa/grid/e2e/GridE2ETests.java +++ b/java/server/test/org/openqa/grid/e2e/GridE2ETests.java @@ -22,6 +22,7 @@ import org.openqa.grid.e2e.misc.ConfigInheritanceTest; import org.openqa.grid.e2e.misc.Grid1HeartbeatTest; import org.openqa.grid.e2e.misc.GridDistributionTest; +import org.openqa.grid.e2e.misc.GridListActiveSessionsTest; import org.openqa.grid.e2e.misc.GridSerializeExceptionTest; import org.openqa.grid.e2e.misc.GridViaCommandLineTest; import org.openqa.grid.e2e.misc.HubRestart; @@ -58,6 +59,7 @@ NodeTimeOutTest.class, SmokeTest.class, // slow WebDriverPriorityDemo.class, + GridListActiveSessionsTest.class }) public class GridE2ETests { } diff --git a/java/server/test/org/openqa/grid/e2e/misc/GridListActiveSessionsTest.java b/java/server/test/org/openqa/grid/e2e/misc/GridListActiveSessionsTest.java new file mode 100644 index 0000000000000..2b6db3300b8ed --- /dev/null +++ b/java/server/test/org/openqa/grid/e2e/misc/GridListActiveSessionsTest.java @@ -0,0 +1,140 @@ +// 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.grid.e2e.misc; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.openqa.grid.e2e.node.SmokeTest; +import org.openqa.grid.web.Hub; +import org.openqa.selenium.chrome.ChromeOptions; +import org.openqa.selenium.json.Json; +import org.openqa.selenium.json.JsonInput; +import org.openqa.selenium.remote.DesiredCapabilities; +import org.openqa.selenium.remote.RemoteWebDriver; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +@SuppressWarnings("unchecked") +public class GridListActiveSessionsTest { + + @Test + public void testNoSessions() throws Exception { + runTest(1, 0, map -> { + List> proxies = extractProxies(map); + assertTrue("No sessions data should be found", proxies.isEmpty()); + }); + } + + @Test + public void testOneSessionWithSingleProxy() throws Exception { + runTestForMultipleSessions(1, 1, 1); + } + + @Test + public void testMultipleSessionsWithSingleProxy() throws Exception { + runTestForMultipleSessions(1, 2, 2); + } + + @Test + public void testMultipleSessionsWithMultipleProxies() throws Exception { + runTestForMultipleSessions(2, 2, 1); + } + + private void runTestForMultipleSessions(int howManyNodes, int howManySessions, + int expectedSessionsPerProxy) throws Exception { + runTest(howManyNodes, + howManySessions, + map -> { + List> proxies = extractProxies(map); + assertEquals("Number of proxies", howManyNodes, proxies.size()); + for (Map proxy : proxies) { + Map sessions = (Map) proxy.get("sessions"); + List values = (List) sessions.get("value"); + assertEquals("Sessions per proxy", expectedSessionsPerProxy, + values.size()); + } + }); + } + + private void runTest(int nodesCount, int howMany, + Consumer> assertions) throws Exception { + Hub hub = null; + List drivers = new ArrayList<>(); + try { + hub = SmokeTest.prepareTestGrid(DesiredCapabilities.chrome(), nodesCount); + drivers = createSession(howMany, hub); + Map sessions = getSessions(hub); + assertions.accept(sessions); + } finally { + drivers.forEach(RemoteWebDriver::quit); + if (hub != null) { + hub.stop(); + } + } + } + + private List createSession(int howMany, Hub hub) { + List drivers = new ArrayList<>(); + if (howMany == 0) { + return drivers; + } + URL url; + try { + url = new URL("http://" + hub.getUrl().getHost() + ":" + + hub.getUrl().getPort() + "/wd/hub"); + } catch (MalformedURLException e) { + return new ArrayList<>(); + } + for (int i = 0; i < howMany; i++) { + drivers.add(new RemoteWebDriver(url, new ChromeOptions())); + } + return drivers; + + } + + private Map getSessions(Hub hub) throws IOException { + String url = String.format("http://%s:%d/grid/api/sessions", hub.getUrl().getHost(), + hub.getUrl().getPort()); + URL grid = new URL(url); + URLConnection connection = grid.openConnection(); + try (InputStream in = connection.getInputStream(); + JsonInput input = new Json().newInput(new BufferedReader(new InputStreamReader(in)))) { + return input.read(Json.MAP_TYPE); + + } + } + + private List> extractProxies(Map map) { + boolean success = Boolean.parseBoolean(map.get("success").toString()); + assertTrue("Status should be true", success); + return (List>) map.get("proxies"); + } + +} diff --git a/java/server/test/org/openqa/grid/e2e/node/SmokeTest.java b/java/server/test/org/openqa/grid/e2e/node/SmokeTest.java index 8502e25415348..5916756361eb1 100644 --- a/java/server/test/org/openqa/grid/e2e/node/SmokeTest.java +++ b/java/server/test/org/openqa/grid/e2e/node/SmokeTest.java @@ -40,24 +40,7 @@ public class SmokeTest { @Before public void prepare() throws Exception { - - hub = GridTestHelper.getHub(); - - SelfRegisteringRemote remote = - GridTestHelper.getRemoteWithoutCapabilities(hub.getUrl(), GridRole.NODE); - remote.addBrowser(GridTestHelper.getDefaultBrowserCapability(), 1); - - DesiredCapabilities capabilities = DesiredCapabilities.htmlUnit(); - capabilities.setCapability(RegistrationRequest.SELENIUM_PROTOCOL,SeleniumProtocol.WebDriver); - - remote.addBrowser(capabilities, 1); - - remote.setRemoteServer(new SeleniumServer(remote.getConfiguration())); - remote.startRemoteServer(); - - remote.getConfiguration().timeout = -1; - remote.sendRegistrationRequest(); - RegistryTestHelper.waitForNode(hub.getRegistry(), 1); + hub = prepareTestGrid(); } @Test @@ -79,4 +62,31 @@ public void browserOnWebDriver() { public void stop() { hub.stop(); } + + public static Hub prepareTestGrid() throws Exception { + return prepareTestGrid(DesiredCapabilities.htmlUnit(),1); + } + + public static Hub prepareTestGrid(DesiredCapabilities caps, int nodesCount) throws Exception { + Hub hub = GridTestHelper.getHub(); + for (int i = 1; i <= nodesCount; i++) { + + SelfRegisteringRemote remote = + GridTestHelper.getRemoteWithoutCapabilities(hub.getUrl(), GridRole.NODE); + remote.addBrowser(caps, 1); + + DesiredCapabilities capabilities = new DesiredCapabilities(caps); + caps.setCapability(RegistrationRequest.SELENIUM_PROTOCOL, SeleniumProtocol.WebDriver); + + remote.addBrowser(capabilities, 1); + + remote.setRemoteServer(new SeleniumServer(remote.getConfiguration())); + remote.startRemoteServer(); + + remote.getConfiguration().timeout = -1; + remote.sendRegistrationRequest(); + RegistryTestHelper.waitForNode(hub.getRegistry(), i); + } + return hub; + } }