/* * (C) Copyright 2014 Kurento (http://kurento.org/) * * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser General Public License * (LGPL) version 2.1 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl-2.1.html * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * */ package com.kurento.kmf.test.base; import static com.kurento.kmf.common.PropertiesManager.getProperty; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Writer; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.apache.commons.httpclient.HttpStatus; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.HttpClientBuilder; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.experimental.categories.Category; import com.google.common.base.Charsets; import com.google.common.io.CharStreams; import com.kurento.kmf.commons.tests.SystemMediaApiTests; import com.kurento.kmf.media.factory.KmfMediaApiProperties; import com.kurento.kmf.test.Shell; import com.kurento.kmf.test.client.Browser; import com.kurento.kmf.test.services.Node; import com.kurento.kmf.test.services.Randomizer; import com.kurento.kmf.test.services.RemoteHost; import com.kurento.kmf.test.services.SeleniumGridHub; import freemarker.template.Configuration; import freemarker.template.Template; /** * Base for tests using kmf-media-api, Jetty Http Server and Selenium Grid. * * @author Boni Garcia (bgarcia@gsyc.es) * @since 4.2.5 */ @Category(SystemMediaApiTests.class) public class GridBrowserMediaApiTest extends BrowserMediaApiTest { public static final int DEFAULT_HUB_PORT = 4444; private static final int TIMEOUT_NODE = 120; // seconds private static final String LAUNCH_SH = "launch-node.sh"; private SeleniumGridHub seleniumGridHub; private String hubAddress; private int hubPort; private CountDownLatch countDownLatch; public List<Node> nodes; @Before public void startGrid() throws Exception { startHub(); startNodes(); } private void startHub() throws Exception { hubAddress = KmfMediaApiProperties.getThriftKmfAddress().getHost(); hubPort = getProperty("test.hub.port", DEFAULT_HUB_PORT); seleniumGridHub = new SeleniumGridHub(hubAddress, hubPort); seleniumGridHub.start(); } private void startNodes() throws InterruptedException { countDownLatch = new CountDownLatch(nodes.size()); for (final Node n : nodes) { Thread t = new Thread() { public void run() { try { startNode(n); } catch (IOException e) { log.error("Exception starting node {} : {}", n.getAddress(), e.getClass()); } } }; t.start(); } if (!countDownLatch.await(TIMEOUT_NODE, TimeUnit.SECONDS)) { Assert.fail("Timeout waiting nodes (" + TIMEOUT_NODE + " seconds)"); } } private void startNode(Node node) throws IOException { log.info("Launching node {}", node.getAddress()); final String chromeDriverName = "/chromedriver"; final String chromeDriverSource = getPathTestFiles() + "/bin/chromedriver/2.9/linux64" + chromeDriverName; final String seleniumJarName = "/selenium-server-standalone-2.42.2.jar"; final String seleniumJarSource = getPathTestFiles() + "/bin/selenium-server" + seleniumJarName; // OverThere SCP need absolute path, so home path must be known String remoteHome = node.getHome(); final String remoteFolder = remoteHome + "/" + node.REMOTE_FOLDER; final String remoteChromeDriver = remoteFolder + chromeDriverName; final String remoteSeleniumJar = remoteFolder + seleniumJarName; final String remoteScript = node.getTmpFolder() + "/" + LAUNCH_SH; final String remotePort = String.valueOf(node.getRemoteHost() .getFreePort()); if (!node.getRemoteHost().exists(remoteFolder) || node.isOverwrite()) { node.getRemoteHost() .execAndWaitCommand("mkdir", "-p", remoteFolder); } if (!node.getRemoteHost().exists(remoteChromeDriver) || node.isOverwrite()) { node.getRemoteHost().scp(chromeDriverSource, remoteChromeDriver); node.getRemoteHost().execAndWaitCommand("chmod", "+x", remoteChromeDriver); } if (!node.getRemoteHost().exists(remoteSeleniumJar) || node.isOverwrite()) { node.getRemoteHost().scp(seleniumJarSource, remoteSeleniumJar); } // Script is always overwritten createRemoteScript(node, remotePort, remoteScript, remoteFolder, remoteChromeDriver, remoteSeleniumJar, node.getBrowser(), node.getMaxInstances()); // Copy video in remote host if necessary if (node.getVideo() != null) { node.getRemoteHost().scp(node.getVideo(), node.getRemoteVideo()); } // Launch node node.getRemoteHost().execCommand(remoteScript); // Wait to be available for Hub waitForNode(node.getAddress(), remotePort); } private void createRemoteScript(Node node, String remotePort, String remoteScript, String remoteFolder, String remoteChromeDriver, String remoteSeleniumJar, Browser browser, int maxInstances) throws IOException { // Create script for Node Configuration cfg = new Configuration(); Map<String, Object> data = new HashMap<String, Object>(); data.put("remotePort", String.valueOf(remotePort)); data.put("maxInstances", String.valueOf(maxInstances)); data.put("hubIp", hubAddress); data.put("hubPort", String.valueOf(hubPort)); data.put("tmpFolder", node.getTmpFolder()); data.put("remoteChromeDriver", remoteChromeDriver); data.put("remoteSeleniumJar", remoteSeleniumJar); data.put("pidFile", node.REMOTE_PID_FILE); data.put("browser", browser); cfg.setClassForTemplateLoading(GridBrowserMediaApiTest.class, "/templates/"); String tmpScript = node.getTmpFolder() + LAUNCH_SH; try { Template template = cfg.getTemplate(LAUNCH_SH + ".ftl"); Writer writer = new FileWriter(new File(tmpScript)); template.process(data, writer); writer.flush(); writer.close(); } catch (Exception e) { throw new RuntimeException( "Exception while creating file from template", e); } // Copy script to remote node node.getRemoteHost().scp(tmpScript, remoteScript); node.getRemoteHost().execAndWaitCommand("chmod", "+x", remoteScript); Shell.run("rm", tmpScript); } private synchronized void waitForNode(String node, String port) { log.info("Waiting for node {} to be ready...", node); int responseStatusCode = 0; HttpClient client = HttpClientBuilder.create().build(); HttpGet httpGet = new HttpGet("http://" + node + ":" + port + "/wd/hub/static/resource/hub.html"); // Wait for a max of TIMEOUT_NODE seconds long maxSystemTime = System.currentTimeMillis() + TIMEOUT_NODE * 1000; do { try { HttpResponse response = client.execute(httpGet); responseStatusCode = response.getStatusLine().getStatusCode(); } catch (Exception e) { try { Thread.sleep(100); } catch (InterruptedException ie) { } if (System.currentTimeMillis() > maxSystemTime) { log.error("Timeout ({} sec) waiting for node {}", TIMEOUT_NODE, node); } } } while (responseStatusCode != HttpStatus.SC_OK); if (responseStatusCode == HttpStatus.SC_OK) { log.info("Node {} ready (responseStatus {})", node, responseStatusCode); countDownLatch.countDown(); } } protected static List<Node> getRandomNodes(int numNodes, Browser browser) { return getRandomNodes(numNodes, browser, null, null); } protected static List<Node> getRandomNodes(int numNodes, Browser browser, String video, String audio) { List<Node> nodes = new ArrayList<Node>(); InputStream inputStream = GridBrowserMediaApiTest.class .getClassLoader().getResourceAsStream("node-list.txt"); List<String> nodeList = null; try { nodeList = CharStreams.readLines(new InputStreamReader(inputStream, Charsets.UTF_8)); } catch (IOException e) { Assert.fail("Exception reading node-list.txt: " + e.getMessage()); } String nodeCandidate; long maxSystemTime = System.currentTimeMillis() + 2 * TIMEOUT_NODE * 1000; do { nodeCandidate = nodeList.get(Randomizer.getInt(0, nodeList.size())); log.debug("Node candidate {}", nodeCandidate); if (RemoteHost.ping(nodeCandidate)) { RemoteHost remoteHost = new RemoteHost(nodeCandidate, getProperty("test.node.login"), getProperty("test.node.passwd")); try { remoteHost.start(); int xvfb = remoteHost.runAndWaitCommand("xvfb-run"); if (xvfb != 2) { log.debug("Node {} has no Xvfb", nodeCandidate); } else { nodes.add(new Node(nodeCandidate, browser, video, audio)); } } catch (Exception e) { log.debug("Invalid credentials to access node {} ", nodeCandidate); } finally { remoteHost.stop(); } } else { log.debug("Node {} seems to be down", nodeCandidate); } nodeList.remove(nodeCandidate); if (System.currentTimeMillis() > maxSystemTime) { Assert.fail("Timeout (" + 2 * TIMEOUT_NODE + " sec) selecting " + numNodes + " nodes"); } } while (nodes.size() < numNodes); return nodes; } @After public void stopGrid() throws Exception { // Stop Hub seleniumGridHub.stop(); // Stop Nodes for (Node n : nodes) { String remotePid = n.getRemoteHost().execAndWaitCommandNoBr("cat", n.getTmpFolder() + "/" + n.REMOTE_PID_FILE); n.getRemoteHost().execCommand("pkill", "-KILL", "-P", remotePid); n.stopRemoteHost(); } } public void runParallel(List<Node> nodeList, Runnable myFunc) throws InterruptedException, ExecutionException { ExecutorService exec = Executors.newFixedThreadPool(nodes.size()); List<Future<?>> results = new ArrayList<>(); for (int i = 0; i < nodes.size(); i++) { results.add(exec.submit(myFunc)); } for (Future<?> r : results) { r.get(); } } }