/* * (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/) * * Licensed 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 io.openvidu.test; import static org.kurento.commons.PropertiesManager.getProperty; import static org.kurento.test.config.TestConfiguration.TEST_HOST_PROPERTY; import static org.kurento.test.config.TestConfiguration.TEST_PORT_PROPERTY; import static org.kurento.test.config.TestConfiguration.TEST_PROTOCOL_DEFAULT; import static org.kurento.test.config.TestConfiguration.TEST_PROTOCOL_PROPERTY; import static org.kurento.test.config.TestConfiguration.TEST_PUBLIC_IP_DEFAULT; import static org.kurento.test.config.TestConfiguration.TEST_PUBLIC_IP_PROPERTY; import static org.kurento.test.config.TestConfiguration.TEST_PUBLIC_PORT_PROPERTY; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.junit.Assert; import org.kurento.client.KurentoClient; import org.kurento.commons.PropertiesManager; import org.kurento.commons.exception.KurentoException; import org.kurento.test.base.KurentoTest; import org.kurento.test.browser.WebPage; import org.kurento.test.config.BrowserScope; import org.kurento.test.config.Protocol; import org.kurento.test.docker.Docker; import org.kurento.test.services.FakeKmsService; import org.kurento.test.services.KmsService; import org.kurento.test.services.Service; import org.kurento.test.services.WebServerService; import org.kurento.test.utils.Shell; import io.openvidu.test.fake.util.FakeSession; /** * Base for Kurento Room tests with browsers and fake clients. * * @author Radu Tom Vlad (rvlad@naevatec.com) * @since 6.2.1 */ public abstract class RoomClientFakeTest extends OpenViduClientBrowserTest { public static @Service(3) KmsService fakeKms = new FakeKmsService(); public static String testFiles = KurentoTest.getTestFilesDiskPath(); // overwritten if running in Docker public static String serverAddress = PropertiesManager.getProperty(TEST_HOST_PROPERTY, getProperty(TEST_PUBLIC_IP_PROPERTY, TEST_PUBLIC_IP_DEFAULT)); public static int serverPort = getProperty(TEST_PORT_PROPERTY, getProperty(TEST_PUBLIC_PORT_PROPERTY, WebServerService.getAppHttpsPort())); public static Protocol protocol = Protocol.valueOf(getProperty(TEST_PROTOCOL_PROPERTY, TEST_PROTOCOL_DEFAULT).toUpperCase()); // might get overwritten by custom room apps public static String appWsPath = "/openvidu"; public long JOIN_ROOM_TOTAL_TIMEOUT_IN_SECONDS = 30; public long ACTIVE_LIVE_TOTAL_TIMEOUT_IN_SECONDS = 180; public long ROOM_ACTIVITY_IN_SECONDS = 30; public long LEAVE_ROOM_TOTAL_TIMEOUT_IN_SECONDS = 10; public URI appWsUrl; public URI appBaseUrl; public URI appUrl; public ConcurrentMap sessions = new ConcurrentHashMap(); public KurentoClient fakeKurentoClient; public RoomClientFakeTest() { super(); } @Override public void setupBrowserTest() throws InterruptedException { super.setupBrowserTest(); calculateUrl(); String wsProtocol = "ws"; if (protocol.equals(Protocol.HTTPS)) { wsProtocol = "wss"; } String hostName = appUrl.getHost(); try { appWsUrl = new URI(wsProtocol, null, hostName, serverPort, appWsPath, null, null); } catch (URISyntaxException e) { throw new KurentoException("Exception generating WS URI from " + wsProtocol + ", " + hostName + ", server port " + serverPort + " and WS path " + appWsPath); } log.debug("Protocol: {}, Hostname: {}, Port: {}, Path: {}", wsProtocol, hostName, serverPort, appWsPath); fakeKurentoClient = fakeKms.getKurentoClient(); Assert.assertNotNull("Fake Kurento Client is null", fakeKurentoClient); } @Override public void teardownBrowserTest() { for (FakeSession s : sessions.values()) { try { s.close(); } catch (IOException e) { log.warn("Error closing session", e); } } fakeKms.closeKurentoClient(); super.teardownBrowserTest(); } public void calculateUrl() { if (appUrl == null) { String hostName = serverAddress; if (BrowserScope.DOCKER.equals(testScope)) { Docker docker = Docker.getSingleton(); if (docker.isRunningInContainer()) { hostName = docker.getContainerIpAddress(); } else { hostName = docker.getHostIpForContainers(); } } log.debug("Protocol: {}, Hostname: {}, Port: {}, Web page type: {}", protocol, hostName, serverPort, webPageType); try { appUrl = new URI(protocol.toString(), null, hostName, serverPort, webPageType.toString(), null, null); } catch (URISyntaxException e) { throw new KurentoException("Exception generating URI from " + protocol + ", " + hostName + ", server port " + serverPort + " and webpage type " + webPageType); } try { appBaseUrl = new URI(protocol.toString(), null, hostName, serverPort, null, null, null); } catch (URISyntaxException e) { throw new KurentoException("Exception generating URI from " + protocol + ", " + hostName + ", server port " + serverPort); } } } public FakeSession getSession(String room) { return sessions.get(room); } public FakeSession createSession(String room) { if (sessions.containsKey(room)) { return sessions.get(room); } FakeSession s = new FakeSession(appWsUrl.toString(), room, fakeKurentoClient); FakeSession old = sessions.putIfAbsent(room, s); if (old != null) { return old; } return s; } public void closeSession(String room) { FakeSession session = sessions.get(room); if (session != null) { try { session.close(); } catch (IOException e) { log.warn("Error closing session", e); } } } public FakeSession removeSession(String room) { return sessions.remove(room); } public CountDownLatch parallelJoinFakeUsers(final List relativePaths, final String room, final KurentoClient kurento) { if (relativePaths == null || relativePaths.isEmpty()) { execExceptions.put("parallelJoinFakeUsers-" + room, new Exception( "Null or empty play paths list")); return null; } int userThreads = relativePaths.size(); log.info("Joining room '{}': {} fake users with relative play paths:\n{}", room, userThreads, relativePaths); final CountDownLatch joinLatch = new CountDownLatch(userThreads); parallelTasks(userThreads, USER_FAKE_PREFIX, "parallelJoinFakeUsers-" + room, new Task() { @Override public void exec(int numTask) throws Exception { try { String userName = getFakeKey(numTask); FakeSession s = createSession(room); String fullPlayPath = getPlaySourcePath(userName, relativePaths.get(numTask)); if (kurento == null) { s.newParticipant(userName, fullPlayPath, true, true); } else { s.newParticipant(userName, fullPlayPath, true, true, kurento); } } finally { joinLatch.countDown(); } } }); return joinLatch; } protected CountDownLatch parallelLeaveFakeUsers(final String room, int userThreads) { final CountDownLatch leaveLatch = new CountDownLatch(userThreads); parallelTasks(userThreads, USER_FAKE_PREFIX, "parallelLeaveFakeUsers-" + room, new Task() { @Override public void exec(int numTask) throws Exception { try { getSession(room).getParticipant(getFakeKey(numTask)).leaveRoom(); } finally { leaveLatch.countDown(); } } }); return leaveLatch; } protected CountDownLatch parallelWaitActiveLive(final String room, int userThreads) { final CountDownLatch waitForLatch = new CountDownLatch(userThreads); parallelTasks(userThreads, USER_FAKE_PREFIX, "parallelWaitForActiveLive-" + room, new Task() { @Override public void exec(int numTask) throws Exception { getSession(room).getParticipant(getFakeKey(numTask)).waitForActiveLive(waitForLatch); } }); return waitForLatch; } public void await(CountDownLatch waitLatch, long actionTimeoutInSeconds, String action, int userThreads) { failWithExceptions(); try { if (!waitLatch.await(actionTimeoutInSeconds, TimeUnit.SECONDS)) { execExceptions.put(action, new Exception("Timeout waiting for '" + action + "' of " + userThreads + " tasks (max " + actionTimeoutInSeconds + "s)")); } else { log.debug("Finished waiting for {}", action); } } catch (InterruptedException e) { log.warn("Interrupted when waiting for {} of {} tasks (max {}s)", action, userThreads, actionTimeoutInSeconds, e); } } public void idlePeriod() { idlePeriod("ACTIVE_LIVE", "LEAVE_ROOM", roomName); } public void idlePeriod(String room) { idlePeriod("ACTIVE_LIVE", "LEAVE_ROOM", room); } public void idlePeriod(String previousAction, String nextAction, String room) { failWithExceptions(); log.info("\n-----------------\n" + "Wait for {} concluded in '{}'" + "\n-----------------\n" + "Waiting {} seconds", previousAction, room, ROOM_ACTIVITY_IN_SECONDS); sleep(ROOM_ACTIVITY_IN_SECONDS); log.info("\n-----------------\n" + "{} in '{}'" + "\n-----------------\n", nextAction, room); } public static String getPlaySourcePath(String userName, String relativePath) throws Exception { return getPlaySourcePath(userName, relativePath, testFiles); } public static String getPlaySourcePath(String userName, String relativePath, String basePath) throws Exception { if (relativePath == null) { throw new Exception("Null play path for user " + userName); } if (!basePath.startsWith("http://") && !basePath.startsWith("https://") && !basePath.startsWith("file://")) { basePath = "file://" + basePath; } URI playerUri = null; try { playerUri = new URI(basePath + relativePath); } catch (URISyntaxException e) { throw new Exception("Unable to construct player URI for user " + userName + " from base path " + basePath + " and file " + relativePath); } String fullPlayPath = playerUri.toString(); log.debug("Fake user '{}': using play URI {}", userName, fullPlayPath); return fullPlayPath; } // use fake.kms.ws.uri instead @Deprecated public static String getFreePort(String wsUri) { if (BrowserScope.DOCKER.equals(testScope)) { log.info("Test is dockerized, returning the same WS for the KMS: {}", wsUri); return wsUri; } URI url; try { url = new URI(wsUri); } catch (URISyntaxException e) { log.warn("WebSocket URI {} is malformed: " + e.getMessage(), wsUri); throw new KurentoException("WebSocket URI " + wsUri + " is malformed"); } int c = 0; do { try { c++; int newPort = url.getPort() + c * c * c; url = new URI(url.getScheme(), null, url.getHost(), newPort, url.getPath(), null, null); String updatedWsUri = url.toString(); log.debug("try#{} Is port available for running (fake) KMS using this URI? {}", c, updatedWsUri); String result = Shell.runAndWait("/bin/bash", "-c", "nc -z " + url.getHost() + " " + url.getPort() + "; echo $?"); if (result.trim().equals("0")) { log.warn("Port " + url.getPort() + " is used. Maybe another KMS instance is running in this port"); } else { log.debug("URI is available: {}", updatedWsUri); return updatedWsUri; } } catch (URISyntaxException e) { log.warn("WebSocket URI {} is malformed: " + e.getMessage(), wsUri); } } while (c < 3); log.warn("Giving up, will return the original URI: {}", wsUri); return wsUri; } }