openvidu-test-e2e: EndReason test

pull/780/head
pabloFuente 2023-02-08 09:49:11 +01:00
parent 9cdca285a2
commit c670bee1aa
11 changed files with 504 additions and 33 deletions

View File

@ -66,10 +66,9 @@ public enum EndReason {
/** /**
* The last participant left the session, which caused the session to be closed. * The last participant left the session, which caused the session to be closed.
* Applies to events webrtcConnectionDestroyed, participantLeft, * Applies to events recordingStatusChanged, broadcastStopped and
* recordingStatusChanged and sessionDestroyed. Can be triggered from other * sessionDestroyed. Can be triggered from other events with other end reasons
* events with other end reasons (disconnect, forceDisconnectByUser, * (disconnect, forceDisconnectByServer, networkDisconnect)
* forceDisconnectByServer, networkDisconnect)
*/ */
lastParticipantLeft, lastParticipantLeft,
@ -79,10 +78,16 @@ public enum EndReason {
*/ */
recordingStoppedByServer, recordingStoppedByServer,
/**
* The server application called the REST operation to stop a broadcast. Applies
* to event broadcastStopped
*/
broadcastStoppedByServer,
/** /**
* The server application called the REST operation to close a session. Applies * The server application called the REST operation to close a session. Applies
* to events webrtcConnectionDestroyed, participantLeft, recordingStatusChanged * to events webrtcConnectionDestroyed, participantLeft, recordingStatusChanged,
* and sessionDestroyed * broadcastStopped and sessionDestroyed
*/ */
sessionClosedByServer, sessionClosedByServer,
@ -96,8 +101,8 @@ public enum EndReason {
/** /**
* A media server disconnected. This is reserved for Media Nodes being * A media server disconnected. This is reserved for Media Nodes being
* gracefully removed from an OpenVidu Pro cluster. Applies to events * gracefully removed from an OpenVidu Pro cluster. Applies to events
* webrtcConnectionDestroyed, participantLeft, recordingStatusChanged and * webrtcConnectionDestroyed, participantLeft, recordingStatusChanged,
* sessionDestroyed * broadcastStopped and sessionDestroyed
*/ */
mediaServerDisconnect, mediaServerDisconnect,
@ -110,22 +115,24 @@ public enum EndReason {
/** /**
* A node has crashed. For now this means a Media Node has crashed. Applies to * A node has crashed. For now this means a Media Node has crashed. Applies to
* events webrtcConnectionDestroyed, participantLeft, recordingStatusChanged and * events webrtcConnectionDestroyed, participantLeft, recordingStatusChanged,
* sessionDestroyed * broadcastStopped and sessionDestroyed
*/ */
nodeCrashed, nodeCrashed,
/** /**
* OpenVidu Server has gracefully stopped. This is reserved for OpenVidu Pro * OpenVidu Server has gracefully stopped. This is reserved for OpenVidu Pro
* restart operation. Applies to events webrtcConnectionDestroyed, * restart operation. Applies to events webrtcConnectionDestroyed,
* participantLeft, recordingStatusChanged and sessionDestroyed * participantLeft, recordingStatusChanged, broadcastStopped and
* sessionDestroyed
*/ */
openviduServerStopped, openviduServerStopped,
/** /**
* A recording has been stopped automatically * A recording has been stopped automatically
* (https://docs.openvidu.io/en/stable/advanced-features/recording/#automatic-stop-of-recordings). * (https://docs.openvidu.io/en/stable/advanced-features/recording/#automatic-stop-of-recordings).
* Applies to event recordingStatusChanged * Applies to event recordingStatusChanged and sessionDestroyed (in case the
* session was inactive except for the recording)
*/ */
automaticStop automaticStop

View File

@ -192,7 +192,7 @@ public abstract class SessionManager {
public abstract void onUnsubscribeFromSpeechToText(Participant participant, Integer transactionId, public abstract void onUnsubscribeFromSpeechToText(Participant participant, Integer transactionId,
String connectionId); String connectionId);
public abstract void stopBroadcastIfNecessary(Session session); public abstract void stopBroadcastIfNecessary(Session session, EndReason reason);
public void onEcho(String participantPrivateId, Integer requestId) { public void onEcho(String participantPrivateId, Integer requestId) {
sessionEventsHandler.onEcho(participantPrivateId, requestId); sessionEventsHandler.onEcho(participantPrivateId, requestId);

View File

@ -262,6 +262,8 @@ public class KurentoSessionManager extends SessionManager {
// "SessionManager.closeSessionAndEmptyCollections" // "SessionManager.closeSessionAndEmptyCollections"
if (!EndReason.sessionClosedByServer.equals(reason)) { if (!EndReason.sessionClosedByServer.equals(reason)) {
final long autoStopTimeout = this.openviduConfig.getOpenviduRecordingAutostopTimeout();
if (remainingParticipants.isEmpty()) { if (remainingParticipants.isEmpty()) {
if (this.recordingManager.sessionIsBeingRecorded(sessionId)) { if (this.recordingManager.sessionIsBeingRecorded(sessionId)) {
// Start countdown to stop recording. Will be aborted if a Publisher starts // Start countdown to stop recording. Will be aborted if a Publisher starts
@ -269,8 +271,9 @@ public class KurentoSessionManager extends SessionManager {
// recordings it would still remain a recorder participant // recordings it would still remain a recorder participant
log.info( log.info(
"Last participant left. Starting {} seconds countdown for stopping recording of session {}", "Last participant left. Starting {} seconds countdown for stopping recording of session {}",
this.openviduConfig.getOpenviduRecordingAutostopTimeout(), sessionId); autoStopTimeout, sessionId);
recordingManager.initAutomaticRecordingStopThread(session); recordingManager.initAutomaticRecordingStopThread(session,
autoStopTimeout == 0 ? EndReason.lastParticipantLeft : EndReason.automaticStop);
} else { } else {
try { try {
if (session.closingLock.writeLock().tryLock(15, TimeUnit.SECONDS)) { if (session.closingLock.writeLock().tryLock(15, TimeUnit.SECONDS)) {
@ -310,8 +313,10 @@ public class KurentoSessionManager extends SessionManager {
// RECORDER or STT participant is the last one standing. Start countdown // RECORDER or STT participant is the last one standing. Start countdown
log.info( log.info(
"Last participant left. Starting {} seconds countdown for stopping recording of session {}", "Last participant left. Starting {} seconds countdown for stopping recording of session {}",
this.openviduConfig.getOpenviduRecordingAutostopTimeout(), sessionId); autoStopTimeout, sessionId);
recordingManager.initAutomaticRecordingStopThread(session); recordingManager.initAutomaticRecordingStopThread(session,
autoStopTimeout == 0 ? EndReason.lastParticipantLeft
: EndReason.automaticStop);
} else if (session.getSessionProperties().defaultRecordingProperties().outputMode() } else if (session.getSessionProperties().defaultRecordingProperties().outputMode()
.equals(Recording.OutputMode.COMPOSED_QUICK_START)) { .equals(Recording.OutputMode.COMPOSED_QUICK_START)) {
@ -329,7 +334,7 @@ public class KurentoSessionManager extends SessionManager {
&& remainingParticipants.stream().anyMatch(p -> p.isBroadcastParticipant()); && remainingParticipants.stream().anyMatch(p -> p.isBroadcastParticipant());
if (broadcastParticipantLeft) { if (broadcastParticipantLeft) {
this.stopBroadcastIfNecessary(session); this.stopBroadcastIfNecessary(session, reason);
} }
} }
} }
@ -1425,7 +1430,7 @@ public class KurentoSessionManager extends SessionManager {
} }
@Override @Override
public void stopBroadcastIfNecessary(Session session) { public void stopBroadcastIfNecessary(Session session, EndReason reason) {
} }
private io.openvidu.server.recording.Recording getActiveRecordingIfAllowedByParticipantRole( private io.openvidu.server.recording.Recording getActiveRecordingIfAllowedByParticipantRole(

View File

@ -316,7 +316,7 @@ public class RecordingManager {
"No publisher in session {}. Starting {} seconds countdown for stopping recording", "No publisher in session {}. Starting {} seconds countdown for stopping recording",
session.getSessionId(), session.getSessionId(),
this.openviduConfig.getOpenviduRecordingAutostopTimeout()); this.openviduConfig.getOpenviduRecordingAutostopTimeout());
this.initAutomaticRecordingStopThread(session); this.initAutomaticRecordingStopThread(session, EndReason.automaticStop);
} }
return recording; return recording;
} }
@ -676,7 +676,7 @@ public class RecordingManager {
return recordingManagerUtils.getRecordingUrl(recording); return recordingManagerUtils.getRecordingUrl(recording);
} }
public void initAutomaticRecordingStopThread(final Session session) { public void initAutomaticRecordingStopThread(final Session session, final EndReason reason) {
final String recordingId = this.sessionsRecordings.get(session.getSessionId()).getId(); final String recordingId = this.sessionsRecordings.get(session.getSessionId()).getId();
this.automaticRecordingStopThreads.computeIfAbsent(session.getSessionId(), f -> { this.automaticRecordingStopThreads.computeIfAbsent(session.getSessionId(), f -> {
@ -695,12 +695,11 @@ public class RecordingManager {
} }
if (session.getParticipants().size() == 0 if (session.getParticipants().size() == 0
|| session.onlyRecorderAndOrSttAndOrBroadcastParticipant()) { || session.onlyRecorderAndOrSttAndOrBroadcastParticipant()) {
// Close session if there are no participants connected (RECORDER/STT/BROADCAST do // Close session if there are no participants connected (RECORDER/STT/BROADCAST
// not count) and publishing // do not count) and publishing
log.info("Closing session {} after automatic stop of recording {}", log.info("Closing session {} after automatic stop of recording {}",
session.getSessionId(), recordingId); session.getSessionId(), recordingId);
sessionManager.closeSessionAndEmptyCollections(session, EndReason.automaticStop, sessionManager.closeSessionAndEmptyCollections(session, reason, true);
true);
} else { } else {
// There are users connected, but no one is publishing // There are users connected, but no one is publishing
// We don't need the lock if session is not closing // We don't need the lock if session is not closing
@ -709,7 +708,7 @@ public class RecordingManager {
log.info( log.info(
"Automatic stopping recording {}. There are users connected to session {}, but no one is publishing", "Automatic stopping recording {}. There are users connected to session {}, but no one is publishing",
recordingId, session.getSessionId()); recordingId, session.getSessionId());
this.stopRecording(session, recordingId, EndReason.automaticStop); this.stopRecording(session, recordingId, reason);
} }
} finally { } finally {
if (!alreadyUnlocked) { if (!alreadyUnlocked) {
@ -750,8 +749,9 @@ public class RecordingManager {
if (session.getParticipants().size() == 0 if (session.getParticipants().size() == 0
|| session.onlyRecorderAndOrSttAndOrBroadcastParticipant()) { || session.onlyRecorderAndOrSttAndOrBroadcastParticipant()) {
// Close session if there are no participants connected (except for // Close session if there are no participants connected (except for
// RECORDER/STT/BROADCAST). This code will only be executed if recording is manually // RECORDER/STT/BROADCAST). This code will only be executed if recording is
// stopped during the automatic stop timeout, so the session must be also closed // manually stopped during the automatic stop timeout, so the session must be
// also closed
log.info( log.info(
"Ongoing recording of session {} was explicetly stopped within timeout for automatic recording stop. Closing session", "Ongoing recording of session {} was explicetly stopped within timeout for automatic recording stop. Closing session",
session.getSessionId()); session.getSessionId());

View File

@ -217,11 +217,9 @@ public abstract class RecordingService {
protected void uploadRecording(final Recording recording, EndReason reason) { protected void uploadRecording(final Recording recording, EndReason reason) {
recordingUploader.uploadRecording(recording, () -> { recordingUploader.uploadRecording(recording, () -> {
final long timestamp = System.currentTimeMillis(); cdr.recordRecordingStatusChanged(recording, reason, System.currentTimeMillis(), recording.getStatus());
cdr.recordRecordingStatusChanged(recording, reason, timestamp, recording.getStatus());
}, () -> { }, () -> {
final long timestamp = System.currentTimeMillis(); cdr.recordRecordingStatusChanged(recording, reason, System.currentTimeMillis(),
cdr.recordRecordingStatusChanged(recording, reason, timestamp,
io.openvidu.java.client.Recording.Status.failed); io.openvidu.java.client.Recording.Status.failed);
}); });
} }

View File

@ -51,7 +51,7 @@ public class CustomWebhook {
public static CountDownLatch initLatch; public static CountDownLatch initLatch;
public static int accumulatedNumberOfEvents = 0; public static int accumulatedNumberOfEvents = 0;
public final static ConcurrentMap<String, List<JsonObject>> accumulatedEvents = new ConcurrentHashMap<>(); public final static ConcurrentMap<String, List<JsonObject>> accumulatedEvents = new ConcurrentHashMap<>();
static final ConcurrentMap<String, BlockingQueue<JsonObject>> events = new ConcurrentHashMap<>(); public static final ConcurrentMap<String, BlockingQueue<JsonObject>> events = new ConcurrentHashMap<>();
static final BlockingQueue<JsonObject> eventsInOrder = new LinkedBlockingDeque<>(); static final BlockingQueue<JsonObject> eventsInOrder = new LinkedBlockingDeque<>();
public static void main(String[] args, CountDownLatch initLatch) { public static void main(String[] args, CountDownLatch initLatch) {

View File

@ -102,6 +102,11 @@
<artifactId>openvidu-java-client</artifactId> <artifactId>openvidu-java-client</artifactId>
<version>${version.openvidu.java.client}</version> <version>${version.openvidu.java.client}</version>
</dependency> </dependency>
<dependency>
<groupId>com.github.docker-java</groupId>
<artifactId>docker-java</artifactId>
<version>${version.dockerjava}</version>
</dependency>
<dependency> <dependency>
<groupId>org.testcontainers</groupId> <groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId> <artifactId>testcontainers</artifactId>

View File

@ -0,0 +1,61 @@
package io.openvidu.test.e2e;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.async.ResultCallback;
import com.github.dockerjava.api.command.ExecCreateCmdResponse;
import com.github.dockerjava.core.DefaultDockerClientConfig;
import com.github.dockerjava.core.DockerClientBuilder;
import com.github.dockerjava.core.DockerClientConfig;
import com.github.dockerjava.jaxrs.JerseyDockerHttpClient;
import com.github.dockerjava.transport.DockerHttpClient;
public class MediaNodeDockerUtils {
private static final Logger log = LoggerFactory.getLogger(MediaNodeDockerUtils.class);
public static void crashMediaNode(String containerId) {
log.info("Stopping Media Node container");
DockerClient dockerClient = getDockerClient();
dockerClient.removeContainerCmd(containerId).withForce(true).exec();
}
public static String getMediaNodeIp(String containerId) {
DockerClient dockerClient = getDockerClient();
return dockerClient.inspectContainerCmd(containerId).exec().getNetworkSettings().getNetworks().get("bridge")
.getIpAddress();
}
public static void crashMediaServerInsideMediaNode(String containerId) {
log.info("Stopping KMS container inside Media Node");
DockerClient dockerClient = getDockerClient();
ExecCreateCmdResponse execCreateCmdResponse = dockerClient.execCreateCmd(containerId).withAttachStdout(true)
.withAttachStderr(true).withCmd("bash", "-c", "docker rm -f kms").exec();
dockerClient.execStartCmd(execCreateCmdResponse.getId()).exec(new ResultCallback.Adapter<>() {
});
}
public static void stopMediaServerInsideMediaNodeAndRecover(String containerId, int millisStop) {
log.info("Stopping and starting KMS container inside Media Node " + containerId + "waiting " + millisStop
+ " ms");
DockerClient dockerClient = getDockerClient();
ExecCreateCmdResponse execCreateCmdResponse = dockerClient.execCreateCmd(containerId).withAttachStdout(true)
.withAttachStderr(true)
.withCmd("bash", "-c", "docker stop kms && sleep " + (millisStop / 1000) + " && docker start kms")
.exec();
dockerClient.execStartCmd(execCreateCmdResponse.getId()).exec(new ResultCallback.Adapter<>() {
});
}
public static DockerClient getDockerClient() {
DockerClientConfig dockerClientConfig = DefaultDockerClientConfig.createDefaultConfigBuilder().build();
DockerHttpClient dockerHttpClient = new JerseyDockerHttpClient.Builder()
.dockerHost(dockerClientConfig.getDockerHost()).sslConfig(dockerClientConfig.getSSLConfig()).build();
return DockerClientBuilder.getInstance(dockerClientConfig).withDockerHttpClient(dockerHttpClient).build();
}
}

View File

@ -210,6 +210,357 @@ public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
} }
} }
/**
* Go through every EndReason and test that all affected entities trigger the
* required Webhook events when being destroyed for that specific reason. Check
* that "reason" property of every event is right and that there are no extra
* events triggered.
*/
@Test
@DisplayName("End reason")
void endReasonTest() throws Exception {
isRecordingTest = true;
log.info("End reason test");
CountDownLatch initLatch = new CountDownLatch(1);
io.openvidu.test.browsers.utils.webhook.CustomWebhook.main(new String[0], initLatch);
try {
if (!initLatch.await(30, TimeUnit.SECONDS)) {
Assertions.fail("Timeout waiting for webhook springboot app to start");
CustomWebhook.shutDown();
return;
}
CustomHttpClient restClient = new CustomHttpClient(OpenViduTestAppE2eTest.OPENVIDU_URL, "OPENVIDUAPP",
OpenViduTestAppE2eTest.OPENVIDU_SECRET);
JsonObject config = restClient.rest(HttpMethod.GET, "/openvidu/api/config", HttpURLConnection.HTTP_OK);
String defaultOpenViduWebhookEndpoint = null;
Integer defaultOpenViduRecordingAutostopTimeout = null;
if (config.has("OPENVIDU_WEBHOOK_ENDPOINT")) {
defaultOpenViduWebhookEndpoint = config.get("OPENVIDU_WEBHOOK_ENDPOINT").getAsString();
}
if (config.has("OPENVIDU_RECORDING_AUTOSTOP_TIMEOUT")) {
defaultOpenViduRecordingAutostopTimeout = config.get("OPENVIDU_RECORDING_AUTOSTOP_TIMEOUT").getAsInt();
}
try {
Map<String, Object> newConfig = Map.of("OPENVIDU_WEBHOOK", true, "OPENVIDU_WEBHOOK_ENDPOINT",
"http://127.0.0.1:7777/webhook", "OPENVIDU_RECORDING_AUTOSTOP_TIMEOUT", 0);
restartOpenViduServer(newConfig);
OpenViduTestappUser user = setupBrowserAndConnectToOpenViduTestapp("chrome");
// unsubscribe: webrtcConnectionDestroyed
this.connectTwoUsers(user, restClient, false, false, false);
user.getDriver().findElement(By.cssSelector("#openvidu-instance-1 .sub-btn")).click();
Assertions.assertEquals("unsubscribe",
CustomWebhook.waitForEvent("webrtcConnectionDestroyed", 2).get("reason").getAsString());
CustomWebhook.events.values().forEach(collection -> Assertions.assertTrue(collection.isEmpty()));
// unpublish: webrtcConnectionDestroyed
this.connectTwoUsers(user, restClient, false, false, false);
user.getDriver().findElement(By.cssSelector("#openvidu-instance-0 .pub-btn")).click();
for (int i = 0; i < 2; i++) {
Assertions.assertEquals("unpublish",
CustomWebhook.waitForEvent("webrtcConnectionDestroyed", 2).get("reason").getAsString());
}
CustomWebhook.events.values().forEach(collection -> Assertions.assertTrue(collection.isEmpty()));
// disconnect: webrtcConnectionDestroyed, participantLeft (and subsequent
// lastParticipantLeft triggered events for sessionDestroyed,
// recordingStatusChanged, [broadcastStopped])
this.connectTwoUsers(user, restClient, false, true, true);
// First user out
user.getDriver().findElement(By.cssSelector("#openvidu-instance-0 .leave-btn")).click();
for (int i = 0; i < 3; i++) {
Assertions.assertEquals("disconnect",
CustomWebhook.waitForEvent("webrtcConnectionDestroyed", 2).get("reason").getAsString());
}
Assertions.assertEquals("disconnect",
CustomWebhook.waitForEvent("participantLeft", 2).get("reason").getAsString());
// Second user out
user.getDriver().findElement(By.cssSelector("#openvidu-instance-1 .leave-btn")).click();
Assertions.assertEquals("disconnect",
CustomWebhook.waitForEvent("webrtcConnectionDestroyed", 2).get("reason").getAsString());
Assertions.assertEquals("disconnect",
CustomWebhook.waitForEvent("participantLeft", 2).get("reason").getAsString());
for (int i = 0; i < 2; i++) {
Assertions.assertEquals("lastParticipantLeft",
CustomWebhook.waitForEvent("recordingStatusChanged", 2).get("reason").getAsString());
}
Assertions.assertEquals("lastParticipantLeft",
CustomWebhook.waitForEvent("sessionDestroyed", 2).get("reason").getAsString());
CustomWebhook.events.values().forEach(collection -> Assertions.assertTrue(collection.isEmpty()));
// forceUnpublishByUser: webrtcConnectionDestroyed
this.connectTwoUsers(user, restClient, true, false, false);
user.getDriver().findElement(By.cssSelector("#openvidu-instance-0 .force-unpub-btn")).click();
for (int i = 0; i < 2; i++) {
Assertions.assertEquals("forceUnpublishByUser",
CustomWebhook.waitForEvent("webrtcConnectionDestroyed", 2).get("reason").getAsString());
}
CustomWebhook.events.values().forEach(collection -> Assertions.assertTrue(collection.isEmpty()));
// forceUnpublishByServer: webrtcConnectionDestroyed
this.connectTwoUsers(user, restClient, false, false, false);
String streamId = restClient
.rest(HttpMethod.GET, "/openvidu/api/sessions/TestSession", HttpURLConnection.HTTP_OK)
.get("connections").getAsJsonObject().get("content").getAsJsonArray().asList().stream()
.filter(con -> con.getAsJsonObject().get("role").getAsString().equals("PUBLISHER")).findFirst()
.get().getAsJsonObject().get("publishers").getAsJsonArray().get(0).getAsJsonObject()
.get("streamId").getAsString();
restClient.rest(HttpMethod.DELETE, "/openvidu/api/sessions/TestSession/stream/" + streamId,
HttpURLConnection.HTTP_NO_CONTENT);
for (int i = 0; i < 2; i++) {
Assertions.assertEquals("forceUnpublishByServer",
CustomWebhook.waitForEvent("webrtcConnectionDestroyed", 2).get("reason").getAsString());
}
CustomWebhook.events.values().forEach(collection -> Assertions.assertTrue(collection.isEmpty()));
// forceDisconnectByUser: webrtcConnectionDestroyed, participantLeft
this.connectTwoUsers(user, restClient, true, true, true);
user.getDriver().findElement(By.cssSelector("#openvidu-instance-0 .force-disconnect-btn")).click();
for (int i = 0; i < 3; i++) {
Assertions.assertEquals("forceDisconnectByUser",
CustomWebhook.waitForEvent("webrtcConnectionDestroyed", 2).get("reason").getAsString());
}
Assertions.assertEquals("forceDisconnectByUser",
CustomWebhook.waitForEvent("participantLeft", 2).get("reason").getAsString());
CustomWebhook.events.values().forEach(collection -> Assertions.assertTrue(collection.isEmpty()));
// forceDisconnectByServer: webrtcConnectionDestroyed, participantLeft (and
// subsequent lastParticipantLeft triggered events for sessionDestroyed,
// recordingStatusChanged, [broadcastStopped])
this.connectTwoUsers(user, restClient, false, true, true);
String[] connectionIds = restClient
.rest(HttpMethod.GET, "/openvidu/api/sessions/TestSession", HttpURLConnection.HTTP_OK)
.get("connections").getAsJsonObject().get("content").getAsJsonArray().asList().stream()
.map(con -> con.getAsJsonObject().get("connectionId").getAsString()).toArray(String[]::new);
// First user out
restClient.rest(HttpMethod.DELETE, "/openvidu/api/sessions/TestSession/connection/" + connectionIds[0],
HttpURLConnection.HTTP_NO_CONTENT);
for (int i = 0; i < 3; i++) {
Assertions.assertEquals("forceDisconnectByServer",
CustomWebhook.waitForEvent("webrtcConnectionDestroyed", 2).get("reason").getAsString());
}
Assertions.assertEquals("forceDisconnectByServer",
CustomWebhook.waitForEvent("participantLeft", 2).get("reason").getAsString());
// Second user out
restClient.rest(HttpMethod.DELETE, "/openvidu/api/sessions/TestSession/connection/" + connectionIds[1],
HttpURLConnection.HTTP_NO_CONTENT);
Assertions.assertEquals("forceDisconnectByServer",
CustomWebhook.waitForEvent("webrtcConnectionDestroyed", 2).get("reason").getAsString());
Assertions.assertEquals("forceDisconnectByServer",
CustomWebhook.waitForEvent("participantLeft", 2).get("reason").getAsString());
for (int i = 0; i < 2; i++) {
Assertions.assertEquals("lastParticipantLeft",
CustomWebhook.waitForEvent("recordingStatusChanged", 2).get("reason").getAsString());
}
Assertions.assertEquals("lastParticipantLeft",
CustomWebhook.waitForEvent("sessionDestroyed", 2).get("reason").getAsString());
CustomWebhook.events.values().forEach(collection -> Assertions.assertTrue(collection.isEmpty()));
// sessionClosedByServer: webrtcConnectionDestroyed, participantLeft,
// sessionDestroyed, recordingStatusChanged
this.connectTwoUsers(user, restClient, false, true, true);
restClient.rest(HttpMethod.DELETE, "/openvidu/api/sessions/TestSession",
HttpURLConnection.HTTP_NO_CONTENT);
for (int i = 0; i < 4; i++) {
Assertions.assertEquals("sessionClosedByServer",
CustomWebhook.waitForEvent("webrtcConnectionDestroyed", 2).get("reason").getAsString());
}
for (int i = 0; i < 2; i++) {
Assertions.assertEquals("sessionClosedByServer",
CustomWebhook.waitForEvent("participantLeft", 2).get("reason").getAsString());
}
for (int i = 0; i < 2; i++) {
Assertions.assertEquals("sessionClosedByServer",
CustomWebhook.waitForEvent("recordingStatusChanged", 2).get("reason").getAsString());
}
Assertions.assertEquals("sessionClosedByServer",
CustomWebhook.waitForEvent("sessionDestroyed", 2).get("reason").getAsString());
CustomWebhook.events.values().forEach(collection -> Assertions.assertTrue(collection.isEmpty()));
// networkDisconnect: webrtcConnectionDestroyed, participantLeft (and
// subsequent lastParticipantLeft triggered events for sessionDestroyed,
// recordingStatusChanged, [broadcastStopped])
this.connectTwoUsers(user, restClient, false, true, true);
// First user out
user.getDriver().findElement(By.cssSelector("#openvidu-instance-0 .network-drop-btn")).click();
for (int i = 0; i < 3; i++) {
Assertions.assertEquals("networkDisconnect",
CustomWebhook.waitForEvent("webrtcConnectionDestroyed", 20).get("reason").getAsString());
}
Assertions.assertEquals("networkDisconnect",
CustomWebhook.waitForEvent("participantLeft", 2).get("reason").getAsString());
// Second user out
user.getDriver().findElement(By.cssSelector("#openvidu-instance-1 .network-drop-btn")).click();
Assertions.assertEquals("networkDisconnect",
CustomWebhook.waitForEvent("webrtcConnectionDestroyed", 20).get("reason").getAsString());
Assertions.assertEquals("networkDisconnect",
CustomWebhook.waitForEvent("participantLeft", 2).get("reason").getAsString());
for (int i = 0; i < 2; i++) {
Assertions.assertEquals("lastParticipantLeft",
CustomWebhook.waitForEvent("recordingStatusChanged", 2).get("reason").getAsString());
}
Assertions.assertEquals("lastParticipantLeft",
CustomWebhook.waitForEvent("sessionDestroyed", 2).get("reason").getAsString());
CustomWebhook.events.values().forEach(collection -> Assertions.assertTrue(collection.isEmpty()));
// mediaServerDisconnect: webrtcConnectionDestroyed, participantLeft,
// sessionDestroyed, recordingStatusChanged, [broadcastStopped]
this.connectTwoUsers(user, restClient, false, true, true);
String mediaNodeId = restClient
.rest(HttpMethod.GET, "/openvidu/api/media-nodes", HttpURLConnection.HTTP_OK).get("content")
.getAsJsonArray().get(0).getAsJsonObject().get("id").getAsString();
restClient.rest(HttpMethod.DELETE,
"/openvidu/api/media-nodes/" + mediaNodeId + "?wait=false&deletion-strategy=now",
HttpURLConnection.HTTP_OK);
CustomWebhook.waitForEvent("mediaNodeStatusChanged", 3);
for (int i = 0; i < 4; i++) {
Assertions.assertEquals("mediaServerDisconnect",
CustomWebhook.waitForEvent("webrtcConnectionDestroyed", 20).get("reason").getAsString());
}
for (int i = 0; i < 2; i++) {
Assertions.assertEquals("mediaServerDisconnect",
CustomWebhook.waitForEvent("participantLeft", 2).get("reason").getAsString());
}
for (int i = 0; i < 2; i++) {
Assertions.assertEquals("mediaServerDisconnect",
CustomWebhook.waitForEvent("recordingStatusChanged", 4).get("reason").getAsString());
}
Assertions.assertEquals("mediaServerDisconnect",
CustomWebhook.waitForEvent("sessionDestroyed", 2).get("reason").getAsString());
CustomWebhook.waitForEvent("mediaNodeStatusChanged", 5);
CustomWebhook.events.values().forEach(collection -> Assertions.assertTrue(collection.isEmpty()));
restartOpenViduServer(new HashMap<>(), true, HttpURLConnection.HTTP_OK);
// mediaServerReconnect: webrtcConnectionDestroyed, recordingStatusChanged
this.connectTwoUsers(user, restClient, false, true, true);
String containerId = restClient
.rest(HttpMethod.GET, "/openvidu/api/media-nodes", HttpURLConnection.HTTP_OK).get("content")
.getAsJsonArray().get(0).getAsJsonObject().get("environmentId").getAsString();
MediaNodeDockerUtils.stopMediaServerInsideMediaNodeAndRecover(containerId, 400);
for (int i = 0; i < 4; i++) {
Assertions.assertEquals("mediaServerReconnect",
CustomWebhook.waitForEvent("webrtcConnectionDestroyed", 2).get("reason").getAsString());
}
for (int i = 0; i < 2; i++) {
Assertions.assertEquals("mediaServerReconnect",
CustomWebhook.waitForEvent("recordingStatusChanged", 2).get("reason").getAsString());
}
CustomWebhook.events.values().forEach(collection -> Assertions.assertTrue(collection.isEmpty()));
// nodeCrashed: webrtcConnectionDestroyed, participantLeft, sessionDestroyed,
// recordingStatusChanged, [broadcastStopped]
this.connectTwoUsers(user, restClient, false, true, true);
containerId = restClient.rest(HttpMethod.GET, "/openvidu/api/media-nodes", HttpURLConnection.HTTP_OK)
.get("content").getAsJsonArray().get(0).getAsJsonObject().get("environmentId").getAsString();
MediaNodeDockerUtils.crashMediaNode(containerId);
CustomWebhook.waitForEvent("nodeCrashed", 10);
CustomWebhook.waitForEvent("mediaNodeStatusChanged", 2);
for (int i = 0; i < 4; i++) {
Assertions.assertEquals("nodeCrashed",
CustomWebhook.waitForEvent("webrtcConnectionDestroyed", 2).get("reason").getAsString());
}
for (int i = 0; i < 2; i++) {
Assertions.assertEquals("nodeCrashed",
CustomWebhook.waitForEvent("participantLeft", 2).get("reason").getAsString());
}
// Only status "stopped" for recording. Not "ready" if node crash
Assertions.assertEquals("nodeCrashed",
CustomWebhook.waitForEvent("recordingStatusChanged", 2).get("reason").getAsString());
Assertions.assertEquals("nodeCrashed",
CustomWebhook.waitForEvent("sessionDestroyed", 2).get("reason").getAsString());
CustomWebhook.waitForEvent("mediaNodeStatusChanged", 2);
CustomWebhook.events.values().forEach(collection -> Assertions.assertTrue(collection.isEmpty()));
restartOpenViduServer(new HashMap<>(), true, HttpURLConnection.HTTP_OK);
// openviduServerStopped: webrtcConnectionDestroyed, participantLeft,
// sessionDestroyed, recordingStatusChanged, [broadcastStopped]
this.connectTwoUsers(user, restClient, false, true, true);
restartOpenViduServer(new HashMap<>(), true, HttpURLConnection.HTTP_OK);
for (int i = 0; i < 4; i++) {
Assertions.assertEquals("openviduServerStopped",
CustomWebhook.waitForEvent("webrtcConnectionDestroyed", 20).get("reason").getAsString());
}
for (int i = 0; i < 2; i++) {
Assertions.assertEquals("openviduServerStopped",
CustomWebhook.waitForEvent("participantLeft", 2).get("reason").getAsString());
}
for (int i = 0; i < 2; i++) {
Assertions.assertEquals("openviduServerStopped",
CustomWebhook.waitForEvent("recordingStatusChanged", 4).get("reason").getAsString());
}
Assertions.assertEquals("openviduServerStopped",
CustomWebhook.waitForEvent("sessionDestroyed", 2).get("reason").getAsString());
for (int i = 0; i < 2; i++) {
CustomWebhook.waitForEvent("mediaNodeStatusChanged", 15);
}
CustomWebhook.events.values().forEach(collection -> Assertions.assertTrue(collection.isEmpty()));
// automaticStop: sessionDestroyed, recordingStatusChanged
newConfig = Map.of("OPENVIDU_RECORDING_AUTOSTOP_TIMEOUT", 1);
restartOpenViduServer(newConfig);
this.connectTwoUsers(user, restClient, false, true, true);
user.getDriver().findElement(By.cssSelector("#openvidu-instance-0 .leave-btn")).click();
user.getDriver().findElement(By.cssSelector("#openvidu-instance-1 .leave-btn")).click();
for (int i = 0; i < 4; i++) {
Assertions.assertEquals("disconnect",
CustomWebhook.waitForEvent("webrtcConnectionDestroyed", 2).get("reason").getAsString());
}
for (int i = 0; i < 2; i++) {
Assertions.assertEquals("disconnect",
CustomWebhook.waitForEvent("participantLeft", 2).get("reason").getAsString());
}
for (int i = 0; i < 2; i++) {
Assertions.assertEquals("automaticStop",
CustomWebhook.waitForEvent("recordingStatusChanged", 4).get("reason").getAsString());
}
Assertions.assertEquals("automaticStop",
CustomWebhook.waitForEvent("sessionDestroyed", 2).get("reason").getAsString());
CustomWebhook.events.values().forEach(collection -> Assertions.assertTrue(collection.isEmpty()));
// recordingStoppedByServer
this.connectTwoUsers(user, restClient, false, true, true);
String recordingId = restClient
.rest(HttpMethod.GET, "/openvidu/api/recordings", HttpURLConnection.HTTP_OK).get("items")
.getAsJsonArray().asList().stream()
.filter(rec -> rec.getAsJsonObject().get("status").getAsString()
.equals(Recording.Status.started.name()))
.findFirst().get().getAsJsonObject().get("id").getAsString();
restClient.rest(HttpMethod.POST, "/openvidu/api/recordings/stop/" + recordingId,
HttpURLConnection.HTTP_OK);
for (int i = 0; i < 2; i++) {
Assertions.assertEquals("recordingStoppedByServer",
CustomWebhook.waitForEvent("recordingStatusChanged", 4).get("reason").getAsString());
}
// [broadcastStoppedByServer]
} finally {
Map<String, Object> oldConfig = new HashMap<>();
oldConfig.put("OPENVIDU_WEBHOOK", false);
if (defaultOpenViduWebhookEndpoint != null) {
oldConfig.put("OPENVIDU_WEBHOOK_ENDPOINT", defaultOpenViduWebhookEndpoint);
}
if (defaultOpenViduRecordingAutostopTimeout != null) {
oldConfig.put("OPENVIDU_RECORDING_AUTOSTOP_TIMEOUT", defaultOpenViduRecordingAutostopTimeout);
}
restartOpenViduServer(oldConfig);
}
} finally {
CustomWebhook.shutDown();
}
}
@Test @Test
@DisplayName("Individual dynamic record") @DisplayName("Individual dynamic record")
void individualDynamicRecordTest() throws Exception { void individualDynamicRecordTest() throws Exception {
@ -2552,4 +2903,40 @@ public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
sttUnsubUser(user, 0, 0, false, true); sttUnsubUser(user, 0, 0, false, true);
} }
private void connectTwoUsers(OpenViduTestappUser user, CustomHttpClient restClient, boolean firstUserIsModerator,
boolean startRecording, boolean startBroadcast) throws Exception {
this.closeAllSessions(OV);
user.getDriver().findElement(By.id("remove-all-users-btn")).click();
user.getEventManager().clearAllCurrentEvents();
CustomWebhook.clean();
user.getDriver().findElement(By.id("add-user-btn")).click();
if (firstUserIsModerator) {
user.getDriver().findElement(By.id("session-settings-btn-0")).click();
Thread.sleep(500);
user.getDriver().findElement(By.id("radio-btn-mod")).click();
user.getDriver().findElement(By.id("save-btn")).click();
Thread.sleep(500);
}
user.getDriver().findElement(By.id("add-user-btn")).click();
user.getDriver().findElements(By.className("join-btn")).forEach(el -> el.sendKeys(Keys.ENTER));
user.getEventManager().waitUntilEventReaches("streamPlaying", 4);
CustomWebhook.waitForEvent("sessionCreated", 1);
for (int i = 0; i < 2; i++) {
CustomWebhook.waitForEvent("participantJoined", 1);
}
for (int i = 0; i < 4; i++) {
CustomWebhook.waitForEvent("webrtcConnectionCreated", 1);
}
if (startRecording) {
restClient.rest(HttpMethod.POST, "/openvidu/api/recordings/start",
"{'session':'TestSession','outputMode':'INDIVIDUAL'}", HttpURLConnection.HTTP_OK);
user.getEventManager().waitUntilEventReaches("recordingStarted", 2);
Assertions.assertEquals(Recording.Status.started.name(),
CustomWebhook.waitForEvent("recordingStatusChanged", 3).get("status").getAsString());
}
if (startBroadcast) {
}
}
} }

View File

@ -124,6 +124,9 @@
<button class="message-btn" (click)="sendMessage()" title="Broadcast message"> <button class="message-btn" (click)="sendMessage()" title="Broadcast message">
<mat-icon aria-label="Send message button" style="font-size: 20px">chat</mat-icon> <mat-icon aria-label="Send message button" style="font-size: 20px">chat</mat-icon>
</button> </button>
<button class="network-drop-btn" (click)="simulateNetworkDrop()" title="Simulate network drop">
<mat-icon aria-label="Simulate network drop button" style="font-size: 20px">wifi_off</mat-icon>
</button>
<button class="leave-btn" (click)="leaveSession()" title="Leave session"> <button class="leave-btn" (click)="leaveSession()" title="Leave session">
<mat-icon aria-label="Leave button">clear</mat-icon> <mat-icon aria-label="Leave button">clear</mat-icon>
</button> </button>

View File

@ -284,6 +284,11 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
this.subscribers = []; this.subscribers = [];
} }
private simulateNetworkDrop(): void {
const jsonRpClient = (this.OV as any).jsonRpcClient;
jsonRpClient.close();
}
updateEventList(eventName: string, eventContent: string, event: Event) { updateEventList(eventName: string, eventContent: string, event: Event) {
const eventInterface: OpenViduEvent = { eventName, eventContent, event }; const eventInterface: OpenViduEvent = { eventName, eventContent, event };
this.events.push(eventInterface); this.events.push(eventInterface);